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

Compare commits

...

262 Commits

Author SHA1 Message Date
Jesse Hills
a67b92a04c Merge pull request #5286 from esphome/bump-2023.8.2
2023.8.2
2023-08-21 13:31:33 +12:00
Jesse Hills
9fb8e9edef Bump version to 2023.8.2 2023-08-21 12:33:49 +12:00
Clyde Stubbs
d2bccbe8ac Reserve keyword "clock" (#5279) 2023-08-21 12:33:48 +12:00
Jesse Hills
e44a60e814 Change htu21d sensors from required to optional (#5285) 2023-08-21 12:33:48 +12:00
Clyde Stubbs
02a71cb6a7 Align SPI data rates in C++ code with Python (#5284) 2023-08-21 12:33:48 +12:00
mwolter805
e600784ebf Resolve offline ESPs in dashboard when using ESPHOME_DASHBOARD_USE_PING=true (#5281) 2023-08-21 12:33:48 +12:00
Jesse Hills
5e19a3b892 Move libcairo to all architectures in docker (#5276) 2023-08-21 12:33:48 +12:00
Jesse Hills
8bf112669f Merge pull request #5273 from esphome/bump-2023.8.1
2023.8.1
2023-08-18 09:09:07 +12:00
Jesse Hills
4278664208 Bump version to 2023.8.1 2023-08-18 08:12:42 +12:00
Jesse Hills
0789657fd5 Change haier from AUTO to HEAT_COOL (#5267) 2023-08-18 08:12:42 +12:00
Mat931
b566c78f00 Fix checksum calculation for sml (#5271) 2023-08-18 08:12:42 +12:00
Jesse Hills
a35122231c Merge pull request #5264 from esphome/bump-2023.8.0
2023.8.0
2023-08-17 16:00:56 +12:00
Jesse Hills
7e4ee32b54 Bump version to 2023.8.0 2023-08-17 14:33:35 +12:00
Jesse Hills
7df80eadcf Merge pull request #5263 from esphome/bump-2023.8.0b4
2023.8.0b4
2023-08-17 14:30:18 +12:00
Jesse Hills
2aaba1d2b8 Bump version to 2023.8.0b4 2023-08-17 13:04:32 +12:00
dependabot[bot]
7c129a4018 Bump zeroconf from 0.74.0 to 0.80.0 (#5260)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-17 13:04:32 +12:00
Jimmy Hedman
cb66ce069e Add delay before enabling ipv6 (#5256) 2023-08-17 13:04:31 +12:00
Pierre Gordon
a27e72362a Add libfreetype-dev Debian package for armv7 Docker builds (#5262) 2023-08-17 13:04:31 +12:00
Jesse Hills
f44e5d3142 Merge pull request #5258 from esphome/bump-2023.8.0b3
2023.8.0b3
2023-08-16 13:25:54 +12:00
Jesse Hills
532163738e Bump version to 2023.8.0b3 2023-08-16 11:49:08 +12:00
Regev Brody
63fa922547 Add configuration flow abilites to the ld2410 component (#4434) 2023-08-16 11:49:07 +12:00
Carson Full
48e4cb5ae2 Fix IDFI2CBus::writev ignoring stop parameter (#4840)
Co-authored-by: Alexander Dimitrov <admin@sharkydog.info>
2023-08-16 11:49:07 +12:00
mulder-fbi
ff8a73c2d1 Fix 24 bit signed integer parsing in sml parser (#5250) 2023-08-16 11:49:07 +12:00
Sergey Dudanov
afd26c6f1a rmt_base additional minor changes (#5245) 2023-08-16 11:49:07 +12:00
MrEditor97
67b06a88b2 Change XL9535 setup_priority to IO (#5246) 2023-08-16 11:49:07 +12:00
Jesse Hills
265e019381 Merge pull request #5244 from esphome/bump-2023.8.0b2
2023.8.0b2
2023-08-14 12:48:17 +12:00
Jesse Hills
560e36a65c Bump version to 2023.8.0b2 2023-08-14 11:09:49 +12:00
Jesse Hills
b05a3fbb55 Fix duplicate tuya time warning (#5243) 2023-08-14 11:09:48 +12:00
Kjell Braden
3a899e28dc tuya: add time sync callback only once to prevent memleak (#5234) 2023-08-14 11:09:48 +12:00
Pavlo Dudnytskyi
f26238e824 Fixing smartair2 protocol implementation if no Wi-Fi (#5238) 2023-08-14 11:09:48 +12:00
Sergey Dudanov
3717e34bba fix midea: undo approved PR#4053 (#5233) 2023-08-14 11:09:48 +12:00
Steve Rodgers
be6f95d43e pca9554 cache reads (#5137) 2023-08-14 11:09:48 +12:00
Pavlo Dudnytskyi
99a765dc06 New features added for Haier integration (#5196)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2023-08-14 11:09:48 +12:00
Jesse Hills
351e7ea16b Expose start to speaker interface (#5228) 2023-08-14 11:09:48 +12:00
Samuel Sieb
2fa79a2e2f fix aeha data template (#5231)
Co-authored-by: Samuel Sieb <samuel@sieb.net>
2023-08-14 11:09:48 +12:00
Jesse Hills
44a917929d Read string of bool env and match against well known values (#5232) 2023-08-14 11:09:48 +12:00
Jesse Hills
21ebc7f95b Merge pull request #5224 from esphome/bump-2023.8.0b1
2023.8.0b1
2023-08-10 19:17:58 +12:00
Jesse Hills
72e72d7d4b Fix duplicate 2023-08-10 18:40:13 +12:00
Jesse Hills
02ed2c0ebe Bump version to 2023.8.0b1 2023-08-10 17:30:26 +12:00
Jesse Hills
0f506ea8eb Merge branch 'dev' into bump-2023.8.0b1 2023-08-10 17:30:26 +12:00
Keith Burzinski
8e7e8da4a3 Tweak Color init because IDF 5+ (#5221) 2023-08-10 17:11:57 +12:00
Samuel Sieb
b56c606523 add value option to timeout filter (#5222)
Co-authored-by: Samuel Sieb <samuel@sieb.net>
2023-08-10 17:11:03 +12:00
Francesco Ciocchetti
f457269a68 Add missing on_(arming|pending|armed_home|armed_night|armed_away|disarmed) triggers to alarm_control_panel (#5219) 2023-08-10 17:09:21 +12:00
matthias882
5b0b9da0b9 Daly BMS improvements (#3388)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
Co-authored-by: Samuel Sieb <samuel-github@sieb.net>
2023-08-10 17:05:01 +12:00
Greg Cormier
0ed0bdc655 New PM sensor Panasonic SN-GCJA5 (#4988) 2023-08-10 17:04:22 +12:00
kahrendt
a8fa4b56f9 New component: Add support for bmp581 pressure and temperature sensors (#4657) 2023-08-08 17:05:08 +12:00
dependabot[bot]
cd514b140e Bump platformio from 6.1.7 to 6.1.9 (#5066)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2023-08-08 14:31:49 +12:00
Rudd-O
f3329fdc8c Add KMeterISO component. (#5170)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2023-08-08 12:32:34 +12:00
dependabot[bot]
689bbf2419 Bump pyupgrade from 3.9.0 to 3.10.1 (#5189)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2023-08-07 23:59:46 +00:00
Francesco Ciocchetti
a6b89e4e8a Add arm night to alarm control panel (#5186) 2023-08-08 11:57:40 +12:00
Stijn Tintel
ffd2cb9814 ledc: check SOC_LEDC_SUPPORT_APB_CLOCK (#5212) 2023-08-07 23:47:57 +00:00
dependabot[bot]
1d5f088740 Bump pytest-asyncio from 0.21.0 to 0.21.1 (#5187)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-08 11:21:51 +12:00
Stijn Tintel
4e7011c25d esp32_ble_beacon: enable CONFIG_BT_BLE_42_FEATURES_SUPPORTED (#5211) 2023-08-08 11:18:06 +12:00
Stijn Tintel
f4ac176d77 core: read ESP32 MAC address from eFuse if IEEE802.15.4 is supported (#5176) 2023-08-07 22:45:50 +00:00
Jesse Hills
e4cf7b86fa Add socket define for rp2040 dev (#4968) 2023-08-08 10:22:10 +12:00
Stijn Tintel
9876d5276c i2c: fix build on ESP-IDF >= 5.1 (#5200)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2023-08-08 09:51:22 +12:00
Jesse Hills
0b1b25191d Add read interface to microphone (#5131) 2023-08-08 09:45:56 +12:00
Jimmy Hedman
9980b9972f Change MQTT client for ESP32 Arduino (#5157)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2023-08-08 09:16:42 +12:00
Trent Houliston
93b7ca77ca Refactor pulse_meter to better handle higher frequencies (#4231) 2023-08-08 08:14:20 +12:00
Lucas Prim
40697fea96 Implemented Waveshare 7.5in B V3 (#5210) 2023-08-08 07:31:09 +12:00
dependabot[bot]
c541fa1763 Bump zeroconf from 0.71.4 to 0.74.0 (#5199)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-07 13:55:38 +12:00
Keith Burzinski
fd08f1e23d Add ESP32-S2/S3 capacitive touch support (#5116)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2023-08-07 13:54:25 +12:00
Paweł Kozubal
3a07121784 Change device name in MQTT discovery messages to friendly names (#5205) 2023-08-07 13:46:31 +12:00
André Cirne
1495fada90 Add support for a01nyub (#4863)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2023-08-07 12:22:18 +12:00
arno1801
62fed4c1eb Improved compensation sgp30 (#5208) 2023-08-07 11:59:17 +12:00
Jesse Hills
00f9af70a9 Fix some configs after #5181 (#5209) 2023-08-07 11:48:23 +12:00
Maciej Sokołowski
0ae3fcb0b7 PWM Output on RP2040 for high frequencies (#5204) 2023-08-07 11:41:44 +12:00
dependabot[bot]
dfffa67c0f Bump click from 8.1.5 to 8.1.6 (#5179)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-02 16:35:16 +12:00
Jesse Hills
f81c556b63 Update components "if x in config" (#5181) 2023-08-02 16:25:26 +12:00
Jesse Hills
ce8091c14e Speaker return bytes written and do not wait for queue (#5182) 2023-08-02 16:24:52 +12:00
Jesse Hills
581cb642ff Add get_board function to esp32 module (#5184) 2023-08-02 16:24:02 +12:00
Jesse Hills
e02aaedc42 Microphone add is_stopped (#5183) 2023-08-02 16:21:30 +12:00
Pavlo Dudnytskyi
8c66de2391 Vertical and horizontal airflow actions fix for Haier climate (#5164)
Co-authored-by: Pavlo Dudnytskyi <pdudnytskyi@astrata.eu>
2023-08-02 11:06:23 +12:00
dependabot[bot]
cb8ca433d9 Bump pyupgrade from 3.7.0 to 3.9.0 (#5083)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2023-08-02 07:57:51 +12:00
Jesse Hills
b914d6e305 Merge pull request #5173 from esphome/bump-2023.7.1
2023.7.1
2023-08-01 16:54:20 +12:00
Jesse Hills
956e19be7d Bump version to 2023.7.1 2023-08-01 12:08:36 +12:00
Maxime Michel
b3d5a4dfdb Fix graininess & streaks for 7.50inV2alt Waveshare e-paper (#5168) 2023-08-01 12:08:36 +12:00
Joris S
c63cdae84f invert min_rssi check (#5150) 2023-08-01 12:08:36 +12:00
J. Nick Koston
dec044ad8b Increase maximum number of BLE notifications (#5155) 2023-08-01 12:08:36 +12:00
PlainTechEnthusiast
2a12ec09fb update "Can't convert" warning to match others in homeassistant_sensor (#5162) 2023-08-01 12:08:36 +12:00
cvwillegen
91e920c498 Slightly lower template switch setup priority (#5163) 2023-08-01 12:08:35 +12:00
Stijn Tintel
9b19c45735 wifi: handle WIFI_REASON_ROAMING reason in event (#5153) 2023-08-01 12:08:35 +12:00
Keith Burzinski
3843d21dbf Swap ADC back to use 'int' because C3 (#5151) 2023-08-01 12:08:35 +12:00
Kuba Szczodrzyński
73db164fb1 Dashboard: use Popen() on Windows (#5110) 2023-08-01 12:08:35 +12:00
Maxime Michel
17be6b106b Fix graininess & streaks for 7.50inV2alt Waveshare e-paper (#5168) 2023-08-01 12:03:34 +12:00
dependabot[bot]
869981cfe4 Bump pylint from 2.17.4 to 2.17.5 (#5172)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-01 10:16:46 +12:00
dependabot[bot]
7dd56fb0fa Bump black from 23.3.0 to 23.7.0 (#5126)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2023-07-30 23:28:26 +00:00
dependabot[bot]
f0f09d3714 Bump zeroconf from 0.69.0 to 0.71.4 (#5148)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-31 10:32:55 +12:00
Joris S
9a66199904 invert min_rssi check (#5150) 2023-07-31 10:30:21 +12:00
J. Nick Koston
bf732f2a2b Increase maximum number of BLE notifications (#5155) 2023-07-31 10:23:52 +12:00
Jimmy Hedman
c418eecf83 Enable IPv6 for ESP32 Arduino, wifi and ethernet (#4865) 2023-07-30 22:20:55 +00:00
Mat931
98bf427600 Add standardized CRC helper functions (#4798)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2023-07-30 21:45:56 +00:00
PlainTechEnthusiast
9aa5ee3372 update "Can't convert" warning to match others in homeassistant_sensor (#5162) 2023-07-31 09:40:55 +12:00
cvwillegen
ccb3d3d308 Slightly lower template switch setup priority (#5163) 2023-07-31 09:32:09 +12:00
Sergey Dudanov
9ff0471274 duty_time: fix build without binary_sensor. Parented in automations. (#5156)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2023-07-30 21:30:11 +00:00
Stijn Tintel
fdb20e4a30 wifi: handle WIFI_REASON_ROAMING reason in event (#5153) 2023-07-31 09:23:30 +12:00
Keith Burzinski
56630bb717 Swap ADC back to use 'int' because C3 (#5151) 2023-07-31 09:19:06 +12:00
mullerdavid
08a41d9bd6 Adding Inkplate 6 v2 model variant (#5165) 2023-07-31 09:10:46 +12:00
Mat931
cd46a69f2c Add 'map_linear' and 'clamp' sensor filters (#5040) 2023-07-31 09:09:09 +12:00
Sergey Dudanov
794a4bd9a1 remote_base changes (#5124) 2023-07-31 08:07:33 +12:00
Sergey Dudanov
a120a455bf climate triggers Climate and ClimateCall references (#5028) 2023-07-31 07:52:01 +12:00
Sergey Dudanov
cd72a2ed7e Bump clang-tidy from 11 to 14 (#5160) 2023-07-31 07:44:56 +12:00
Jimmy Hedman
3eff7e76aa Prepare some components for idf >= 5 (#5061) 2023-07-27 00:18:02 -05:00
esphomebot
959d1944fd Synchronise Device Classes from Home Assistant (#5147) 2023-07-24 07:17:18 +00:00
Jesse Hills
b0966532bf Allow esp32 idf components to specify submodules and specific components (#5128) 2023-07-23 20:22:46 +12:00
Sergey Dudanov
827b2def1e Coolix IR protocol improvements (#5105)
* coolix protocol

* tests

* 24-bit range

* some DRY in coolix

* added short condition

* one more change

* final prettify

* v2023.8
2023-07-23 08:15:37 +12:00
Kuba Szczodrzyński
80154b280e Init colorama in ESPHome main (#5111) 2023-07-22 20:25:01 +12:00
Jesse Hills
efd0dd4c3d Update known boards to 5.4.0 (#5134) 2023-07-22 20:24:40 +12:00
esphomebot
c91b775b73 Synchronise Device Classes from Home Assistant (#5136) 2023-07-21 22:17:48 +12:00
Jimmy Hedman
1c237aef77 Version bump for ESP32 IDF and Arduino (#5035) 2023-07-21 15:35:44 +12:00
Graham Brown
89c5298bb9 Streamer mode (#5119) 2023-07-21 08:47:37 +12:00
Jimmy Hedman
76c0d0912f Change datatype in e131 addressable light (#5127) 2023-07-20 15:54:25 +12:00
Jimmy Hedman
5eb12ac5fe Make docker use pip installed pillow (#5074) 2023-07-20 15:52:41 +12:00
Jesse Hills
d238155640 Add size getter to CallbackManager (#5129) 2023-07-20 12:37:42 +12:00
Jesse Hills
de626c0f5f ignore components folder in root (#5130) 2023-07-20 12:37:29 +12:00
Kuba Szczodrzyński
973e78355f Dashboard: use Popen() on Windows (#5110) 2023-07-20 08:39:35 +12:00
Jesse Hills
ab32dd7420 Merge pull request #5122 from esphome/bump-2023.7.0
2023.7.0
2023-07-19 15:44:34 +12:00
dependabot[bot]
b82c7ad608 Bump pyyaml from 6.0 to 6.0.1 (#5117)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-19 14:30:12 +12:00
Jesse Hills
2a7aa2fc0d bump pyyaml to 6.0.1 2023-07-19 14:07:42 +12:00
Jesse Hills
f5e98eb86f Bump version to 2023.7.0 2023-07-19 12:59:51 +12:00
Jesse Hills
362a19c2e1 Merge pull request #5121 from esphome/bump-2023.7.0b3
2023.7.0b3
2023-07-19 12:40:27 +12:00
Jesse Hills
f4a4956dd4 Bump version to 2023.7.0b3 2023-07-19 11:41:24 +12:00
Jesse Hills
746488cabf Fix silence detection flag on voice assistant (#5120) 2023-07-19 11:41:24 +12:00
voed
4449248c6f [LD2410] Remove baud_rate check (#5112) 2023-07-19 11:41:24 +12:00
PlainTechEnthusiast
036e14ab7f Sigma delta fix (#4911) 2023-07-19 11:41:24 +12:00
Kevin P. Fleming
f840eee1b7 airthings_wave: Silence compiler warnings (#5098) 2023-07-19 11:41:24 +12:00
bwynants
553132443f P1 values for capacity tariff in Belgium (#5081) 2023-07-19 11:41:24 +12:00
Jesse Hills
417d45939f Fix silence detection flag on voice assistant (#5120) 2023-07-19 11:38:47 +12:00
voed
837c749cd7 [LD2410] Remove baud_rate check (#5112) 2023-07-18 12:50:32 +12:00
PlainTechEnthusiast
b0e286972d Sigma delta fix (#4911) 2023-07-18 12:49:04 +12:00
dependabot[bot]
3cfe1e3083 Bump click from 8.1.3 to 8.1.5 (#5099)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-18 12:47:06 +12:00
Kevin P. Fleming
6738295475 airthings_wave: Silence compiler warnings (#5098) 2023-07-17 10:43:57 +12:00
bwynants
1617eba764 P1 values for capacity tariff in Belgium (#5081) 2023-07-17 10:42:49 +12:00
Jesse Hills
d20242f589 Merge pull request #5107 from esphome/bump-2023.7.0b2
2023.7.0b2
2023-07-17 10:19:32 +12:00
Jesse Hills
68affce274 Bump version to 2023.7.0b2 2023-07-17 09:29:32 +12:00
Clyde Stubbs
c4b9065749 Add timeout filter (#5104) 2023-07-17 09:29:32 +12:00
Jesse Hills
d57a5d1793 Remove template switch restore_state (#5106) 2023-07-17 09:29:32 +12:00
Ilia Sotnikov
74e062fdb3 [Sprinkler] Resume fixes (#5100) 2023-07-17 09:29:32 +12:00
Pierre-Alexis Ciavaldini
6bdc0c92fe ESP32 enable ADC2 when wifi is disabled (#4381)
Co-authored-by: Keith Burzinski <kbx81x@gmail.com>
2023-07-17 09:29:32 +12:00
Jesse Hills
d7945de001 Dont do mqtt ip lookup if use_address has ip address (#5096)
* Dont do mqtt ip lookup id `use_address` is in config

* Fix after actually testing =)
2023-07-17 09:29:32 +12:00
Dave T
899aa31df3 Mark repo as safe directory to git config (#5102) 2023-07-17 09:19:08 +12:00
Clyde Stubbs
ac81fae855 Add timeout filter (#5104) 2023-07-17 09:17:31 +12:00
Jesse Hills
8c6cddf1bb Remove template switch restore_state (#5106) 2023-07-17 09:11:43 +12:00
Ilia Sotnikov
508392db6e [Sprinkler] Resume fixes (#5100) 2023-07-16 15:28:31 -05:00
Pierre-Alexis Ciavaldini
3ac0165f00 ESP32 enable ADC2 when wifi is disabled (#4381)
Co-authored-by: Keith Burzinski <kbx81x@gmail.com>
2023-07-17 07:42:01 +12:00
Kamil Trzciński
1691c13b47 display: Add helper methods to Display::clip and Display::clamp_x/y_ (#5003)
* display: `Rect` make most of methods `const`

* display: add `clip` and `clamp_x/y_` methods for clipping to `Display`
2023-07-14 15:30:19 -05:00
Jimmy Hedman
f8df694aa3 Mk2 to prepare color.h for idf >= 5 (#5070) 2023-07-12 19:32:05 -05:00
Jesse Hills
ac05495781 Dont do mqtt ip lookup if use_address has ip address (#5096)
* Dont do mqtt ip lookup id `use_address` is in config

* Fix after actually testing =)
2023-07-12 19:19:04 -05:00
Jesse Hills
3ba2a29e54 Merge pull request #5095 from esphome/bump-2023.7.0b1
2023.7.0b1
2023-07-13 10:53:51 +12:00
Jesse Hills
306ab0c56c Bump version to 2023.8.0-dev 2023-07-13 09:50:48 +12:00
Jesse Hills
76b438f79c Bump version to 2023.7.0b1 2023-07-13 09:50:48 +12:00
Jesse Hills
bc14f06a07 Merge branch 'dev' into bump-2023.7.0b1 2023-07-13 09:50:47 +12:00
Jesse Hills
844cf316e2 Edit error message for pillow install to add version restrictions (#5094) 2023-07-13 09:38:24 +12:00
Lewis Baker
9344d85414 Fix PIDController::in_deadband() to give correct result when error is zero (#5078) 2023-07-13 08:57:45 +12:00
Sergey Dudanov
a539197bc4 New 'Duty Time' sensor component (#5069) 2023-07-13 08:48:16 +12:00
Sergey Dudanov
eb859e83f8 Fix use of optional<T> (#5091) 2023-07-13 08:44:30 +12:00
Pavlo Dudnytskyi
e4a640844c Fixing colon for tm1637 display if inverted set true (#5072) 2023-07-13 08:24:49 +12:00
Christian
119bbba254 Grove amend name (#5093) 2023-07-13 08:13:50 +12:00
danieltwagner
8c5978599a Add support for ATM90E26 (#4366)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2023-07-12 17:10:22 +12:00
Sergey Dudanov
bbf3d382e8 added uart final validate data bits (#5079) 2023-07-12 16:12:40 +12:00
Jesse Hills
c85f70a236 Bump esphome-dashboard to 20230711.0 (#5085) 2023-07-12 16:02:37 +12:00
Jesse Hills
7e52d4f5d6 Restrict pillow to versions before 10.0.0 (#5090) 2023-07-12 15:28:20 +12:00
Clyde Stubbs
6d9dbf9e54 Correct message for standard transmission. (#5088) 2023-07-12 15:22:52 +12:00
Clyde Stubbs
ec37dece12 Add MCP2515 12MHz xtal support (#5089) 2023-07-12 15:12:48 +12:00
Christian
e0fd8cd850 Add support for Grove tb6612 fng (#4797)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2023-07-12 15:02:53 +12:00
Kevin P. Fleming
cf65bd8ad7 airthings_wave: Battery level reporting (#4979) 2023-07-12 13:38:52 +12:00
Stefan Klug
8a9352939a Fix typo in mpu6050.cpp (#5086) 2023-07-12 13:29:38 +12:00
kswt
6ecc1c14d2 tuya_light: fix float->int conversion while setting color temperature (#5067)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
Co-authored-by: kswt <kswt@xmpp.is>
2023-07-12 13:28:48 +12:00
Stefan Rado
5f531ac9b0 Add TT21100 touchscreen component (#4793)
Co-authored-by: Rajan Patel <rpatel3001@gmail.com>
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2023-07-12 13:19:19 +12:00
dentra
7a551081ee web server esp idf suppport (#3500)
* initial web_server_idf implementation

* initial web_server_idf implementation

* fix lint errors

* fix lint errors

* add captive_portal support

* fix lint errors

* fix lint errors

* add url decode

* Increase the max supported size of headers section in HTTP request

* add ota support

* add mulipart form data support (ota required)

* make linter happy

* make linter happy

* make linter happy

* fix review marks

* add DefaultHeaders support

* add DefaultHeaders support

* unify file names

* using std::isnan

* parse multipart requests only when ota enabled

* parse multipart requests only when ota enabled

* parse multipart requests only when ota enabled

* parse multipart requests only when ota enabled

* parse multipart requests only when ota enabled

* drop multipart request support

* drop multipart request support

* drop multipart request support

* OTA is disabled by default

* fail when OTA enabled on IDF framework

* changing file permissions to remove execute bit

* return back PGM_P and strncpy_P macro

* temp web_server fix to be compat with 2022.12

* fix config handling w/o web_server

* fix compilation with "local"

* fully remove all idf ota

* merge with esphome 2023.6

* add core/hal to web_server_base

* Update esphome/components/web_server_base/__init__.py

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

* Update __init__.py

* Update __init__.py

---------

Co-authored-by: Keith Burzinski <kbx81x@gmail.com>
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2023-07-11 19:08:03 -05:00
KoenBreeman
74139985c9 RTC implementation of pcf8563 (#4998)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2023-07-12 09:19:28 +12:00
jan-hofmeier
f3cdcc008a Add Alpha3 pump component (#3787)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2023-07-11 17:12:43 +12:00
kahrendt
a391815921 Add Zio Ultrasonic Distance Sensor Component (#5059) 2023-07-11 16:24:18 +12:00
Kamil Trzciński
98fd092053 display: rename DisplayBufferRef to DisplayRef (#5002) 2023-07-10 16:38:28 -05:00
Jesse Hills
feee075122 Merge pull request #5077 from esphome/bump-2023.6.5
2023.6.5
2023-07-10 13:53:25 +12:00
Jimmy Hedman
ddde1ee31e Allow pillow versions over 10 (#5071) 2023-07-10 11:34:43 +12:00
Jimmy Hedman
c5aacdd682 Update RP2040 Aruino framwork and platform to latest (#5025) 2023-07-10 11:30:39 +12:00
Jesse Hills
a77cf1beec Bump version to 2023.6.5 2023-07-10 11:24:49 +12:00
Kevin P. Fleming
d7bfdd0efc binary_sensor: Validate max_length for on_click/on_double_click (#5068) 2023-07-10 11:24:49 +12:00
J. Nick Koston
62aee36f82 Fix bulk and single Bluetooth parser coexistence (#5073) 2023-07-10 11:24:49 +12:00
Trevor North
8ca9115dc8 Improve BME680 BSEC sensor device classes (#4859) 2023-07-10 10:03:54 +12:00
Fabian
8bf8892ab3 [Ethernet] ksz8081rna support (#4739)
Co-authored-by: Your Name <you@example.com>
2023-07-10 10:02:42 +12:00
Kevin P. Fleming
8739552c0b binary_sensor: Validate max_length for on_click/on_double_click (#5068) 2023-07-10 09:55:02 +12:00
J. Nick Koston
e6834f25ed Fix bulk and single Bluetooth parser coexistence (#5073) 2023-07-10 09:08:46 +12:00
NP v/d Spek
f9fc438de8 Fixed ili9xxx_display update() method (#5013)
There was an obsolete `if` statement left over from an other implementation.
2023-07-05 20:58:04 -05:00
Kamil Trzciński
677b2c6618 display: split DisplayBuffer and Display (#5001) 2023-07-05 14:33:26 -05:00
Tobias Oort
301a78f983 Adds 1.54" e-ink display (gdew0154m09) support to waveshare_epaper component (#4939)
* Added GDEW0154M09 in waveshare_epaper component

* noop change - trigger workflow

* Make linter happy

* Update test4.yaml

* linter doing linty things

* revert the newline removal.

* revert to prove unstable test

* add code back into test.

* no partial updates supported yet - removed from test.

* Update esphome/components/waveshare_epaper/waveshare_epaper.cpp

Co-authored-by: Keith Burzinski <kbx81x@gmail.com>

---------

Co-authored-by: Keith Burzinski <kbx81x@gmail.com>
2023-07-05 14:32:00 -05:00
Jimmy Hedman
979f014799 Make scheduler debuging work with idf >= 5 (#5052) 2023-07-05 22:05:27 +12:00
Fabian
a326dcaf0e [ili9xxx] Allow config of spi data rate. (#4701)
* Allow 80MHz ili9xxx display.

* python foo.

* update based on feedback.

* Change python

---------

Co-authored-by: Your Name <you@example.com>
2023-07-05 02:53:14 -05:00
lnicolas83
5bf2fa5c56 [ILI9xxx] Add ili9488_a (alternative gamma configuration for ILI9488) (#5027)
* Add ili9488_a

* Fix clang-tidy
2023-07-04 19:21:26 -05:00
Jimmy Hedman
fe0404a084 Some tests wasn't running (locally) (#5050) 2023-07-05 10:33:46 +12:00
Jesse Hills
22a1134f0e Fix when idf component has broken symlinks (#5058) 2023-07-05 10:31:58 +12:00
Jimmy Hedman
fc3d558d47 Initial debug component support for rp2040 (#5056) 2023-07-05 10:28:12 +12:00
Jesse Hills
45c72f1f22 Log start of i2c setup (#5049) 2023-07-04 15:26:31 +12:00
Jesse Hills
fd9cca565b Log component long time message at warning level (#5048) 2023-07-04 15:02:53 +12:00
Jesse Hills
d64d1650e3 Update webserver to ea86d81 (#5023) 2023-07-04 13:45:06 +12:00
J. Nick Koston
a74abb8ea8 Adjust signature for on_disconnect (#5009) 2023-07-04 12:57:44 +12:00
Fabian
e74ab00b3e Mopeka std fixes (#5041)
Co-authored-by: Your Name <you@example.com>
2023-07-04 12:55:04 +12:00
J. Nick Koston
2e2ac53071 Advertise noise is enabled (#5034) 2023-07-04 12:52:42 +12:00
Jimmy Hedman
87c0f48095 Prepare debug and logger component to work with idf 5.0 (#5036) 2023-07-04 12:49:27 +12:00
Jimmy Hedman
25b9bde0a5 Prepare ethernet to work with esp idf 5.0 (#5037) 2023-07-04 12:48:05 +12:00
guillempages
63d3a0e8b3 Improve the gamma settings for the S3-Box-lite display (#5046) 2023-07-04 12:43:03 +12:00
Graham Brown
4cc0f3fd53 Add alarm to reserved ids (#5042) 2023-07-04 12:28:19 +12:00
Sergey Dudanov
5b2176562b binary_sensor filters templatable delays (#5029) 2023-07-04 12:25:48 +12:00
Sergey Dudanov
099dc8d1d2 fix template binary_sensor publish_initial_state option (#5033) 2023-07-04 12:18:51 +12:00
Sergey Dudanov
cf98c497d5 binary_sensor removed unused filter (#5039) 2023-07-03 10:35:53 +12:00
dependabot[bot]
c5eb3941b9 Bump pytest-mock from 3.10.0 to 3.11.1 (#4977)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-29 11:44:23 +12:00
dependabot[bot]
0e93b8ee0d Bump esptool from 4.6 to 4.6.2 (#4949)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-29 11:44:10 +12:00
Ryan DeShone
807621402d [SCD30] Disable negative temperature offset (#4850) 2023-06-29 11:42:39 +12:00
Jesse Hills
ac5246e21d Remove yaml test cache (#5019) 2023-06-28 12:22:14 +12:00
Jesse Hills
951157dc26 Add CONFIG_BT_BLE_42_FEATURES_SUPPORTED for ble (#5008) 2023-06-28 11:35:35 +12:00
Jesse Hills
68119ddcd4 Attempt to fix script parameters (#4627) 2023-06-28 11:34:08 +12:00
F.D.Castel
c82be2cd60 Fixes compressed downloads (#5014)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2023-06-28 11:13:14 +12:00
esphomebot
9a149a7aba Synchronise Device Classes from Home Assistant (#5018) 2023-06-27 22:19:36 +00:00
dependabot[bot]
108fabe18f Bump pytest from 7.3.2 to 7.4.0 (#5000)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-28 09:41:39 +12:00
dependabot[bot]
8ce98dd15a Bump pyupgrade from 3.4.0 to 3.7.0 (#4971)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2023-06-28 09:41:02 +12:00
dependabot[bot]
9d21cccac1 Bump aioesphomeapi from 14.1.0 to 15.0.0 (#5012)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-28 09:40:08 +12:00
Jesse Hills
8f4abf6a63 Update sync workflow (#5017) 2023-06-28 09:34:31 +12:00
jerome992
bd9a4ff8de add water delivered to dsmr component (#4237)
Co-authored-by: Jerome <jerome992@internet.lu>
2023-06-27 15:35:20 -03:00
Philippe Vlérick
d9398a91d1 update dsmr to 0.7 (#5011) 2023-06-26 17:09:52 -03:00
Jesse Hills
ef84937fd6 Update webserver to 56d73b5 (#5007) 2023-06-26 10:27:03 +12:00
Guillermo Ruffino
2a2d20a7fc support empty schemas and one platform components (#4999) 2023-06-26 09:38:36 +12:00
Kamil Trzciński
8a1c49a4ae display: move Image, Font and Animation code into components (#4967)
* display: move `Font` to `components/font`

* display: move `Animation` to `components/animation`

* display: move `Image` to `components/image`
2023-06-24 17:56:29 -05:00
Jesse Hills
eb145757e5 Fix rp2040 pio tool download (#4994) 2023-06-23 16:42:37 +12:00
Samuel Sieb
fc0e1a3cb9 remove unused static declarations (#4993) 2023-06-23 13:03:31 +12:00
Jesse Hills
ec3d5fc427 Merge branch 'release' into dev 2023-06-23 07:49:08 +12:00
Kamil Trzciński
85608a8ab7 display: fix white screen on binary displays (#4991) 2023-06-22 14:18:29 -05:00
Jimmy Hedman
52d7d2cae7 Make ethernet_info work with esp-idf framework (#4976) 2023-06-22 16:09:00 +12:00
F.D.Castel
f72b07eb0e dashboard: Adds "compressed=1" to /download.bin endpoint. (...) (#4966)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2023-06-22 11:48:17 +12:00
J. Nick Koston
314c1c8b5c Migrate VOC sensors that use ppb to use volatile_organic_compounds_parts device class (#4982) 2023-06-22 11:45:41 +12:00
Jesse Hills
211453df43 Update webserver and captive portal pages to 67c48ee9 (#4986) 2023-06-22 10:12:25 +12:00
Dion Hulse
1cc7428445 Add configuration option to disable the log UI. (#4419)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2023-06-22 09:58:49 +12:00
Jesse Hills
e8ce7048d8 Fix pypi release (#4983) 2023-06-21 16:36:54 +12:00
Jesse Hills
de6c527ca4 Bump esphome-dashboard to 20230621.0 (#4980) 2023-06-21 14:30:19 +12:00
Onne
9e7e3708e3 Make growatt play nicer with other modbus components. (#4947)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2023-06-21 00:22:32 +00:00
Kevin P. Fleming
8bd9f50659 airthings_wave: refactor to eliminate code duplication (#4910) 2023-06-21 11:53:44 +12:00
Stijn Tintel
cb5a01da29 mqtt: add ESP-IDF >= 5.0 support (#4854)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2023-06-20 23:53:32 +00:00
Martin Murray
bfe85dd710 Apply configured IIR filter setting in generated BMP280 code (#4975)
Co-authored-by: Martin Murray <murrayma@gmail.com>
2023-06-21 11:53:21 +12:00
dependabot[bot]
24067312f6 Bump zeroconf from 0.63.0 to 0.69.0 (#4970)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-20 11:18:06 +12:00
guillempages
ee12c68b8f Add actions to animation (#4959) 2023-06-20 10:50:02 +12:00
dependabot[bot]
b2ccd32cd7 Bump aioesphomeapi from 14.0.0 to 14.1.0 (#4972)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-20 10:35:26 +12:00
Jimmy Hedman
7ceb16cc5a Preprocess away unused code when IPv6 is disabled (#4973) 2023-06-20 10:34:46 +12:00
Jesse Hills
5a8b7c17da Fix python venv restoring (#4965)
* Fix python venv restoring

* Add shell

* Fix indentation
2023-06-19 16:08:23 -05:00
MrEditor97
41a618737b XL9535 I/O Expander (#4899)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2023-06-19 15:26:06 +12:00
Carson Full
67771abc9d Add read/write for 16bit registers (#4844) 2023-06-19 14:10:05 +12:00
dependabot[bot]
c151df32bc Bump pytest from 7.3.1 to 7.3.2 (#4936)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-19 13:58:01 +12:00
Stanislav Habich
b346ad8080 Update pca9685_output.cpp (#4929) 2023-06-19 13:56:12 +12:00
J. Nick Koston
cd57271386 Construct web_server assets at build time instead of run time (#4944)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2023-06-19 13:51:19 +12:00
J. Nick Koston
b9f20b36cb Store app comment and compilation_time in flash (#4945) 2023-06-19 11:35:47 +12:00
Kamil Trzciński
62d2640c37 display: move Rect into rect.cpp/.h (#4957) 2023-06-18 23:32:39 +00:00
Kamil Trzciński
54eb52c19a display/font: optimise font rendering by about 25% (#4956) 2023-06-18 23:29:43 +00:00
Hawawa McTaru
77a7d3f24b Fix for Fujitsu AC not having Quiet Fan Mode (#4962) 2023-06-19 11:20:32 +12:00
Kamil Trzciński
8c9d63f48f display: add BaseFont and introduce Font::draw methods (#4963) 2023-06-19 11:04:19 +12:00
Pavlo Dudnytskyi
5a8e93ed0a Upgraded Haier climate component implementation (#4521)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
Co-authored-by: Pavlo Dudnytskyi <pdudnytskyi@astrata.eu>
Co-authored-by: esphomebot <esphome@nabucasa.com>
2023-06-19 10:24:52 +12:00
Jesse Hills
d4099d68a7 Use HW SPI for rp2040 (#4955) 2023-06-19 07:24:44 +12:00
Kamil Trzciński
e1b0d86098 display: allow to align image with ImageAlign (#4933) 2023-06-19 07:24:23 +12:00
guillempages
1a7f121ac6 Add support for ESP32-S3-BOX displays (#4942)
The ESP32-S3-BOX display has an ILI9xxx driver
Add the needed configuration so that it works.
2023-06-17 03:38:44 -05:00
guillempages
ffa669899a Split display_buffer sub-components into own files (#4950)
* Split display_buffer sub-components into own files

Move the Image, Animation and Font classes to their own h/cpp pairs,
instead of having everything into the display_buffer h/cpp files.

* Fixed COLOR_ON duplicate definition
2023-06-17 03:32:07 -05:00
guillempages
17fed954bf Add support for ESP32-S3-BOX-Lite displays (#4941) 2023-06-16 11:39:50 +12:00
Samuel Sieb
467e42d8aa fix vbus sensor offsets (#4952) 2023-06-15 01:05:28 -07:00
Clyde Stubbs
a023f24a08 Add support in vbus component for Deltasol BS 2009 (#4943) 2023-06-14 23:51:44 -07:00
Jesse Hills
27f69f5439 Bump version to 2023.7.0-dev 2023-06-15 14:25:36 +12:00
398 changed files with 15665 additions and 3871 deletions

View File

@@ -5,9 +5,12 @@ Checks: >-
-altera-*, -altera-*,
-android-*, -android-*,
-boost-*, -boost-*,
-bugprone-easily-swappable-parameters,
-bugprone-implicit-widening-of-multiplication-result,
-bugprone-narrowing-conversions, -bugprone-narrowing-conversions,
-bugprone-signed-char-misuse, -bugprone-signed-char-misuse,
-cert-dcl50-cpp, -cert-dcl50-cpp,
-cert-err33-c,
-cert-err58-cpp, -cert-err58-cpp,
-cert-oop57-cpp, -cert-oop57-cpp,
-cert-str34-c, -cert-str34-c,
@@ -15,6 +18,7 @@ Checks: >-
-clang-analyzer-osx.*, -clang-analyzer-osx.*,
-clang-diagnostic-delete-abstract-non-virtual-dtor, -clang-diagnostic-delete-abstract-non-virtual-dtor,
-clang-diagnostic-delete-non-abstract-non-virtual-dtor, -clang-diagnostic-delete-non-abstract-non-virtual-dtor,
-clang-diagnostic-ignored-optimization-argument,
-clang-diagnostic-shadow-field, -clang-diagnostic-shadow-field,
-clang-diagnostic-unused-const-variable, -clang-diagnostic-unused-const-variable,
-clang-diagnostic-unused-parameter, -clang-diagnostic-unused-parameter,
@@ -25,6 +29,7 @@ Checks: >-
-cppcoreguidelines-macro-usage, -cppcoreguidelines-macro-usage,
-cppcoreguidelines-narrowing-conversions, -cppcoreguidelines-narrowing-conversions,
-cppcoreguidelines-non-private-member-variables-in-classes, -cppcoreguidelines-non-private-member-variables-in-classes,
-cppcoreguidelines-prefer-member-initializer,
-cppcoreguidelines-pro-bounds-array-to-pointer-decay, -cppcoreguidelines-pro-bounds-array-to-pointer-decay,
-cppcoreguidelines-pro-bounds-constant-array-index, -cppcoreguidelines-pro-bounds-constant-array-index,
-cppcoreguidelines-pro-bounds-pointer-arithmetic, -cppcoreguidelines-pro-bounds-pointer-arithmetic,
@@ -36,6 +41,7 @@ Checks: >-
-cppcoreguidelines-pro-type-union-access, -cppcoreguidelines-pro-type-union-access,
-cppcoreguidelines-pro-type-vararg, -cppcoreguidelines-pro-type-vararg,
-cppcoreguidelines-special-member-functions, -cppcoreguidelines-special-member-functions,
-cppcoreguidelines-virtual-class-destructor,
-fuchsia-multiple-inheritance, -fuchsia-multiple-inheritance,
-fuchsia-overloaded-operator, -fuchsia-overloaded-operator,
-fuchsia-statically-constructed-objects, -fuchsia-statically-constructed-objects,
@@ -68,6 +74,7 @@ Checks: >-
-modernize-use-nodiscard, -modernize-use-nodiscard,
-mpi-*, -mpi-*,
-objc-*, -objc-*,
-readability-container-data-pointer,
-readability-convert-member-functions-to-static, -readability-convert-member-functions-to-static,
-readability-else-after-return, -readability-else-after-return,
-readability-function-cognitive-complexity, -readability-function-cognitive-complexity,
@@ -82,8 +89,6 @@ WarningsAsErrors: '*'
AnalyzeTemporaryDtors: false AnalyzeTemporaryDtors: false
FormatStyle: google FormatStyle: google
CheckOptions: CheckOptions:
- key: google-readability-braces-around-statements.ShortStatementLines
value: '1'
- key: google-readability-function-size.StatementThreshold - key: google-readability-function-size.StatementThreshold
value: '800' value: '800'
- key: google-runtime-int.TypeSuffix - key: google-runtime-int.TypeSuffix
@@ -158,3 +163,9 @@ CheckOptions:
value: '' value: ''
- key: readability-qualified-auto.AddConstToQualified - key: readability-qualified-auto.AddConstToQualified
value: 0 value: 0
- key: readability-identifier-length.MinimumVariableNameLength
value: 0
- key: readability-identifier-length.MinimumParameterNameLength
value: 0
- key: readability-identifier-length.MinimumLoopCounterNameLength
value: 0

View File

@@ -0,0 +1,38 @@
name: Restore Python
inputs:
python-version:
description: Python version to restore
required: true
type: string
cache-key:
description: Cache key to use
required: true
type: string
outputs:
python-version:
description: Python version restored
value: ${{ steps.python.outputs.python-version }}
runs:
using: "composite"
steps:
- name: Set up Python ${{ inputs.python-version }}
id: python
uses: actions/setup-python@v4.6.0
with:
python-version: ${{ inputs.python-version }}
- name: Restore Python virtual environment
id: cache-venv
uses: actions/cache/restore@v3.3.1
with:
path: venv
# yamllint disable-line rule:line-length
key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-venv-${{ inputs.cache-key }}
- name: Create Python virtual environment
if: steps.cache-venv.outputs.cache-hit != 'true'
shell: bash
run: |
python -m venv venv
. venv/bin/activate
python --version
pip install -r requirements.txt -r requirements_optional.txt -r requirements_test.txt
pip install -e .

View File

@@ -26,10 +26,16 @@ jobs:
common: common:
name: Create common environment name: Create common environment
runs-on: ubuntu-latest runs-on: ubuntu-latest
outputs:
cache-key: ${{ steps.cache-key.outputs.key }}
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v3.5.2 uses: actions/checkout@v3.5.2
- name: Generate cache-key
id: cache-key
run: echo key="${{ hashFiles('requirements.txt', 'requirements_optional.txt', 'requirements_test.txt') }}" >> $GITHUB_OUTPUT
- name: Set up Python ${{ env.DEFAULT_PYTHON }} - name: Set up Python ${{ env.DEFAULT_PYTHON }}
id: python
uses: actions/setup-python@v4.6.0 uses: actions/setup-python@v4.6.0
with: with:
python-version: ${{ env.DEFAULT_PYTHON }} python-version: ${{ env.DEFAULT_PYTHON }}
@@ -39,7 +45,7 @@ jobs:
with: with:
path: venv path: venv
# yamllint disable-line rule:line-length # yamllint disable-line rule:line-length
key: ${{ runner.os }}-${{ env.DEFAULT_PYTHON }}-venv-${{ hashFiles('requirements.txt', 'requirements_optional.txt', 'requirements_test.txt') }} key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-venv-${{ steps.cache-key.outputs.key }}
- name: Create Python virtual environment - name: Create Python virtual environment
if: steps.cache-venv.outputs.cache-hit != 'true' if: steps.cache-venv.outputs.cache-hit != 'true'
run: | run: |
@@ -66,12 +72,11 @@ jobs:
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v3.5.2 uses: actions/checkout@v3.5.2
- name: Restore Python virtual environment - name: Restore Python
uses: actions/cache/restore@v3.3.1 uses: ./.github/actions/restore-python
with: with:
path: venv python-version: ${{ env.DEFAULT_PYTHON }}
# yamllint disable-line rule:line-length cache-key: ${{ needs.common.outputs.cache-key }}
key: ${{ runner.os }}-${{ env.DEFAULT_PYTHON }}-venv-${{ hashFiles('requirements.txt', 'requirements_optional.txt', 'requirements_test.txt') }}
- name: Run black - name: Run black
run: | run: |
. venv/bin/activate . venv/bin/activate
@@ -88,12 +93,11 @@ jobs:
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v3.5.2 uses: actions/checkout@v3.5.2
- name: Restore Python virtual environment - name: Restore Python
uses: actions/cache/restore@v3.3.1 uses: ./.github/actions/restore-python
with: with:
path: venv python-version: ${{ env.DEFAULT_PYTHON }}
# yamllint disable-line rule:line-length cache-key: ${{ needs.common.outputs.cache-key }}
key: ${{ runner.os }}-${{ env.DEFAULT_PYTHON }}-venv-${{ hashFiles('requirements.txt', 'requirements_optional.txt', 'requirements_test.txt') }}
- name: Run flake8 - name: Run flake8
run: | run: |
. venv/bin/activate . venv/bin/activate
@@ -110,12 +114,11 @@ jobs:
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v3.5.2 uses: actions/checkout@v3.5.2
- name: Restore Python virtual environment - name: Restore Python
uses: actions/cache/restore@v3.3.1 uses: ./.github/actions/restore-python
with: with:
path: venv python-version: ${{ env.DEFAULT_PYTHON }}
# yamllint disable-line rule:line-length cache-key: ${{ needs.common.outputs.cache-key }}
key: ${{ runner.os }}-${{ env.DEFAULT_PYTHON }}-venv-${{ hashFiles('requirements.txt', 'requirements_optional.txt', 'requirements_test.txt') }}
- name: Run pylint - name: Run pylint
run: | run: |
. venv/bin/activate . venv/bin/activate
@@ -132,12 +135,11 @@ jobs:
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v3.5.2 uses: actions/checkout@v3.5.2
- name: Restore Python virtual environment - name: Restore Python
uses: actions/cache/restore@v3.3.1 uses: ./.github/actions/restore-python
with: with:
path: venv python-version: ${{ env.DEFAULT_PYTHON }}
# yamllint disable-line rule:line-length cache-key: ${{ needs.common.outputs.cache-key }}
key: ${{ runner.os }}-${{ env.DEFAULT_PYTHON }}-venv-${{ hashFiles('requirements.txt', 'requirements_optional.txt', 'requirements_test.txt') }}
- name: Run pyupgrade - name: Run pyupgrade
run: | run: |
. venv/bin/activate . venv/bin/activate
@@ -154,12 +156,11 @@ jobs:
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v3.5.2 uses: actions/checkout@v3.5.2
- name: Restore Python virtual environment - name: Restore Python
uses: actions/cache/restore@v3.3.1 uses: ./.github/actions/restore-python
with: with:
path: venv python-version: ${{ env.DEFAULT_PYTHON }}
# yamllint disable-line rule:line-length cache-key: ${{ needs.common.outputs.cache-key }}
key: ${{ runner.os }}-${{ env.DEFAULT_PYTHON }}-venv-${{ hashFiles('requirements.txt', 'requirements_optional.txt', 'requirements_test.txt') }}
- name: Register matcher - name: Register matcher
run: echo "::add-matcher::.github/workflows/matchers/ci-custom.json" run: echo "::add-matcher::.github/workflows/matchers/ci-custom.json"
- name: Run script/ci-custom - name: Run script/ci-custom
@@ -176,12 +177,11 @@ jobs:
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v3.5.2 uses: actions/checkout@v3.5.2
- name: Restore Python virtual environment - name: Restore Python
uses: actions/cache/restore@v3.3.1 uses: ./.github/actions/restore-python
with: with:
path: venv python-version: ${{ env.DEFAULT_PYTHON }}
# yamllint disable-line rule:line-length cache-key: ${{ needs.common.outputs.cache-key }}
key: ${{ runner.os }}-${{ env.DEFAULT_PYTHON }}-venv-${{ hashFiles('requirements.txt', 'requirements_optional.txt', 'requirements_test.txt') }}
- name: Register matcher - name: Register matcher
run: echo "::add-matcher::.github/workflows/matchers/pytest.json" run: echo "::add-matcher::.github/workflows/matchers/pytest.json"
- name: Run pytest - name: Run pytest
@@ -197,12 +197,11 @@ jobs:
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v3.5.2 uses: actions/checkout@v3.5.2
- name: Restore Python virtual environment - name: Restore Python
uses: actions/cache/restore@v3.3.1 uses: ./.github/actions/restore-python
with: with:
path: venv python-version: ${{ env.DEFAULT_PYTHON }}
# yamllint disable-line rule:line-length cache-key: ${{ needs.common.outputs.cache-key }}
key: ${{ runner.os }}-${{ env.DEFAULT_PYTHON }}-venv-${{ hashFiles('requirements.txt', 'requirements_optional.txt', 'requirements_test.txt') }}
- name: Install clang-format - name: Install clang-format
run: | run: |
. venv/bin/activate . venv/bin/activate
@@ -237,18 +236,11 @@ jobs:
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v3.5.2 uses: actions/checkout@v3.5.2
- name: Restore Python virtual environment - name: Restore Python
uses: actions/cache/restore@v3.3.1 uses: ./.github/actions/restore-python
with: with:
path: venv python-version: ${{ env.DEFAULT_PYTHON }}
# yamllint disable-line rule:line-length cache-key: ${{ needs.common.outputs.cache-key }}
key: ${{ runner.os }}-${{ env.DEFAULT_PYTHON }}-venv-${{ hashFiles('requirements.txt', 'requirements_optional.txt', 'requirements_test.txt') }}
- name: Cache platformio
uses: actions/cache@v3.3.1
with:
path: ~/.platformio
# yamllint disable-line rule:line-length
key: platformio-test${{ matrix.file }}-${{ hashFiles('platformio.ini') }}
- name: Run esphome compile tests/test${{ matrix.file }}.yaml - name: Run esphome compile tests/test${{ matrix.file }}.yaml
run: | run: |
. venv/bin/activate . venv/bin/activate
@@ -300,13 +292,11 @@ jobs:
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v3.5.2 uses: actions/checkout@v3.5.2
- name: Restore Python virtual environment - name: Restore Python
uses: actions/cache/restore@v3.3.1 uses: ./.github/actions/restore-python
with: with:
path: venv python-version: ${{ env.DEFAULT_PYTHON }}
# yamllint disable-line rule:line-length cache-key: ${{ needs.common.outputs.cache-key }}
key: ${{ runner.os }}-${{ env.DEFAULT_PYTHON }}-venv-${{ hashFiles('requirements.txt', 'requirements_optional.txt', 'requirements_test.txt') }}
# Use per check platformio cache because checks use different parts
- name: Cache platformio - name: Cache platformio
uses: actions/cache@v3.3.1 uses: actions/cache@v3.3.1
with: with:
@@ -315,7 +305,7 @@ jobs:
key: platformio-${{ matrix.pio_cache_key }}-${{ hashFiles('platformio.ini') }} key: platformio-${{ matrix.pio_cache_key }}-${{ hashFiles('platformio.ini') }}
- name: Install clang-tidy - name: Install clang-tidy
run: sudo apt-get install clang-tidy-11 run: sudo apt-get install clang-tidy-14
- name: Register problem matchers - name: Register problem matchers
run: | run: |

View File

@@ -6,14 +6,12 @@ on:
schedule: schedule:
- cron: '45 6 * * *' - cron: '45 6 * * *'
permissions:
contents: write
pull-requests: write
jobs: jobs:
sync: sync:
name: Sync Device Classes name: Sync Device Classes
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: github.repository == 'esphome/esphome'
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v3
@@ -38,15 +36,6 @@ jobs:
run: | run: |
python ./script/sync-device_class.py python ./script/sync-device_class.py
- name: Get PR template
id: pr-template-body
run: |
body=$(cat .github/PULL_REQUEST_TEMPLATE.md)
delimiter="$(openssl rand -hex 8)"
echo "body<<$delimiter" >> $GITHUB_OUTPUT
echo "$body" >> $GITHUB_OUTPUT
echo "$delimiter" >> $GITHUB_OUTPUT
- name: Commit changes - name: Commit changes
uses: peter-evans/create-pull-request@v5 uses: peter-evans/create-pull-request@v5
with: with:
@@ -56,5 +45,5 @@ jobs:
branch: sync/device-classes branch: sync/device-classes
delete-branch: true delete-branch: true
title: "Synchronise Device Classes from Home Assistant" title: "Synchronise Device Classes from Home Assistant"
body: ${{ steps.pr-template-body.outputs.body }} body-path: .github/PULL_REQUEST_TEMPLATE.md
token: ${{ secrets.DEVICE_CLASS_SYNC_TOKEN }} token: ${{ secrets.DEVICE_CLASS_SYNC_TOKEN }}

4
.gitignore vendored
View File

@@ -129,4 +129,6 @@ tests/.esphome/
sdkconfig.* sdkconfig.*
!sdkconfig.defaults !sdkconfig.defaults
.tests/ .tests/
/components

View File

@@ -3,7 +3,7 @@
# See https://pre-commit.com/hooks.html for more hooks # See https://pre-commit.com/hooks.html for more hooks
repos: repos:
- repo: https://github.com/psf/black - repo: https://github.com/psf/black
rev: 23.3.0 rev: 23.7.0
hooks: hooks:
- id: black - id: black
args: args:
@@ -27,7 +27,7 @@ repos:
- --branch=release - --branch=release
- --branch=beta - --branch=beta
- repo: https://github.com/asottile/pyupgrade - repo: https://github.com/asottile/pyupgrade
rev: v3.4.0 rev: v3.10.1
hooks: hooks:
- id: pyupgrade - id: pyupgrade
args: [--py39-plus] args: [--py39-plus]

View File

@@ -11,16 +11,18 @@ esphome/*.py @esphome/core
esphome/core/* @esphome/core esphome/core/* @esphome/core
# Integrations # Integrations
esphome/components/a01nyub/* @MrSuicideParrot
esphome/components/absolute_humidity/* @DAVe3283 esphome/components/absolute_humidity/* @DAVe3283
esphome/components/ac_dimmer/* @glmnet esphome/components/ac_dimmer/* @glmnet
esphome/components/adc/* @esphome/core esphome/components/adc/* @esphome/core
esphome/components/adc128s102/* @DeerMaximum esphome/components/adc128s102/* @DeerMaximum
esphome/components/addressable_light/* @justfalter esphome/components/addressable_light/* @justfalter
esphome/components/airthings_ble/* @jeromelaban esphome/components/airthings_ble/* @jeromelaban
esphome/components/airthings_wave_base/* @jeromelaban @ncareau esphome/components/airthings_wave_base/* @jeromelaban @kpfleming @ncareau
esphome/components/airthings_wave_mini/* @ncareau esphome/components/airthings_wave_mini/* @ncareau
esphome/components/airthings_wave_plus/* @jeromelaban esphome/components/airthings_wave_plus/* @jeromelaban
esphome/components/alarm_control_panel/* @grahambrown11 esphome/components/alarm_control_panel/* @grahambrown11
esphome/components/alpha3/* @jan-hofmeier
esphome/components/am43/* @buxtronix esphome/components/am43/* @buxtronix
esphome/components/am43/cover/* @buxtronix esphome/components/am43/cover/* @buxtronix
esphome/components/am43/sensor/* @buxtronix esphome/components/am43/sensor/* @buxtronix
@@ -31,6 +33,7 @@ esphome/components/api/* @OttoWinter
esphome/components/as7341/* @mrgnr esphome/components/as7341/* @mrgnr
esphome/components/async_tcp/* @OttoWinter esphome/components/async_tcp/* @OttoWinter
esphome/components/atc_mithermometer/* @ahpohl esphome/components/atc_mithermometer/* @ahpohl
esphome/components/atm90e26/* @danieltwagner
esphome/components/b_parasite/* @rbaron esphome/components/b_parasite/* @rbaron
esphome/components/ballu/* @bazuchan esphome/components/ballu/* @bazuchan
esphome/components/bang_bang/* @OttoWinter esphome/components/bang_bang/* @OttoWinter
@@ -46,6 +49,7 @@ esphome/components/ble_client/* @buxtronix
esphome/components/bluetooth_proxy/* @jesserockz esphome/components/bluetooth_proxy/* @jesserockz
esphome/components/bme680_bsec/* @trvrnrth esphome/components/bme680_bsec/* @trvrnrth
esphome/components/bmp3xx/* @martgras esphome/components/bmp3xx/* @martgras
esphome/components/bmp581/* @kahrendt
esphome/components/bp1658cj/* @Cossid esphome/components/bp1658cj/* @Cossid
esphome/components/bp5758d/* @Cossid esphome/components/bp5758d/* @Cossid
esphome/components/button/* @esphome/core esphome/components/button/* @esphome/core
@@ -76,6 +80,7 @@ esphome/components/display_menu_base/* @numo68
esphome/components/dps310/* @kbx81 esphome/components/dps310/* @kbx81
esphome/components/ds1307/* @badbadc0ffee esphome/components/ds1307/* @badbadc0ffee
esphome/components/dsmr/* @glmnet @zuidwijk esphome/components/dsmr/* @glmnet @zuidwijk
esphome/components/duty_time/* @dudanov
esphome/components/ee895/* @Stock-M esphome/components/ee895/* @Stock-M
esphome/components/ektf2232/* @jesserockz esphome/components/ektf2232/* @jesserockz
esphome/components/ens210/* @itn3rd77 esphome/components/ens210/* @itn3rd77
@@ -97,13 +102,15 @@ esphome/components/fastled_base/* @OttoWinter
esphome/components/feedback/* @ianchi esphome/components/feedback/* @ianchi
esphome/components/fingerprint_grow/* @OnFreund @loongyh esphome/components/fingerprint_grow/* @OnFreund @loongyh
esphome/components/fs3000/* @kahrendt esphome/components/fs3000/* @kahrendt
esphome/components/gcja5/* @gcormier
esphome/components/globals/* @esphome/core esphome/components/globals/* @esphome/core
esphome/components/gp8403/* @jesserockz esphome/components/gp8403/* @jesserockz
esphome/components/gpio/* @esphome/core esphome/components/gpio/* @esphome/core
esphome/components/gps/* @coogle esphome/components/gps/* @coogle
esphome/components/graph/* @synco esphome/components/graph/* @synco
esphome/components/grove_tb6612fng/* @max246
esphome/components/growatt_solar/* @leeuwte esphome/components/growatt_solar/* @leeuwte
esphome/components/haier/* @Yarikx esphome/components/haier/* @paveldn
esphome/components/havells_solar/* @sourabhjaiswal esphome/components/havells_solar/* @sourabhjaiswal
esphome/components/hbridge/fan/* @WeekendWarrior esphome/components/hbridge/fan/* @WeekendWarrior
esphome/components/hbridge/light/* @DotNetDann esphome/components/hbridge/light/* @DotNetDann
@@ -137,7 +144,7 @@ esphome/components/key_collector/* @ssieb
esphome/components/key_provider/* @ssieb esphome/components/key_provider/* @ssieb
esphome/components/kuntze/* @ssieb esphome/components/kuntze/* @ssieb
esphome/components/lcd_menu/* @numo68 esphome/components/lcd_menu/* @numo68
esphome/components/ld2410/* @sebcaps esphome/components/ld2410/* @regevbr @sebcaps
esphome/components/ledc/* @OttoWinter esphome/components/ledc/* @OttoWinter
esphome/components/light/* @esphome/core esphome/components/light/* @esphome/core
esphome/components/lilygo_t5_47/touchscreen/* @jesserockz esphome/components/lilygo_t5_47/touchscreen/* @jesserockz
@@ -200,6 +207,7 @@ esphome/components/output/* @esphome/core
esphome/components/pca6416a/* @Mat931 esphome/components/pca6416a/* @Mat931
esphome/components/pca9554/* @hwstar esphome/components/pca9554/* @hwstar
esphome/components/pcf85063/* @brogon esphome/components/pcf85063/* @brogon
esphome/components/pcf8563/* @KoenBreeman
esphome/components/pid/* @OttoWinter esphome/components/pid/* @OttoWinter
esphome/components/pipsolar/* @andreashergert1984 esphome/components/pipsolar/* @andreashergert1984
esphome/components/pm1006/* @habbie esphome/components/pm1006/* @habbie
@@ -294,6 +302,7 @@ esphome/components/tof10120/* @wstrzalka
esphome/components/toshiba/* @kbx81 esphome/components/toshiba/* @kbx81
esphome/components/touchscreen/* @jesserockz esphome/components/touchscreen/* @jesserockz
esphome/components/tsl2591/* @wjcarpenter esphome/components/tsl2591/* @wjcarpenter
esphome/components/tt21100/* @kroimon
esphome/components/tuya/binary_sensor/* @jesserockz esphome/components/tuya/binary_sensor/* @jesserockz
esphome/components/tuya/climate/* @jesserockz esphome/components/tuya/climate/* @jesserockz
esphome/components/tuya/number/* @frankiboy1 esphome/components/tuya/number/* @frankiboy1
@@ -310,6 +319,7 @@ esphome/components/version/* @esphome/core
esphome/components/voice_assistant/* @jesserockz esphome/components/voice_assistant/* @jesserockz
esphome/components/wake_on_lan/* @willwill2will54 esphome/components/wake_on_lan/* @willwill2will54
esphome/components/web_server_base/* @OttoWinter esphome/components/web_server_base/* @OttoWinter
esphome/components/web_server_idf/* @dentra
esphome/components/whirlpool/* @glmnet esphome/components/whirlpool/* @glmnet
esphome/components/whynter/* @aeonsablaze esphome/components/whynter/* @aeonsablaze
esphome/components/wiegand/* @ssieb esphome/components/wiegand/* @ssieb
@@ -319,4 +329,6 @@ esphome/components/xiaomi_lywsd03mmc/* @ahpohl
esphome/components/xiaomi_mhoc303/* @drug123 esphome/components/xiaomi_mhoc303/* @drug123
esphome/components/xiaomi_mhoc401/* @vevsvevs esphome/components/xiaomi_mhoc401/* @vevsvevs
esphome/components/xiaomi_rtcgq02lm/* @jesserockz esphome/components/xiaomi_rtcgq02lm/* @jesserockz
esphome/components/xl9535/* @mreditor97
esphome/components/xpt2046/* @nielsnl68 @numo68 esphome/components/xpt2046/* @nielsnl68 @numo68
esphome/components/zio_ultrasonic/* @kahrendt

View File

@@ -22,16 +22,23 @@ RUN \
python3=3.9.2-3 \ python3=3.9.2-3 \
python3-pip=20.3.4-4+deb11u1 \ python3-pip=20.3.4-4+deb11u1 \
python3-setuptools=52.0.0-4 \ python3-setuptools=52.0.0-4 \
python3-pil=8.1.2+dfsg-0.3+deb11u1 \
python3-cryptography=3.3.2-1 \ python3-cryptography=3.3.2-1 \
python3-venv=3.9.2-3 \ python3-venv=3.9.2-3 \
iputils-ping=3:20210202-1 \ iputils-ping=3:20210202-1 \
git=1:2.30.2-1+deb11u2 \ git=1:2.30.2-1+deb11u2 \
curl=7.74.0-1.3+deb11u7 \ curl=7.74.0-1.3+deb11u7 \
openssh-client=1:8.4p1-5+deb11u1 \ openssh-client=1:8.4p1-5+deb11u1 \
libcairo2=1.16.0-5 \
python3-cffi=1.14.5-1 \ python3-cffi=1.14.5-1 \
&& rm -rf \ libcairo2=1.16.0-5; \
if [ "$TARGETARCH$TARGETVARIANT" = "armv7" ]; then \
apt-get install -y --no-install-recommends \
build-essential=12.9 \
python3-dev=3.9.2-3 \
zlib1g-dev=1:1.2.11.dfsg-2+deb11u2 \
libjpeg-dev=1:2.0.6-4 \
libfreetype-dev=2.10.4+dfsg-1+deb11u1; \
fi; \
rm -rf \
/tmp/* \ /tmp/* \
/var/{cache,log}/* \ /var/{cache,log}/* \
/var/lib/apt/lists/* /var/lib/apt/lists/*

View File

@@ -32,7 +32,7 @@ from esphome.const import (
SECRETS_FILES, SECRETS_FILES,
) )
from esphome.core import CORE, EsphomeError, coroutine from esphome.core import CORE, EsphomeError, coroutine
from esphome.helpers import indent from esphome.helpers import indent, is_ip_address
from esphome.util import ( from esphome.util import (
run_external_command, run_external_command,
run_external_process, run_external_process,
@@ -308,8 +308,10 @@ def upload_program(config, args, host):
password = ota_conf.get(CONF_PASSWORD, "") password = ota_conf.get(CONF_PASSWORD, "")
if ( if (
get_port_type(host) == "MQTT" or config[CONF_MDNS][CONF_DISABLED] not is_ip_address(CORE.address)
) and CONF_MQTT in config: and (get_port_type(host) == "MQTT" or config[CONF_MDNS][CONF_DISABLED])
and CONF_MQTT in config
):
from esphome import mqtt from esphome import mqtt
host = mqtt.get_esphome_device_ip( host = mqtt.get_esphome_device_ip(
@@ -363,10 +365,16 @@ def command_wizard(args):
def command_config(args, config): def command_config(args, config):
_LOGGER.info("Configuration is valid!")
if not CORE.verbose: if not CORE.verbose:
config = strip_default_ids(config) config = strip_default_ids(config)
safe_print(yaml_util.dump(config, args.show_secrets)) output = yaml_util.dump(config, args.show_secrets)
# add the console decoration so the front-end can hide the secrets
if not args.show_secrets:
output = re.sub(
r"(password|key|psk|ssid)\:\s(.*)", r"\1: \\033[5m\2\\033[6m", output
)
safe_print(output)
_LOGGER.info("Configuration is valid!")
return 0 return 0

View File

@@ -0,0 +1 @@
CODEOWNERS = ["@MrSuicideParrot"]

View File

@@ -0,0 +1,57 @@
// Datasheet https://wiki.dfrobot.com/A01NYUB%20Waterproof%20Ultrasonic%20Sensor%20SKU:%20SEN0313
#include "a01nyub.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
namespace esphome {
namespace a01nyub {
static const char *const TAG = "a01nyub.sensor";
static const uint8_t MAX_DATA_LENGTH_BYTES = 4;
void A01nyubComponent::loop() {
uint8_t data;
while (this->available() > 0) {
if (this->read_byte(&data)) {
buffer_.push_back(data);
this->check_buffer_();
}
}
}
void A01nyubComponent::check_buffer_() {
if (this->buffer_.size() >= MAX_DATA_LENGTH_BYTES) {
size_t i;
for (i = 0; i < this->buffer_.size(); i++) {
// Look for the first packet
if (this->buffer_[i] == 0xFF) {
if (i + 1 + 3 < this->buffer_.size()) { // Packet is not complete
return; // Wait for completion
}
uint8_t checksum = (this->buffer_[i] + this->buffer_[i + 1] + this->buffer_[i + 2]) & 0xFF;
if (this->buffer_[i + 3] == checksum) {
float distance = (this->buffer_[i + 1] << 8) + this->buffer_[i + 2];
if (distance > 280) {
float meters = distance / 1000.0;
ESP_LOGV(TAG, "Distance from sensor: %f mm, %f m", distance, meters);
this->publish_state(meters);
} else {
ESP_LOGW(TAG, "Invalid data read from sensor: %s", format_hex_pretty(this->buffer_).c_str());
}
}
break;
}
}
this->buffer_.clear();
}
}
void A01nyubComponent::dump_config() {
ESP_LOGCONFIG(TAG, "A01nyub Sensor:");
LOG_SENSOR(" ", "Distance", this);
}
} // namespace a01nyub
} // namespace esphome

View File

@@ -0,0 +1,27 @@
#pragma once
#include <vector>
#include "esphome/core/component.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/uart/uart.h"
namespace esphome {
namespace a01nyub {
class A01nyubComponent : public sensor::Sensor, public Component, public uart::UARTDevice {
public:
// Nothing really public.
// ========== INTERNAL METHODS ==========
void loop() override;
void dump_config() override;
protected:
void check_buffer_();
std::vector<uint8_t> buffer_;
};
} // namespace a01nyub
} // namespace esphome

View File

@@ -0,0 +1,41 @@
import esphome.codegen as cg
from esphome.components import sensor, uart
from esphome.const import (
STATE_CLASS_MEASUREMENT,
UNIT_METER,
ICON_ARROW_EXPAND_VERTICAL,
DEVICE_CLASS_DISTANCE,
)
CODEOWNERS = ["@MrSuicideParrot"]
DEPENDENCIES = ["uart"]
a01nyub_ns = cg.esphome_ns.namespace("a01nyub")
A01nyubComponent = a01nyub_ns.class_(
"A01nyubComponent", sensor.Sensor, cg.Component, uart.UARTDevice
)
CONFIG_SCHEMA = sensor.sensor_schema(
A01nyubComponent,
unit_of_measurement=UNIT_METER,
icon=ICON_ARROW_EXPAND_VERTICAL,
accuracy_decimals=3,
state_class=STATE_CLASS_MEASUREMENT,
device_class=DEVICE_CLASS_DISTANCE,
).extend(uart.UART_DEVICE_SCHEMA)
FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema(
"a01nyub",
baud_rate=9600,
require_tx=False,
require_rx=True,
data_bits=8,
parity=None,
stop_bits=1,
)
async def to_code(config):
var = await sensor.new_sensor(config)
await cg.register_component(var, config)
await uart.register_uart_device(var, config)

View File

@@ -28,6 +28,6 @@ async def to_code(config):
dir_pin = await cg.gpio_pin_expression(config[CONF_DIR_PIN]) dir_pin = await cg.gpio_pin_expression(config[CONF_DIR_PIN])
cg.add(var.set_dir_pin(dir_pin)) cg.add(var.set_dir_pin(dir_pin))
if CONF_SLEEP_PIN in config: if sleep_pin_config := config.get(CONF_SLEEP_PIN):
sleep_pin = await cg.gpio_pin_expression(config[CONF_SLEEP_PIN]) sleep_pin = await cg.gpio_pin_expression(sleep_pin_config)
cg.add(var.set_sleep_pin(sleep_pin)) cg.add(var.set_sleep_pin(sleep_pin))

View File

@@ -24,6 +24,7 @@ ATTENUATION_MODES = {
} }
adc1_channel_t = cg.global_ns.enum("adc1_channel_t") adc1_channel_t = cg.global_ns.enum("adc1_channel_t")
adc2_channel_t = cg.global_ns.enum("adc2_channel_t")
# From https://github.com/espressif/esp-idf/blob/master/components/driver/include/driver/adc_common.h # From https://github.com/espressif/esp-idf/blob/master/components/driver/include/driver/adc_common.h
# pin to adc1 channel mapping # pin to adc1 channel mapping
@@ -78,6 +79,49 @@ ESP32_VARIANT_ADC1_PIN_TO_CHANNEL = {
}, },
} }
ESP32_VARIANT_ADC2_PIN_TO_CHANNEL = {
# TODO: add other variants
VARIANT_ESP32: {
4: adc2_channel_t.ADC2_CHANNEL_0,
0: adc2_channel_t.ADC2_CHANNEL_1,
2: adc2_channel_t.ADC2_CHANNEL_2,
15: adc2_channel_t.ADC2_CHANNEL_3,
13: adc2_channel_t.ADC2_CHANNEL_4,
12: adc2_channel_t.ADC2_CHANNEL_5,
14: adc2_channel_t.ADC2_CHANNEL_6,
27: adc2_channel_t.ADC2_CHANNEL_7,
25: adc2_channel_t.ADC2_CHANNEL_8,
26: adc2_channel_t.ADC2_CHANNEL_9,
},
VARIANT_ESP32S2: {
11: adc2_channel_t.ADC2_CHANNEL_0,
12: adc2_channel_t.ADC2_CHANNEL_1,
13: adc2_channel_t.ADC2_CHANNEL_2,
14: adc2_channel_t.ADC2_CHANNEL_3,
15: adc2_channel_t.ADC2_CHANNEL_4,
16: adc2_channel_t.ADC2_CHANNEL_5,
17: adc2_channel_t.ADC2_CHANNEL_6,
18: adc2_channel_t.ADC2_CHANNEL_7,
19: adc2_channel_t.ADC2_CHANNEL_8,
20: adc2_channel_t.ADC2_CHANNEL_9,
},
VARIANT_ESP32S3: {
11: adc2_channel_t.ADC2_CHANNEL_0,
12: adc2_channel_t.ADC2_CHANNEL_1,
13: adc2_channel_t.ADC2_CHANNEL_2,
14: adc2_channel_t.ADC2_CHANNEL_3,
15: adc2_channel_t.ADC2_CHANNEL_4,
16: adc2_channel_t.ADC2_CHANNEL_5,
17: adc2_channel_t.ADC2_CHANNEL_6,
18: adc2_channel_t.ADC2_CHANNEL_7,
19: adc2_channel_t.ADC2_CHANNEL_8,
20: adc2_channel_t.ADC2_CHANNEL_9,
},
VARIANT_ESP32C3: {
5: adc2_channel_t.ADC2_CHANNEL_0,
},
}
def validate_adc_pin(value): def validate_adc_pin(value):
if str(value).upper() == "VCC": if str(value).upper() == "VCC":
@@ -89,11 +133,18 @@ def validate_adc_pin(value):
if CORE.is_esp32: if CORE.is_esp32:
value = pins.internal_gpio_input_pin_number(value) value = pins.internal_gpio_input_pin_number(value)
variant = get_esp32_variant() variant = get_esp32_variant()
if variant not in ESP32_VARIANT_ADC1_PIN_TO_CHANNEL: if (
variant not in ESP32_VARIANT_ADC1_PIN_TO_CHANNEL
and variant not in ESP32_VARIANT_ADC2_PIN_TO_CHANNEL
):
raise cv.Invalid(f"This ESP32 variant ({variant}) is not supported") raise cv.Invalid(f"This ESP32 variant ({variant}) is not supported")
if value not in ESP32_VARIANT_ADC1_PIN_TO_CHANNEL[variant]: if (
value not in ESP32_VARIANT_ADC1_PIN_TO_CHANNEL[variant]
and value not in ESP32_VARIANT_ADC2_PIN_TO_CHANNEL[variant]
):
raise cv.Invalid(f"{variant} doesn't support ADC on this pin") raise cv.Invalid(f"{variant} doesn't support ADC on this pin")
return pins.internal_gpio_input_pin_schema(value) return pins.internal_gpio_input_pin_schema(value)
if CORE.is_esp8266: if CORE.is_esp8266:
@@ -104,7 +155,7 @@ def validate_adc_pin(value):
) )
if value != 17: # A0 if value != 17: # A0
raise cv.Invalid("ESP8266: Only pin A0 (GPIO17) supports ADC.") raise cv.Invalid("ESP8266: Only pin A0 (GPIO17) supports ADC")
return pins.gpio_pin_schema( return pins.gpio_pin_schema(
{CONF_ANALOG: True, CONF_INPUT: True}, internal=True {CONF_ANALOG: True, CONF_INPUT: True}, internal=True
)(value) )(value)
@@ -112,7 +163,7 @@ def validate_adc_pin(value):
if CORE.is_rp2040: if CORE.is_rp2040:
value = pins.internal_gpio_input_pin_number(value) value = pins.internal_gpio_input_pin_number(value)
if value not in (26, 27, 28, 29): if value not in (26, 27, 28, 29):
raise cv.Invalid("RP2040: Only pins 26, 27, 28 and 29 support ADC.") raise cv.Invalid("RP2040: Only pins 26, 27, 28 and 29 support ADC")
return pins.internal_gpio_input_pin_schema(value) return pins.internal_gpio_input_pin_schema(value)
raise NotImplementedError raise NotImplementedError

View File

@@ -20,15 +20,15 @@ namespace adc {
static const char *const TAG = "adc"; static const char *const TAG = "adc";
// 13bit for S2, and 12bit for all other esp32 variants // 13-bit for S2, 12-bit for all other ESP32 variants
#ifdef USE_ESP32 #ifdef USE_ESP32
static const adc_bits_width_t ADC_WIDTH_MAX_SOC_BITS = static_cast<adc_bits_width_t>(ADC_WIDTH_MAX - 1); static const adc_bits_width_t ADC_WIDTH_MAX_SOC_BITS = static_cast<adc_bits_width_t>(ADC_WIDTH_MAX - 1);
#ifndef SOC_ADC_RTC_MAX_BITWIDTH #ifndef SOC_ADC_RTC_MAX_BITWIDTH
#if USE_ESP32_VARIANT_ESP32S2 #if USE_ESP32_VARIANT_ESP32S2
static const int SOC_ADC_RTC_MAX_BITWIDTH = 13; static const int32_t SOC_ADC_RTC_MAX_BITWIDTH = 13;
#else #else
static const int SOC_ADC_RTC_MAX_BITWIDTH = 12; static const int32_t SOC_ADC_RTC_MAX_BITWIDTH = 12;
#endif #endif
#endif #endif
@@ -47,14 +47,21 @@ extern "C"
#endif #endif
#ifdef USE_ESP32 #ifdef USE_ESP32
adc1_config_width(ADC_WIDTH_MAX_SOC_BITS); if (channel1_ != ADC1_CHANNEL_MAX) {
if (!autorange_) { adc1_config_width(ADC_WIDTH_MAX_SOC_BITS);
adc1_config_channel_atten(channel_, attenuation_); if (!autorange_) {
adc1_config_channel_atten(channel1_, attenuation_);
}
} else if (channel2_ != ADC2_CHANNEL_MAX) {
if (!autorange_) {
adc2_config_channel_atten(channel2_, attenuation_);
}
} }
// load characteristics for each attenuation // load characteristics for each attenuation
for (int i = 0; i < (int) ADC_ATTEN_MAX; i++) { for (int32_t i = 0; i <= ADC_ATTEN_DB_11; i++) {
auto cal_value = esp_adc_cal_characterize(ADC_UNIT_1, (adc_atten_t) i, ADC_WIDTH_MAX_SOC_BITS, auto adc_unit = channel1_ != ADC1_CHANNEL_MAX ? ADC_UNIT_1 : ADC_UNIT_2;
auto cal_value = esp_adc_cal_characterize(adc_unit, (adc_atten_t) i, ADC_WIDTH_MAX_SOC_BITS,
1100, // default vref 1100, // default vref
&cal_characteristics_[i]); &cal_characteristics_[i]);
switch (cal_value) { switch (cal_value) {
@@ -136,9 +143,9 @@ void ADCSensor::update() {
#ifdef USE_ESP8266 #ifdef USE_ESP8266
float ADCSensor::sample() { float ADCSensor::sample() {
#ifdef USE_ADC_SENSOR_VCC #ifdef USE_ADC_SENSOR_VCC
int raw = ESP.getVcc(); // NOLINT(readability-static-accessed-through-instance) int32_t raw = ESP.getVcc(); // NOLINT(readability-static-accessed-through-instance)
#else #else
int raw = analogRead(this->pin_->get_pin()); // NOLINT int32_t raw = analogRead(this->pin_->get_pin()); // NOLINT
#endif #endif
if (output_raw_) { if (output_raw_) {
return raw; return raw;
@@ -150,29 +157,53 @@ float ADCSensor::sample() {
#ifdef USE_ESP32 #ifdef USE_ESP32
float ADCSensor::sample() { float ADCSensor::sample() {
if (!autorange_) { if (!autorange_) {
int raw = adc1_get_raw(channel_); int raw = -1;
if (channel1_ != ADC1_CHANNEL_MAX) {
raw = adc1_get_raw(channel1_);
} else if (channel2_ != ADC2_CHANNEL_MAX) {
adc2_get_raw(channel2_, ADC_WIDTH_MAX_SOC_BITS, &raw);
}
if (raw == -1) { if (raw == -1) {
return NAN; return NAN;
} }
if (output_raw_) { if (output_raw_) {
return raw; return raw;
} }
uint32_t mv = esp_adc_cal_raw_to_voltage(raw, &cal_characteristics_[(int) attenuation_]); uint32_t mv = esp_adc_cal_raw_to_voltage(raw, &cal_characteristics_[(int32_t) attenuation_]);
return mv / 1000.0f; return mv / 1000.0f;
} }
int raw11, raw6 = ADC_MAX, raw2 = ADC_MAX, raw0 = ADC_MAX; int raw11 = ADC_MAX, raw6 = ADC_MAX, raw2 = ADC_MAX, raw0 = ADC_MAX;
adc1_config_channel_atten(channel_, ADC_ATTEN_DB_11);
raw11 = adc1_get_raw(channel_); if (channel1_ != ADC1_CHANNEL_MAX) {
if (raw11 < ADC_MAX) { adc1_config_channel_atten(channel1_, ADC_ATTEN_DB_11);
adc1_config_channel_atten(channel_, ADC_ATTEN_DB_6); raw11 = adc1_get_raw(channel1_);
raw6 = adc1_get_raw(channel_); if (raw11 < ADC_MAX) {
if (raw6 < ADC_MAX) { adc1_config_channel_atten(channel1_, ADC_ATTEN_DB_6);
adc1_config_channel_atten(channel_, ADC_ATTEN_DB_2_5); raw6 = adc1_get_raw(channel1_);
raw2 = adc1_get_raw(channel_); if (raw6 < ADC_MAX) {
if (raw2 < ADC_MAX) { adc1_config_channel_atten(channel1_, ADC_ATTEN_DB_2_5);
adc1_config_channel_atten(channel_, ADC_ATTEN_DB_0); raw2 = adc1_get_raw(channel1_);
raw0 = adc1_get_raw(channel_); if (raw2 < ADC_MAX) {
adc1_config_channel_atten(channel1_, ADC_ATTEN_DB_0);
raw0 = adc1_get_raw(channel1_);
}
}
}
} else if (channel2_ != ADC2_CHANNEL_MAX) {
adc2_config_channel_atten(channel2_, ADC_ATTEN_DB_11);
adc2_get_raw(channel2_, ADC_WIDTH_MAX_SOC_BITS, &raw11);
if (raw11 < ADC_MAX) {
adc2_config_channel_atten(channel2_, ADC_ATTEN_DB_6);
adc2_get_raw(channel2_, ADC_WIDTH_MAX_SOC_BITS, &raw6);
if (raw6 < ADC_MAX) {
adc2_config_channel_atten(channel2_, ADC_ATTEN_DB_2_5);
adc2_get_raw(channel2_, ADC_WIDTH_MAX_SOC_BITS, &raw2);
if (raw2 < ADC_MAX) {
adc2_config_channel_atten(channel2_, ADC_ATTEN_DB_0);
adc2_get_raw(channel2_, ADC_WIDTH_MAX_SOC_BITS, &raw0);
}
} }
} }
} }
@@ -181,10 +212,10 @@ float ADCSensor::sample() {
return NAN; return NAN;
} }
uint32_t mv11 = esp_adc_cal_raw_to_voltage(raw11, &cal_characteristics_[(int) ADC_ATTEN_DB_11]); uint32_t mv11 = esp_adc_cal_raw_to_voltage(raw11, &cal_characteristics_[(int32_t) ADC_ATTEN_DB_11]);
uint32_t mv6 = esp_adc_cal_raw_to_voltage(raw6, &cal_characteristics_[(int) ADC_ATTEN_DB_6]); uint32_t mv6 = esp_adc_cal_raw_to_voltage(raw6, &cal_characteristics_[(int32_t) ADC_ATTEN_DB_6]);
uint32_t mv2 = esp_adc_cal_raw_to_voltage(raw2, &cal_characteristics_[(int) ADC_ATTEN_DB_2_5]); uint32_t mv2 = esp_adc_cal_raw_to_voltage(raw2, &cal_characteristics_[(int32_t) ADC_ATTEN_DB_2_5]);
uint32_t mv0 = esp_adc_cal_raw_to_voltage(raw0, &cal_characteristics_[(int) ADC_ATTEN_DB_0]); uint32_t mv0 = esp_adc_cal_raw_to_voltage(raw0, &cal_characteristics_[(int32_t) ADC_ATTEN_DB_0]);
// Contribution of each value, in range 0-2048 (12 bit ADC) or 0-4096 (13 bit ADC) // Contribution of each value, in range 0-2048 (12 bit ADC) or 0-4096 (13 bit ADC)
uint32_t c11 = std::min(raw11, ADC_HALF); uint32_t c11 = std::min(raw11, ADC_HALF);
@@ -212,7 +243,7 @@ float ADCSensor::sample() {
adc_select_input(pin - 26); adc_select_input(pin - 26);
} }
int raw = adc_read(); int32_t raw = adc_read();
if (this->is_temperature_) { if (this->is_temperature_) {
adc_set_temp_sensor_enabled(false); adc_set_temp_sensor_enabled(false);
} }

View File

@@ -19,16 +19,23 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage
#ifdef USE_ESP32 #ifdef USE_ESP32
/// Set the attenuation for this pin. Only available on the ESP32. /// Set the attenuation for this pin. Only available on the ESP32.
void set_attenuation(adc_atten_t attenuation) { attenuation_ = attenuation; } void set_attenuation(adc_atten_t attenuation) { attenuation_ = attenuation; }
void set_channel(adc1_channel_t channel) { channel_ = channel; } void set_channel1(adc1_channel_t channel) {
channel1_ = channel;
channel2_ = ADC2_CHANNEL_MAX;
}
void set_channel2(adc2_channel_t channel) {
channel2_ = channel;
channel1_ = ADC1_CHANNEL_MAX;
}
void set_autorange(bool autorange) { autorange_ = autorange; } void set_autorange(bool autorange) { autorange_ = autorange; }
#endif #endif
/// Update adc values. /// Update ADC values
void update() override; void update() override;
/// Setup ADc /// Setup ADC
void setup() override; void setup() override;
void dump_config() override; void dump_config() override;
/// `HARDWARE_LATE` setup priority. /// `HARDWARE_LATE` setup priority
float get_setup_priority() const override; float get_setup_priority() const override;
void set_pin(InternalGPIOPin *pin) { this->pin_ = pin; } void set_pin(InternalGPIOPin *pin) { this->pin_ = pin; }
void set_output_raw(bool output_raw) { output_raw_ = output_raw; } void set_output_raw(bool output_raw) { output_raw_ = output_raw; }
@@ -52,9 +59,10 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage
#ifdef USE_ESP32 #ifdef USE_ESP32
adc_atten_t attenuation_{ADC_ATTEN_DB_0}; adc_atten_t attenuation_{ADC_ATTEN_DB_0};
adc1_channel_t channel_{}; adc1_channel_t channel1_{ADC1_CHANNEL_MAX};
adc2_channel_t channel2_{ADC2_CHANNEL_MAX};
bool autorange_{false}; bool autorange_{false};
esp_adc_cal_characteristics_t cal_characteristics_[(int) ADC_ATTEN_MAX] = {}; esp_adc_cal_characteristics_t cal_characteristics_[ADC_ATTEN_MAX] = {};
#endif #endif
}; };

View File

@@ -1,5 +1,7 @@
import esphome.codegen as cg import esphome.codegen as cg
import esphome.config_validation as cv import esphome.config_validation as cv
import esphome.final_validate as fv
from esphome.core import CORE
from esphome.components import sensor, voltage_sampler from esphome.components import sensor, voltage_sampler
from esphome.components.esp32 import get_esp32_variant from esphome.components.esp32 import get_esp32_variant
from esphome.const import ( from esphome.const import (
@@ -8,15 +10,15 @@ from esphome.const import (
CONF_NUMBER, CONF_NUMBER,
CONF_PIN, CONF_PIN,
CONF_RAW, CONF_RAW,
CONF_WIFI,
DEVICE_CLASS_VOLTAGE, DEVICE_CLASS_VOLTAGE,
STATE_CLASS_MEASUREMENT, STATE_CLASS_MEASUREMENT,
UNIT_VOLT, UNIT_VOLT,
) )
from esphome.core import CORE
from . import ( from . import (
ATTENUATION_MODES, ATTENUATION_MODES,
ESP32_VARIANT_ADC1_PIN_TO_CHANNEL, ESP32_VARIANT_ADC1_PIN_TO_CHANNEL,
ESP32_VARIANT_ADC2_PIN_TO_CHANNEL,
validate_adc_pin, validate_adc_pin,
) )
@@ -25,7 +27,23 @@ AUTO_LOAD = ["voltage_sampler"]
def validate_config(config): def validate_config(config):
if config[CONF_RAW] and config.get(CONF_ATTENUATION, None) == "auto": if config[CONF_RAW] and config.get(CONF_ATTENUATION, None) == "auto":
raise cv.Invalid("Automatic attenuation cannot be used when raw output is set.") raise cv.Invalid("Automatic attenuation cannot be used when raw output is set")
return config
def final_validate_config(config):
if CORE.is_esp32:
variant = get_esp32_variant()
if (
CONF_WIFI in fv.full_config.get()
and config[CONF_PIN][CONF_NUMBER]
in ESP32_VARIANT_ADC2_PIN_TO_CHANNEL[variant]
):
raise cv.Invalid(
f"{variant} doesn't support ADC on this pin when Wi-Fi is configured"
)
return config return config
@@ -55,6 +73,8 @@ CONFIG_SCHEMA = cv.All(
validate_config, validate_config,
) )
FINAL_VALIDATE_SCHEMA = final_validate_config
async def to_code(config): async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID]) var = cg.new_Pvariable(config[CONF_ID])
@@ -69,17 +89,26 @@ async def to_code(config):
pin = await cg.gpio_pin_expression(config[CONF_PIN]) pin = await cg.gpio_pin_expression(config[CONF_PIN])
cg.add(var.set_pin(pin)) cg.add(var.set_pin(pin))
if CONF_RAW in config: cg.add(var.set_output_raw(config[CONF_RAW]))
cg.add(var.set_output_raw(config[CONF_RAW]))
if CONF_ATTENUATION in config: if attenuation := config.get(CONF_ATTENUATION):
if config[CONF_ATTENUATION] == "auto": if attenuation == "auto":
cg.add(var.set_autorange(cg.global_ns.true)) cg.add(var.set_autorange(cg.global_ns.true))
else: else:
cg.add(var.set_attenuation(config[CONF_ATTENUATION])) cg.add(var.set_attenuation(attenuation))
if CORE.is_esp32: if CORE.is_esp32:
variant = get_esp32_variant() variant = get_esp32_variant()
pin_num = config[CONF_PIN][CONF_NUMBER] pin_num = config[CONF_PIN][CONF_NUMBER]
chan = ESP32_VARIANT_ADC1_PIN_TO_CHANNEL[variant][pin_num] if (
cg.add(var.set_channel(chan)) variant in ESP32_VARIANT_ADC1_PIN_TO_CHANNEL
and pin_num in ESP32_VARIANT_ADC1_PIN_TO_CHANNEL[variant]
):
chan = ESP32_VARIANT_ADC1_PIN_TO_CHANNEL[variant][pin_num]
cg.add(var.set_channel1(chan))
elif (
variant in ESP32_VARIANT_ADC2_PIN_TO_CHANNEL
and pin_num in ESP32_VARIANT_ADC2_PIN_TO_CHANNEL[variant]
):
chan = ESP32_VARIANT_ADC2_PIN_TO_CHANNEL[variant][pin_num]
cg.add(var.set_channel2(chan))

View File

@@ -48,16 +48,16 @@ async def to_code(config):
await cg.register_component(var, config) await cg.register_component(var, config)
await display.register_display(var, config) await display.register_display(var, config)
if CONF_PIXEL_MAPPER in config: if pixel_mapper := config.get(CONF_PIXEL_MAPPER):
pixel_mapper_template_ = await cg.process_lambda( pixel_mapper_template_ = await cg.process_lambda(
config[CONF_PIXEL_MAPPER], pixel_mapper,
[(int, "x"), (int, "y")], [(int, "x"), (int, "y")],
return_type=cg.int_, return_type=cg.int_,
) )
cg.add(var.set_pixel_mapper(pixel_mapper_template_)) cg.add(var.set_pixel_mapper(pixel_mapper_template_))
if CONF_LAMBDA in config: if lambda_config := config.get(CONF_LAMBDA):
lambda_ = await cg.process_lambda( lambda_ = await cg.process_lambda(
config[CONF_LAMBDA], [(display.DisplayBufferRef, "it")], return_type=cg.void lambda_config, [(display.DisplayRef, "it")], return_type=cg.void
) )
cg.add(var.set_writer(lambda_)) cg.add(var.set_writer(lambda_))

View File

@@ -72,8 +72,8 @@ async def to_code(config):
await cg.register_component(var, config) await cg.register_component(var, config)
await i2c.register_i2c_device(var, config) await i2c.register_i2c_device(var, config)
if CONF_IRQ_PIN in config: if irq_pin_config := config.get(CONF_IRQ_PIN):
irq_pin = await cg.gpio_pin_expression(config[CONF_IRQ_PIN]) irq_pin = await cg.gpio_pin_expression(irq_pin_config)
cg.add(var.set_irq_pin(irq_pin)) cg.add(var.set_irq_pin(irq_pin))
for key in [ for key in [

View File

@@ -45,10 +45,10 @@ async def to_code(config):
await cg.register_component(var, config) await cg.register_component(var, config)
await i2c.register_i2c_device(var, config) await i2c.register_i2c_device(var, config)
if CONF_TEMPERATURE in config: if temperature := config.get(CONF_TEMPERATURE):
sens = await sensor.new_sensor(config[CONF_TEMPERATURE]) sens = await sensor.new_sensor(temperature)
cg.add(var.set_temperature_sensor(sens)) cg.add(var.set_temperature_sensor(sens))
if CONF_HUMIDITY in config: if humidity := config.get(CONF_HUMIDITY):
sens = await sensor.new_sensor(config[CONF_HUMIDITY]) sens = await sensor.new_sensor(humidity)
cg.add(var.set_humidity_sensor(sens)) cg.add(var.set_humidity_sensor(sens))

View File

@@ -1,5 +1,6 @@
#include "airthings_listener.h" #include "airthings_listener.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
#include <cinttypes>
#ifdef USE_ESP32 #ifdef USE_ESP32
@@ -19,7 +20,7 @@ bool AirthingsListener::parse_device(const esp32_ble_tracker::ESPBTDevice &devic
sn |= ((uint32_t) it.data[2] << 16); sn |= ((uint32_t) it.data[2] << 16);
sn |= ((uint32_t) it.data[3] << 24); sn |= ((uint32_t) it.data[3] << 24);
ESP_LOGD(TAG, "Found AirThings device Serial:%u (MAC: %s)", sn, device.address_str().c_str()); ESP_LOGD(TAG, "Found AirThings device Serial:%" PRIu32 " (MAC: %s)", sn, device.address_str().c_str());
return true; return true;
} }
} }

View File

@@ -3,26 +3,31 @@ import esphome.config_validation as cv
from esphome.components import sensor, ble_client from esphome.components import sensor, ble_client
from esphome.const import ( from esphome.const import (
DEVICE_CLASS_HUMIDITY, CONF_BATTERY_VOLTAGE,
DEVICE_CLASS_TEMPERATURE,
DEVICE_CLASS_PRESSURE,
STATE_CLASS_MEASUREMENT,
UNIT_PERCENT,
UNIT_CELSIUS,
UNIT_HECTOPASCAL,
CONF_HUMIDITY, CONF_HUMIDITY,
CONF_TVOC,
CONF_PRESSURE, CONF_PRESSURE,
CONF_TEMPERATURE, CONF_TEMPERATURE,
CONF_TVOC,
DEVICE_CLASS_VOLTAGE,
DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_PRESSURE,
DEVICE_CLASS_TEMPERATURE,
DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS_PARTS, DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS_PARTS,
ENTITY_CATEGORY_DIAGNOSTIC,
STATE_CLASS_MEASUREMENT,
UNIT_CELSIUS,
UNIT_HECTOPASCAL,
UNIT_PARTS_PER_BILLION, UNIT_PARTS_PER_BILLION,
ICON_RADIATOR, UNIT_PERCENT,
UNIT_VOLT,
) )
CODEOWNERS = ["@ncareau", "@jeromelaban"] CODEOWNERS = ["@ncareau", "@jeromelaban", "@kpfleming"]
DEPENDENCIES = ["ble_client"] DEPENDENCIES = ["ble_client"]
CONF_BATTERY_UPDATE_INTERVAL = "battery_update_interval"
airthings_wave_base_ns = cg.esphome_ns.namespace("airthings_wave_base") airthings_wave_base_ns = cg.esphome_ns.namespace("airthings_wave_base")
AirthingsWaveBase = airthings_wave_base_ns.class_( AirthingsWaveBase = airthings_wave_base_ns.class_(
"AirthingsWaveBase", cg.PollingComponent, ble_client.BLEClientNode "AirthingsWaveBase", cg.PollingComponent, ble_client.BLEClientNode
@@ -34,9 +39,9 @@ BASE_SCHEMA = (
{ {
cv.Optional(CONF_HUMIDITY): sensor.sensor_schema( cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
unit_of_measurement=UNIT_PERCENT, unit_of_measurement=UNIT_PERCENT,
accuracy_decimals=0,
device_class=DEVICE_CLASS_HUMIDITY, device_class=DEVICE_CLASS_HUMIDITY,
state_class=STATE_CLASS_MEASUREMENT, state_class=STATE_CLASS_MEASUREMENT,
accuracy_decimals=0,
), ),
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
unit_of_measurement=UNIT_CELSIUS, unit_of_measurement=UNIT_CELSIUS,
@@ -52,11 +57,21 @@ BASE_SCHEMA = (
), ),
cv.Optional(CONF_TVOC): sensor.sensor_schema( cv.Optional(CONF_TVOC): sensor.sensor_schema(
unit_of_measurement=UNIT_PARTS_PER_BILLION, unit_of_measurement=UNIT_PARTS_PER_BILLION,
icon=ICON_RADIATOR,
accuracy_decimals=0, accuracy_decimals=0,
device_class=DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS_PARTS, device_class=DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS_PARTS,
state_class=STATE_CLASS_MEASUREMENT, state_class=STATE_CLASS_MEASUREMENT,
), ),
cv.Optional(CONF_BATTERY_VOLTAGE): sensor.sensor_schema(
unit_of_measurement=UNIT_VOLT,
accuracy_decimals=3,
device_class=DEVICE_CLASS_VOLTAGE,
state_class=STATE_CLASS_MEASUREMENT,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
cv.Optional(
CONF_BATTERY_UPDATE_INTERVAL,
default="24h",
): cv.update_interval,
} }
) )
.extend(cv.polling_component_schema("5min")) .extend(cv.polling_component_schema("5min"))
@@ -69,15 +84,20 @@ async def wave_base_to_code(var, config):
await ble_client.register_ble_node(var, config) await ble_client.register_ble_node(var, config)
if CONF_HUMIDITY in config: if config_humidity := config.get(CONF_HUMIDITY):
sens = await sensor.new_sensor(config[CONF_HUMIDITY]) sens = await sensor.new_sensor(config_humidity)
cg.add(var.set_humidity(sens)) cg.add(var.set_humidity(sens))
if CONF_TEMPERATURE in config: if config_temperature := config.get(CONF_TEMPERATURE):
sens = await sensor.new_sensor(config[CONF_TEMPERATURE]) sens = await sensor.new_sensor(config_temperature)
cg.add(var.set_temperature(sens)) cg.add(var.set_temperature(sens))
if CONF_PRESSURE in config: if config_pressure := config.get(CONF_PRESSURE):
sens = await sensor.new_sensor(config[CONF_PRESSURE]) sens = await sensor.new_sensor(config_pressure)
cg.add(var.set_pressure(sens)) cg.add(var.set_pressure(sens))
if CONF_TVOC in config: if config_tvoc := config.get(CONF_TVOC):
sens = await sensor.new_sensor(config[CONF_TVOC]) sens = await sensor.new_sensor(config_tvoc)
cg.add(var.set_tvoc(sens)) cg.add(var.set_tvoc(sens))
if config_battery_voltage := config.get(CONF_BATTERY_VOLTAGE):
sens = await sensor.new_sensor(config_battery_voltage)
cg.add(var.set_battery_voltage(sens))
if config_battery_update_interval := config.get(CONF_BATTERY_UPDATE_INTERVAL):
cg.add(var.set_battery_update_interval(config_battery_update_interval))

View File

@@ -1,5 +1,8 @@
#include "airthings_wave_base.h" #include "airthings_wave_base.h"
// All information related to reading battery information came from the sensors.airthings_wave
// project by Sverre Hamre (https://github.com/sverrham/sensor.airthings_wave)
#ifdef USE_ESP32 #ifdef USE_ESP32
namespace esphome { namespace esphome {
@@ -18,22 +21,26 @@ void AirthingsWaveBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt
} }
case ESP_GATTC_DISCONNECT_EVT: { case ESP_GATTC_DISCONNECT_EVT: {
this->handle_ = 0;
this->acp_handle_ = 0;
this->cccd_handle_ = 0;
ESP_LOGW(TAG, "Disconnected!"); ESP_LOGW(TAG, "Disconnected!");
break; break;
} }
case ESP_GATTC_SEARCH_CMPL_EVT: { case ESP_GATTC_SEARCH_CMPL_EVT: {
this->handle_ = 0; if (this->request_read_values_()) {
auto *chr = this->parent()->get_characteristic(this->service_uuid_, this->sensors_data_characteristic_uuid_); if (!this->read_battery_next_update_) {
if (chr == nullptr) { this->node_state = espbt::ClientState::ESTABLISHED;
ESP_LOGW(TAG, "No sensor characteristic found at service %s char %s", this->service_uuid_.to_string().c_str(), } else {
this->sensors_data_characteristic_uuid_.to_string().c_str()); // delay setting node_state to ESTABLISHED until confirmation of the notify registration
break; this->request_battery_();
}
} }
this->handle_ = chr->handle;
this->node_state = esp32_ble_tracker::ClientState::ESTABLISHED;
this->request_read_values_(); // ensure that the client will be disconnected even if no responses arrive
this->set_response_timeout_();
break; break;
} }
@@ -50,15 +57,29 @@ void AirthingsWaveBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt
break; break;
} }
case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
this->node_state = espbt::ClientState::ESTABLISHED;
break;
}
case ESP_GATTC_NOTIFY_EVT: {
if (param->notify.conn_id != this->parent()->get_conn_id())
break;
if (param->notify.handle == this->acp_handle_) {
this->read_battery_(param->notify.value, param->notify.value_len);
}
break;
}
default: default:
break; break;
} }
} }
bool AirthingsWaveBase::is_valid_voc_value_(uint16_t voc) { return 0 <= voc && voc <= 16383; } bool AirthingsWaveBase::is_valid_voc_value_(uint16_t voc) { return voc <= 16383; }
void AirthingsWaveBase::update() { void AirthingsWaveBase::update() {
if (this->node_state != esp32_ble_tracker::ClientState::ESTABLISHED) { if (this->node_state != espbt::ClientState::ESTABLISHED) {
if (!this->parent()->enabled) { if (!this->parent()->enabled) {
ESP_LOGW(TAG, "Reconnecting to device"); ESP_LOGW(TAG, "Reconnecting to device");
this->parent()->set_enabled(true); this->parent()->set_enabled(true);
@@ -69,12 +90,119 @@ void AirthingsWaveBase::update() {
} }
} }
void AirthingsWaveBase::request_read_values_() { bool AirthingsWaveBase::request_read_values_() {
auto *chr = this->parent()->get_characteristic(this->service_uuid_, this->sensors_data_characteristic_uuid_);
if (chr == nullptr) {
ESP_LOGW(TAG, "No sensor characteristic found at service %s char %s", this->service_uuid_.to_string().c_str(),
this->sensors_data_characteristic_uuid_.to_string().c_str());
return false;
}
this->handle_ = chr->handle;
auto status = esp_ble_gattc_read_char(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), this->handle_, auto status = esp_ble_gattc_read_char(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), this->handle_,
ESP_GATT_AUTH_REQ_NONE); ESP_GATT_AUTH_REQ_NONE);
if (status) { if (status) {
ESP_LOGW(TAG, "Error sending read request for sensor, status=%d", status); ESP_LOGW(TAG, "Error sending read request for sensor, status=%d", status);
return false;
} }
this->response_pending_();
return true;
}
bool AirthingsWaveBase::request_battery_() {
uint8_t battery_command = ACCESS_CONTROL_POINT_COMMAND;
uint8_t cccd_value[2] = {1, 0};
auto *chr = this->parent()->get_characteristic(this->service_uuid_, this->access_control_point_characteristic_uuid_);
if (chr == nullptr) {
ESP_LOGW(TAG, "No access control point characteristic found at service %s char %s",
this->service_uuid_.to_string().c_str(),
this->access_control_point_characteristic_uuid_.to_string().c_str());
return false;
}
auto *descr = this->parent()->get_descriptor(this->service_uuid_, this->access_control_point_characteristic_uuid_,
CLIENT_CHARACTERISTIC_CONFIGURATION_DESCRIPTOR_UUID);
if (descr == nullptr) {
ESP_LOGW(TAG, "No CCC descriptor found at service %s char %s", this->service_uuid_.to_string().c_str(),
this->access_control_point_characteristic_uuid_.to_string().c_str());
return false;
}
auto reg_status =
esp_ble_gattc_register_for_notify(this->parent()->get_gattc_if(), this->parent()->get_remote_bda(), chr->handle);
if (reg_status) {
ESP_LOGW(TAG, "esp_ble_gattc_register_for_notify failed, status=%d", reg_status);
return false;
}
this->acp_handle_ = chr->handle;
this->cccd_handle_ = descr->handle;
auto descr_status =
esp_ble_gattc_write_char_descr(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), this->cccd_handle_,
2, cccd_value, ESP_GATT_WRITE_TYPE_RSP, ESP_GATT_AUTH_REQ_NONE);
if (descr_status) {
ESP_LOGW(TAG, "Error sending CCC descriptor write request, status=%d", descr_status);
return false;
}
auto chr_status =
esp_ble_gattc_write_char(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), this->acp_handle_, 1,
&battery_command, ESP_GATT_WRITE_TYPE_RSP, ESP_GATT_AUTH_REQ_NONE);
if (chr_status) {
ESP_LOGW(TAG, "Error sending read request for battery, status=%d", chr_status);
return false;
}
this->response_pending_();
return true;
}
void AirthingsWaveBase::read_battery_(uint8_t *raw_value, uint16_t value_len) {
auto *value = (AccessControlPointResponse *) (&raw_value[2]);
if ((value_len >= (sizeof(AccessControlPointResponse) + 2)) && (raw_value[0] == ACCESS_CONTROL_POINT_COMMAND)) {
ESP_LOGD(TAG, "Battery received: %u mV", (unsigned int) value->battery);
if (this->battery_voltage_ != nullptr) {
float voltage = value->battery / 1000.0f;
this->battery_voltage_->publish_state(voltage);
}
// read the battery again at the configured update interval
if (this->battery_update_interval_ != this->update_interval_) {
this->read_battery_next_update_ = false;
this->set_timeout("battery", this->battery_update_interval_,
[this]() { this->read_battery_next_update_ = true; });
}
}
this->response_received_();
}
void AirthingsWaveBase::response_pending_() {
this->responses_pending_++;
this->set_response_timeout_();
}
void AirthingsWaveBase::response_received_() {
if (--this->responses_pending_ == 0) {
// This instance must not stay connected
// so other clients can connect to it (e.g. the
// mobile app).
this->parent()->set_enabled(false);
}
}
void AirthingsWaveBase::set_response_timeout_() {
this->set_timeout("response_timeout", 30 * 1000, [this]() {
this->responses_pending_ = 1;
this->response_received_();
});
} }
} // namespace airthings_wave_base } // namespace airthings_wave_base

View File

@@ -1,5 +1,8 @@
#pragma once #pragma once
// All information related to reading battery levels came from the sensors.airthings_wave
// project by Sverre Hamre (https://github.com/sverrham/sensor.airthings_wave)
#ifdef USE_ESP32 #ifdef USE_ESP32
#include <esp_gattc_api.h> #include <esp_gattc_api.h>
@@ -14,6 +17,11 @@
namespace esphome { namespace esphome {
namespace airthings_wave_base { namespace airthings_wave_base {
namespace espbt = esphome::esp32_ble_tracker;
static const uint8_t ACCESS_CONTROL_POINT_COMMAND = 0x6d;
static const auto CLIENT_CHARACTERISTIC_CONFIGURATION_DESCRIPTOR_UUID = espbt::ESPBTUUID::from_uint16(0x2902);
class AirthingsWaveBase : public PollingComponent, public ble_client::BLEClientNode { class AirthingsWaveBase : public PollingComponent, public ble_client::BLEClientNode {
public: public:
AirthingsWaveBase() = default; AirthingsWaveBase() = default;
@@ -27,21 +35,53 @@ class AirthingsWaveBase : public PollingComponent, public ble_client::BLEClientN
void set_humidity(sensor::Sensor *humidity) { humidity_sensor_ = humidity; } void set_humidity(sensor::Sensor *humidity) { humidity_sensor_ = humidity; }
void set_pressure(sensor::Sensor *pressure) { pressure_sensor_ = pressure; } void set_pressure(sensor::Sensor *pressure) { pressure_sensor_ = pressure; }
void set_tvoc(sensor::Sensor *tvoc) { tvoc_sensor_ = tvoc; } void set_tvoc(sensor::Sensor *tvoc) { tvoc_sensor_ = tvoc; }
void set_battery_voltage(sensor::Sensor *voltage) {
battery_voltage_ = voltage;
this->read_battery_next_update_ = true;
}
void set_battery_update_interval(uint32_t interval) { battery_update_interval_ = interval; }
protected: protected:
bool is_valid_voc_value_(uint16_t voc); bool is_valid_voc_value_(uint16_t voc);
virtual void read_sensors(uint8_t *value, uint16_t value_len) = 0; bool request_read_values_();
void request_read_values_(); virtual void read_sensors(uint8_t *raw_value, uint16_t value_len) = 0;
sensor::Sensor *temperature_sensor_{nullptr}; sensor::Sensor *temperature_sensor_{nullptr};
sensor::Sensor *humidity_sensor_{nullptr}; sensor::Sensor *humidity_sensor_{nullptr};
sensor::Sensor *pressure_sensor_{nullptr}; sensor::Sensor *pressure_sensor_{nullptr};
sensor::Sensor *tvoc_sensor_{nullptr}; sensor::Sensor *tvoc_sensor_{nullptr};
sensor::Sensor *battery_voltage_{nullptr};
uint16_t handle_; uint16_t handle_;
esp32_ble_tracker::ESPBTUUID service_uuid_; espbt::ESPBTUUID service_uuid_;
esp32_ble_tracker::ESPBTUUID sensors_data_characteristic_uuid_; espbt::ESPBTUUID sensors_data_characteristic_uuid_;
uint16_t acp_handle_{0};
uint16_t cccd_handle_{0};
espbt::ESPBTUUID access_control_point_characteristic_uuid_;
uint8_t responses_pending_{0};
void response_pending_();
void response_received_();
void set_response_timeout_();
// default to *not* reading battery voltage from the device; the
// set_* function for the battery sensor will set this to 'true'
bool read_battery_next_update_{false};
bool request_battery_();
void read_battery_(uint8_t *raw_value, uint16_t value_len);
uint32_t battery_update_interval_{};
struct AccessControlPointResponse {
uint32_t unused1;
uint8_t unused2;
uint8_t illuminance;
uint8_t unused3[10];
uint16_t unused4[4];
uint16_t battery;
uint16_t unused5;
};
}; };
} // namespace airthings_wave_base } // namespace airthings_wave_base

View File

@@ -26,12 +26,9 @@ void AirthingsWaveMini::read_sensors(uint8_t *raw_value, uint16_t value_len) {
if ((this->tvoc_sensor_ != nullptr) && this->is_valid_voc_value_(value->voc)) { if ((this->tvoc_sensor_ != nullptr) && this->is_valid_voc_value_(value->voc)) {
this->tvoc_sensor_->publish_state(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).
this->parent()->set_enabled(false);
} }
this->response_received_();
} }
void AirthingsWaveMini::dump_config() { void AirthingsWaveMini::dump_config() {
@@ -42,11 +39,14 @@ void AirthingsWaveMini::dump_config() {
LOG_SENSOR(" ", "Temperature", this->temperature_sensor_); LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
LOG_SENSOR(" ", "Pressure", this->pressure_sensor_); LOG_SENSOR(" ", "Pressure", this->pressure_sensor_);
LOG_SENSOR(" ", "TVOC", this->tvoc_sensor_); LOG_SENSOR(" ", "TVOC", this->tvoc_sensor_);
LOG_SENSOR(" ", "Battery Voltage", this->battery_voltage_);
} }
AirthingsWaveMini::AirthingsWaveMini() { AirthingsWaveMini::AirthingsWaveMini() {
this->service_uuid_ = esp32_ble_tracker::ESPBTUUID::from_raw(SERVICE_UUID); this->service_uuid_ = espbt::ESPBTUUID::from_raw(SERVICE_UUID);
this->sensors_data_characteristic_uuid_ = esp32_ble_tracker::ESPBTUUID::from_raw(CHARACTERISTIC_UUID); this->sensors_data_characteristic_uuid_ = espbt::ESPBTUUID::from_raw(CHARACTERISTIC_UUID);
this->access_control_point_characteristic_uuid_ =
espbt::ESPBTUUID::from_raw(ACCESS_CONTROL_POINT_CHARACTERISTIC_UUID);
} }
} // namespace airthings_wave_mini } // namespace airthings_wave_mini

View File

@@ -7,8 +7,11 @@
namespace esphome { namespace esphome {
namespace airthings_wave_mini { namespace airthings_wave_mini {
namespace espbt = esphome::esp32_ble_tracker;
static const char *const SERVICE_UUID = "b42e3882-ade7-11e4-89d3-123b93f75cba"; static const char *const SERVICE_UUID = "b42e3882-ade7-11e4-89d3-123b93f75cba";
static const char *const CHARACTERISTIC_UUID = "b42e3b98-ade7-11e4-89d3-123b93f75cba"; static const char *const CHARACTERISTIC_UUID = "b42e3b98-ade7-11e4-89d3-123b93f75cba";
static const char *const ACCESS_CONTROL_POINT_CHARACTERISTIC_UUID = "b42e3ef4-ade7-11e4-89d3-123b93f75cba";
class AirthingsWaveMini : public airthings_wave_base::AirthingsWaveBase { class AirthingsWaveMini : public airthings_wave_base::AirthingsWaveBase {
public: public:
@@ -17,7 +20,7 @@ class AirthingsWaveMini : public airthings_wave_base::AirthingsWaveBase {
void dump_config() override; void dump_config() override;
protected: protected:
void read_sensors(uint8_t *value, uint16_t value_len) override; void read_sensors(uint8_t *raw_value, uint16_t value_len) override;
struct WaveMiniReadings { struct WaveMiniReadings {
uint16_t unused01; uint16_t unused01;

View File

@@ -43,20 +43,17 @@ void AirthingsWavePlus::read_sensors(uint8_t *raw_value, uint16_t value_len) {
if ((this->tvoc_sensor_ != nullptr) && this->is_valid_voc_value_(value->voc)) { if ((this->tvoc_sensor_ != nullptr) && this->is_valid_voc_value_(value->voc)) {
this->tvoc_sensor_->publish_state(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).
this->parent()->set_enabled(false);
} else { } else {
ESP_LOGE(TAG, "Invalid payload version (%d != 1, newer version or not a Wave Plus?)", value->version); ESP_LOGE(TAG, "Invalid payload version (%d != 1, newer version or not a Wave Plus?)", value->version);
} }
} }
this->response_received_();
} }
bool AirthingsWavePlus::is_valid_radon_value_(uint16_t radon) { return 0 <= radon && radon <= 16383; } bool AirthingsWavePlus::is_valid_radon_value_(uint16_t radon) { return radon <= 16383; }
bool AirthingsWavePlus::is_valid_co2_value_(uint16_t co2) { return 0 <= co2 && co2 <= 16383; } bool AirthingsWavePlus::is_valid_co2_value_(uint16_t co2) { return co2 <= 16383; }
void AirthingsWavePlus::dump_config() { void AirthingsWavePlus::dump_config() {
// these really don't belong here, but there doesn't seem to be a // these really don't belong here, but there doesn't seem to be a
@@ -66,6 +63,7 @@ void AirthingsWavePlus::dump_config() {
LOG_SENSOR(" ", "Temperature", this->temperature_sensor_); LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
LOG_SENSOR(" ", "Pressure", this->pressure_sensor_); LOG_SENSOR(" ", "Pressure", this->pressure_sensor_);
LOG_SENSOR(" ", "TVOC", this->tvoc_sensor_); LOG_SENSOR(" ", "TVOC", this->tvoc_sensor_);
LOG_SENSOR(" ", "Battery Voltage", this->battery_voltage_);
LOG_SENSOR(" ", "Radon", this->radon_sensor_); LOG_SENSOR(" ", "Radon", this->radon_sensor_);
LOG_SENSOR(" ", "Radon Long Term", this->radon_long_term_sensor_); LOG_SENSOR(" ", "Radon Long Term", this->radon_long_term_sensor_);
@@ -73,8 +71,10 @@ void AirthingsWavePlus::dump_config() {
} }
AirthingsWavePlus::AirthingsWavePlus() { AirthingsWavePlus::AirthingsWavePlus() {
this->service_uuid_ = esp32_ble_tracker::ESPBTUUID::from_raw(SERVICE_UUID); this->service_uuid_ = espbt::ESPBTUUID::from_raw(SERVICE_UUID);
this->sensors_data_characteristic_uuid_ = esp32_ble_tracker::ESPBTUUID::from_raw(CHARACTERISTIC_UUID); this->sensors_data_characteristic_uuid_ = espbt::ESPBTUUID::from_raw(CHARACTERISTIC_UUID);
this->access_control_point_characteristic_uuid_ =
espbt::ESPBTUUID::from_raw(ACCESS_CONTROL_POINT_CHARACTERISTIC_UUID);
} }
} // namespace airthings_wave_plus } // namespace airthings_wave_plus

View File

@@ -7,8 +7,11 @@
namespace esphome { namespace esphome {
namespace airthings_wave_plus { namespace airthings_wave_plus {
namespace espbt = esphome::esp32_ble_tracker;
static const char *const SERVICE_UUID = "b42e1c08-ade7-11e4-89d3-123b93f75cba"; static const char *const SERVICE_UUID = "b42e1c08-ade7-11e4-89d3-123b93f75cba";
static const char *const CHARACTERISTIC_UUID = "b42e2a68-ade7-11e4-89d3-123b93f75cba"; static const char *const CHARACTERISTIC_UUID = "b42e2a68-ade7-11e4-89d3-123b93f75cba";
static const char *const ACCESS_CONTROL_POINT_CHARACTERISTIC_UUID = "b42e2d06-ade7-11e4-89d3-123b93f75cba";
class AirthingsWavePlus : public airthings_wave_base::AirthingsWaveBase { class AirthingsWavePlus : public airthings_wave_base::AirthingsWaveBase {
public: public:
@@ -24,7 +27,7 @@ class AirthingsWavePlus : public airthings_wave_base::AirthingsWaveBase {
bool is_valid_radon_value_(uint16_t radon); bool is_valid_radon_value_(uint16_t radon);
bool is_valid_co2_value_(uint16_t co2); bool is_valid_co2_value_(uint16_t co2);
void read_sensors(uint8_t *value, uint16_t value_len) override; void read_sensors(uint8_t *raw_value, uint16_t value_len) override;
sensor::Sensor *radon_sensor_{nullptr}; sensor::Sensor *radon_sensor_{nullptr};
sensor::Sensor *radon_long_term_sensor_{nullptr}; sensor::Sensor *radon_long_term_sensor_{nullptr};

View File

@@ -53,12 +53,12 @@ async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID]) var = cg.new_Pvariable(config[CONF_ID])
await airthings_wave_base.wave_base_to_code(var, config) await airthings_wave_base.wave_base_to_code(var, config)
if CONF_RADON in config: if config_radon := config.get(CONF_RADON):
sens = await sensor.new_sensor(config[CONF_RADON]) sens = await sensor.new_sensor(config_radon)
cg.add(var.set_radon(sens)) cg.add(var.set_radon(sens))
if CONF_RADON_LONG_TERM in config: if config_radon_long_term := config.get(CONF_RADON_LONG_TERM):
sens = await sensor.new_sensor(config[CONF_RADON_LONG_TERM]) sens = await sensor.new_sensor(config_radon_long_term)
cg.add(var.set_radon_long_term(sens)) cg.add(var.set_radon_long_term(sens))
if CONF_CO2 in config: if config_co2 := config.get(CONF_CO2):
sens = await sensor.new_sensor(config[CONF_CO2]) sens = await sensor.new_sensor(config_co2)
cg.add(var.set_co2(sens)) cg.add(var.set_co2(sens))

View File

@@ -16,6 +16,12 @@ IS_PLATFORM_COMPONENT = True
CONF_ON_TRIGGERED = "on_triggered" CONF_ON_TRIGGERED = "on_triggered"
CONF_ON_CLEARED = "on_cleared" CONF_ON_CLEARED = "on_cleared"
CONF_ON_ARMING = "on_arming"
CONF_ON_PENDING = "on_pending"
CONF_ON_ARMED_HOME = "on_armed_home"
CONF_ON_ARMED_NIGHT = "on_armed_night"
CONF_ON_ARMED_AWAY = "on_armed_away"
CONF_ON_DISARMED = "on_disarmed"
alarm_control_panel_ns = cg.esphome_ns.namespace("alarm_control_panel") alarm_control_panel_ns = cg.esphome_ns.namespace("alarm_control_panel")
AlarmControlPanel = alarm_control_panel_ns.class_("AlarmControlPanel", cg.EntityBase) AlarmControlPanel = alarm_control_panel_ns.class_("AlarmControlPanel", cg.EntityBase)
@@ -29,8 +35,27 @@ TriggeredTrigger = alarm_control_panel_ns.class_(
ClearedTrigger = alarm_control_panel_ns.class_( ClearedTrigger = alarm_control_panel_ns.class_(
"ClearedTrigger", automation.Trigger.template() "ClearedTrigger", automation.Trigger.template()
) )
ArmingTrigger = alarm_control_panel_ns.class_(
"ArmingTrigger", automation.Trigger.template()
)
PendingTrigger = alarm_control_panel_ns.class_(
"PendingTrigger", automation.Trigger.template()
)
ArmedHomeTrigger = alarm_control_panel_ns.class_(
"ArmedHomeTrigger", automation.Trigger.template()
)
ArmedNightTrigger = alarm_control_panel_ns.class_(
"ArmedNightTrigger", automation.Trigger.template()
)
ArmedAwayTrigger = alarm_control_panel_ns.class_(
"ArmedAwayTrigger", automation.Trigger.template()
)
DisarmedTrigger = alarm_control_panel_ns.class_(
"DisarmedTrigger", automation.Trigger.template()
)
ArmAwayAction = alarm_control_panel_ns.class_("ArmAwayAction", automation.Action) ArmAwayAction = alarm_control_panel_ns.class_("ArmAwayAction", automation.Action)
ArmHomeAction = alarm_control_panel_ns.class_("ArmHomeAction", automation.Action) ArmHomeAction = alarm_control_panel_ns.class_("ArmHomeAction", automation.Action)
ArmNightAction = alarm_control_panel_ns.class_("ArmNightAction", automation.Action)
DisarmAction = alarm_control_panel_ns.class_("DisarmAction", automation.Action) DisarmAction = alarm_control_panel_ns.class_("DisarmAction", automation.Action)
PendingAction = alarm_control_panel_ns.class_("PendingAction", automation.Action) PendingAction = alarm_control_panel_ns.class_("PendingAction", automation.Action)
TriggeredAction = alarm_control_panel_ns.class_("TriggeredAction", automation.Action) TriggeredAction = alarm_control_panel_ns.class_("TriggeredAction", automation.Action)
@@ -51,6 +76,36 @@ ALARM_CONTROL_PANEL_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(TriggeredTrigger), cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(TriggeredTrigger),
} }
), ),
cv.Optional(CONF_ON_ARMING): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ArmingTrigger),
}
),
cv.Optional(CONF_ON_PENDING): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(PendingTrigger),
}
),
cv.Optional(CONF_ON_ARMED_HOME): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ArmedHomeTrigger),
}
),
cv.Optional(CONF_ON_ARMED_NIGHT): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ArmedNightTrigger),
}
),
cv.Optional(CONF_ON_ARMED_AWAY): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ArmedAwayTrigger),
}
),
cv.Optional(CONF_ON_DISARMED): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(DisarmedTrigger),
}
),
cv.Optional(CONF_ON_CLEARED): automation.validate_automation( cv.Optional(CONF_ON_CLEARED): automation.validate_automation(
{ {
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ClearedTrigger), cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ClearedTrigger),
@@ -81,6 +136,24 @@ async def setup_alarm_control_panel_core_(var, config):
for conf in config.get(CONF_ON_TRIGGERED, []): for conf in config.get(CONF_ON_TRIGGERED, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf) await automation.build_automation(trigger, [], conf)
for conf in config.get(CONF_ON_ARMING, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)
for conf in config.get(CONF_ON_PENDING, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)
for conf in config.get(CONF_ON_ARMED_HOME, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)
for conf in config.get(CONF_ON_ARMED_NIGHT, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)
for conf in config.get(CONF_ON_ARMED_AWAY, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)
for conf in config.get(CONF_ON_DISARMED, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)
for conf in config.get(CONF_ON_CLEARED, []): for conf in config.get(CONF_ON_CLEARED, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf) await automation.build_automation(trigger, [], conf)
@@ -99,8 +172,8 @@ async def register_alarm_control_panel(var, config):
async def alarm_action_arm_away_to_code(config, action_id, template_arg, args): async def alarm_action_arm_away_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID]) paren = await cg.get_variable(config[CONF_ID])
var = cg.new_Pvariable(action_id, template_arg, paren) var = cg.new_Pvariable(action_id, template_arg, paren)
if CONF_CODE in config: if code_config := config.get(CONF_CODE):
templatable_ = await cg.templatable(config[CONF_CODE], args, cg.std_string) templatable_ = await cg.templatable(code_config, args, cg.std_string)
cg.add(var.set_code(templatable_)) cg.add(var.set_code(templatable_))
return var return var
@@ -109,6 +182,18 @@ async def alarm_action_arm_away_to_code(config, action_id, template_arg, args):
"alarm_control_panel.arm_home", ArmHomeAction, ALARM_CONTROL_PANEL_ACTION_SCHEMA "alarm_control_panel.arm_home", ArmHomeAction, ALARM_CONTROL_PANEL_ACTION_SCHEMA
) )
async def alarm_action_arm_home_to_code(config, action_id, template_arg, args): async def alarm_action_arm_home_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID])
var = cg.new_Pvariable(action_id, template_arg, paren)
if code_config := config.get(CONF_CODE):
templatable_ = await cg.templatable(code_config, args, cg.std_string)
cg.add(var.set_code(templatable_))
return var
@automation.register_action(
"alarm_control_panel.arm_night", ArmNightAction, ALARM_CONTROL_PANEL_ACTION_SCHEMA
)
async def alarm_action_arm_night_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID]) paren = await cg.get_variable(config[CONF_ID])
var = cg.new_Pvariable(action_id, template_arg, paren) var = cg.new_Pvariable(action_id, template_arg, paren)
if CONF_CODE in config: if CONF_CODE in config:
@@ -123,8 +208,8 @@ async def alarm_action_arm_home_to_code(config, action_id, template_arg, args):
async def alarm_action_disarm_to_code(config, action_id, template_arg, args): async def alarm_action_disarm_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID]) paren = await cg.get_variable(config[CONF_ID])
var = cg.new_Pvariable(action_id, template_arg, paren) var = cg.new_Pvariable(action_id, template_arg, paren)
if CONF_CODE in config: if code_config := config.get(CONF_CODE):
templatable_ = await cg.templatable(config[CONF_CODE], args, cg.std_string) templatable_ = await cg.templatable(code_config, args, cg.std_string)
cg.add(var.set_code(templatable_)) cg.add(var.set_code(templatable_))
return var return var

View File

@@ -36,7 +36,20 @@ void AlarmControlPanel::publish_state(AlarmControlPanelState state) {
this->state_callback_.call(); this->state_callback_.call();
if (state == ACP_STATE_TRIGGERED) { if (state == ACP_STATE_TRIGGERED) {
this->triggered_callback_.call(); this->triggered_callback_.call();
} else if (state == ACP_STATE_ARMING) {
this->arming_callback_.call();
} else if (state == ACP_STATE_PENDING) {
this->pending_callback_.call();
} else if (state == ACP_STATE_ARMED_HOME) {
this->armed_home_callback_.call();
} else if (state == ACP_STATE_ARMED_NIGHT) {
this->armed_night_callback_.call();
} else if (state == ACP_STATE_ARMED_AWAY) {
this->armed_away_callback_.call();
} else if (state == ACP_STATE_DISARMED) {
this->disarmed_callback_.call();
} }
if (prev_state == ACP_STATE_TRIGGERED) { if (prev_state == ACP_STATE_TRIGGERED) {
this->cleared_callback_.call(); this->cleared_callback_.call();
} }
@@ -55,6 +68,30 @@ void AlarmControlPanel::add_on_triggered_callback(std::function<void()> &&callba
this->triggered_callback_.add(std::move(callback)); this->triggered_callback_.add(std::move(callback));
} }
void AlarmControlPanel::add_on_arming_callback(std::function<void()> &&callback) {
this->arming_callback_.add(std::move(callback));
}
void AlarmControlPanel::add_on_armed_home_callback(std::function<void()> &&callback) {
this->armed_home_callback_.add(std::move(callback));
}
void AlarmControlPanel::add_on_armed_night_callback(std::function<void()> &&callback) {
this->armed_night_callback_.add(std::move(callback));
}
void AlarmControlPanel::add_on_armed_away_callback(std::function<void()> &&callback) {
this->armed_away_callback_.add(std::move(callback));
}
void AlarmControlPanel::add_on_pending_callback(std::function<void()> &&callback) {
this->pending_callback_.add(std::move(callback));
}
void AlarmControlPanel::add_on_disarmed_callback(std::function<void()> &&callback) {
this->disarmed_callback_.add(std::move(callback));
}
void AlarmControlPanel::add_on_cleared_callback(std::function<void()> &&callback) { void AlarmControlPanel::add_on_cleared_callback(std::function<void()> &&callback) {
this->cleared_callback_.add(std::move(callback)); this->cleared_callback_.add(std::move(callback));
} }

View File

@@ -47,6 +47,42 @@ class AlarmControlPanel : public EntityBase {
*/ */
void add_on_triggered_callback(std::function<void()> &&callback); void add_on_triggered_callback(std::function<void()> &&callback);
/** Add a callback for when the state of the alarm_control_panel chanes to arming
*
* @param callback The callback function
*/
void add_on_arming_callback(std::function<void()> &&callback);
/** Add a callback for when the state of the alarm_control_panel changes to pending
*
* @param callback The callback function
*/
void add_on_pending_callback(std::function<void()> &&callback);
/** Add a callback for when the state of the alarm_control_panel changes to armed_home
*
* @param callback The callback function
*/
void add_on_armed_home_callback(std::function<void()> &&callback);
/** Add a callback for when the state of the alarm_control_panel changes to armed_night
*
* @param callback The callback function
*/
void add_on_armed_night_callback(std::function<void()> &&callback);
/** Add a callback for when the state of the alarm_control_panel changes to armed_away
*
* @param callback The callback function
*/
void add_on_armed_away_callback(std::function<void()> &&callback);
/** Add a callback for when the state of the alarm_control_panel changes to disarmed
*
* @param callback The callback function
*/
void add_on_disarmed_callback(std::function<void()> &&callback);
/** Add a callback for when the state of the alarm_control_panel clears from triggered /** Add a callback for when the state of the alarm_control_panel clears from triggered
* *
* @param callback The callback function * @param callback The callback function
@@ -128,6 +164,18 @@ class AlarmControlPanel : public EntityBase {
CallbackManager<void()> state_callback_{}; CallbackManager<void()> state_callback_{};
// trigger callback // trigger callback
CallbackManager<void()> triggered_callback_{}; CallbackManager<void()> triggered_callback_{};
// arming callback
CallbackManager<void()> arming_callback_{};
// pending callback
CallbackManager<void()> pending_callback_{};
// armed_home callback
CallbackManager<void()> armed_home_callback_{};
// armed_night callback
CallbackManager<void()> armed_night_callback_{};
// armed_away callback
CallbackManager<void()> armed_away_callback_{};
// disarmed callback
CallbackManager<void()> disarmed_callback_{};
// clear callback // clear callback
CallbackManager<void()> cleared_callback_{}; CallbackManager<void()> cleared_callback_{};
}; };

View File

@@ -85,6 +85,11 @@ void AlarmControlPanelCall::validate_() {
this->state_.reset(); this->state_.reset();
return; return;
} }
if (state == ACP_STATE_ARMED_NIGHT && (this->parent_->get_supported_features() & ACP_FEAT_ARM_NIGHT) == 0) {
ESP_LOGW(TAG, "Cannot arm night when not supported");
this->state_.reset();
return;
}
} }
} }

View File

@@ -12,7 +12,7 @@ const LogString *alarm_control_panel_state_to_string(AlarmControlPanelState stat
case ACP_STATE_ARMED_AWAY: case ACP_STATE_ARMED_AWAY:
return LOG_STR("ARMED_AWAY"); return LOG_STR("ARMED_AWAY");
case ACP_STATE_ARMED_NIGHT: case ACP_STATE_ARMED_NIGHT:
return LOG_STR("NIGHT"); return LOG_STR("ARMED_NIGHT");
case ACP_STATE_ARMED_VACATION: case ACP_STATE_ARMED_VACATION:
return LOG_STR("ARMED_VACATION"); return LOG_STR("ARMED_VACATION");
case ACP_STATE_ARMED_CUSTOM_BYPASS: case ACP_STATE_ARMED_CUSTOM_BYPASS:

View File

@@ -20,6 +20,48 @@ class TriggeredTrigger : public Trigger<> {
} }
}; };
class ArmingTrigger : public Trigger<> {
public:
explicit ArmingTrigger(AlarmControlPanel *alarm_control_panel) {
alarm_control_panel->add_on_arming_callback([this]() { this->trigger(); });
}
};
class PendingTrigger : public Trigger<> {
public:
explicit PendingTrigger(AlarmControlPanel *alarm_control_panel) {
alarm_control_panel->add_on_pending_callback([this]() { this->trigger(); });
}
};
class ArmedHomeTrigger : public Trigger<> {
public:
explicit ArmedHomeTrigger(AlarmControlPanel *alarm_control_panel) {
alarm_control_panel->add_on_armed_home_callback([this]() { this->trigger(); });
}
};
class ArmedNightTrigger : public Trigger<> {
public:
explicit ArmedNightTrigger(AlarmControlPanel *alarm_control_panel) {
alarm_control_panel->add_on_armed_night_callback([this]() { this->trigger(); });
}
};
class ArmedAwayTrigger : public Trigger<> {
public:
explicit ArmedAwayTrigger(AlarmControlPanel *alarm_control_panel) {
alarm_control_panel->add_on_armed_away_callback([this]() { this->trigger(); });
}
};
class DisarmedTrigger : public Trigger<> {
public:
explicit DisarmedTrigger(AlarmControlPanel *alarm_control_panel) {
alarm_control_panel->add_on_disarmed_callback([this]() { this->trigger(); });
}
};
class ClearedTrigger : public Trigger<> { class ClearedTrigger : public Trigger<> {
public: public:
explicit ClearedTrigger(AlarmControlPanel *alarm_control_panel) { explicit ClearedTrigger(AlarmControlPanel *alarm_control_panel) {
@@ -67,6 +109,26 @@ template<typename... Ts> class ArmHomeAction : public Action<Ts...> {
AlarmControlPanel *alarm_control_panel_; AlarmControlPanel *alarm_control_panel_;
}; };
template<typename... Ts> class ArmNightAction : public Action<Ts...> {
public:
explicit ArmNightAction(AlarmControlPanel *alarm_control_panel) : alarm_control_panel_(alarm_control_panel) {}
TEMPLATABLE_VALUE(std::string, code)
void play(Ts... x) override {
auto call = this->alarm_control_panel_->make_call();
auto code = this->code_.optional_value(x...);
if (code.has_value()) {
call.set_code(code.value());
}
call.arm_night();
call.perform();
}
protected:
AlarmControlPanel *alarm_control_panel_;
};
template<typename... Ts> class DisarmAction : public Action<Ts...> { template<typename... Ts> class DisarmAction : public Action<Ts...> {
public: public:
explicit DisarmAction(AlarmControlPanel *alarm_control_panel) : alarm_control_panel_(alarm_control_panel) {} explicit DisarmAction(AlarmControlPanel *alarm_control_panel) : alarm_control_panel_(alarm_control_panel) {}

View File

@@ -0,0 +1 @@
CODEOWNERS = ["@jan-hofmeier"]

View File

@@ -0,0 +1,189 @@
#include "alpha3.h"
#include "esphome/core/log.h"
#include "esphome/core/application.h"
#include <lwip/sockets.h> //gives ntohl
#ifdef USE_ESP32
namespace esphome {
namespace alpha3 {
static const char *const TAG = "alpha3";
void Alpha3::dump_config() {
ESP_LOGCONFIG(TAG, "ALPHA3");
LOG_SENSOR(" ", "Flow", this->flow_sensor_);
LOG_SENSOR(" ", "Head", this->head_sensor_);
LOG_SENSOR(" ", "Power", this->power_sensor_);
LOG_SENSOR(" ", "Current", this->current_sensor_);
LOG_SENSOR(" ", "Speed", this->speed_sensor_);
LOG_SENSOR(" ", "Voltage", this->voltage_sensor_);
}
void Alpha3::setup() {}
void Alpha3::extract_publish_sensor_value_(const uint8_t *response, int16_t length, int16_t response_offset,
int16_t value_offset, sensor::Sensor *sensor, float factor) {
if (sensor == nullptr)
return;
// we need to handle cases where a value is split over two packets
const int16_t value_length = 4; // 32bit float
// offset inside current response packet
auto rel_offset = value_offset - response_offset;
if (rel_offset <= -value_length)
return; // aready passed the value completly
if (rel_offset >= length)
return; // value not in this packet
auto start_offset = std::max(0, rel_offset);
auto end_offset = std::min((int16_t) (rel_offset + value_length), length);
auto copy_length = end_offset - start_offset;
auto buffer_offset = std::max(-rel_offset, 0);
std::memcpy(this->buffer_ + buffer_offset, response + start_offset, copy_length);
if (rel_offset + value_length <= length) {
// we have the whole value
void *buffer = this->buffer_; // to prevent warnings when casting the pointer
*((int32_t *) buffer) = ntohl(*((int32_t *) buffer)); // values are big endian
float fvalue = *((float *) buffer);
sensor->publish_state(fvalue * factor);
}
}
bool Alpha3::is_current_response_type_(const uint8_t *response_type) {
return !std::memcmp(this->response_type_, response_type, GENI_RESPONSE_TYPE_LENGTH);
}
void Alpha3::handle_geni_response_(const uint8_t *response, uint16_t length) {
if (this->response_offset_ >= this->response_length_) {
ESP_LOGD(TAG, "[%s] GENI response begin", this->parent_->address_str().c_str());
if (length < GENI_RESPONSE_HEADER_LENGTH) {
ESP_LOGW(TAG, "[%s] response to short", this->parent_->address_str().c_str());
return;
}
if (response[0] != 36 || response[2] != 248 || response[3] != 231 || response[4] != 10) {
ESP_LOGW(TAG, "[%s] response bytes %d %d %d %d %d don't match GENI HEADER", this->parent_->address_str().c_str(),
response[0], response[1], response[2], response[3], response[4]);
return;
}
this->response_length_ = response[1] - GENI_RESPONSE_HEADER_LENGTH + 2; // maybe 2 byte checksum
this->response_offset_ = -GENI_RESPONSE_HEADER_LENGTH;
std::memcpy(this->response_type_, response + 5, GENI_RESPONSE_TYPE_LENGTH);
}
auto extract_publish_sensor_value = [response, length, this](int16_t value_offset, sensor::Sensor *sensor,
float factor) {
this->extract_publish_sensor_value_(response, length, this->response_offset_, value_offset, sensor, factor);
};
if (this->is_current_response_type_(GENI_RESPONSE_TYPE_FLOW_HEAD)) {
ESP_LOGD(TAG, "[%s] FLOW HEAD Response", this->parent_->address_str().c_str());
extract_publish_sensor_value(GENI_RESPONSE_FLOW_OFFSET, this->flow_sensor_, 3600.0F);
extract_publish_sensor_value(GENI_RESPONSE_HEAD_OFFSET, this->head_sensor_, .0001F);
} else if (this->is_current_response_type_(GENI_RESPONSE_TYPE_POWER)) {
ESP_LOGD(TAG, "[%s] POWER Response", this->parent_->address_str().c_str());
extract_publish_sensor_value(GENI_RESPONSE_POWER_OFFSET, this->power_sensor_, 1.0F);
extract_publish_sensor_value(GENI_RESPONSE_CURRENT_OFFSET, this->current_sensor_, 1.0F);
extract_publish_sensor_value(GENI_RESPONSE_MOTOR_SPEED_OFFSET, this->speed_sensor_, 1.0F);
extract_publish_sensor_value(GENI_RESPONSE_VOLTAGE_AC_OFFSET, this->voltage_sensor_, 1.0F);
} else {
ESP_LOGW(TAG, "unkown GENI response Type %d %d %d %d %d %d %d %d", this->response_type_[0], this->response_type_[1],
this->response_type_[2], this->response_type_[3], this->response_type_[4], this->response_type_[5],
this->response_type_[6], this->response_type_[7]);
}
this->response_offset_ += length;
}
void Alpha3::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->response_offset_ = 0;
this->response_length_ = 0;
ESP_LOGI(TAG, "[%s] connection open", this->parent_->address_str().c_str());
break;
}
case ESP_GATTC_CONNECT_EVT: {
if (std::memcmp(param->connect.remote_bda, this->parent_->get_remote_bda(), 6) != 0)
return;
auto ret = esp_ble_set_encryption(param->connect.remote_bda, ESP_BLE_SEC_ENCRYPT);
if (ret) {
ESP_LOGW(TAG, "esp_ble_set_encryption failed, status=%x", ret);
}
break;
}
case ESP_GATTC_DISCONNECT_EVT: {
this->node_state = espbt::ClientState::IDLE;
if (this->flow_sensor_ != nullptr)
this->flow_sensor_->publish_state(NAN);
if (this->head_sensor_ != nullptr)
this->head_sensor_->publish_state(NAN);
if (this->power_sensor_ != nullptr)
this->power_sensor_->publish_state(NAN);
if (this->current_sensor_ != nullptr)
this->current_sensor_->publish_state(NAN);
if (this->speed_sensor_ != nullptr)
this->speed_sensor_->publish_state(NAN);
if (this->speed_sensor_ != nullptr)
this->voltage_sensor_->publish_state(NAN);
break;
}
case ESP_GATTC_SEARCH_CMPL_EVT: {
auto *chr = this->parent_->get_characteristic(ALPHA3_GENI_SERVICE_UUID, ALPHA3_GENI_CHARACTERISTIC_UUID);
if (chr == nullptr) {
ESP_LOGE(TAG, "[%s] No GENI service found at device, not an Alpha3..?", this->parent_->address_str().c_str());
break;
}
auto status = esp_ble_gattc_register_for_notify(this->parent_->get_gattc_if(), this->parent_->get_remote_bda(),
chr->handle);
if (status) {
ESP_LOGW(TAG, "esp_ble_gattc_register_for_notify failed, status=%d", status);
}
this->geni_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->geni_handle_) {
this->handle_geni_response_(param->notify.value, param->notify.value_len);
}
break;
}
default:
break;
}
}
void Alpha3::send_request_(uint8_t *request, size_t len) {
auto status =
esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), this->geni_handle_, len,
request, 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 Alpha3::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->flow_sensor_ != nullptr || this->head_sensor_ != nullptr) {
uint8_t geni_request_flow_head[] = {39, 7, 231, 248, 10, 3, 93, 1, 33, 82, 31};
this->send_request_(geni_request_flow_head, sizeof(geni_request_flow_head));
delay(25); // need to wait between requests
}
if (this->power_sensor_ != nullptr || this->current_sensor_ != nullptr || this->speed_sensor_ != nullptr ||
this->voltage_sensor_ != nullptr) {
uint8_t geni_request_power[] = {39, 7, 231, 248, 10, 3, 87, 0, 69, 138, 205};
this->send_request_(geni_request_power, sizeof(geni_request_power));
delay(25); // need to wait between requests
}
}
} // namespace alpha3
} // namespace esphome
#endif

View File

@@ -0,0 +1,73 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/ble_client/ble_client.h"
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
#include "esphome/components/sensor/sensor.h"
#ifdef USE_ESP32
#include <esp_gattc_api.h>
namespace esphome {
namespace alpha3 {
namespace espbt = esphome::esp32_ble_tracker;
static const espbt::ESPBTUUID ALPHA3_GENI_SERVICE_UUID = espbt::ESPBTUUID::from_uint16(0xfe5d);
static const espbt::ESPBTUUID ALPHA3_GENI_CHARACTERISTIC_UUID =
espbt::ESPBTUUID::from_raw({static_cast<char>(0xa9), 0x7b, static_cast<char>(0xb8), static_cast<char>(0x85), 0x0,
0x1a, 0x28, static_cast<char>(0xaa), 0x2a, 0x43, 0x6e, 0x3, static_cast<char>(0xd1),
static_cast<char>(0xff), static_cast<char>(0x9c), static_cast<char>(0x85)});
static const int16_t GENI_RESPONSE_HEADER_LENGTH = 13;
static const size_t GENI_RESPONSE_TYPE_LENGTH = 8;
static const uint8_t GENI_RESPONSE_TYPE_FLOW_HEAD[GENI_RESPONSE_TYPE_LENGTH] = {31, 0, 1, 48, 1, 0, 0, 24};
static const int16_t GENI_RESPONSE_FLOW_OFFSET = 0;
static const int16_t GENI_RESPONSE_HEAD_OFFSET = 4;
static const uint8_t GENI_RESPONSE_TYPE_POWER[GENI_RESPONSE_TYPE_LENGTH] = {44, 0, 1, 0, 1, 0, 0, 37};
static const int16_t GENI_RESPONSE_VOLTAGE_AC_OFFSET = 0;
static const int16_t GENI_RESPONSE_VOLTAGE_DC_OFFSET = 4;
static const int16_t GENI_RESPONSE_CURRENT_OFFSET = 8;
static const int16_t GENI_RESPONSE_POWER_OFFSET = 12;
static const int16_t GENI_RESPONSE_MOTOR_POWER_OFFSET = 16; // not sure
static const int16_t GENI_RESPONSE_MOTOR_SPEED_OFFSET = 20;
class Alpha3 : 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_flow_sensor(sensor::Sensor *sensor) { this->flow_sensor_ = sensor; }
void set_head_sensor(sensor::Sensor *sensor) { this->head_sensor_ = sensor; }
void set_power_sensor(sensor::Sensor *sensor) { this->power_sensor_ = sensor; }
void set_current_sensor(sensor::Sensor *sensor) { this->current_sensor_ = sensor; }
void set_speed_sensor(sensor::Sensor *sensor) { this->speed_sensor_ = sensor; }
void set_voltage_sensor(sensor::Sensor *sensor) { this->voltage_sensor_ = sensor; }
protected:
sensor::Sensor *flow_sensor_{nullptr};
sensor::Sensor *head_sensor_{nullptr};
sensor::Sensor *power_sensor_{nullptr};
sensor::Sensor *current_sensor_{nullptr};
sensor::Sensor *speed_sensor_{nullptr};
sensor::Sensor *voltage_sensor_{nullptr};
uint16_t geni_handle_;
int16_t response_length_;
int16_t response_offset_;
uint8_t response_type_[GENI_RESPONSE_TYPE_LENGTH];
uint8_t buffer_[4];
void extract_publish_sensor_value_(const uint8_t *response, int16_t length, int16_t response_offset,
int16_t value_offset, sensor::Sensor *sensor, float factor);
void handle_geni_response_(const uint8_t *response, uint16_t length);
void send_request_(uint8_t *request, size_t len);
bool is_current_response_type_(const uint8_t *response_type);
};
} // namespace alpha3
} // namespace esphome
#endif

View File

@@ -0,0 +1,85 @@
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_CURRENT,
CONF_FLOW,
CONF_HEAD,
CONF_POWER,
CONF_SPEED,
CONF_VOLTAGE,
UNIT_AMPERE,
UNIT_VOLT,
UNIT_WATT,
UNIT_METER,
UNIT_CUBIC_METER_PER_HOUR,
UNIT_REVOLUTIONS_PER_MINUTE,
)
alpha3_ns = cg.esphome_ns.namespace("alpha3")
Alpha3 = alpha3_ns.class_("Alpha3", ble_client.BLEClientNode, cg.PollingComponent)
CONFIG_SCHEMA = (
cv.Schema(
{
cv.GenerateID(): cv.declare_id(Alpha3),
cv.Optional(CONF_FLOW): sensor.sensor_schema(
unit_of_measurement=UNIT_CUBIC_METER_PER_HOUR,
accuracy_decimals=2,
),
cv.Optional(CONF_HEAD): sensor.sensor_schema(
unit_of_measurement=UNIT_METER,
accuracy_decimals=2,
),
cv.Optional(CONF_POWER): sensor.sensor_schema(
unit_of_measurement=UNIT_WATT,
accuracy_decimals=2,
),
cv.Optional(CONF_CURRENT): sensor.sensor_schema(
unit_of_measurement=UNIT_AMPERE,
accuracy_decimals=2,
),
cv.Optional(CONF_SPEED): sensor.sensor_schema(
unit_of_measurement=UNIT_REVOLUTIONS_PER_MINUTE,
accuracy_decimals=2,
),
cv.Optional(CONF_VOLTAGE): sensor.sensor_schema(
unit_of_measurement=UNIT_VOLT,
accuracy_decimals=2,
),
}
)
.extend(ble_client.BLE_CLIENT_SCHEMA)
.extend(cv.polling_component_schema("15s"))
)
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 flow_config := config.get(CONF_FLOW):
sens = await sensor.new_sensor(flow_config)
cg.add(var.set_flow_sensor(sens))
if head_config := config.get(CONF_HEAD):
sens = await sensor.new_sensor(head_config)
cg.add(var.set_head_sensor(sens))
if power_config := config.get(CONF_POWER):
sens = await sensor.new_sensor(power_config)
cg.add(var.set_power_sensor(sens))
if current_config := config.get(CONF_CURRENT):
sens = await sensor.new_sensor(current_config)
cg.add(var.set_current_sensor(sens))
if speed_config := config.get(CONF_SPEED):
sens = await sensor.new_sensor(speed_config)
cg.add(var.set_speed_sensor(sens))
if voltage_config := config.get(CONF_VOLTAGE):
sens = await sensor.new_sensor(voltage_config)
cg.add(var.set_voltage_sensor(sens))

View File

@@ -47,10 +47,10 @@ async def to_code(config):
await cg.register_component(var, config) await cg.register_component(var, config)
await i2c.register_i2c_device(var, config) await i2c.register_i2c_device(var, config)
if CONF_TEMPERATURE in config: if temperature_config := config.get(CONF_TEMPERATURE):
sens = await sensor.new_sensor(config[CONF_TEMPERATURE]) sens = await sensor.new_sensor(temperature_config)
cg.add(var.set_temperature_sensor(sens)) cg.add(var.set_temperature_sensor(sens))
if CONF_HUMIDITY in config: if humidity_config := config.get(CONF_HUMIDITY):
sens = await sensor.new_sensor(config[CONF_HUMIDITY]) sens = await sensor.new_sensor(humidity_config)
cg.add(var.set_humidity_sensor(sens)) cg.add(var.set_humidity_sensor(sens))

View File

@@ -44,10 +44,10 @@ async def to_code(config):
await cg.register_component(var, config) await cg.register_component(var, config)
await ble_client.register_ble_node(var, config) await ble_client.register_ble_node(var, config)
if CONF_BATTERY_LEVEL in config: if battery_level_config := config.get(CONF_BATTERY_LEVEL):
sens = await sensor.new_sensor(config[CONF_BATTERY_LEVEL]) sens = await sensor.new_sensor(battery_level_config)
cg.add(var.set_battery(sens)) cg.add(var.set_battery(sens))
if CONF_ILLUMINANCE in config: if illuminance_config := config.get(CONF_ILLUMINANCE):
sens = await sensor.new_sensor(config[CONF_ILLUMINANCE]) sens = await sensor.new_sensor(illuminance_config)
cg.add(var.set_illuminance(sens)) cg.add(var.set_illuminance(sens))

View File

@@ -1,7 +1,7 @@
import logging import logging
from esphome import core from esphome import automation, core
from esphome.components import display, font from esphome.components import font
import esphome.components.image as espImage import esphome.components.image as espImage
from esphome.components.image import CONF_USE_TRANSPARENCY from esphome.components.image import CONF_USE_TRANSPARENCY
import esphome.config_validation as cv import esphome.config_validation as cv
@@ -18,14 +18,30 @@ from esphome.core import CORE, HexInt
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
AUTO_LOAD = ["image"]
CODEOWNERS = ["@syndlex"]
DEPENDENCIES = ["display"] DEPENDENCIES = ["display"]
MULTI_CONF = True MULTI_CONF = True
CONF_LOOP = "loop" CONF_LOOP = "loop"
CONF_START_FRAME = "start_frame" CONF_START_FRAME = "start_frame"
CONF_END_FRAME = "end_frame" CONF_END_FRAME = "end_frame"
CONF_FRAME = "frame"
Animation_ = display.display_ns.class_("Animation", espImage.Image_) animation_ns = cg.esphome_ns.namespace("animation")
Animation_ = animation_ns.class_("Animation", espImage.Image_)
# Actions
NextFrameAction = animation_ns.class_(
"AnimationNextFrameAction", automation.Action, cg.Parented.template(Animation_)
)
PrevFrameAction = animation_ns.class_(
"AnimationPrevFrameAction", automation.Action, cg.Parented.template(Animation_)
)
SetFrameAction = animation_ns.class_(
"AnimationSetFrameAction", automation.Action, cg.Parented.template(Animation_)
)
def validate_cross_dependencies(config): def validate_cross_dependencies(config):
@@ -74,7 +90,35 @@ ANIMATION_SCHEMA = cv.Schema(
CONFIG_SCHEMA = cv.All(font.validate_pillow_installed, ANIMATION_SCHEMA) CONFIG_SCHEMA = cv.All(font.validate_pillow_installed, ANIMATION_SCHEMA)
CODEOWNERS = ["@syndlex"] NEXT_FRAME_SCHEMA = automation.maybe_simple_id(
{
cv.GenerateID(): cv.use_id(Animation_),
}
)
PREV_FRAME_SCHEMA = automation.maybe_simple_id(
{
cv.GenerateID(): cv.use_id(Animation_),
}
)
SET_FRAME_SCHEMA = cv.Schema(
{
cv.GenerateID(): cv.use_id(Animation_),
cv.Required(CONF_FRAME): cv.uint16_t,
}
)
@automation.register_action("animation.next_frame", NextFrameAction, NEXT_FRAME_SCHEMA)
@automation.register_action("animation.prev_frame", PrevFrameAction, PREV_FRAME_SCHEMA)
@automation.register_action("animation.set_frame", SetFrameAction, SET_FRAME_SCHEMA)
async def animation_action_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID])
var = cg.new_Pvariable(action_id, template_arg, paren)
if (frame := config.get(CONF_FRAME)) is not None:
template_ = await cg.templatable(frame, args, cg.uint16)
cg.add(var.set_frame(template_))
return var
async def to_code(config): async def to_code(config):
@@ -245,8 +289,8 @@ async def to_code(config):
espImage.IMAGE_TYPE[config[CONF_TYPE]], espImage.IMAGE_TYPE[config[CONF_TYPE]],
) )
cg.add(var.set_transparency(transparent)) cg.add(var.set_transparency(transparent))
if CONF_LOOP in config: if loop_config := config.get(CONF_LOOP):
start = config[CONF_LOOP][CONF_START_FRAME] start = loop_config[CONF_START_FRAME]
end = config[CONF_LOOP].get(CONF_END_FRAME, frames) end = loop_config.get(CONF_END_FRAME, frames)
count = config[CONF_LOOP].get(CONF_REPEAT, -1) count = loop_config.get(CONF_REPEAT, -1)
cg.add(var.set_loop(start, end, count)) cg.add(var.set_loop(start, end, count))

View File

@@ -3,9 +3,10 @@
#include "esphome/core/hal.h" #include "esphome/core/hal.h"
namespace esphome { namespace esphome {
namespace display { namespace animation {
Animation::Animation(const uint8_t *data_start, int width, int height, uint32_t animation_frame_count, ImageType type) Animation::Animation(const uint8_t *data_start, int width, int height, uint32_t animation_frame_count,
image::ImageType type)
: Image(data_start, width, height, type), : Image(data_start, width, height, type),
animation_data_start_(data_start), animation_data_start_(data_start),
current_frame_(0), current_frame_(0),
@@ -65,5 +66,5 @@ void Animation::update_data_start_() {
this->data_start_ = this->animation_data_start_ + image_size * this->current_frame_; this->data_start_ = this->animation_data_start_ + image_size * this->current_frame_;
} }
} // namespace display } // namespace animation
} // namespace esphome } // namespace esphome

View File

@@ -0,0 +1,67 @@
#pragma once
#include "esphome/components/image/image.h"
#include "esphome/core/automation.h"
namespace esphome {
namespace animation {
class Animation : public image::Image {
public:
Animation(const uint8_t *data_start, int width, int height, uint32_t animation_frame_count, image::ImageType type);
uint32_t get_animation_frame_count() const;
int get_current_frame() const;
void next_frame();
void prev_frame();
/** Selects a specific frame within the animation.
*
* @param frame If possitive, advance to the frame. If negative, recede to that frame from the end frame.
*/
void set_frame(int frame);
void set_loop(uint32_t start_frame, uint32_t end_frame, int count);
protected:
void update_data_start_();
const uint8_t *animation_data_start_;
int current_frame_;
uint32_t animation_frame_count_;
uint32_t loop_start_frame_;
uint32_t loop_end_frame_;
int loop_count_;
int loop_current_iteration_;
};
template<typename... Ts> class AnimationNextFrameAction : public Action<Ts...> {
public:
AnimationNextFrameAction(Animation *parent) : parent_(parent) {}
void play(Ts... x) override { this->parent_->next_frame(); }
protected:
Animation *parent_;
};
template<typename... Ts> class AnimationPrevFrameAction : public Action<Ts...> {
public:
AnimationPrevFrameAction(Animation *parent) : parent_(parent) {}
void play(Ts... x) override { this->parent_->prev_frame(); }
protected:
Animation *parent_;
};
template<typename... Ts> class AnimationSetFrameAction : public Action<Ts...> {
public:
AnimationSetFrameAction(Animation *parent) : parent_(parent) {}
TEMPLATABLE_VALUE(uint16_t, frame)
void play(Ts... x) override { this->parent_->set_frame(this->frame_.value(x...)); }
protected:
Animation *parent_;
};
} // namespace animation
} // namespace esphome

View File

@@ -116,9 +116,8 @@ async def to_code(config):
cg.add(var.register_user_service(trigger)) cg.add(var.register_user_service(trigger))
await automation.build_automation(trigger, func_args, conf) await automation.build_automation(trigger, func_args, conf)
if CONF_ENCRYPTION in config: if encryption_config := config.get(CONF_ENCRYPTION):
conf = config[CONF_ENCRYPTION] decoded = base64.b64decode(encryption_config[CONF_KEY])
decoded = base64.b64decode(conf[CONF_KEY])
cg.add(var.set_noise_psk(list(decoded))) cg.add(var.set_noise_psk(list(decoded)))
cg.add_define("USE_API_NOISE") cg.add_define("USE_API_NOISE")
cg.add_library("esphome/noise-c", "0.1.4") cg.add_library("esphome/noise-c", "0.1.4")

View File

@@ -1420,6 +1420,7 @@ message VoiceAssistantRequest {
bool start = 1; bool start = 1;
string conversation_id = 2; string conversation_id = 2;
bool use_vad = 3;
} }
message VoiceAssistantResponse { message VoiceAssistantResponse {

View File

@@ -907,12 +907,13 @@ BluetoothConnectionsFreeResponse APIConnection::subscribe_bluetooth_connections_
#endif #endif
#ifdef USE_VOICE_ASSISTANT #ifdef USE_VOICE_ASSISTANT
bool APIConnection::request_voice_assistant(bool start, const std::string &conversation_id) { bool APIConnection::request_voice_assistant(bool start, const std::string &conversation_id, bool use_vad) {
if (!this->voice_assistant_subscription_) if (!this->voice_assistant_subscription_)
return false; return false;
VoiceAssistantRequest msg; VoiceAssistantRequest msg;
msg.start = start; msg.start = start;
msg.conversation_id = conversation_id; msg.conversation_id = conversation_id;
msg.use_vad = use_vad;
return this->send_voice_assistant_request(msg); return this->send_voice_assistant_request(msg);
} }
void APIConnection::on_voice_assistant_response(const VoiceAssistantResponse &msg) { void APIConnection::on_voice_assistant_response(const VoiceAssistantResponse &msg) {

View File

@@ -124,7 +124,7 @@ class APIConnection : public APIServerConnection {
void subscribe_voice_assistant(const SubscribeVoiceAssistantRequest &msg) override { void subscribe_voice_assistant(const SubscribeVoiceAssistantRequest &msg) override {
this->voice_assistant_subscription_ = msg.subscribe; this->voice_assistant_subscription_ = msg.subscribe;
} }
bool request_voice_assistant(bool start, const std::string &conversation_id); bool request_voice_assistant(bool start, const std::string &conversation_id, bool use_vad);
void on_voice_assistant_response(const VoiceAssistantResponse &msg) override; void on_voice_assistant_response(const VoiceAssistantResponse &msg) override;
void on_voice_assistant_event_response(const VoiceAssistantEventResponse &msg) override; void on_voice_assistant_event_response(const VoiceAssistantEventResponse &msg) override;
#endif #endif

View File

@@ -6348,6 +6348,10 @@ bool VoiceAssistantRequest::decode_varint(uint32_t field_id, ProtoVarInt value)
this->start = value.as_bool(); this->start = value.as_bool();
return true; return true;
} }
case 3: {
this->use_vad = value.as_bool();
return true;
}
default: default:
return false; return false;
} }
@@ -6365,6 +6369,7 @@ bool VoiceAssistantRequest::decode_length(uint32_t field_id, ProtoLengthDelimite
void VoiceAssistantRequest::encode(ProtoWriteBuffer buffer) const { void VoiceAssistantRequest::encode(ProtoWriteBuffer buffer) const {
buffer.encode_bool(1, this->start); buffer.encode_bool(1, this->start);
buffer.encode_string(2, this->conversation_id); buffer.encode_string(2, this->conversation_id);
buffer.encode_bool(3, this->use_vad);
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void VoiceAssistantRequest::dump_to(std::string &out) const { void VoiceAssistantRequest::dump_to(std::string &out) const {
@@ -6377,6 +6382,10 @@ void VoiceAssistantRequest::dump_to(std::string &out) const {
out.append(" conversation_id: "); out.append(" conversation_id: ");
out.append("'").append(this->conversation_id).append("'"); out.append("'").append(this->conversation_id).append("'");
out.append("\n"); out.append("\n");
out.append(" use_vad: ");
out.append(YESNO(this->use_vad));
out.append("\n");
out.append("}"); out.append("}");
} }
#endif #endif

View File

@@ -1655,6 +1655,7 @@ class VoiceAssistantRequest : public ProtoMessage {
public: public:
bool start{false}; bool start{false};
std::string conversation_id{}; std::string conversation_id{};
bool use_vad{false};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;

View File

@@ -323,16 +323,16 @@ void APIServer::on_shutdown() {
} }
#ifdef USE_VOICE_ASSISTANT #ifdef USE_VOICE_ASSISTANT
bool APIServer::start_voice_assistant(const std::string &conversation_id) { bool APIServer::start_voice_assistant(const std::string &conversation_id, bool use_vad) {
for (auto &c : this->clients_) { for (auto &c : this->clients_) {
if (c->request_voice_assistant(true, conversation_id)) if (c->request_voice_assistant(true, conversation_id, use_vad))
return true; return true;
} }
return false; return false;
} }
void APIServer::stop_voice_assistant() { void APIServer::stop_voice_assistant() {
for (auto &c : this->clients_) { for (auto &c : this->clients_) {
if (c->request_voice_assistant(false, "")) if (c->request_voice_assistant(false, "", false))
return; return;
} }
} }

View File

@@ -81,7 +81,7 @@ class APIServer : public Component, public Controller {
#endif #endif
#ifdef USE_VOICE_ASSISTANT #ifdef USE_VOICE_ASSISTANT
bool start_voice_assistant(const std::string &conversation_id); bool start_voice_assistant(const std::string &conversation_id, bool use_vad);
void stop_voice_assistant(); void stop_voice_assistant();
#endif #endif

View File

@@ -47,7 +47,7 @@ async def async_run_logs(config, address):
except APIConnectionError: except APIConnectionError:
cli.disconnect() cli.disconnect()
async def on_disconnect(): async def on_disconnect(expected_disconnect: bool) -> None:
_LOGGER.warning("Disconnected from API") _LOGGER.warning("Disconnected from API")
zc = zeroconf.Zeroconf() zc = zeroconf.Zeroconf()

View File

@@ -31,12 +31,10 @@ CONFIG_SCHEMA = cv.Schema(
async def to_code(config): async def to_code(config):
hub = await cg.get_variable(config[CONF_AS3935_ID]) hub = await cg.get_variable(config[CONF_AS3935_ID])
if CONF_DISTANCE in config: if distance_config := config.get(CONF_DISTANCE):
conf = config[CONF_DISTANCE] sens = await sensor.new_sensor(distance_config)
distance_sensor = await sensor.new_sensor(conf) cg.add(hub.set_distance_sensor(sens))
cg.add(hub.set_distance_sensor(distance_sensor))
if CONF_LIGHTNING_ENERGY in config: if lightning_energy_config := config.get(CONF_LIGHTNING_ENERGY):
conf = config[CONF_LIGHTNING_ENERGY] sens = await sensor.new_sensor(lightning_energy_config)
lightning_energy_sensor = await sensor.new_sensor(conf) cg.add(hub.set_energy_sensor(sens))
cg.add(hub.set_energy_sensor(lightning_energy_sensor))

View File

@@ -107,6 +107,6 @@ async def to_code(config):
cg.add(var.set_astep(config[CONF_ASTEP])) cg.add(var.set_astep(config[CONF_ASTEP]))
for conf_id, set_sensor_func in SENSORS.items(): for conf_id, set_sensor_func in SENSORS.items():
if conf_id in config: if sens_config := config.get(conf_id):
sens = await sensor.new_sensor(config[conf_id]) sens = await sensor.new_sensor(sens_config)
cg.add(getattr(var, set_sensor_func)(sens)) cg.add(getattr(var, set_sensor_func)(sens))

View File

@@ -83,18 +83,18 @@ async def to_code(config):
cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex)) cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex))
if CONF_TEMPERATURE in config: if temperature_config := config.get(CONF_TEMPERATURE):
sens = await sensor.new_sensor(config[CONF_TEMPERATURE]) sens = await sensor.new_sensor(temperature_config)
cg.add(var.set_temperature(sens)) cg.add(var.set_temperature(sens))
if CONF_HUMIDITY in config: if humidity_config := config.get(CONF_HUMIDITY):
sens = await sensor.new_sensor(config[CONF_HUMIDITY]) sens = await sensor.new_sensor(humidity_config)
cg.add(var.set_humidity(sens)) cg.add(var.set_humidity(sens))
if CONF_BATTERY_LEVEL in config: if battery_level_config := config.get(CONF_BATTERY_LEVEL):
sens = await sensor.new_sensor(config[CONF_BATTERY_LEVEL]) sens = await sensor.new_sensor(battery_level_config)
cg.add(var.set_battery_level(sens)) cg.add(var.set_battery_level(sens))
if CONF_BATTERY_VOLTAGE in config: if battery_voltage_config := config.get(CONF_BATTERY_VOLTAGE):
sens = await sensor.new_sensor(config[CONF_BATTERY_VOLTAGE]) sens = await sensor.new_sensor(battery_voltage_config)
cg.add(var.set_battery_voltage(sens)) cg.add(var.set_battery_voltage(sens))
if CONF_SIGNAL_STRENGTH in config: if signal_strength_config := config.get(CONF_SIGNAL_STRENGTH):
sens = await sensor.new_sensor(config[CONF_SIGNAL_STRENGTH]) sens = await sensor.new_sensor(signal_strength_config)
cg.add(var.set_signal_strength(sens)) cg.add(var.set_signal_strength(sens))

View File

@@ -0,0 +1 @@
CODEOWNERS = ["@danieltwagner"]

View File

@@ -0,0 +1,235 @@
#include "atm90e26.h"
#include "atm90e26_reg.h"
#include "esphome/core/log.h"
namespace esphome {
namespace atm90e26 {
static const char *const TAG = "atm90e26";
void ATM90E26Component::update() {
if (this->read16_(ATM90E26_REGISTER_FUNCEN) != 0x0030) {
this->status_set_warning();
return;
}
if (this->voltage_sensor_ != nullptr) {
this->voltage_sensor_->publish_state(this->get_line_voltage_());
}
if (this->current_sensor_ != nullptr) {
this->current_sensor_->publish_state(this->get_line_current_());
}
if (this->power_sensor_ != nullptr) {
this->power_sensor_->publish_state(this->get_active_power_());
}
if (this->reactive_power_sensor_ != nullptr) {
this->reactive_power_sensor_->publish_state(this->get_reactive_power_());
}
if (this->power_factor_sensor_ != nullptr) {
this->power_factor_sensor_->publish_state(this->get_power_factor_());
}
if (this->forward_active_energy_sensor_ != nullptr) {
this->forward_active_energy_sensor_->publish_state(this->get_forward_active_energy_());
}
if (this->reverse_active_energy_sensor_ != nullptr) {
this->reverse_active_energy_sensor_->publish_state(this->get_reverse_active_energy_());
}
if (this->freq_sensor_ != nullptr) {
this->freq_sensor_->publish_state(this->get_frequency_());
}
this->status_clear_warning();
}
void ATM90E26Component::setup() {
ESP_LOGCONFIG(TAG, "Setting up ATM90E26 Component...");
this->spi_setup();
uint16_t mmode = 0x422; // default values for everything but L/N line current gains
mmode |= (gain_pga_ & 0x7) << 13;
mmode |= (n_line_gain_ & 0x3) << 11;
this->write16_(ATM90E26_REGISTER_SOFTRESET, 0x789A); // Perform soft reset
this->write16_(ATM90E26_REGISTER_FUNCEN,
0x0030); // Voltage sag irq=1, report on warnout pin=1, energy dir change irq=0
uint16_t read = this->read16_(ATM90E26_REGISTER_LASTDATA);
if (read != 0x0030) {
ESP_LOGW(TAG, "Could not initialize ATM90E26 IC, check SPI settings: %d", read);
this->mark_failed();
return;
}
// TODO: 100 * <nominal voltage, e.g. 230> * sqrt(2) * <fraction of nominal, e.g. 0.9> / (4 * gain_voltage/32768)
this->write16_(ATM90E26_REGISTER_SAGTH, 0x17DD); // Voltage sag threshhold 0x1F2F
// Set metering calibration values
this->write16_(ATM90E26_REGISTER_CALSTART, 0x5678); // CAL Metering calibration startup command
// Configure
this->write16_(ATM90E26_REGISTER_MMODE, mmode); // Metering Mode Configuration (see above)
this->write16_(ATM90E26_REGISTER_PLCONSTH, (pl_const_ >> 16)); // PL Constant MSB
this->write16_(ATM90E26_REGISTER_PLCONSTL, pl_const_ & 0xFFFF); // PL Constant LSB
// Calibrate this to be 1 pulse per Wh
this->write16_(ATM90E26_REGISTER_LGAIN, gain_metering_); // L Line Calibration Gain (active power metering)
this->write16_(ATM90E26_REGISTER_LPHI, 0x0000); // L Line Calibration Angle
this->write16_(ATM90E26_REGISTER_NGAIN, 0x0000); // N Line Calibration Gain
this->write16_(ATM90E26_REGISTER_NPHI, 0x0000); // N Line Calibration Angle
this->write16_(ATM90E26_REGISTER_PSTARTTH, 0x08BD); // Active Startup Power Threshold (default) = 2237
this->write16_(ATM90E26_REGISTER_PNOLTH, 0x0000); // Active No-Load Power Threshold
this->write16_(ATM90E26_REGISTER_QSTARTTH, 0x0AEC); // Reactive Startup Power Threshold (default) = 2796
this->write16_(ATM90E26_REGISTER_QNOLTH, 0x0000); // Reactive No-Load Power Threshold
// Compute Checksum for the registers we set above
// low byte = sum of all bytes
uint16_t cs =
((mmode >> 8) + (mmode & 0xFF) + (pl_const_ >> 24) + ((pl_const_ >> 16) & 0xFF) + ((pl_const_ >> 8) & 0xFF) +
(pl_const_ & 0xFF) + (gain_metering_ >> 8) + (gain_metering_ & 0xFF) + 0x08 + 0xBD + 0x0A + 0xEC) &
0xFF;
// high byte = XOR of all bytes
cs |= ((mmode >> 8) ^ (mmode & 0xFF) ^ (pl_const_ >> 24) ^ ((pl_const_ >> 16) & 0xFF) ^ ((pl_const_ >> 8) & 0xFF) ^
(pl_const_ & 0xFF) ^ (gain_metering_ >> 8) ^ (gain_metering_ & 0xFF) ^ 0x08 ^ 0xBD ^ 0x0A ^ 0xEC)
<< 8;
this->write16_(ATM90E26_REGISTER_CS1, cs);
ESP_LOGVV(TAG, "Set CS1 to: 0x%04X", cs);
// Set measurement calibration values
this->write16_(ATM90E26_REGISTER_ADJSTART, 0x5678); // Measurement calibration startup command, registers 31-3A
this->write16_(ATM90E26_REGISTER_UGAIN, gain_voltage_); // Voltage RMS gain
this->write16_(ATM90E26_REGISTER_IGAINL, gain_ct_); // L line current RMS gain
this->write16_(ATM90E26_REGISTER_IGAINN, 0x7530); // N Line Current RMS Gain
this->write16_(ATM90E26_REGISTER_UOFFSET, 0x0000); // Voltage Offset
this->write16_(ATM90E26_REGISTER_IOFFSETL, 0x0000); // L Line Current Offset
this->write16_(ATM90E26_REGISTER_IOFFSETN, 0x0000); // N Line Current Offse
this->write16_(ATM90E26_REGISTER_POFFSETL, 0x0000); // L Line Active Power Offset
this->write16_(ATM90E26_REGISTER_QOFFSETL, 0x0000); // L Line Reactive Power Offset
this->write16_(ATM90E26_REGISTER_POFFSETN, 0x0000); // N Line Active Power Offset
this->write16_(ATM90E26_REGISTER_QOFFSETN, 0x0000); // N Line Reactive Power Offset
// Compute Checksum for the registers we set above
cs = ((gain_voltage_ >> 8) + (gain_voltage_ & 0xFF) + (gain_ct_ >> 8) + (gain_ct_ & 0xFF) + 0x75 + 0x30) & 0xFF;
cs |= ((gain_voltage_ >> 8) ^ (gain_voltage_ & 0xFF) ^ (gain_ct_ >> 8) ^ (gain_ct_ & 0xFF) ^ 0x75 ^ 0x30) << 8;
this->write16_(ATM90E26_REGISTER_CS2, cs);
ESP_LOGVV(TAG, "Set CS2 to: 0x%04X", cs);
this->write16_(ATM90E26_REGISTER_CALSTART,
0x8765); // Checks correctness of 21-2B registers and starts normal metering if ok
this->write16_(ATM90E26_REGISTER_ADJSTART,
0x8765); // Checks correctness of 31-3A registers and starts normal measurement if ok
uint16_t sys_status = this->read16_(ATM90E26_REGISTER_SYSSTATUS);
if (sys_status & 0xC000) { // Checksum 1 Error
ESP_LOGW(TAG, "Could not initialize ATM90E26 IC: CS1 was incorrect, expected: 0x%04X",
this->read16_(ATM90E26_REGISTER_CS1));
this->mark_failed();
}
if (sys_status & 0x3000) { // Checksum 2 Error
ESP_LOGW(TAG, "Could not initialize ATM90E26 IC: CS2 was incorrect, expected: 0x%04X",
this->read16_(ATM90E26_REGISTER_CS2));
this->mark_failed();
}
}
void ATM90E26Component::dump_config() {
ESP_LOGCONFIG("", "ATM90E26:");
LOG_PIN(" CS Pin: ", this->cs_);
if (this->is_failed()) {
ESP_LOGE(TAG, "Communication with ATM90E26 failed!");
}
LOG_UPDATE_INTERVAL(this);
LOG_SENSOR(" ", "Voltage A", this->voltage_sensor_);
LOG_SENSOR(" ", "Current A", this->current_sensor_);
LOG_SENSOR(" ", "Power A", this->power_sensor_);
LOG_SENSOR(" ", "Reactive Power A", this->reactive_power_sensor_);
LOG_SENSOR(" ", "PF A", this->power_factor_sensor_);
LOG_SENSOR(" ", "Active Forward Energy A", this->forward_active_energy_sensor_);
LOG_SENSOR(" ", "Active Reverse Energy A", this->reverse_active_energy_sensor_);
LOG_SENSOR(" ", "Frequency", this->freq_sensor_);
}
float ATM90E26Component::get_setup_priority() const { return setup_priority::DATA; }
uint16_t ATM90E26Component::read16_(uint8_t a_register) {
uint8_t data[2];
uint16_t output;
this->enable();
delayMicroseconds(4);
this->write_byte(a_register | 0x80);
delayMicroseconds(4);
this->read_array(data, 2);
this->disable();
output = (uint16_t(data[0] & 0xFF) << 8) | (data[1] & 0xFF);
ESP_LOGVV(TAG, "read16_ 0x%04X output 0x%04X", a_register, output);
return output;
}
void ATM90E26Component::write16_(uint8_t a_register, uint16_t val) {
ESP_LOGVV(TAG, "write16_ 0x%04X val 0x%04X", a_register, val);
this->enable();
delayMicroseconds(4);
this->write_byte(a_register & 0x7F);
delayMicroseconds(4);
this->write_byte((val >> 8) & 0xFF);
this->write_byte(val & 0xFF);
this->disable();
}
float ATM90E26Component::get_line_current_() {
uint16_t current = this->read16_(ATM90E26_REGISTER_IRMS);
return current / 1000.0f;
}
float ATM90E26Component::get_line_voltage_() {
uint16_t voltage = this->read16_(ATM90E26_REGISTER_URMS);
return voltage / 100.0f;
}
float ATM90E26Component::get_active_power_() {
int16_t val = this->read16_(ATM90E26_REGISTER_PMEAN); // two's complement
return (float) val;
}
float ATM90E26Component::get_reactive_power_() {
int16_t val = this->read16_(ATM90E26_REGISTER_QMEAN); // two's complement
return (float) val;
}
float ATM90E26Component::get_power_factor_() {
uint16_t val = this->read16_(ATM90E26_REGISTER_POWERF); // signed
if (val & 0x8000) {
return -(val & 0x7FF) / 1000.0f;
} else {
return val / 1000.0f;
}
}
float ATM90E26Component::get_forward_active_energy_() {
uint16_t val = this->read16_(ATM90E26_REGISTER_APENERGY);
if ((UINT32_MAX - this->cumulative_forward_active_energy_) > val) {
this->cumulative_forward_active_energy_ += val;
} else {
this->cumulative_forward_active_energy_ = val;
}
// The register holds thenths of pulses, we want to output Wh
return (this->cumulative_forward_active_energy_ * 100.0f / meter_constant_);
}
float ATM90E26Component::get_reverse_active_energy_() {
uint16_t val = this->read16_(ATM90E26_REGISTER_ANENERGY);
if (UINT32_MAX - this->cumulative_reverse_active_energy_ > val) {
this->cumulative_reverse_active_energy_ += val;
} else {
this->cumulative_reverse_active_energy_ = val;
}
return (this->cumulative_reverse_active_energy_ * 100.0f / meter_constant_);
}
float ATM90E26Component::get_frequency_() {
uint16_t freq = this->read16_(ATM90E26_REGISTER_FREQ);
return freq / 100.0f;
}
} // namespace atm90e26
} // namespace esphome

View File

@@ -0,0 +1,72 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/spi/spi.h"
namespace esphome {
namespace atm90e26 {
class ATM90E26Component : public PollingComponent,
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_HIGH,
spi::CLOCK_PHASE_TRAILING, spi::DATA_RATE_200KHZ> {
public:
void setup() override;
void dump_config() override;
float get_setup_priority() const override;
void update() override;
void set_voltage_sensor(sensor::Sensor *obj) { this->voltage_sensor_ = obj; }
void set_current_sensor(sensor::Sensor *obj) { this->current_sensor_ = obj; }
void set_power_sensor(sensor::Sensor *obj) { this->power_sensor_ = obj; }
void set_reactive_power_sensor(sensor::Sensor *obj) { this->reactive_power_sensor_ = obj; }
void set_forward_active_energy_sensor(sensor::Sensor *obj) { this->forward_active_energy_sensor_ = obj; }
void set_reverse_active_energy_sensor(sensor::Sensor *obj) { this->reverse_active_energy_sensor_ = obj; }
void set_power_factor_sensor(sensor::Sensor *obj) { this->power_factor_sensor_ = obj; }
void set_freq_sensor(sensor::Sensor *freq_sensor) { freq_sensor_ = freq_sensor; }
void set_line_freq(int freq) { line_freq_ = freq; }
void set_meter_constant(float val) { meter_constant_ = val; }
void set_pl_const(uint32_t pl_const) { pl_const_ = pl_const; }
void set_gain_metering(uint16_t gain) { this->gain_metering_ = gain; }
void set_gain_voltage(uint16_t gain) { this->gain_voltage_ = gain; }
void set_gain_ct(uint16_t gain) { this->gain_ct_ = gain; }
void set_gain_pga(uint16_t gain) { gain_pga_ = gain; }
void set_n_line_gain(uint16_t gain) { n_line_gain_ = gain; }
protected:
uint16_t read16_(uint8_t a_register);
int read32_(uint8_t addr_h, uint8_t addr_l);
void write16_(uint8_t a_register, uint16_t val);
float get_line_voltage_();
float get_line_current_();
float get_active_power_();
float get_reactive_power_();
float get_power_factor_();
float get_forward_active_energy_();
float get_reverse_active_energy_();
float get_frequency_();
float get_chip_temperature_();
sensor::Sensor *freq_sensor_{nullptr};
sensor::Sensor *voltage_sensor_{nullptr};
sensor::Sensor *current_sensor_{nullptr};
sensor::Sensor *power_sensor_{nullptr};
sensor::Sensor *reactive_power_sensor_{nullptr};
sensor::Sensor *power_factor_sensor_{nullptr};
sensor::Sensor *forward_active_energy_sensor_{nullptr};
sensor::Sensor *reverse_active_energy_sensor_{nullptr};
uint32_t cumulative_forward_active_energy_{0};
uint32_t cumulative_reverse_active_energy_{0};
uint16_t gain_metering_{7481};
uint16_t gain_voltage_{26400};
uint16_t gain_ct_{31251};
uint16_t gain_pga_{0x4};
uint16_t n_line_gain_{0x2};
int line_freq_{60};
float meter_constant_{3200.0f};
uint32_t pl_const_{1429876};
};
} // namespace atm90e26
} // namespace esphome

View File

@@ -0,0 +1,70 @@
#pragma once
namespace esphome {
namespace atm90e26 {
/* Status and Special Register */
static const uint8_t ATM90E26_REGISTER_SOFTRESET = 0x00; // Software Reset
static const uint8_t ATM90E26_REGISTER_SYSSTATUS = 0x01; // System Status
static const uint8_t ATM90E26_REGISTER_FUNCEN = 0x02; // Function Enable
static const uint8_t ATM90E26_REGISTER_SAGTH = 0x03; // Voltage Sag Threshold
static const uint8_t ATM90E26_REGISTER_SMALLPMOD = 0x04; // Small-Power Mode
static const uint8_t ATM90E26_REGISTER_LASTDATA = 0x06; // Last Read/Write SPI/UART Value
/* Metering Calibration and Configuration Register */
static const uint8_t ATM90E26_REGISTER_LSB = 0x08; // RMS/Power 16-bit LSB
static const uint8_t ATM90E26_REGISTER_CALSTART = 0x20; // Calibration Start Command
static const uint8_t ATM90E26_REGISTER_PLCONSTH = 0x21; // High Word of PL_Constant
static const uint8_t ATM90E26_REGISTER_PLCONSTL = 0x22; // Low Word of PL_Constant
static const uint8_t ATM90E26_REGISTER_LGAIN = 0x23; // L Line Calibration Gain
static const uint8_t ATM90E26_REGISTER_LPHI = 0x24; // L Line Calibration Angle
static const uint8_t ATM90E26_REGISTER_NGAIN = 0x25; // N Line Calibration Gain
static const uint8_t ATM90E26_REGISTER_NPHI = 0x26; // N Line Calibration Angle
static const uint8_t ATM90E26_REGISTER_PSTARTTH = 0x27; // Active Startup Power Threshold
static const uint8_t ATM90E26_REGISTER_PNOLTH = 0x28; // Active No-Load Power Threshold
static const uint8_t ATM90E26_REGISTER_QSTARTTH = 0x29; // Reactive Startup Power Threshold
static const uint8_t ATM90E26_REGISTER_QNOLTH = 0x2A; // Reactive No-Load Power Threshold
static const uint8_t ATM90E26_REGISTER_MMODE = 0x2B; // Metering Mode Configuration
static const uint8_t ATM90E26_REGISTER_CS1 = 0x2C; // Checksum 1
/* Measurement Calibration Register */
static const uint8_t ATM90E26_REGISTER_ADJSTART = 0x30; // Measurement Calibration Start Command
static const uint8_t ATM90E26_REGISTER_UGAIN = 0x31; // Voltage RMS Gain
static const uint8_t ATM90E26_REGISTER_IGAINL = 0x32; // L Line Current RMS Gain
static const uint8_t ATM90E26_REGISTER_IGAINN = 0x33; // N Line Current RMS Gain
static const uint8_t ATM90E26_REGISTER_UOFFSET = 0x34; // Voltage Offset
static const uint8_t ATM90E26_REGISTER_IOFFSETL = 0x35; // L Line Current Offset
static const uint8_t ATM90E26_REGISTER_IOFFSETN = 0x36; // N Line Current Offse
static const uint8_t ATM90E26_REGISTER_POFFSETL = 0x37; // L Line Active Power Offset
static const uint8_t ATM90E26_REGISTER_QOFFSETL = 0x38; // L Line Reactive Power Offset
static const uint8_t ATM90E26_REGISTER_POFFSETN = 0x39; // N Line Active Power Offset
static const uint8_t ATM90E26_REGISTER_QOFFSETN = 0x3A; // N Line Reactive Power Offset
static const uint8_t ATM90E26_REGISTER_CS2 = 0x3B; // Checksum 2
/* Energy Register */
static const uint8_t ATM90E26_REGISTER_APENERGY = 0x40; // Forward Active Energy
static const uint8_t ATM90E26_REGISTER_ANENERGY = 0x41; // Reverse Active Energy
static const uint8_t ATM90E26_REGISTER_ATENERGY = 0x42; // Absolute Active Energy
static const uint8_t ATM90E26_REGISTER_RPENERGY = 0x43; // Forward (Inductive) Reactive Energy
static const uint8_t ATM90E26_REGISTER_RNENERG = 0x44; // Reverse (Capacitive) Reactive Energy
static const uint8_t ATM90E26_REGISTER_RTENERGY = 0x45; // Absolute Reactive Energy
static const uint8_t ATM90E26_REGISTER_ENSTATUS = 0x46; // Metering Status
/* Measurement Register */
static const uint8_t ATM90E26_REGISTER_IRMS = 0x48; // L Line Current RMS
static const uint8_t ATM90E26_REGISTER_URMS = 0x49; // Voltage RMS
static const uint8_t ATM90E26_REGISTER_PMEAN = 0x4A; // L Line Mean Active Power
static const uint8_t ATM90E26_REGISTER_QMEAN = 0x4B; // L Line Mean Reactive Power
static const uint8_t ATM90E26_REGISTER_FREQ = 0x4C; // Voltage Frequency
static const uint8_t ATM90E26_REGISTER_POWERF = 0x4D; // L Line Power Factor
static const uint8_t ATM90E26_REGISTER_PANGLE = 0x4E; // Phase Angle between Voltage and L Line Current
static const uint8_t ATM90E26_REGISTER_SMEAN = 0x4F; // L Line Mean Apparent Power
static const uint8_t ATM90E26_REGISTER_IRMS2 = 0x68; // N Line Current rms
static const uint8_t ATM90E26_REGISTER_PMEAN2 = 0x6A; // N Line Mean Active Power
static const uint8_t ATM90E26_REGISTER_QMEAN2 = 0x6B; // N Line Mean Reactive Power
static const uint8_t ATM90E26_REGISTER_POWERF2 = 0x6D; // N Line Power Factor
static const uint8_t ATM90E26_REGISTER_PANGLE2 = 0x6E; // Phase Angle between Voltage and N Line Current
static const uint8_t ATM90E26_REGISTER_SMEAN2 = 0x6F; // N Line Mean Apparent Power
} // namespace atm90e26
} // namespace esphome

View File

@@ -0,0 +1,157 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import sensor, spi
from esphome.const import (
CONF_ID,
CONF_REACTIVE_POWER,
CONF_VOLTAGE,
CONF_CURRENT,
CONF_POWER,
CONF_POWER_FACTOR,
CONF_FREQUENCY,
CONF_FORWARD_ACTIVE_ENERGY,
CONF_REVERSE_ACTIVE_ENERGY,
DEVICE_CLASS_CURRENT,
DEVICE_CLASS_ENERGY,
DEVICE_CLASS_POWER,
DEVICE_CLASS_POWER_FACTOR,
DEVICE_CLASS_VOLTAGE,
ICON_LIGHTBULB,
ICON_CURRENT_AC,
STATE_CLASS_MEASUREMENT,
STATE_CLASS_TOTAL_INCREASING,
UNIT_HERTZ,
UNIT_VOLT,
UNIT_AMPERE,
UNIT_WATT,
UNIT_VOLT_AMPS_REACTIVE,
UNIT_WATT_HOURS,
)
CONF_LINE_FREQUENCY = "line_frequency"
CONF_METER_CONSTANT = "meter_constant"
CONF_PL_CONST = "pl_const"
CONF_GAIN_PGA = "gain_pga"
CONF_GAIN_METERING = "gain_metering"
CONF_GAIN_VOLTAGE = "gain_voltage"
CONF_GAIN_CT = "gain_ct"
LINE_FREQS = {
"50HZ": 50,
"60HZ": 60,
}
PGA_GAINS = {
"1X": 0x4,
"4X": 0x0,
"8X": 0x1,
"16X": 0x2,
"24X": 0x3,
}
atm90e26_ns = cg.esphome_ns.namespace("atm90e26")
ATM90E26Component = atm90e26_ns.class_(
"ATM90E26Component", cg.PollingComponent, spi.SPIDevice
)
CONFIG_SCHEMA = (
cv.Schema(
{
cv.GenerateID(): cv.declare_id(ATM90E26Component),
cv.Optional(CONF_VOLTAGE): sensor.sensor_schema(
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_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_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_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(
accuracy_decimals=2,
device_class=DEVICE_CLASS_POWER_FACTOR,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_FORWARD_ACTIVE_ENERGY): sensor.sensor_schema(
unit_of_measurement=UNIT_WATT_HOURS,
accuracy_decimals=2,
device_class=DEVICE_CLASS_ENERGY,
state_class=STATE_CLASS_TOTAL_INCREASING,
),
cv.Optional(CONF_REVERSE_ACTIVE_ENERGY): sensor.sensor_schema(
unit_of_measurement=UNIT_WATT_HOURS,
accuracy_decimals=2,
device_class=DEVICE_CLASS_ENERGY,
state_class=STATE_CLASS_TOTAL_INCREASING,
),
cv.Optional(CONF_FREQUENCY): sensor.sensor_schema(
unit_of_measurement=UNIT_HERTZ,
icon=ICON_CURRENT_AC,
accuracy_decimals=1,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Required(CONF_LINE_FREQUENCY): cv.enum(LINE_FREQS, upper=True),
cv.Required(CONF_METER_CONSTANT): cv.positive_float,
cv.Optional(CONF_PL_CONST, default=1429876): cv.uint32_t,
cv.Optional(CONF_GAIN_METERING, default=7481): cv.uint16_t,
cv.Optional(CONF_GAIN_VOLTAGE, default=26400): cv.int_range(
min=0, max=32767
),
cv.Optional(CONF_GAIN_CT, default=31251): cv.uint16_t,
cv.Optional(CONF_GAIN_PGA, default="1X"): cv.enum(PGA_GAINS, upper=True),
}
)
.extend(cv.polling_component_schema("60s"))
.extend(spi.spi_device_schema())
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await spi.register_spi_device(var, config)
if voltage_config := config.get(CONF_VOLTAGE):
sens = await sensor.new_sensor(voltage_config)
cg.add(var.set_voltage_sensor(sens))
if current_config := config.get(CONF_CURRENT):
sens = await sensor.new_sensor(current_config)
cg.add(var.set_current_sensor(sens))
if power_config := config.get(CONF_POWER):
sens = await sensor.new_sensor(power_config)
cg.add(var.set_power_sensor(sens))
if reactive_power_config := config.get(CONF_REACTIVE_POWER):
sens = await sensor.new_sensor(reactive_power_config)
cg.add(var.set_reactive_power_sensor(sens))
if power_factor_config := config.get(CONF_POWER_FACTOR):
sens = await sensor.new_sensor(power_factor_config)
cg.add(var.set_power_factor_sensor(sens))
if forward_active_energy_config := config.get(CONF_FORWARD_ACTIVE_ENERGY):
sens = await sensor.new_sensor(forward_active_energy_config)
cg.add(var.set_forward_active_energy_sensor(sens))
if reverse_active_energy_config := config.get(CONF_REVERSE_ACTIVE_ENERGY):
sens = await sensor.new_sensor(reverse_active_energy_config)
cg.add(var.set_reverse_active_energy_sensor(sens))
if frequency_config := config.get(CONF_FREQUENCY):
sens = await sensor.new_sensor(frequency_config)
cg.add(var.set_freq_sensor(sens))
cg.add(var.set_line_freq(config[CONF_LINE_FREQUENCY]))
cg.add(var.set_meter_constant(config[CONF_METER_CONSTANT]))
cg.add(var.set_pl_const(config[CONF_PL_CONST]))
cg.add(var.set_gain_metering(config[CONF_GAIN_METERING]))
cg.add(var.set_gain_voltage(config[CONF_GAIN_VOLTAGE]))
cg.add(var.set_gain_ct(config[CONF_GAIN_CT]))
cg.add(var.set_gain_pga(config[CONF_GAIN_PGA]))

View File

@@ -151,33 +151,35 @@ async def to_code(config):
conf = config[phase] conf = config[phase]
cg.add(var.set_volt_gain(i, conf[CONF_GAIN_VOLTAGE])) cg.add(var.set_volt_gain(i, conf[CONF_GAIN_VOLTAGE]))
cg.add(var.set_ct_gain(i, conf[CONF_GAIN_CT])) cg.add(var.set_ct_gain(i, conf[CONF_GAIN_CT]))
if CONF_VOLTAGE in conf: if voltage_config := conf.get(CONF_VOLTAGE):
sens = await sensor.new_sensor(conf[CONF_VOLTAGE]) sens = await sensor.new_sensor(voltage_config)
cg.add(var.set_voltage_sensor(i, sens)) cg.add(var.set_voltage_sensor(i, sens))
if CONF_CURRENT in conf: if current_config := conf.get(CONF_CURRENT):
sens = await sensor.new_sensor(conf[CONF_CURRENT]) sens = await sensor.new_sensor(current_config)
cg.add(var.set_current_sensor(i, sens)) cg.add(var.set_current_sensor(i, sens))
if CONF_POWER in conf: if power_config := conf.get(CONF_POWER):
sens = await sensor.new_sensor(conf[CONF_POWER]) sens = await sensor.new_sensor(power_config)
cg.add(var.set_power_sensor(i, sens)) cg.add(var.set_power_sensor(i, sens))
if CONF_REACTIVE_POWER in conf: if reactive_power_config := conf.get(CONF_REACTIVE_POWER):
sens = await sensor.new_sensor(conf[CONF_REACTIVE_POWER]) sens = await sensor.new_sensor(reactive_power_config)
cg.add(var.set_reactive_power_sensor(i, sens)) cg.add(var.set_reactive_power_sensor(i, sens))
if CONF_POWER_FACTOR in conf: if power_factor_config := conf.get(CONF_POWER_FACTOR):
sens = await sensor.new_sensor(conf[CONF_POWER_FACTOR]) sens = await sensor.new_sensor(power_factor_config)
cg.add(var.set_power_factor_sensor(i, sens)) cg.add(var.set_power_factor_sensor(i, sens))
if CONF_FORWARD_ACTIVE_ENERGY in conf: if forward_active_energy_config := conf.get(CONF_FORWARD_ACTIVE_ENERGY):
sens = await sensor.new_sensor(conf[CONF_FORWARD_ACTIVE_ENERGY]) sens = await sensor.new_sensor(forward_active_energy_config)
cg.add(var.set_forward_active_energy_sensor(i, sens)) cg.add(var.set_forward_active_energy_sensor(i, sens))
if CONF_REVERSE_ACTIVE_ENERGY in conf: if reverse_active_energy_config := conf.get(CONF_REVERSE_ACTIVE_ENERGY):
sens = await sensor.new_sensor(conf[CONF_REVERSE_ACTIVE_ENERGY]) sens = await sensor.new_sensor(reverse_active_energy_config)
cg.add(var.set_reverse_active_energy_sensor(i, sens)) cg.add(var.set_reverse_active_energy_sensor(i, sens))
if CONF_FREQUENCY in config:
sens = await sensor.new_sensor(config[CONF_FREQUENCY]) if frequency_config := config.get(CONF_FREQUENCY):
sens = await sensor.new_sensor(frequency_config)
cg.add(var.set_freq_sensor(sens)) cg.add(var.set_freq_sensor(sens))
if CONF_CHIP_TEMPERATURE in config: if chip_temperature_config := config.get(CONF_CHIP_TEMPERATURE):
sens = await sensor.new_sensor(config[CONF_CHIP_TEMPERATURE]) sens = await sensor.new_sensor(chip_temperature_config)
cg.add(var.set_chip_temperature_sensor(sens)) cg.add(var.set_chip_temperature_sensor(sens))
cg.add(var.set_line_freq(config[CONF_LINE_FREQUENCY])) cg.add(var.set_line_freq(config[CONF_LINE_FREQUENCY]))
cg.add(var.set_current_phases(config[CONF_CURRENT_PHASES])) cg.add(var.set_current_phases(config[CONF_CURRENT_PHASES]))
cg.add(var.set_pga_gain(config[CONF_GAIN_PGA])) cg.add(var.set_pga_gain(config[CONF_GAIN_PGA]))

View File

@@ -87,6 +87,6 @@ async def to_code(config):
(CONF_MOISTURE, var.set_soil_moisture), (CONF_MOISTURE, var.set_soil_moisture),
(CONF_ILLUMINANCE, var.set_illuminance), (CONF_ILLUMINANCE, var.set_illuminance),
]: ]:
if config_key in config: if sensor_config := config.get(config_key):
sens = await sensor.new_sensor(config[config_key]) sens = await sensor.new_sensor(sensor_config)
cg.add(setter(sens)) cg.add(setter(sens))

View File

@@ -57,19 +57,18 @@ async def to_code(config):
var.get_idle_trigger(), [], config[CONF_IDLE_ACTION] var.get_idle_trigger(), [], config[CONF_IDLE_ACTION]
) )
if CONF_COOL_ACTION in config: if cool_action_config := config.get(CONF_COOL_ACTION):
await automation.build_automation( await automation.build_automation(
var.get_cool_trigger(), [], config[CONF_COOL_ACTION] var.get_cool_trigger(), [], cool_action_config
) )
cg.add(var.set_supports_cool(True)) cg.add(var.set_supports_cool(True))
if CONF_HEAT_ACTION in config: if heat_action_config := config.get(CONF_HEAT_ACTION):
await automation.build_automation( await automation.build_automation(
var.get_heat_trigger(), [], config[CONF_HEAT_ACTION] var.get_heat_trigger(), [], heat_action_config
) )
cg.add(var.set_supports_heat(True)) cg.add(var.set_supports_heat(True))
if CONF_AWAY_CONFIG in config: if away := config.get(CONF_AWAY_CONFIG):
away = config[CONF_AWAY_CONFIG]
away_config = BangBangClimateTargetTempConfig( away_config = BangBangClimateTargetTempConfig(
away[CONF_DEFAULT_TARGET_TEMPERATURE_LOW], away[CONF_DEFAULT_TARGET_TEMPERATURE_LOW],
away[CONF_DEFAULT_TARGET_TEMPERATURE_HIGH], away[CONF_DEFAULT_TARGET_TEMPERATURE_HIGH],

View File

@@ -45,8 +45,8 @@ async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID]) var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config) await cg.register_component(var, config)
await ble_client.register_ble_node(var, config) await ble_client.register_ble_node(var, config)
if CONF_TIME_ID in config: if time_id := config.get(CONF_TIME_ID):
time_ = await cg.get_variable(config[CONF_TIME_ID]) time_ = await cg.get_variable(time_id)
cg.add(var.set_time_id(time_)) cg.add(var.set_time_id(time_))
if CONF_RECEIVE_TIMEOUT in config: if (receive_timeout := config.get(CONF_RECEIVE_TIMEOUT)) is not None:
cg.add(var.set_status_timeout(config[CONF_RECEIVE_TIMEOUT])) cg.add(var.set_status_timeout(receive_timeout))

View File

@@ -3,6 +3,7 @@
#include "bedjet_hub.h" #include "bedjet_hub.h"
#include "bedjet_child.h" #include "bedjet_child.h"
#include "bedjet_const.h" #include "bedjet_const.h"
#include <cinttypes>
namespace esphome { namespace esphome {
namespace bedjet { namespace bedjet {
@@ -373,7 +374,7 @@ void BedJetHub::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga
if (this->last_notify_ == 0 || delta > MIN_NOTIFY_THROTTLE || this->force_refresh_) { if (this->last_notify_ == 0 || delta > MIN_NOTIFY_THROTTLE || this->force_refresh_) {
// Set reentrant flag to prevent processing multiple packets. // Set reentrant flag to prevent processing multiple packets.
this->processing_ = true; this->processing_ = true;
ESP_LOGVV(TAG, "[%s] Decoding packet: last=%d, delta=%d, force=%s", this->get_name().c_str(), ESP_LOGVV(TAG, "[%s] Decoding packet: last=%" PRId32 ", delta=%" PRId32 ", force=%s", this->get_name().c_str(),
this->last_notify_, delta, this->force_refresh_ ? "y" : "n"); this->last_notify_, delta, this->force_refresh_ ? "y" : "n");
bool needs_extra = this->codec_->decode_notify(param->notify.value, param->notify.value_len); bool needs_extra = this->codec_->decode_notify(param->notify.value, param->notify.value_len);
@@ -523,11 +524,11 @@ void BedJetHub::dispatch_status_() {
ESP_LOGI(TAG, "[%s] Still waiting for first GATT notify event.", this->get_name().c_str()); ESP_LOGI(TAG, "[%s] Still waiting for first GATT notify event.", this->get_name().c_str());
} else if (diff > NOTIFY_WARN_THRESHOLD) { } else if (diff > NOTIFY_WARN_THRESHOLD) {
ESP_LOGW(TAG, "[%s] Last GATT notify was %d seconds ago.", this->get_name().c_str(), diff / 1000); ESP_LOGW(TAG, "[%s] Last GATT notify was %" PRId32 " seconds ago.", this->get_name().c_str(), diff / 1000);
} }
if (this->timeout_ > 0 && diff > this->timeout_ && this->parent()->enabled) { if (this->timeout_ > 0 && diff > this->timeout_ && this->parent()->enabled) {
ESP_LOGW(TAG, "[%s] Timed out after %d sec. Retrying...", this->get_name().c_str(), this->timeout_); ESP_LOGW(TAG, "[%s] Timed out after %" PRId32 " sec. Retrying...", this->get_name().c_str(), this->timeout_);
// set_enabled(false) will only close the connection if state != IDLE. // set_enabled(false) will only close the connection if state != IDLE.
this->parent()->set_state(espbt::ClientState::CONNECTING); this->parent()->set_state(espbt::ClientState::CONNECTING);
this->parent()->set_enabled(false); this->parent()->set_enabled(false);

View File

@@ -29,10 +29,10 @@ async def to_code(config):
output_ = await cg.get_variable(config[CONF_OUTPUT]) output_ = await cg.get_variable(config[CONF_OUTPUT])
cg.add(var.set_output(output_)) cg.add(var.set_output(output_))
if CONF_OSCILLATION_OUTPUT in config: if oscillation_output_id := config.get(CONF_OSCILLATION_OUTPUT):
oscillation_output = await cg.get_variable(config[CONF_OSCILLATION_OUTPUT]) oscillation_output = await cg.get_variable(oscillation_output_id)
cg.add(var.set_oscillating(oscillation_output)) cg.add(var.set_oscillating(oscillation_output))
if CONF_DIRECTION_OUTPUT in config: if direction_output_id := config.get(CONF_DIRECTION_OUTPUT):
direction_output = await cg.get_variable(config[CONF_DIRECTION_OUTPUT]) direction_output = await cg.get_variable(direction_output_id)
cg.add(var.set_direction(direction_output)) cg.add(var.set_direction(direction_output))

View File

@@ -95,6 +95,14 @@ DEVICE_CLASSES = [
IS_PLATFORM_COMPONENT = True IS_PLATFORM_COMPONENT = True
CONF_TIME_OFF = "time_off"
CONF_TIME_ON = "time_on"
DEFAULT_DELAY = "1s"
DEFAULT_TIME_OFF = "100ms"
DEFAULT_TIME_ON = "900ms"
binary_sensor_ns = cg.esphome_ns.namespace("binary_sensor") binary_sensor_ns = cg.esphome_ns.namespace("binary_sensor")
BinarySensor = binary_sensor_ns.class_("BinarySensor", cg.EntityBase) BinarySensor = binary_sensor_ns.class_("BinarySensor", cg.EntityBase)
BinarySensorInitiallyOff = binary_sensor_ns.class_( BinarySensorInitiallyOff = binary_sensor_ns.class_(
@@ -138,47 +146,75 @@ FILTER_REGISTRY = Registry()
validate_filters = cv.validate_registry("filter", FILTER_REGISTRY) validate_filters = cv.validate_registry("filter", FILTER_REGISTRY)
@FILTER_REGISTRY.register("invert", InvertFilter, {}) def register_filter(name, filter_type, schema):
return FILTER_REGISTRY.register(name, filter_type, schema)
@register_filter("invert", InvertFilter, {})
async def invert_filter_to_code(config, filter_id): async def invert_filter_to_code(config, filter_id):
return cg.new_Pvariable(filter_id) return cg.new_Pvariable(filter_id)
@FILTER_REGISTRY.register( @register_filter(
"delayed_on_off", DelayedOnOffFilter, cv.positive_time_period_milliseconds "delayed_on_off",
DelayedOnOffFilter,
cv.Any(
cv.templatable(cv.positive_time_period_milliseconds),
cv.Schema(
{
cv.Required(CONF_TIME_ON): cv.templatable(
cv.positive_time_period_milliseconds
),
cv.Required(CONF_TIME_OFF): cv.templatable(
cv.positive_time_period_milliseconds
),
}
),
msg="'delayed_on_off' filter requires either a delay time to be used for both "
"turn-on and turn-off delays, or two parameters 'time_on' and 'time_off' if "
"different delay times are required.",
),
) )
async def delayed_on_off_filter_to_code(config, filter_id): async def delayed_on_off_filter_to_code(config, filter_id):
var = cg.new_Pvariable(filter_id, config) var = cg.new_Pvariable(filter_id)
await cg.register_component(var, {}) await cg.register_component(var, {})
if isinstance(config, dict):
template_ = await cg.templatable(config[CONF_TIME_ON], [], cg.uint32)
cg.add(var.set_on_delay(template_))
template_ = await cg.templatable(config[CONF_TIME_OFF], [], cg.uint32)
cg.add(var.set_off_delay(template_))
else:
template_ = await cg.templatable(config, [], cg.uint32)
cg.add(var.set_on_delay(template_))
cg.add(var.set_off_delay(template_))
return var return var
@FILTER_REGISTRY.register( @register_filter(
"delayed_on", DelayedOnFilter, cv.positive_time_period_milliseconds "delayed_on", DelayedOnFilter, cv.templatable(cv.positive_time_period_milliseconds)
) )
async def delayed_on_filter_to_code(config, filter_id): async def delayed_on_filter_to_code(config, filter_id):
var = cg.new_Pvariable(filter_id, config) var = cg.new_Pvariable(filter_id)
await cg.register_component(var, {}) await cg.register_component(var, {})
template_ = await cg.templatable(config, [], cg.uint32)
cg.add(var.set_delay(template_))
return var return var
@FILTER_REGISTRY.register( @register_filter(
"delayed_off", DelayedOffFilter, cv.positive_time_period_milliseconds "delayed_off",
DelayedOffFilter,
cv.templatable(cv.positive_time_period_milliseconds),
) )
async def delayed_off_filter_to_code(config, filter_id): async def delayed_off_filter_to_code(config, filter_id):
var = cg.new_Pvariable(filter_id, config) var = cg.new_Pvariable(filter_id)
await cg.register_component(var, {}) await cg.register_component(var, {})
template_ = await cg.templatable(config, [], cg.uint32)
cg.add(var.set_delay(template_))
return var return var
CONF_TIME_OFF = "time_off" @register_filter(
CONF_TIME_ON = "time_on"
DEFAULT_DELAY = "1s"
DEFAULT_TIME_OFF = "100ms"
DEFAULT_TIME_ON = "900ms"
@FILTER_REGISTRY.register(
"autorepeat", "autorepeat",
AutorepeatFilter, AutorepeatFilter,
cv.All( cv.All(
@@ -215,7 +251,7 @@ async def autorepeat_filter_to_code(config, filter_id):
return var return var
@FILTER_REGISTRY.register("lambda", LambdaFilter, cv.returning_lambda) @register_filter("lambda", LambdaFilter, cv.returning_lambda)
async def lambda_filter_to_code(config, filter_id): async def lambda_filter_to_code(config, filter_id):
lambda_ = await cg.process_lambda( lambda_ = await cg.process_lambda(
config, [(bool, "x")], return_type=cg.optional.template(bool) config, [(bool, "x")], return_type=cg.optional.template(bool)
@@ -323,6 +359,18 @@ def validate_multi_click_timing(value):
validate_device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space="_") validate_device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space="_")
def validate_click_timing(value):
for v in value:
min_length = v.get(CONF_MIN_LENGTH)
max_length = v.get(CONF_MAX_LENGTH)
if max_length < min_length:
raise cv.Invalid(
f"Max length ({max_length}) must be larger than min length ({min_length})."
)
return value
BINARY_SENSOR_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend( BINARY_SENSOR_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend(
{ {
cv.GenerateID(): cv.declare_id(BinarySensor), cv.GenerateID(): cv.declare_id(BinarySensor),
@@ -342,27 +390,33 @@ BINARY_SENSOR_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).ex
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ReleaseTrigger), cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ReleaseTrigger),
} }
), ),
cv.Optional(CONF_ON_CLICK): automation.validate_automation( cv.Optional(CONF_ON_CLICK): cv.All(
{ automation.validate_automation(
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ClickTrigger), {
cv.Optional( cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ClickTrigger),
CONF_MIN_LENGTH, default="50ms" cv.Optional(
): cv.positive_time_period_milliseconds, CONF_MIN_LENGTH, default="50ms"
cv.Optional( ): cv.positive_time_period_milliseconds,
CONF_MAX_LENGTH, default="350ms" cv.Optional(
): cv.positive_time_period_milliseconds, CONF_MAX_LENGTH, default="350ms"
} ): cv.positive_time_period_milliseconds,
}
),
validate_click_timing,
), ),
cv.Optional(CONF_ON_DOUBLE_CLICK): automation.validate_automation( cv.Optional(CONF_ON_DOUBLE_CLICK): cv.All(
{ automation.validate_automation(
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(DoubleClickTrigger), {
cv.Optional( cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(DoubleClickTrigger),
CONF_MIN_LENGTH, default="50ms" cv.Optional(
): cv.positive_time_period_milliseconds, CONF_MIN_LENGTH, default="50ms"
cv.Optional( ): cv.positive_time_period_milliseconds,
CONF_MAX_LENGTH, default="350ms" cv.Optional(
): cv.positive_time_period_milliseconds, CONF_MAX_LENGTH, default="350ms"
} ): cv.positive_time_period_milliseconds,
}
),
validate_click_timing,
), ),
cv.Optional(CONF_ON_MULTI_CLICK): automation.validate_automation( cv.Optional(CONF_ON_MULTI_CLICK): automation.validate_automation(
{ {
@@ -413,14 +467,14 @@ def binary_sensor_schema(
async def setup_binary_sensor_core_(var, config): async def setup_binary_sensor_core_(var, config):
await setup_entity(var, config) await setup_entity(var, config)
if CONF_DEVICE_CLASS in config: if (device_class := config.get(CONF_DEVICE_CLASS)) is not None:
cg.add(var.set_device_class(config[CONF_DEVICE_CLASS])) cg.add(var.set_device_class(device_class))
if CONF_PUBLISH_INITIAL_STATE in config: if publish_initial_state := config.get(CONF_PUBLISH_INITIAL_STATE):
cg.add(var.set_publish_initial_state(config[CONF_PUBLISH_INITIAL_STATE])) cg.add(var.set_publish_initial_state(publish_initial_state))
if CONF_INVERTED in config: if inverted := config.get(CONF_INVERTED):
cg.add(var.set_inverted(config[CONF_INVERTED])) cg.add(var.set_inverted(inverted))
if CONF_FILTERS in config: if filters_config := config.get(CONF_FILTERS):
filters = await cg.build_registry_list(FILTER_REGISTRY, config[CONF_FILTERS]) filters = await cg.build_registry_list(FILTER_REGISTRY, filters_config)
cg.add(var.add_filters(filters)) cg.add(var.add_filters(filters))
for conf in config.get(CONF_ON_PRESS, []): for conf in config.get(CONF_ON_PRESS, []):
@@ -464,8 +518,8 @@ async def setup_binary_sensor_core_(var, config):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [(bool, "x")], conf) await automation.build_automation(trigger, [(bool, "x")], conf)
if CONF_MQTT_ID in config: if mqtt_id := config.get(CONF_MQTT_ID):
mqtt_ = cg.new_Pvariable(config[CONF_MQTT_ID], var) mqtt_ = cg.new_Pvariable(mqtt_id, var)
await mqtt.register_mqtt_component(mqtt_, config) await mqtt.register_mqtt_component(mqtt_, config)

View File

@@ -26,22 +26,20 @@ void Filter::input(bool value, bool is_initial) {
} }
} }
DelayedOnOffFilter::DelayedOnOffFilter(uint32_t delay) : delay_(delay) {}
optional<bool> DelayedOnOffFilter::new_value(bool value, bool is_initial) { optional<bool> DelayedOnOffFilter::new_value(bool value, bool is_initial) {
if (value) { if (value) {
this->set_timeout("ON_OFF", this->delay_, [this, is_initial]() { this->output(true, is_initial); }); this->set_timeout("ON_OFF", this->on_delay_.value(), [this, is_initial]() { this->output(true, is_initial); });
} else { } else {
this->set_timeout("ON_OFF", this->delay_, [this, is_initial]() { this->output(false, is_initial); }); this->set_timeout("ON_OFF", this->off_delay_.value(), [this, is_initial]() { this->output(false, is_initial); });
} }
return {}; return {};
} }
float DelayedOnOffFilter::get_setup_priority() const { return setup_priority::HARDWARE; } float DelayedOnOffFilter::get_setup_priority() const { return setup_priority::HARDWARE; }
DelayedOnFilter::DelayedOnFilter(uint32_t delay) : delay_(delay) {}
optional<bool> DelayedOnFilter::new_value(bool value, bool is_initial) { optional<bool> DelayedOnFilter::new_value(bool value, bool is_initial) {
if (value) { if (value) {
this->set_timeout("ON", this->delay_, [this, is_initial]() { this->output(true, is_initial); }); this->set_timeout("ON", this->delay_.value(), [this, is_initial]() { this->output(true, is_initial); });
return {}; return {};
} else { } else {
this->cancel_timeout("ON"); this->cancel_timeout("ON");
@@ -51,10 +49,9 @@ optional<bool> DelayedOnFilter::new_value(bool value, bool is_initial) {
float DelayedOnFilter::get_setup_priority() const { return setup_priority::HARDWARE; } float DelayedOnFilter::get_setup_priority() const { return setup_priority::HARDWARE; }
DelayedOffFilter::DelayedOffFilter(uint32_t delay) : delay_(delay) {}
optional<bool> DelayedOffFilter::new_value(bool value, bool is_initial) { optional<bool> DelayedOffFilter::new_value(bool value, bool is_initial) {
if (!value) { if (!value) {
this->set_timeout("OFF", this->delay_, [this, is_initial]() { this->output(false, is_initial); }); this->set_timeout("OFF", this->delay_.value(), [this, is_initial]() { this->output(false, is_initial); });
return {}; return {};
} else { } else {
this->cancel_timeout("OFF"); this->cancel_timeout("OFF");
@@ -114,15 +111,6 @@ LambdaFilter::LambdaFilter(std::function<optional<bool>(bool)> f) : f_(std::move
optional<bool> LambdaFilter::new_value(bool value, bool is_initial) { return this->f_(value); } optional<bool> LambdaFilter::new_value(bool value, bool is_initial) { return this->f_(value); }
optional<bool> UniqueFilter::new_value(bool value, bool is_initial) {
if (this->last_value_.has_value() && *this->last_value_ == value) {
return {};
} else {
this->last_value_ = value;
return value;
}
}
} // namespace binary_sensor } // namespace binary_sensor
} // namespace esphome } // namespace esphome

View File

@@ -1,5 +1,6 @@
#pragma once #pragma once
#include "esphome/core/automation.h"
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/core/helpers.h" #include "esphome/core/helpers.h"
@@ -29,38 +30,40 @@ class Filter {
class DelayedOnOffFilter : public Filter, public Component { class DelayedOnOffFilter : public Filter, public Component {
public: public:
explicit DelayedOnOffFilter(uint32_t delay);
optional<bool> new_value(bool value, bool is_initial) override; optional<bool> new_value(bool value, bool is_initial) override;
float get_setup_priority() const override; float get_setup_priority() const override;
template<typename T> void set_on_delay(T delay) { this->on_delay_ = delay; }
template<typename T> void set_off_delay(T delay) { this->off_delay_ = delay; }
protected: protected:
uint32_t delay_; TemplatableValue<uint32_t> on_delay_{};
TemplatableValue<uint32_t> off_delay_{};
}; };
class DelayedOnFilter : public Filter, public Component { class DelayedOnFilter : public Filter, public Component {
public: public:
explicit DelayedOnFilter(uint32_t delay);
optional<bool> new_value(bool value, bool is_initial) override; optional<bool> new_value(bool value, bool is_initial) override;
float get_setup_priority() const override; float get_setup_priority() const override;
template<typename T> void set_delay(T delay) { this->delay_ = delay; }
protected: protected:
uint32_t delay_; TemplatableValue<uint32_t> delay_{};
}; };
class DelayedOffFilter : public Filter, public Component { class DelayedOffFilter : public Filter, public Component {
public: public:
explicit DelayedOffFilter(uint32_t delay);
optional<bool> new_value(bool value, bool is_initial) override; optional<bool> new_value(bool value, bool is_initial) override;
float get_setup_priority() const override; float get_setup_priority() const override;
template<typename T> void set_delay(T delay) { this->delay_ = delay; }
protected: protected:
uint32_t delay_; TemplatableValue<uint32_t> delay_{};
}; };
class InvertFilter : public Filter { class InvertFilter : public Filter {
@@ -105,14 +108,6 @@ class LambdaFilter : public Filter {
std::function<optional<bool>(bool)> f_; std::function<optional<bool>(bool)> f_;
}; };
class UniqueFilter : public Filter {
public:
optional<bool> new_value(bool value, bool is_initial) override;
protected:
optional<bool> last_value_{};
};
} // namespace binary_sensor } // namespace binary_sensor
} // namespace esphome } // namespace esphome

View File

@@ -93,35 +93,27 @@ async def to_code(config):
await cg.register_component(var, config) await cg.register_component(var, config)
await uart.register_uart_device(var, config) await uart.register_uart_device(var, config)
if CONF_VOLTAGE in config: if voltage_config := config.get(CONF_VOLTAGE):
conf = config[CONF_VOLTAGE] sens = await sensor.new_sensor(voltage_config)
sens = await sensor.new_sensor(conf)
cg.add(var.set_voltage_sensor(sens)) cg.add(var.set_voltage_sensor(sens))
if CONF_CURRENT_1 in config: if current_1_config := config.get(CONF_CURRENT_1):
conf = config[CONF_CURRENT_1] sens = await sensor.new_sensor(current_1_config)
sens = await sensor.new_sensor(conf)
cg.add(var.set_current_sensor_1(sens)) cg.add(var.set_current_sensor_1(sens))
if CONF_CURRENT_2 in config: if current_2_config := config.get(CONF_CURRENT_2):
conf = config[CONF_CURRENT_2] sens = await sensor.new_sensor(current_2_config)
sens = await sensor.new_sensor(conf)
cg.add(var.set_current_sensor_2(sens)) cg.add(var.set_current_sensor_2(sens))
if CONF_ACTIVE_POWER_1 in config: if active_power_1_config := config.get(CONF_ACTIVE_POWER_1):
conf = config[CONF_ACTIVE_POWER_1] sens = await sensor.new_sensor(active_power_1_config)
sens = await sensor.new_sensor(conf)
cg.add(var.set_power_sensor_1(sens)) cg.add(var.set_power_sensor_1(sens))
if CONF_ACTIVE_POWER_2 in config: if active_power_2_config := config.get(CONF_ACTIVE_POWER_2):
conf = config[CONF_ACTIVE_POWER_2] sens = await sensor.new_sensor(active_power_2_config)
sens = await sensor.new_sensor(conf)
cg.add(var.set_power_sensor_2(sens)) cg.add(var.set_power_sensor_2(sens))
if CONF_ENERGY_1 in config: if energy_1_config := config.get(CONF_ENERGY_1):
conf = config[CONF_ENERGY_1] sens = await sensor.new_sensor(energy_1_config)
sens = await sensor.new_sensor(conf)
cg.add(var.set_energy_sensor_1(sens)) cg.add(var.set_energy_sensor_1(sens))
if CONF_ENERGY_2 in config: if energy_2_config := config.get(CONF_ENERGY_2):
conf = config[CONF_ENERGY_2] sens = await sensor.new_sensor(energy_2_config)
sens = await sensor.new_sensor(conf)
cg.add(var.set_energy_sensor_2(sens)) cg.add(var.set_energy_sensor_2(sens))
if CONF_ENERGY_TOTAL in config: if energy_total_config := config.get(CONF_ENERGY_TOTAL):
conf = config[CONF_ENERGY_TOTAL] sens = await sensor.new_sensor(energy_total_config)
sens = await sensor.new_sensor(conf)
cg.add(var.set_energy_sensor_sum(sens)) cg.add(var.set_energy_sensor_sum(sens))

View File

@@ -79,27 +79,21 @@ async def to_code(config):
await cg.register_component(var, config) await cg.register_component(var, config)
await uart.register_uart_device(var, config) await uart.register_uart_device(var, config)
if CONF_VOLTAGE in config: if voltage_config := config.get(CONF_VOLTAGE):
conf = config[CONF_VOLTAGE] sens = await sensor.new_sensor(voltage_config)
sens = await sensor.new_sensor(conf)
cg.add(var.set_voltage_sensor(sens)) cg.add(var.set_voltage_sensor(sens))
if CONF_CURRENT in config: if current_config := config.get(CONF_CURRENT):
conf = config[CONF_CURRENT] sens = await sensor.new_sensor(current_config)
sens = await sensor.new_sensor(conf)
cg.add(var.set_current_sensor(sens)) cg.add(var.set_current_sensor(sens))
if CONF_POWER in config: if power_config := config.get(CONF_POWER):
conf = config[CONF_POWER] sens = await sensor.new_sensor(power_config)
sens = await sensor.new_sensor(conf)
cg.add(var.set_power_sensor(sens)) cg.add(var.set_power_sensor(sens))
if CONF_ENERGY in config: if energy_config := config.get(CONF_ENERGY):
conf = config[CONF_ENERGY] sens = await sensor.new_sensor(energy_config)
sens = await sensor.new_sensor(conf)
cg.add(var.set_energy_sensor(sens)) cg.add(var.set_energy_sensor(sens))
if CONF_INTERNAL_TEMPERATURE in config: if internal_temperature_config := config.get(CONF_INTERNAL_TEMPERATURE):
conf = config[CONF_INTERNAL_TEMPERATURE] sens = await sensor.new_sensor(internal_temperature_config)
sens = await sensor.new_sensor(conf)
cg.add(var.set_internal_temperature_sensor(sens)) cg.add(var.set_internal_temperature_sensor(sens))
if CONF_EXTERNAL_TEMPERATURE in config: if external_temperature_config := config.get(CONF_EXTERNAL_TEMPERATURE):
conf = config[CONF_EXTERNAL_TEMPERATURE] sens = await sensor.new_sensor(external_temperature_config)
sens = await sensor.new_sensor(conf)
cg.add(var.set_external_temperature_sensor(sens)) cg.add(var.set_external_temperature_sensor(sens))

View File

@@ -71,23 +71,18 @@ async def to_code(config):
await cg.register_component(var, config) await cg.register_component(var, config)
await uart.register_uart_device(var, config) await uart.register_uart_device(var, config)
if CONF_VOLTAGE in config: if voltage_config := config.get(CONF_VOLTAGE):
conf = config[CONF_VOLTAGE] sens = await sensor.new_sensor(voltage_config)
sens = await sensor.new_sensor(conf)
cg.add(var.set_voltage_sensor(sens)) cg.add(var.set_voltage_sensor(sens))
if CONF_CURRENT in config: if current_config := config.get(CONF_CURRENT):
conf = config[CONF_CURRENT] sens = await sensor.new_sensor(current_config)
sens = await sensor.new_sensor(conf)
cg.add(var.set_current_sensor(sens)) cg.add(var.set_current_sensor(sens))
if CONF_POWER in config: if power_config := config.get(CONF_POWER):
conf = config[CONF_POWER] sens = await sensor.new_sensor(power_config)
sens = await sensor.new_sensor(conf)
cg.add(var.set_power_sensor(sens)) cg.add(var.set_power_sensor(sens))
if CONF_ENERGY in config: if energy_config := config.get(CONF_ENERGY):
conf = config[CONF_ENERGY] sens = await sensor.new_sensor(energy_config)
sens = await sensor.new_sensor(conf)
cg.add(var.set_energy_sensor(sens)) cg.add(var.set_energy_sensor(sens))
if CONF_FREQUENCY in config: if frequency_config := config.get(CONF_FREQUENCY):
conf = config[CONF_FREQUENCY] sens = await sensor.new_sensor(frequency_config)
sens = await sensor.new_sensor(conf)
cg.add(var.set_frequency_sensor(sens)) cg.add(var.set_frequency_sensor(sens))

View File

@@ -129,32 +129,18 @@ async def characteristic_sensor_to_code(config):
) )
cg.add(var.set_char_uuid128(uuid128)) cg.add(var.set_char_uuid128(uuid128))
if CONF_DESCRIPTOR_UUID in config: if descriptor_uuid := config.get(CONF_DESCRIPTOR_UUID):
if len(config[CONF_DESCRIPTOR_UUID]) == len(esp32_ble_tracker.bt_uuid16_format): if len(descriptor_uuid) == len(esp32_ble_tracker.bt_uuid16_format):
cg.add( cg.add(var.set_descr_uuid16(esp32_ble_tracker.as_hex(descriptor_uuid)))
var.set_descr_uuid16( elif len(descriptor_uuid) == len(esp32_ble_tracker.bt_uuid32_format):
esp32_ble_tracker.as_hex(config[CONF_DESCRIPTOR_UUID]) cg.add(var.set_descr_uuid32(esp32_ble_tracker.as_hex(descriptor_uuid)))
) elif len(descriptor_uuid) == len(esp32_ble_tracker.bt_uuid128_format):
) uuid128 = esp32_ble_tracker.as_reversed_hex_array(descriptor_uuid)
elif len(config[CONF_DESCRIPTOR_UUID]) == len(
esp32_ble_tracker.bt_uuid32_format
):
cg.add(
var.set_descr_uuid32(
esp32_ble_tracker.as_hex(config[CONF_DESCRIPTOR_UUID])
)
)
elif len(config[CONF_DESCRIPTOR_UUID]) == len(
esp32_ble_tracker.bt_uuid128_format
):
uuid128 = esp32_ble_tracker.as_reversed_hex_array(
config[CONF_DESCRIPTOR_UUID]
)
cg.add(var.set_descr_uuid128(uuid128)) cg.add(var.set_descr_uuid128(uuid128))
if CONF_LAMBDA in config: if lambda_config := config.get(CONF_LAMBDA):
lambda_ = await cg.process_lambda( lambda_ = await cg.process_lambda(
config[CONF_LAMBDA], [(adv_data_t_const_ref, "x")], return_type=cg.float_ lambda_config, [(adv_data_t_const_ref, "x")], return_type=cg.float_
) )
cg.add(var.set_data_to_value(lambda_)) cg.add(var.set_data_to_value(lambda_))

View File

@@ -88,27 +88,13 @@ async def to_code(config):
) )
cg.add(var.set_char_uuid128(uuid128)) cg.add(var.set_char_uuid128(uuid128))
if CONF_DESCRIPTOR_UUID in config: if descriptor_uuid := config:
if len(config[CONF_DESCRIPTOR_UUID]) == len(esp32_ble_tracker.bt_uuid16_format): if len(descriptor_uuid) == len(esp32_ble_tracker.bt_uuid16_format):
cg.add( cg.add(var.set_descr_uuid16(esp32_ble_tracker.as_hex(descriptor_uuid)))
var.set_descr_uuid16( elif len(descriptor_uuid) == len(esp32_ble_tracker.bt_uuid32_format):
esp32_ble_tracker.as_hex(config[CONF_DESCRIPTOR_UUID]) cg.add(var.set_descr_uuid32(esp32_ble_tracker.as_hex(descriptor_uuid)))
) elif len(descriptor_uuid) == len(esp32_ble_tracker.bt_uuid128_format):
) uuid128 = esp32_ble_tracker.as_reversed_hex_array(descriptor_uuid)
elif len(config[CONF_DESCRIPTOR_UUID]) == len(
esp32_ble_tracker.bt_uuid32_format
):
cg.add(
var.set_descr_uuid32(
esp32_ble_tracker.as_hex(config[CONF_DESCRIPTOR_UUID])
)
)
elif len(config[CONF_DESCRIPTOR_UUID]) == len(
esp32_ble_tracker.bt_uuid128_format
):
uuid128 = esp32_ble_tracker.as_reversed_hex_array(
config[CONF_DESCRIPTOR_UUID]
)
cg.add(var.set_descr_uuid128(uuid128)) cg.add(var.set_descr_uuid128(uuid128))
await cg.register_component(var, config) await cg.register_component(var, config)

View File

@@ -39,7 +39,7 @@ CONFIG_SCHEMA = cv.All(
cv.Optional(CONF_IBEACON_MINOR): cv.uint16_t, cv.Optional(CONF_IBEACON_MINOR): cv.uint16_t,
cv.Optional(CONF_IBEACON_UUID): cv.uuid, cv.Optional(CONF_IBEACON_UUID): cv.uuid,
cv.Optional(CONF_MIN_RSSI): cv.All( cv.Optional(CONF_MIN_RSSI): cv.All(
cv.decibel, cv.int_range(min=-90, max=-30) cv.decibel, cv.int_range(min=-100, max=-30)
), ),
} }
) )
@@ -55,35 +55,27 @@ async def to_code(config):
await cg.register_component(var, config) await cg.register_component(var, config)
await esp32_ble_tracker.register_ble_device(var, config) await esp32_ble_tracker.register_ble_device(var, config)
if CONF_MIN_RSSI in config: if min_rssi := config.get(CONF_MIN_RSSI):
cg.add(var.set_minimum_rssi(config[CONF_MIN_RSSI])) cg.add(var.set_minimum_rssi(min_rssi))
if CONF_MAC_ADDRESS in config: if mac_address := config.get(CONF_MAC_ADDRESS):
cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex)) cg.add(var.set_address(mac_address.as_hex))
if CONF_SERVICE_UUID in config: if service_uuid := config.get(CONF_SERVICE_UUID):
if len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid16_format): if len(service_uuid) == len(esp32_ble_tracker.bt_uuid16_format):
cg.add( cg.add(var.set_service_uuid16(esp32_ble_tracker.as_hex(service_uuid)))
var.set_service_uuid16( elif len(service_uuid) == len(esp32_ble_tracker.bt_uuid32_format):
esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID]) cg.add(var.set_service_uuid32(esp32_ble_tracker.as_hex(service_uuid)))
) elif len(service_uuid) == len(esp32_ble_tracker.bt_uuid128_format):
) uuid128 = esp32_ble_tracker.as_reversed_hex_array(service_uuid)
elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid32_format):
cg.add(
var.set_service_uuid32(
esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID])
)
)
elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid128_format):
uuid128 = esp32_ble_tracker.as_reversed_hex_array(config[CONF_SERVICE_UUID])
cg.add(var.set_service_uuid128(uuid128)) cg.add(var.set_service_uuid128(uuid128))
if CONF_IBEACON_UUID in config: if ibeacon_uuid := config.get(CONF_IBEACON_UUID):
ibeacon_uuid = esp32_ble_tracker.as_hex_array(str(config[CONF_IBEACON_UUID])) ibeacon_uuid = esp32_ble_tracker.as_hex_array(str(ibeacon_uuid))
cg.add(var.set_ibeacon_uuid(ibeacon_uuid)) cg.add(var.set_ibeacon_uuid(ibeacon_uuid))
if CONF_IBEACON_MAJOR in config: if (ibeacon_major := config.get(CONF_IBEACON_MAJOR)) is not None:
cg.add(var.set_ibeacon_major(config[CONF_IBEACON_MAJOR])) cg.add(var.set_ibeacon_major(ibeacon_major))
if CONF_IBEACON_MINOR in config: if (ibeacon_minor := config.get(CONF_IBEACON_MINOR)) is not None:
cg.add(var.set_ibeacon_minor(config[CONF_IBEACON_MINOR])) cg.add(var.set_ibeacon_minor(ibeacon_minor))

View File

@@ -51,7 +51,7 @@ class BLEPresenceDevice : public binary_sensor::BinarySensorInitiallyOff,
this->found_ = false; this->found_ = false;
} }
bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override { bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override {
if (this->check_minimum_rssi_ && this->minimum_rssi_ <= device.get_rssi()) { if (this->check_minimum_rssi_ && this->minimum_rssi_ > device.get_rssi()) {
return false; return false;
} }
switch (this->match_by_) { switch (this->match_by_) {

View File

@@ -57,32 +57,24 @@ async def to_code(config):
await cg.register_component(var, config) await cg.register_component(var, config)
await esp32_ble_tracker.register_ble_device(var, config) await esp32_ble_tracker.register_ble_device(var, config)
if CONF_MAC_ADDRESS in config: if mac_address := config.get(CONF_MAC_ADDRESS):
cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex)) cg.add(var.set_address(mac_address.as_hex))
if CONF_SERVICE_UUID in config: if service_uuid := config.get(CONF_SERVICE_UUID):
if len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid16_format): if len(service_uuid) == len(esp32_ble_tracker.bt_uuid16_format):
cg.add( cg.add(var.set_service_uuid16(esp32_ble_tracker.as_hex(service_uuid)))
var.set_service_uuid16( elif len(service_uuid) == len(esp32_ble_tracker.bt_uuid32_format):
esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID]) cg.add(var.set_service_uuid32(esp32_ble_tracker.as_hex(service_uuid)))
) elif len(service_uuid) == len(esp32_ble_tracker.bt_uuid128_format):
) uuid128 = esp32_ble_tracker.as_reversed_hex_array(service_uuid)
elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid32_format):
cg.add(
var.set_service_uuid32(
esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID])
)
)
elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid128_format):
uuid128 = esp32_ble_tracker.as_reversed_hex_array(config[CONF_SERVICE_UUID])
cg.add(var.set_service_uuid128(uuid128)) cg.add(var.set_service_uuid128(uuid128))
if CONF_IBEACON_UUID in config: if ibeacon_uuid := config.get(CONF_IBEACON_UUID):
ibeacon_uuid = esp32_ble_tracker.as_hex_array(str(config[CONF_IBEACON_UUID])) ibeacon_uuid = esp32_ble_tracker.as_hex_array(str(ibeacon_uuid))
cg.add(var.set_ibeacon_uuid(ibeacon_uuid)) cg.add(var.set_ibeacon_uuid(ibeacon_uuid))
if CONF_IBEACON_MAJOR in config: if (ibeacon_major := config.get(CONF_IBEACON_MAJOR)) is not None:
cg.add(var.set_ibeacon_major(config[CONF_IBEACON_MAJOR])) cg.add(var.set_ibeacon_major(ibeacon_major))
if CONF_IBEACON_MINOR in config: if (ibeacon_minor := config.get(CONF_IBEACON_MINOR)) is not None:
cg.add(var.set_ibeacon_minor(config[CONF_IBEACON_MINOR])) cg.add(var.set_ibeacon_minor(ibeacon_minor))

View File

@@ -275,6 +275,10 @@ esp_err_t BluetoothConnection::notify_characteristic(uint16_t handle, bool enabl
return ESP_OK; return ESP_OK;
} }
esp32_ble_tracker::AdvertisementParserType BluetoothConnection::get_advertisement_parser_type() {
return this->proxy_->get_advertisement_parser_type();
}
} // namespace bluetooth_proxy } // namespace bluetooth_proxy
} // namespace esphome } // namespace esphome

View File

@@ -14,6 +14,7 @@ class BluetoothConnection : public esp32_ble_client::BLEClientBase {
bool gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, bool gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
esp_ble_gattc_cb_param_t *param) override; esp_ble_gattc_cb_param_t *param) override;
void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override; void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override;
esp32_ble_tracker::AdvertisementParserType get_advertisement_parser_type() override;
esp_err_t read_characteristic(uint16_t handle); esp_err_t read_characteristic(uint16_t handle);
esp_err_t write_characteristic(uint16_t handle, const std::string &data, bool response); esp_err_t write_characteristic(uint16_t handle, const std::string &data, bool response);

View File

@@ -198,6 +198,12 @@ void BluetoothProxy::loop() {
} }
} }
esp32_ble_tracker::AdvertisementParserType BluetoothProxy::get_advertisement_parser_type() {
if (this->raw_advertisements_)
return esp32_ble_tracker::AdvertisementParserType::RAW_ADVERTISEMENTS;
return esp32_ble_tracker::AdvertisementParserType::PARSED_ADVERTISEMENTS;
}
BluetoothConnection *BluetoothProxy::get_connection_(uint64_t address, bool reserve) { BluetoothConnection *BluetoothProxy::get_connection_(uint64_t address, bool reserve) {
for (auto *connection : this->connections_) { for (auto *connection : this->connections_) {
if (connection->get_address() == address) if (connection->get_address() == address)
@@ -435,6 +441,7 @@ void BluetoothProxy::subscribe_api_connection(api::APIConnection *api_connection
} }
this->api_connection_ = api_connection; this->api_connection_ = api_connection;
this->raw_advertisements_ = flags & BluetoothProxySubscriptionFlag::SUBSCRIPTION_RAW_ADVERTISEMENTS; this->raw_advertisements_ = flags & BluetoothProxySubscriptionFlag::SUBSCRIPTION_RAW_ADVERTISEMENTS;
this->parent_->recalculate_advertisement_parser_types();
} }
void BluetoothProxy::unsubscribe_api_connection(api::APIConnection *api_connection) { void BluetoothProxy::unsubscribe_api_connection(api::APIConnection *api_connection) {
@@ -444,6 +451,7 @@ void BluetoothProxy::unsubscribe_api_connection(api::APIConnection *api_connecti
} }
this->api_connection_ = nullptr; this->api_connection_ = nullptr;
this->raw_advertisements_ = false; this->raw_advertisements_ = false;
this->parent_->recalculate_advertisement_parser_types();
} }
void BluetoothProxy::send_device_connection(uint64_t address, bool connected, uint16_t mtu, esp_err_t error) { void BluetoothProxy::send_device_connection(uint64_t address, bool connected, uint16_t mtu, esp_err_t error) {

View File

@@ -51,6 +51,7 @@ class BluetoothProxy : public esp32_ble_tracker::ESPBTDeviceListener, public Com
bool parse_devices(esp_ble_gap_cb_param_t::ble_scan_result_evt_param *advertisements, size_t count) override; bool parse_devices(esp_ble_gap_cb_param_t::ble_scan_result_evt_param *advertisements, size_t count) override;
void dump_config() override; void dump_config() override;
void loop() override; void loop() override;
esp32_ble_tracker::AdvertisementParserType get_advertisement_parser_type() override;
void register_connection(BluetoothConnection *connection) { void register_connection(BluetoothConnection *connection) {
this->connections_.push_back(connection); this->connections_.push_back(connection);

View File

@@ -98,22 +98,19 @@ async def to_code(config):
await cg.register_component(var, config) await cg.register_component(var, config)
await i2c.register_i2c_device(var, config) await i2c.register_i2c_device(var, config)
if CONF_TEMPERATURE in config: if temperature_config := config.get(CONF_TEMPERATURE):
conf = config[CONF_TEMPERATURE] sens = await sensor.new_sensor(temperature_config)
sens = await sensor.new_sensor(conf)
cg.add(var.set_temperature_sensor(sens)) cg.add(var.set_temperature_sensor(sens))
cg.add(var.set_temperature_oversampling(conf[CONF_OVERSAMPLING])) cg.add(var.set_temperature_oversampling(temperature_config[CONF_OVERSAMPLING]))
if CONF_PRESSURE in config: if pressure_config := config.get(CONF_PRESSURE):
conf = config[CONF_PRESSURE] sens = await sensor.new_sensor(pressure_config)
sens = await sensor.new_sensor(conf)
cg.add(var.set_pressure_sensor(sens)) cg.add(var.set_pressure_sensor(sens))
cg.add(var.set_pressure_oversampling(conf[CONF_OVERSAMPLING])) cg.add(var.set_pressure_oversampling(pressure_config[CONF_OVERSAMPLING]))
if CONF_HUMIDITY in config: if humidity_config := config.get(CONF_HUMIDITY):
conf = config[CONF_HUMIDITY] sens = await sensor.new_sensor(humidity_config)
sens = await sensor.new_sensor(conf)
cg.add(var.set_humidity_sensor(sens)) cg.add(var.set_humidity_sensor(sens))
cg.add(var.set_humidity_oversampling(conf[CONF_OVERSAMPLING])) cg.add(var.set_humidity_oversampling(humidity_config[CONF_OVERSAMPLING]))
cg.add(var.set_iir_filter(config[CONF_IIR_FILTER])) cg.add(var.set_iir_filter(config[CONF_IIR_FILTER]))

View File

@@ -130,27 +130,23 @@ async def to_code(config):
await cg.register_component(var, config) await cg.register_component(var, config)
await i2c.register_i2c_device(var, config) await i2c.register_i2c_device(var, config)
if CONF_TEMPERATURE in config: if temperature_config := config.get(CONF_TEMPERATURE):
conf = config[CONF_TEMPERATURE] sens = await sensor.new_sensor(temperature_config)
sens = await sensor.new_sensor(conf)
cg.add(var.set_temperature_sensor(sens)) cg.add(var.set_temperature_sensor(sens))
cg.add(var.set_temperature_oversampling(conf[CONF_OVERSAMPLING])) cg.add(var.set_temperature_oversampling(temperature_config[CONF_OVERSAMPLING]))
if CONF_PRESSURE in config: if pressure_config := config.get(CONF_PRESSURE):
conf = config[CONF_PRESSURE] sens = await sensor.new_sensor(pressure_config)
sens = await sensor.new_sensor(conf)
cg.add(var.set_pressure_sensor(sens)) cg.add(var.set_pressure_sensor(sens))
cg.add(var.set_pressure_oversampling(conf[CONF_OVERSAMPLING])) cg.add(var.set_pressure_oversampling(pressure_config[CONF_OVERSAMPLING]))
if CONF_HUMIDITY in config: if humidity_config := config.get(CONF_HUMIDITY):
conf = config[CONF_HUMIDITY] sens = await sensor.new_sensor(humidity_config)
sens = await sensor.new_sensor(conf)
cg.add(var.set_humidity_sensor(sens)) cg.add(var.set_humidity_sensor(sens))
cg.add(var.set_humidity_oversampling(conf[CONF_OVERSAMPLING])) cg.add(var.set_humidity_oversampling(humidity_config[CONF_OVERSAMPLING]))
if CONF_GAS_RESISTANCE in config: if gas_resistance_config := config.get(CONF_GAS_RESISTANCE):
conf = config[CONF_GAS_RESISTANCE] sens = await sensor.new_sensor(gas_resistance_config)
sens = await sensor.new_sensor(conf)
cg.add(var.set_gas_resistance_sensor(sens)) cg.add(var.set_gas_resistance_sensor(sens))
cg.add(var.set_iir_filter(IIR_FILTER_OPTIONS[config[CONF_IIR_FILTER]])) cg.add(var.set_iir_filter(IIR_FILTER_OPTIONS[config[CONF_IIR_FILTER]]))

View File

@@ -6,8 +6,10 @@ from esphome.const import (
CONF_HUMIDITY, CONF_HUMIDITY,
CONF_PRESSURE, CONF_PRESSURE,
CONF_TEMPERATURE, CONF_TEMPERATURE,
DEVICE_CLASS_CARBON_DIOXIDE,
DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_PRESSURE, DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS_PARTS,
DEVICE_CLASS_ATMOSPHERIC_PRESSURE,
DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_TEMPERATURE,
STATE_CLASS_MEASUREMENT, STATE_CLASS_MEASUREMENT,
UNIT_CELSIUS, UNIT_CELSIUS,
@@ -17,8 +19,6 @@ from esphome.const import (
UNIT_PERCENT, UNIT_PERCENT,
ICON_GAS_CYLINDER, ICON_GAS_CYLINDER,
ICON_GAUGE, ICON_GAUGE,
ICON_THERMOMETER,
ICON_WATER_PERCENT,
) )
from . import ( from . import (
BME680BSECComponent, BME680BSECComponent,
@@ -35,7 +35,6 @@ CONF_CO2_EQUIVALENT = "co2_equivalent"
CONF_BREATH_VOC_EQUIVALENT = "breath_voc_equivalent" CONF_BREATH_VOC_EQUIVALENT = "breath_voc_equivalent"
UNIT_IAQ = "IAQ" UNIT_IAQ = "IAQ"
ICON_ACCURACY = "mdi:checkbox-marked-circle-outline" ICON_ACCURACY = "mdi:checkbox-marked-circle-outline"
ICON_TEST_TUBE = "mdi:test-tube"
TYPES = [ TYPES = [
CONF_TEMPERATURE, CONF_TEMPERATURE,
@@ -53,7 +52,6 @@ CONFIG_SCHEMA = cv.Schema(
cv.GenerateID(CONF_BME680_BSEC_ID): cv.use_id(BME680BSECComponent), cv.GenerateID(CONF_BME680_BSEC_ID): cv.use_id(BME680BSECComponent),
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
unit_of_measurement=UNIT_CELSIUS, unit_of_measurement=UNIT_CELSIUS,
icon=ICON_THERMOMETER,
accuracy_decimals=1, accuracy_decimals=1,
device_class=DEVICE_CLASS_TEMPERATURE, device_class=DEVICE_CLASS_TEMPERATURE,
state_class=STATE_CLASS_MEASUREMENT, state_class=STATE_CLASS_MEASUREMENT,
@@ -62,16 +60,14 @@ CONFIG_SCHEMA = cv.Schema(
), ),
cv.Optional(CONF_PRESSURE): sensor.sensor_schema( cv.Optional(CONF_PRESSURE): sensor.sensor_schema(
unit_of_measurement=UNIT_HECTOPASCAL, unit_of_measurement=UNIT_HECTOPASCAL,
icon=ICON_GAUGE,
accuracy_decimals=1, accuracy_decimals=1,
device_class=DEVICE_CLASS_PRESSURE, device_class=DEVICE_CLASS_ATMOSPHERIC_PRESSURE,
state_class=STATE_CLASS_MEASUREMENT, state_class=STATE_CLASS_MEASUREMENT,
).extend( ).extend(
{cv.Optional(CONF_SAMPLE_RATE): cv.enum(SAMPLE_RATE_OPTIONS, upper=True)} {cv.Optional(CONF_SAMPLE_RATE): cv.enum(SAMPLE_RATE_OPTIONS, upper=True)}
), ),
cv.Optional(CONF_HUMIDITY): sensor.sensor_schema( cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
unit_of_measurement=UNIT_PERCENT, unit_of_measurement=UNIT_PERCENT,
icon=ICON_WATER_PERCENT,
accuracy_decimals=1, accuracy_decimals=1,
device_class=DEVICE_CLASS_HUMIDITY, device_class=DEVICE_CLASS_HUMIDITY,
state_class=STATE_CLASS_MEASUREMENT, state_class=STATE_CLASS_MEASUREMENT,
@@ -97,14 +93,14 @@ CONFIG_SCHEMA = cv.Schema(
), ),
cv.Optional(CONF_CO2_EQUIVALENT): sensor.sensor_schema( cv.Optional(CONF_CO2_EQUIVALENT): sensor.sensor_schema(
unit_of_measurement=UNIT_PARTS_PER_MILLION, unit_of_measurement=UNIT_PARTS_PER_MILLION,
icon=ICON_TEST_TUBE,
accuracy_decimals=1, accuracy_decimals=1,
device_class=DEVICE_CLASS_CARBON_DIOXIDE,
state_class=STATE_CLASS_MEASUREMENT, state_class=STATE_CLASS_MEASUREMENT,
), ),
cv.Optional(CONF_BREATH_VOC_EQUIVALENT): sensor.sensor_schema( cv.Optional(CONF_BREATH_VOC_EQUIVALENT): sensor.sensor_schema(
unit_of_measurement=UNIT_PARTS_PER_MILLION, unit_of_measurement=UNIT_PARTS_PER_MILLION,
icon=ICON_TEST_TUBE,
accuracy_decimals=1, accuracy_decimals=1,
device_class=DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS_PARTS,
state_class=STATE_CLASS_MEASUREMENT, state_class=STATE_CLASS_MEASUREMENT,
), ),
} }
@@ -112,12 +108,13 @@ CONFIG_SCHEMA = cv.Schema(
async def setup_conf(config, key, hub): async def setup_conf(config, key, hub):
if key in config: if sensor_config := config.get(key):
conf = config[key] sens = await sensor.new_sensor(sensor_config)
sens = await sensor.new_sensor(conf)
cg.add(getattr(hub, f"set_{key}_sensor")(sens)) cg.add(getattr(hub, f"set_{key}_sensor")(sens))
if CONF_SAMPLE_RATE in conf: if CONF_SAMPLE_RATE in sensor_config:
cg.add(getattr(hub, f"set_{key}_sample_rate")(conf[CONF_SAMPLE_RATE])) cg.add(
getattr(hub, f"set_{key}_sample_rate")(sensor_config[CONF_SAMPLE_RATE])
)
async def to_code(config): async def to_code(config):

View File

@@ -21,9 +21,8 @@ CONFIG_SCHEMA = cv.Schema(
async def setup_conf(config, key, hub): async def setup_conf(config, key, hub):
if key in config: if sensor_config := config.get(key):
conf = config[key] sens = await text_sensor.new_text_sensor(sensor_config)
sens = await text_sensor.new_text_sensor(conf)
cg.add(getattr(hub, f"set_{key}_text_sensor")(sens)) cg.add(getattr(hub, f"set_{key}_text_sensor")(sens))

View File

@@ -47,12 +47,10 @@ async def to_code(config):
await cg.register_component(var, config) await cg.register_component(var, config)
await i2c.register_i2c_device(var, config) await i2c.register_i2c_device(var, config)
if CONF_TEMPERATURE in config: if temperature_config := config.get(CONF_TEMPERATURE):
conf = config[CONF_TEMPERATURE] sens = await sensor.new_sensor(temperature_config)
sens = await sensor.new_sensor(conf)
cg.add(var.set_temperature(sens)) cg.add(var.set_temperature(sens))
if CONF_PRESSURE in config: if pressure_config := config.get(CONF_PRESSURE):
conf = config[CONF_PRESSURE] sens = await sensor.new_sensor(pressure_config)
sens = await sensor.new_sensor(conf)
cg.add(var.set_pressure(sens)) cg.add(var.set_pressure(sens))

View File

@@ -83,16 +83,14 @@ async def to_code(config):
await cg.register_component(var, config) await cg.register_component(var, config)
await i2c.register_i2c_device(var, config) await i2c.register_i2c_device(var, config)
if CONF_TEMPERATURE in config: if temperature_config := config.get(CONF_TEMPERATURE):
conf = config[CONF_TEMPERATURE] sens = await sensor.new_sensor(temperature_config)
sens = await sensor.new_sensor(conf)
cg.add(var.set_temperature_sensor(sens)) cg.add(var.set_temperature_sensor(sens))
cg.add(var.set_temperature_oversampling(conf[CONF_OVERSAMPLING])) cg.add(var.set_temperature_oversampling(temperature_config[CONF_OVERSAMPLING]))
if CONF_PRESSURE in config: if pressure_config := config.get(CONF_PRESSURE):
conf = config[CONF_PRESSURE] sens = await sensor.new_sensor(pressure_config)
sens = await sensor.new_sensor(conf)
cg.add(var.set_pressure_sensor(sens)) cg.add(var.set_pressure_sensor(sens))
cg.add(var.set_pressure_oversampling(conf[CONF_OVERSAMPLING])) cg.add(var.set_pressure_oversampling(pressure_config[CONF_OVERSAMPLING]))
cg.add(var.set_iir_filter(config[CONF_IIR_FILTER])) cg.add(var.set_iir_filter(config[CONF_IIR_FILTER]))

View File

@@ -87,14 +87,16 @@ async def to_code(config):
await cg.register_component(var, config) await cg.register_component(var, config)
await i2c.register_i2c_device(var, config) await i2c.register_i2c_device(var, config)
cg.add(var.set_iir_filter_config(config[CONF_IIR_FILTER])) cg.add(var.set_iir_filter_config(config[CONF_IIR_FILTER]))
if CONF_TEMPERATURE in config: if temperature_config := config.get(CONF_TEMPERATURE):
conf = config[CONF_TEMPERATURE] sens = await sensor.new_sensor(temperature_config)
sens = await sensor.new_sensor(conf)
cg.add(var.set_temperature_sensor(sens)) cg.add(var.set_temperature_sensor(sens))
cg.add(var.set_temperature_oversampling_config(conf[CONF_OVERSAMPLING])) cg.add(
var.set_temperature_oversampling_config(
temperature_config[CONF_OVERSAMPLING]
)
)
if CONF_PRESSURE in config: if pressure_config := config.get(CONF_PRESSURE):
conf = config[CONF_PRESSURE] sens = await sensor.new_sensor(pressure_config)
sens = await sensor.new_sensor(conf)
cg.add(var.set_pressure_sensor(sens)) cg.add(var.set_pressure_sensor(sens))
cg.add(var.set_pressure_oversampling_config(conf[CONF_OVERSAMPLING])) cg.add(var.set_pressure_oversampling_config(pressure_config[CONF_OVERSAMPLING]))

View File

View File

@@ -0,0 +1,596 @@
/*
* Adds support for Bosch's BMP581 high accuracy pressure and temperature sensor
* - Component structure based on ESPHome's BMP3XX component (as of March, 2023)
* - Implementation is easier as the sensor itself automatically compensates pressure for the temperature
* - Temperature and pressure data is converted via simple divison operations in this component
* - IIR filter level can independently be applied to temperature and pressure measurements
* - Bosch's BMP5-Sensor-API was consulted to verify that sensor configuration is done correctly
* - Copyright (c) 2022 Bosch Sensortec Gmbh, SPDX-License-Identifier: BSD-3-Clause
* - This component uses forced power mode only so measurements are synchronized by the host
* - All datasheet page references refer to Bosch Document Number BST-BMP581-DS004-04 (revision number 1.4)
*/
#include "bmp581.h"
#include "esphome/core/log.h"
#include "esphome/core/hal.h"
namespace esphome {
namespace bmp581 {
static const char *const TAG = "bmp581";
static const LogString *oversampling_to_str(Oversampling oversampling) {
switch (oversampling) {
case Oversampling::OVERSAMPLING_NONE:
return LOG_STR("None");
case Oversampling::OVERSAMPLING_X2:
return LOG_STR("2x");
case Oversampling::OVERSAMPLING_X4:
return LOG_STR("4x");
case Oversampling::OVERSAMPLING_X8:
return LOG_STR("8x");
case Oversampling::OVERSAMPLING_X16:
return LOG_STR("16x");
case Oversampling::OVERSAMPLING_X32:
return LOG_STR("32x");
case Oversampling::OVERSAMPLING_X64:
return LOG_STR("64x");
case Oversampling::OVERSAMPLING_X128:
return LOG_STR("128x");
default:
return LOG_STR("");
}
}
static const LogString *iir_filter_to_str(IIRFilter filter) {
switch (filter) {
case IIRFilter::IIR_FILTER_OFF:
return LOG_STR("OFF");
case IIRFilter::IIR_FILTER_2:
return LOG_STR("2x");
case IIRFilter::IIR_FILTER_4:
return LOG_STR("4x");
case IIRFilter::IIR_FILTER_8:
return LOG_STR("8x");
case IIRFilter::IIR_FILTER_16:
return LOG_STR("16x");
case IIRFilter::IIR_FILTER_32:
return LOG_STR("32x");
case IIRFilter::IIR_FILTER_64:
return LOG_STR("64x");
case IIRFilter::IIR_FILTER_128:
return LOG_STR("128x");
default:
return LOG_STR("");
}
}
void BMP581Component::dump_config() {
ESP_LOGCONFIG(TAG, "BMP581:");
switch (this->error_code_) {
case NONE:
break;
case ERROR_COMMUNICATION_FAILED:
ESP_LOGE(TAG, " Communication with BMP581 failed!");
break;
case ERROR_WRONG_CHIP_ID:
ESP_LOGE(TAG, " BMP581 has wrong chip ID - please verify you are using a BMP 581");
break;
case ERROR_SENSOR_RESET:
ESP_LOGE(TAG, " BMP581 failed to reset");
break;
case ERROR_SENSOR_STATUS:
ESP_LOGE(TAG, " BMP581 sensor status failed, there were NVM problems");
break;
case ERROR_PRIME_IIR_FAILED:
ESP_LOGE(TAG, " BMP581's IIR Filter failed to prime with an initial measurement");
break;
default:
ESP_LOGE(TAG, " BMP581 error code %d", (int) this->error_code_);
break;
}
LOG_I2C_DEVICE(this);
LOG_UPDATE_INTERVAL(this);
ESP_LOGCONFIG(TAG, " Measurement conversion time: %ums", this->conversion_time_);
if (this->temperature_sensor_) {
LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
ESP_LOGCONFIG(TAG, " IIR Filter: %s", LOG_STR_ARG(iir_filter_to_str(this->iir_temperature_level_)));
ESP_LOGCONFIG(TAG, " Oversampling: %s", LOG_STR_ARG(oversampling_to_str(this->temperature_oversampling_)));
}
if (this->pressure_sensor_) {
LOG_SENSOR(" ", "Pressure", this->pressure_sensor_);
ESP_LOGCONFIG(TAG, " IIR Filter: %s", LOG_STR_ARG(iir_filter_to_str(this->iir_pressure_level_)));
ESP_LOGCONFIG(TAG, " Oversampling: %s", LOG_STR_ARG(oversampling_to_str(this->pressure_oversampling_)));
}
}
void BMP581Component::setup() {
/*
* Setup goes through several stages, which follows the post-power-up procedure (page 18 of datasheet) and then sets
* configured options
* 1) Soft reboot
* 2) Verify ASIC chip ID matches BMP581
* 3) Verify sensor status (check if NVM is okay)
* 4) Enable data ready interrupt
* 5) Write oversampling settings and set internal configuration values
* 6) Configure and prime IIR Filter(s), if enabled
*/
this->error_code_ = NONE;
ESP_LOGCONFIG(TAG, "Setting up BMP581...");
////////////////////
// 1) Soft reboot //
////////////////////
// Power-On-Reboot bit is asserted if sensor successfully reset
if (!this->reset_()) {
ESP_LOGE(TAG, "BMP581 failed to reset");
this->error_code_ = ERROR_SENSOR_RESET;
this->mark_failed();
return;
}
///////////////////////////////////////////
// 2) Verify ASIC chip ID matches BMP581 //
///////////////////////////////////////////
uint8_t chip_id;
// read chip id from sensor
if (!this->read_byte(BMP581_CHIP_ID, &chip_id)) {
ESP_LOGE(TAG, "Failed to read chip id");
this->error_code_ = ERROR_COMMUNICATION_FAILED;
this->mark_failed();
return;
}
// verify id
if (chip_id != BMP581_ASIC_ID) {
ESP_LOGE(TAG, "Unknown chip ID, is this a BMP581?");
this->error_code_ = ERROR_WRONG_CHIP_ID;
this->mark_failed();
return;
}
////////////////////////////////////////////////////
// 3) Verify sensor status (check if NVM is okay) //
////////////////////////////////////////////////////
if (!this->read_byte(BMP581_STATUS, &this->status_.reg)) {
ESP_LOGE(TAG, "Failed to read status register");
this->error_code_ = ERROR_COMMUNICATION_FAILED;
this->mark_failed();
return;
}
// verify status_nvm_rdy bit (it is asserted if boot was successful)
if (!(this->status_.bit.status_nvm_rdy)) {
ESP_LOGE(TAG, "NVM not ready after boot");
this->error_code_ = ERROR_SENSOR_STATUS;
this->mark_failed();
return;
}
// verify status_nvm_err bit (it is asserted if an error is detected)
if (this->status_.bit.status_nvm_err) {
ESP_LOGE(TAG, "NVM error detected on boot");
this->error_code_ = ERROR_SENSOR_STATUS;
this->mark_failed();
return;
}
////////////////////////////////////
// 4) Enable data ready interrupt //
////////////////////////////////////
// enable the data ready interrupt source
if (!this->write_interrupt_source_settings_(true)) {
ESP_LOGE(TAG, "Failed to write interrupt source register");
this->error_code_ = ERROR_COMMUNICATION_FAILED;
this->mark_failed();
return;
}
//////////////////////////////////////////////////////////////////////////
// 5) Write oversampling settings and set internal configuration values //
//////////////////////////////////////////////////////////////////////////
// configure pressure readings, if sensor is defined
// otherwise, disable pressure oversampling
if (this->pressure_sensor_) {
this->osr_config_.bit.press_en = true;
} else {
this->pressure_oversampling_ = OVERSAMPLING_NONE;
}
// write oversampling settings
if (!this->write_oversampling_settings_(this->temperature_oversampling_, this->pressure_oversampling_)) {
ESP_LOGE(TAG, "Failed to write oversampling register");
this->error_code_ = ERROR_COMMUNICATION_FAILED;
this->mark_failed();
return;
}
// set output data rate to 4 Hz=0x19 (page 65 of datasheet)
// - ?shouldn't? matter as this component only uses FORCED_MODE - datasheet is ambiguous
// - If in NORMAL_MODE or NONSTOP_MODE, then this would still allow deep standby to save power
// - will be written to BMP581 at next requested measurement
this->odr_config_.bit.odr = 0x19;
///////////////////////////////////////////////////////
/// 6) Configure and prime IIR Filter(s), if enabled //
///////////////////////////////////////////////////////
if ((this->iir_temperature_level_ != IIR_FILTER_OFF) || (this->iir_pressure_level_ != IIR_FILTER_OFF)) {
if (!this->write_iir_settings_(this->iir_temperature_level_, this->iir_pressure_level_)) {
ESP_LOGE(TAG, "Failed to write IIR configuration registers");
this->error_code_ = ERROR_COMMUNICATION_FAILED;
this->mark_failed();
return;
}
if (!this->prime_iir_filter_()) {
ESP_LOGE(TAG, "Failed to prime the IIR filter with an intiial measurement");
this->error_code_ = ERROR_PRIME_IIR_FAILED;
this->mark_failed();
return;
}
}
}
void BMP581Component::update() {
/*
* Each update goes through several stages
* 0) Verify either a temperature or pressure sensor is defined before proceeding
* 1) Request a measurement
* 2) Wait for measurement to finish (based on oversampling rates)
* 3) Read data registers for temperature and pressure, if applicable
* 4) Publish measurements to sensor(s), if applicable
*/
////////////////////////////////////////////////////////////////////////////////////
// 0) Verify either a temperature or pressure sensor is defined before proceeding //
////////////////////////////////////////////////////////////////////////////////////
if ((!this->temperature_sensor_) && (!this->pressure_sensor_)) {
return;
}
//////////////////////////////
// 1) Request a measurement //
//////////////////////////////
ESP_LOGVV(TAG, "Requesting a measurement from sensor");
if (!this->start_measurement_()) {
ESP_LOGW(TAG, "Failed to request forced measurement of sensor");
this->status_set_warning();
return;
}
//////////////////////////////////////////////////////////////////////
// 2) Wait for measurement to finish (based on oversampling rates) //
//////////////////////////////////////////////////////////////////////
ESP_LOGVV(TAG, "Measurement is expected to take %d ms to complete", this->conversion_time_);
this->set_timeout("measurement", this->conversion_time_, [this]() {
float temperature = 0.0;
float pressure = 0.0;
////////////////////////////////////////////////////////////////////////
// 3) Read data registers for temperature and pressure, if applicable //
////////////////////////////////////////////////////////////////////////
if (this->pressure_sensor_) {
if (!this->read_temperature_and_pressure_(temperature, pressure)) {
ESP_LOGW(TAG, "Failed to read temperature and pressure measurements, skipping update");
this->status_set_warning();
return;
}
} else {
if (!this->read_temperature_(temperature)) {
ESP_LOGW(TAG, "Failed to read temperature measurement, skipping update");
this->status_set_warning();
return;
}
}
/////////////////////////////////////////////////////////
// 4) Publish measurements to sensor(s), if applicable //
/////////////////////////////////////////////////////////
if (this->temperature_sensor_) {
this->temperature_sensor_->publish_state(temperature);
}
if (this->pressure_sensor_) {
this->pressure_sensor_->publish_state(pressure);
}
this->status_clear_warning();
});
}
bool BMP581Component::check_data_readiness_() {
// - verifies component is not internally in standby mode
// - reads interrupt status register
// - checks if data ready bit is asserted
// - If true, then internally sets component to standby mode if in forced mode
// - returns data readiness state
if (this->odr_config_.bit.pwr_mode == STANDBY_MODE) {
ESP_LOGD(TAG, "Data is not ready, sensor is in standby mode");
return false;
}
uint8_t status;
if (!this->read_byte(BMP581_INT_STATUS, &status)) {
ESP_LOGE(TAG, "Failed to read interrupt status register");
return false;
}
this->int_status_.reg = status;
if (this->int_status_.bit.drdy_data_reg) {
// If in forced mode, then set internal record of the power mode to STANDBY_MODE
// - sensor automatically returns to standby mode after completing a forced measurement
if (this->odr_config_.bit.pwr_mode == FORCED_MODE) {
this->odr_config_.bit.pwr_mode = STANDBY_MODE;
}
return true;
}
return false;
}
bool BMP581Component::prime_iir_filter_() {
// - temporarily disables oversampling for a fast initial measurement; avoids slowing down ESPHome's startup process
// - enables IIR filter flushing with forced measurements
// - forces a measurement; flushing the IIR filter and priming it with a current value
// - disables IIR filter flushing with forced measurements
// - reverts to internally configured oversampling rates
// - returns success of all register writes/priming
// store current internal oversampling settings to revert to after priming
Oversampling current_temperature_oversampling = (Oversampling) this->osr_config_.bit.osr_t;
Oversampling current_pressure_oversampling = (Oversampling) this->osr_config_.bit.osr_p;
// temporarily disables oversampling for temperature and pressure for a fast priming measurement
if (!this->write_oversampling_settings_(OVERSAMPLING_NONE, OVERSAMPLING_NONE)) {
ESP_LOGE(TAG, "Failed to write oversampling register");
return false;
}
// flush the IIR filter with forced measurements (we will only flush once)
this->dsp_config_.bit.iir_flush_forced_en = true;
if (!this->write_byte(BMP581_DSP, this->dsp_config_.reg)) {
ESP_LOGE(TAG, "Failed to write IIR source register");
return false;
}
// forces an intial measurement
// - this measurements flushes the IIR filter reflecting written DSP settings
// - flushing with this initial reading avoids having the internal previous data aquisition being 0, which
// (I)nfinitely affects future values
if (!this->start_measurement_()) {
ESP_LOGE(TAG, "Failed to request a forced measurement");
return false;
}
// wait for priming measurement to complete
// - with oversampling disabled, the conversion time for a single measurement for pressure and temperature is
// ceilf(1.05*(1.0+1.0)) = 3ms
// - see page 12 of datasheet for details
delay(3);
if (!this->check_data_readiness_()) {
ESP_LOGE(TAG, "IIR priming measurement was not ready");
return false;
}
// disable IIR filter flushings on future forced measurements
this->dsp_config_.bit.iir_flush_forced_en = false;
if (!this->write_byte(BMP581_DSP, this->dsp_config_.reg)) {
ESP_LOGE(TAG, "Failed to write IIR source register");
return false;
}
// revert oversampling rates to original settings
return this->write_oversampling_settings_(current_temperature_oversampling, current_pressure_oversampling);
}
bool BMP581Component::read_temperature_(float &temperature) {
// - verifies data is ready to be read
// - reads in 3 bytes of temperature data
// - returns whether successful, where the the variable parameter contains
// - the measured temperature (in degrees Celsius)
if (!this->check_data_readiness_()) {
ESP_LOGW(TAG, "Data from sensor isn't ready, skipping this update");
this->status_set_warning();
return false;
}
uint8_t data[3];
if (!this->read_bytes(BMP581_MEASUREMENT_DATA, &data[0], 3)) {
ESP_LOGW(TAG, "Failed to read sensor's measurement data");
this->status_set_warning();
return false;
}
// temperature MSB is in data[2], LSB is in data[1], XLSB in data[0]
int32_t raw_temp = (int32_t) data[2] << 16 | (int32_t) data[1] << 8 | (int32_t) data[0];
temperature = (float) (raw_temp / 65536.0); // convert measurement to degrees Celsius (page 22 of datasheet)
return true;
}
bool BMP581Component::read_temperature_and_pressure_(float &temperature, float &pressure) {
// - verifies data is ready to be read
// - reads in 6 bytes of temperature data (3 for temeperature, 3 for pressure)
// - returns whether successful, where the variable parameters contain
// - the measured temperature (in degrees Celsius)
// - the measured pressure (in Pa)
if (!this->check_data_readiness_()) {
ESP_LOGW(TAG, "Data from sensor isn't ready, skipping this update");
this->status_set_warning();
return false;
}
uint8_t data[6];
if (!this->read_bytes(BMP581_MEASUREMENT_DATA, &data[0], 6)) {
ESP_LOGW(TAG, "Failed to read sensor's measurement data");
this->status_set_warning();
return false;
}
// temperature MSB is in data[2], LSB is in data[1], XLSB in data[0]
int32_t raw_temp = (int32_t) data[2] << 16 | (int32_t) data[1] << 8 | (int32_t) data[0];
temperature = (float) (raw_temp / 65536.0); // convert measurement to degrees Celsius (page 22 of datasheet)
// pressure MSB is in data[5], LSB is in data[4], XLSB in data[3]
int32_t raw_press = (int32_t) data[5] << 16 | (int32_t) data[4] << 8 | (int32_t) data[3];
pressure = (float) (raw_press / 64.0); // Divide by 2^6=64 for Pa (page 22 of datasheet)
return true;
}
bool BMP581Component::reset_() {
// - writes reset command to the command register
// - waits for sensor to complete reset
// - returns the Power-On-Reboot interrupt status, which is asserted if successful
// writes reset command to BMP's command register
if (!this->write_byte(BMP581_COMMAND, RESET_COMMAND)) {
ESP_LOGE(TAG, "Failed to write reset command");
return false;
}
// t_{soft_res} = 2ms (page 11 of datasheet); time it takes to enter standby mode
// - round up to 3 ms
delay(3);
// read interrupt status register
if (!this->read_byte(BMP581_INT_STATUS, &this->int_status_.reg)) {
ESP_LOGE(TAG, "Failed to read interrupt status register");
return false;
}
// Power-On-Reboot bit is asserted if sensor successfully reset
return this->int_status_.bit.por;
}
bool BMP581Component::start_measurement_() {
// - only pushes the sensor into FORCED_MODE for a reading if already in STANDBY_MODE
// - returns whether a measurement is in progress or has been initiated
if (this->odr_config_.bit.pwr_mode == STANDBY_MODE) {
return this->write_power_mode_(FORCED_MODE);
} else {
return true;
}
}
bool BMP581Component::write_iir_settings_(IIRFilter temperature_iir, IIRFilter pressure_iir) {
// - ensures data registers store filtered values
// - sets IIR filter levels on sensor
// - matches other default settings on sensor
// - writes configuration to the two relevant registers
// - returns success or failure of write to the registers
// If the temperature/pressure IIR filter is configured, then ensure data registers store the filtered measurement
this->dsp_config_.bit.shdw_sel_iir_t = (temperature_iir != IIR_FILTER_OFF);
this->dsp_config_.bit.shdw_sel_iir_p = (pressure_iir != IIR_FILTER_OFF);
// set temperature and pressure IIR filter level to configured values
this->iir_config_.bit.set_iir_t = temperature_iir;
this->iir_config_.bit.set_iir_p = pressure_iir;
// enable pressure and temperature compensation (page 61 of datasheet)
// - ?only relevant if IIR filter is applied?; the datasheet is ambiguous
// - matches BMP's default setting
this->dsp_config_.bit.comp_pt_en = 0x3;
// BMP581_DSP register and BMP581_DSP_IIR registers are successive
// - allows us to write the IIR configuration with one command to both registers
uint8_t register_data[2] = {this->dsp_config_.reg, this->iir_config_.reg};
return this->write_bytes(BMP581_DSP, register_data, sizeof(register_data));
}
bool BMP581Component::write_interrupt_source_settings_(bool data_ready_enable) {
// - updates component's internal setting
// - returns success or failure of write to interrupt source register
this->int_source_.bit.drdy_data_reg_en = data_ready_enable;
// write interrupt source register
return this->write_byte(BMP581_INT_SOURCE, this->int_source_.reg);
}
bool BMP581Component::write_oversampling_settings_(Oversampling temperature_oversampling,
Oversampling pressure_oversampling) {
// - updates component's internal setting
// - returns success or failure of write to Over-Sampling Rate register
this->osr_config_.bit.osr_t = temperature_oversampling;
this->osr_config_.bit.osr_p = pressure_oversampling;
return this->write_byte(BMP581_OSR, this->osr_config_.reg);
}
bool BMP581Component::write_power_mode_(OperationMode mode) {
// - updates the component's internal power mode
// - returns success or failure of write to Output Data Rate register
this->odr_config_.bit.pwr_mode = mode;
// write odr register
return this->write_byte(BMP581_ODR, this->odr_config_.reg);
}
} // namespace bmp581
} // namespace esphome

View File

@@ -0,0 +1,222 @@
// All datasheet page references refer to Bosch Document Number BST-BMP581-DS004-04 (revision number 1.4)
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/i2c/i2c.h"
#include "esphome/components/sensor/sensor.h"
namespace esphome {
namespace bmp581 {
static const uint8_t BMP581_ASIC_ID = 0x50; // BMP581's ASIC chip ID (page 51 of datasheet)
static const uint8_t RESET_COMMAND = 0xB6; // Soft reset command
// BMP581 Register Addresses
enum {
BMP581_CHIP_ID = 0x01, // read chip ID
BMP581_INT_SOURCE = 0x15, // write interrupt sources
BMP581_MEASUREMENT_DATA =
0x1D, // read measurement registers, 0x1D-0x1F are temperature XLSB to MSB and 0x20-0x22 are pressure XLSB to MSB
BMP581_INT_STATUS = 0x27, // read interrupt statuses
BMP581_STATUS = 0x28, // read sensor status
BMP581_DSP = 0x30, // write sensor configuration
BMP581_DSP_IIR = 0x31, // write IIR filter configuration
BMP581_OSR = 0x36, // write oversampling configuration
BMP581_ODR = 0x37, // write data rate and power mode configuration
BMP581_COMMAND = 0x7E // write sensor command
};
// BMP581 Power mode operations
enum OperationMode {
STANDBY_MODE = 0x0, // no active readings
NORMAL_MODE = 0x1, // read continuously at ODR configured rate and standby between
FORCED_MODE = 0x2, // read sensor once (only reading mode used by this component)
NONSTOP_MODE = 0x3 // read continuously with no standby
};
// Temperature and pressure sensors can be oversampled to reduce noise
enum Oversampling {
OVERSAMPLING_NONE = 0x0,
OVERSAMPLING_X2 = 0x1,
OVERSAMPLING_X4 = 0x2,
OVERSAMPLING_X8 = 0x3,
OVERSAMPLING_X16 = 0x4,
OVERSAMPLING_X32 = 0x5,
OVERSAMPLING_X64 = 0x6,
OVERSAMPLING_X128 = 0x7
};
// Infinite Impulse Response filter reduces noise caused by ambient disturbances
enum IIRFilter {
IIR_FILTER_OFF = 0x0,
IIR_FILTER_2 = 0x1,
IIR_FILTER_4 = 0x2,
IIR_FILTER_8 = 0x3,
IIR_FILTER_16 = 0x4,
IIR_FILTER_32 = 0x5,
IIR_FILTER_64 = 0x6,
IIR_FILTER_128 = 0x7
};
class BMP581Component : public PollingComponent, public i2c::I2CDevice {
public:
float get_setup_priority() const override { return setup_priority::DATA; }
void dump_config() override;
void setup() override;
void update() override;
void set_temperature_sensor(sensor::Sensor *temperature_sensor) { this->temperature_sensor_ = temperature_sensor; }
void set_pressure_sensor(sensor::Sensor *pressure_sensor) { this->pressure_sensor_ = pressure_sensor; }
void set_temperature_oversampling_config(Oversampling temperature_oversampling) {
this->temperature_oversampling_ = temperature_oversampling;
}
void set_pressure_oversampling_config(Oversampling pressure_oversampling) {
this->pressure_oversampling_ = pressure_oversampling;
}
void set_temperature_iir_filter_config(IIRFilter iir_temperature_level) {
this->iir_temperature_level_ = iir_temperature_level;
}
void set_pressure_iir_filter_config(IIRFilter iir_pressure_level) { this->iir_pressure_level_ = iir_pressure_level; }
void set_conversion_time(uint8_t conversion_time) { this->conversion_time_ = conversion_time; }
protected:
sensor::Sensor *temperature_sensor_{nullptr};
sensor::Sensor *pressure_sensor_{nullptr};
Oversampling temperature_oversampling_;
Oversampling pressure_oversampling_;
IIRFilter iir_temperature_level_;
IIRFilter iir_pressure_level_;
// Stores the sensors conversion time needed for a measurement based on oversampling settings and datasheet (page 12)
// Computed in Python during codegen
uint8_t conversion_time_;
// Checks if the BMP581 has measurement data ready by checking the sensor's interrupts
bool check_data_readiness_();
// Flushes the IIR filter and primes an initial reading
bool prime_iir_filter_();
// Reads temperature data from sensor and converts data to measurement in degrees Celsius
bool read_temperature_(float &temperature);
// Reads temperature and pressure data from sensor and converts data to measurements in degrees Celsius and Pa
bool read_temperature_and_pressure_(float &temperature, float &pressure);
// Soft resets the BMP581
bool reset_();
// Initiates a measurement on sensor by switching to FORCED_MODE
bool start_measurement_();
// Writes the IIR filter configuration to the DSP and DSP_IIR registers
bool write_iir_settings_(IIRFilter temperature_iir, IIRFilter pressure_iir);
// Writes whether to enable the data ready interrupt to the interrupt source register
bool write_interrupt_source_settings_(bool data_ready_enable);
// Writes the oversampling settings to the OSR register
bool write_oversampling_settings_(Oversampling temperature_oversampling, Oversampling pressure_oversampling);
// Sets the power mode on the BMP581 by writing to the ODR register
bool write_power_mode_(OperationMode mode);
enum ErrorCode {
NONE = 0,
ERROR_COMMUNICATION_FAILED,
ERROR_WRONG_CHIP_ID,
ERROR_SENSOR_STATUS,
ERROR_SENSOR_RESET,
ERROR_PRIME_IIR_FAILED
} error_code_{NONE};
// BMP581's interrupt source register (address 0x15) to configure which interrupts are enabled (page 54 of datasheet)
union {
struct {
uint8_t drdy_data_reg_en : 1; // Data ready interrupt enable
uint8_t fifo_full_en : 1; // FIFO full interrupt enable
uint8_t fifo_ths_en : 1; // FIFO threshold/watermark interrupt enable
uint8_t oor_p_en : 1; // Pressure data out-of-range interrupt enable
} bit;
uint8_t reg;
} int_source_ = {.reg = 0};
// BMP581's interrupt status register (address 0x27) to determine ensor's current state (page 58 of datasheet)
union {
struct {
uint8_t drdy_data_reg : 1; // Data ready
uint8_t fifo_full : 1; // FIFO full
uint8_t fifo_ths : 1; // FIFO fhreshold/watermark
uint8_t oor_p : 1; // Pressure data out-of-range
uint8_t por : 1; // Power-On-Reset complete
} bit;
uint8_t reg;
} int_status_ = {.reg = 0};
// BMP581's status register (address 0x28) to determine if sensor has setup correctly (page 58 of datasheet)
union {
struct {
uint8_t status_core_rdy : 1;
uint8_t status_nvm_rdy : 1; // asserted if NVM is ready of operations
uint8_t status_nvm_err : 1; // asserted if NVM error
uint8_t status_nvm_cmd_err : 1; // asserted if boot command error
uint8_t status_boot_err_corrected : 1; // asserted if a boot error has been corrected
uint8_t : 2;
uint8_t st_crack_pass : 1; // asserted if crack check has executed without detecting a crack
} bit;
uint8_t reg;
} status_ = {.reg = 0};
// BMP581's dsp register (address 0x30) to configure data registers iir selection (page 61 of datasheet)
union {
struct {
uint8_t comp_pt_en : 2; // enable temperature and pressure compensation
uint8_t iir_flush_forced_en : 1; // IIR filter is flushed in forced mode
uint8_t shdw_sel_iir_t : 1; // temperature data register value selected before or after iir
uint8_t fifo_sel_iir_t : 1; // FIFO temperature data register value secected before or after iir
uint8_t shdw_sel_iir_p : 1; // pressure data register value selected before or after iir
uint8_t fifo_sel_iir_p : 1; // FIFO pressure data register value selected before or after iir
uint8_t oor_sel_iir_p : 1; // pressure out-of-range value selected before or after iir
} bit;
uint8_t reg;
} dsp_config_ = {.reg = 0};
// BMP581's iir register (address 0x31) to configure iir filtering(page 62 of datasheet)
union {
struct {
uint8_t set_iir_t : 3; // Temperature IIR filter coefficient
uint8_t set_iir_p : 3; // Pressure IIR filter coefficient
} bit;
uint8_t reg;
} iir_config_ = {.reg = 0};
// BMP581's OSR register (address 0x36) to configure Over-Sampling Rates (page 64 of datasheet)
union {
struct {
uint8_t osr_t : 3; // Temperature oversampling
uint8_t osr_p : 3; // Pressure oversampling
uint8_t press_en : 1; // Enables pressure measurement
} bit;
uint8_t reg;
} osr_config_ = {.reg = 0};
// BMP581's odr register (address 0x37) to configure output data rate and power mode (page 64 of datasheet)
union {
struct {
uint8_t pwr_mode : 2; // power mode of sensor
uint8_t odr : 5; // output data rate
uint8_t deep_dis : 1; // deep standby disabled if asserted
} bit;
uint8_t reg;
} odr_config_ = {.reg = 0};
};
} // namespace bmp581
} // namespace esphome

View File

@@ -0,0 +1,163 @@
import math
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import i2c, sensor
from esphome.const import (
CONF_ID,
CONF_IIR_FILTER,
CONF_OVERSAMPLING,
CONF_PRESSURE,
CONF_TEMPERATURE,
DEVICE_CLASS_ATMOSPHERIC_PRESSURE,
DEVICE_CLASS_TEMPERATURE,
STATE_CLASS_MEASUREMENT,
UNIT_CELSIUS,
UNIT_PASCAL,
)
CODEOWNERS = ["@kahrendt"]
DEPENDENCIES = ["i2c"]
bmp581_ns = cg.esphome_ns.namespace("bmp581")
Oversampling = bmp581_ns.enum("Oversampling")
OVERSAMPLING_OPTIONS = {
"NONE": Oversampling.OVERSAMPLING_NONE,
"2X": Oversampling.OVERSAMPLING_X2,
"4X": Oversampling.OVERSAMPLING_X4,
"8X": Oversampling.OVERSAMPLING_X8,
"16X": Oversampling.OVERSAMPLING_X16,
"32X": Oversampling.OVERSAMPLING_X32,
"64X": Oversampling.OVERSAMPLING_X64,
"128X": Oversampling.OVERSAMPLING_X128,
}
IIRFilter = bmp581_ns.enum("IIRFilter")
IIR_FILTER_OPTIONS = {
"OFF": IIRFilter.IIR_FILTER_OFF,
"2X": IIRFilter.IIR_FILTER_2,
"4X": IIRFilter.IIR_FILTER_4,
"8X": IIRFilter.IIR_FILTER_8,
"16X": IIRFilter.IIR_FILTER_16,
"32X": IIRFilter.IIR_FILTER_32,
"64X": IIRFilter.IIR_FILTER_64,
"128X": IIRFilter.IIR_FILTER_128,
}
BMP581Component = bmp581_ns.class_(
"BMP581Component", cg.PollingComponent, i2c.I2CDevice
)
def compute_measurement_conversion_time(config):
# - adds up sensor conversion time based on temperature and pressure oversampling rates given in datasheet
# - returns a rounded up time in ms
# Page 12 of datasheet
PRESSURE_OVERSAMPLING_CONVERSION_TIMES = {
"NONE": 1.0,
"2X": 1.7,
"4X": 2.9,
"8X": 5.4,
"16X": 10.4,
"32X": 20.4,
"64X": 40.4,
"128X": 80.4,
}
# Page 12 of datasheet
TEMPERATURE_OVERSAMPLING_CONVERSION_TIMES = {
"NONE": 1.0,
"2X": 1.1,
"4X": 1.5,
"8X": 2.1,
"16X": 3.3,
"32X": 5.8,
"64X": 10.8,
"128X": 20.8,
}
pressure_conversion_time = (
0.0 # No conversion time necessary without a pressure sensor
)
if pressure_config := config.get(CONF_PRESSURE):
pressure_conversion_time = PRESSURE_OVERSAMPLING_CONVERSION_TIMES[
pressure_config.get(CONF_OVERSAMPLING)
]
temperature_conversion_time = (
1.0 # BMP581 always samples the temperature even if only reading pressure
)
if temperature_config := config.get(CONF_TEMPERATURE):
temperature_conversion_time = TEMPERATURE_OVERSAMPLING_CONVERSION_TIMES[
temperature_config.get(CONF_OVERSAMPLING)
]
# Datasheet indicates a 5% possible error in each conversion time listed
return math.ceil(1.05 * (pressure_conversion_time + temperature_conversion_time))
CONFIG_SCHEMA = (
cv.Schema(
{
cv.GenerateID(): cv.declare_id(BMP581Component),
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
unit_of_measurement=UNIT_CELSIUS,
accuracy_decimals=1,
device_class=DEVICE_CLASS_TEMPERATURE,
state_class=STATE_CLASS_MEASUREMENT,
).extend(
{
cv.Optional(CONF_OVERSAMPLING, default="NONE"): cv.enum(
OVERSAMPLING_OPTIONS, upper=True
),
cv.Optional(CONF_IIR_FILTER, default="OFF"): cv.enum(
IIR_FILTER_OPTIONS, upper=True
),
}
),
cv.Optional(CONF_PRESSURE): sensor.sensor_schema(
unit_of_measurement=UNIT_PASCAL,
accuracy_decimals=0,
device_class=DEVICE_CLASS_ATMOSPHERIC_PRESSURE,
state_class=STATE_CLASS_MEASUREMENT,
).extend(
{
cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum(
OVERSAMPLING_OPTIONS, upper=True
),
cv.Optional(CONF_IIR_FILTER, default="OFF"): cv.enum(
IIR_FILTER_OPTIONS, upper=True
),
}
),
}
)
.extend(cv.polling_component_schema("60s"))
.extend(i2c.i2c_device_schema(0x46))
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await i2c.register_i2c_device(var, config)
if temperature_config := config.get(CONF_TEMPERATURE):
sens = await sensor.new_sensor(temperature_config)
cg.add(var.set_temperature_sensor(sens))
cg.add(
var.set_temperature_oversampling_config(
temperature_config[CONF_OVERSAMPLING]
)
)
cg.add(
var.set_temperature_iir_filter_config(temperature_config[CONF_IIR_FILTER])
)
if pressure_config := config.get(CONF_PRESSURE):
sens = await sensor.new_sensor(pressure_config)
cg.add(var.set_pressure_sensor(sens))
cg.add(var.set_pressure_oversampling_config(pressure_config[CONF_OVERSAMPLING]))
cg.add(var.set_pressure_iir_filter_config(pressure_config[CONF_IIR_FILTER]))
cg.add(var.set_conversion_time(compute_measurement_conversion_time(config)))

View File

@@ -85,11 +85,11 @@ async def setup_button_core_(var, config):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf) await automation.build_automation(trigger, [], conf)
if CONF_DEVICE_CLASS in config: if device_class := config.get(CONF_DEVICE_CLASS):
cg.add(var.set_device_class(config[CONF_DEVICE_CLASS])) cg.add(var.set_device_class(device_class))
if CONF_MQTT_ID in config: if mqtt_id := config.get(CONF_MQTT_ID):
mqtt_ = cg.new_Pvariable(config[CONF_MQTT_ID], var) mqtt_ = cg.new_Pvariable(mqtt_id, var)
await mqtt.register_mqtt_component(mqtt_, config) await mqtt.register_mqtt_component(mqtt_, config)

View File

@@ -17,12 +17,11 @@ CONF_ON_FRAME = "on_frame"
def validate_id(config): def validate_id(config):
if CONF_CAN_ID in config: can_id = config[CONF_CAN_ID]
id_value = config[CONF_CAN_ID] id_ext = config[CONF_USE_EXTENDED_ID]
id_ext = config[CONF_USE_EXTENDED_ID] if not id_ext:
if not id_ext: if can_id > 0x7FF:
if id_value > 0x7FF: raise cv.Invalid("Standard IDs must be 11 Bit (0x000-0x7ff / 0-2047)")
raise cv.Invalid("Standard IDs must be 11 Bit (0x000-0x7ff / 0-2047)")
return config return config
@@ -145,8 +144,8 @@ async def canbus_action_to_code(config, action_id, template_arg, args):
var = cg.new_Pvariable(action_id, template_arg) var = cg.new_Pvariable(action_id, template_arg)
await cg.register_parented(var, config[CONF_CANBUS_ID]) await cg.register_parented(var, config[CONF_CANBUS_ID])
if CONF_CAN_ID in config: if can_id := config.get(CONF_CAN_ID):
can_id = await cg.templatable(config[CONF_CAN_ID], args, cg.uint32) can_id = await cg.templatable(can_id, args, cg.uint32)
cg.add(var.set_can_id(can_id)) cg.add(var.set_can_id(can_id))
use_extended_id = await cg.templatable( use_extended_id = await cg.templatable(
config[CONF_USE_EXTENDED_ID], args, cg.uint32 config[CONF_USE_EXTENDED_ID], args, cg.uint32

View File

@@ -30,7 +30,7 @@ void Canbus::send_data(uint32_t can_id, bool use_extended_id, bool remote_transm
if (use_extended_id) { if (use_extended_id) {
ESP_LOGD(TAG, "send extended id=0x%08x rtr=%s size=%d", can_id, TRUEFALSE(remote_transmission_request), size); ESP_LOGD(TAG, "send extended id=0x%08x rtr=%s size=%d", can_id, TRUEFALSE(remote_transmission_request), size);
} else { } else {
ESP_LOGD(TAG, "send extended id=0x%03x rtr=%s size=%d", can_id, TRUEFALSE(remote_transmission_request), size); ESP_LOGD(TAG, "send standard id=0x%03x rtr=%s size=%d", can_id, TRUEFALSE(remote_transmission_request), size);
} }
if (size > CAN_MAX_DATA_LENGTH) if (size > CAN_MAX_DATA_LENGTH)
size = CAN_MAX_DATA_LENGTH; size = CAN_MAX_DATA_LENGTH;

View File

@@ -37,8 +37,8 @@ async def to_code(config):
cg.add(var.set_touch_threshold(config[CONF_TOUCH_THRESHOLD])) cg.add(var.set_touch_threshold(config[CONF_TOUCH_THRESHOLD]))
cg.add(var.set_allow_multiple_touches(config[CONF_ALLOW_MULTIPLE_TOUCHES])) cg.add(var.set_allow_multiple_touches(config[CONF_ALLOW_MULTIPLE_TOUCHES]))
if CONF_RESET_PIN in config: if reset_pin_config := config.get(CONF_RESET_PIN):
pin = await cg.gpio_pin_expression(config[CONF_RESET_PIN]) pin = await cg.gpio_pin_expression(reset_pin_config)
cg.add(var.set_reset_pin(pin)) cg.add(var.set_reset_pin(pin))
await cg.register_component(var, config) await cg.register_component(var, config)

View File

@@ -21,7 +21,6 @@ CONFIG_SCHEMA = cv.All(
), ),
} }
).extend(cv.COMPONENT_SCHEMA), ).extend(cv.COMPONENT_SCHEMA),
cv.only_with_arduino,
cv.only_on(["esp32", "esp8266"]), cv.only_on(["esp32", "esp8266"]),
) )
@@ -34,8 +33,9 @@ async def to_code(config):
await cg.register_component(var, config) await cg.register_component(var, config)
cg.add_define("USE_CAPTIVE_PORTAL") cg.add_define("USE_CAPTIVE_PORTAL")
if CORE.is_esp32: if CORE.using_arduino:
cg.add_library("DNSServer", None) if CORE.is_esp32:
cg.add_library("WiFi", None) cg.add_library("DNSServer", None)
if CORE.is_esp8266: cg.add_library("WiFi", None)
cg.add_library("DNSServer", None) if CORE.is_esp8266:
cg.add_library("DNSServer", None)

View File

@@ -1,5 +1,3 @@
#ifdef USE_ARDUINO
#include "captive_portal.h" #include "captive_portal.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
#include "esphome/core/application.h" #include "esphome/core/application.h"
@@ -46,10 +44,12 @@ void CaptivePortal::start() {
this->base_->add_ota_handler(); this->base_->add_ota_handler();
} }
#ifdef USE_ARDUINO
this->dns_server_ = make_unique<DNSServer>(); this->dns_server_ = make_unique<DNSServer>();
this->dns_server_->setErrorReplyCode(DNSReplyCode::NoError); this->dns_server_->setErrorReplyCode(DNSReplyCode::NoError);
network::IPAddress ip = wifi::global_wifi_component->wifi_soft_ap_ip(); network::IPAddress ip = wifi::global_wifi_component->wifi_soft_ap_ip();
this->dns_server_->start(53, "*", (uint32_t) ip); this->dns_server_->start(53, "*", (uint32_t) ip);
#endif
this->base_->get_server()->onNotFound([this](AsyncWebServerRequest *req) { this->base_->get_server()->onNotFound([this](AsyncWebServerRequest *req) {
if (!this->active_ || req->host().c_str() == wifi::global_wifi_component->wifi_soft_ap_ip().str()) { if (!this->active_ || req->host().c_str() == wifi::global_wifi_component->wifi_soft_ap_ip().str()) {
@@ -67,7 +67,7 @@ void CaptivePortal::start() {
void CaptivePortal::handleRequest(AsyncWebServerRequest *req) { void CaptivePortal::handleRequest(AsyncWebServerRequest *req) {
if (req->url() == "/") { if (req->url() == "/") {
AsyncWebServerResponse *response = req->beginResponse_P(200, "text/html", INDEX_GZ, sizeof(INDEX_GZ)); auto *response = req->beginResponse_P(200, "text/html", INDEX_GZ, sizeof(INDEX_GZ));
response->addHeader("Content-Encoding", "gzip"); response->addHeader("Content-Encoding", "gzip");
req->send(response); req->send(response);
return; return;
@@ -91,5 +91,3 @@ CaptivePortal *global_captive_portal = nullptr; // NOLINT(cppcoreguidelines-avo
} // namespace captive_portal } // namespace captive_portal
} // namespace esphome } // namespace esphome
#endif // USE_ARDUINO

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