Compare commits

..

302 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
0709367587 Merge pull request #5047 from esphome/bump-2023.6.4
2023.6.4
2023-07-04 14:42:36 +12:00
Jesse Hills
98277f6ceb Bump version to 2023.6.4 2023-07-04 14:03:58 +12:00
Jesse Hills
8dd509ba53 Update webserver to ea86d81 (#5023) 2023-07-04 14:03:57 +12:00
J. Nick Koston
8df455f55b Advertise noise is enabled (#5034) 2023-07-04 14:03:57 +12:00
Graham Brown
36782f13bf Add alarm to reserved ids (#5042) 2023-07-04 14:03:57 +12:00
Sergey Dudanov
e823067a6b fix template binary_sensor publish_initial_state option (#5033) 2023-07-04 14:03:57 +12:00
Ryan DeShone
c3ef12d580 [SCD30] Disable negative temperature offset (#4850) 2023-07-04 14:03:57 +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
321155eb40 Merge pull request #5020 from esphome/bump-2023.6.3
2023.6.3
2023-06-29 07:26:05 +12:00
Jesse Hills
d34c074b92 Bump version to 2023.6.3 2023-06-28 12:35:16 +12:00
Jesse Hills
abc8e903c1 Add CONFIG_BT_BLE_42_FEATURES_SUPPORTED for ble (#5008) 2023-06-28 12:35:15 +12:00
F.D.Castel
832ba38f1b Fixes compressed downloads (#5014)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2023-06-28 12:35:15 +12:00
esphomebot
70de2f5278 Synchronise Device Classes from Home Assistant (#5018) 2023-06-28 12:35:15 +12:00
Jesse Hills
604d4eec79 Update webserver to 56d73b5 (#5007) 2023-06-28 12:35:14 +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
b806eb6a61 Merge pull request #4997 from esphome/bump-2023.6.2
2023.6.2
2023-06-23 22:47:41 +12:00
Jesse Hills
39948db59a Bump version to 2023.6.2 2023-06-23 17:17:43 +12:00
Jesse Hills
fbfb4e2a73 Fix rp2040 pio tool download (#4994) 2023-06-23 17:17:43 +12:00
Samuel Sieb
595ac84779 remove unused static declarations (#4993) 2023-06-23 17:17:43 +12: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
746f72a279 Merge pull request #4992 from esphome/bump-2023.6.1
2023.6.1
2023-06-23 08:50:39 +12:00
Jesse Hills
ec3d5fc427 Merge branch 'release' into dev 2023-06-23 07:49:08 +12:00
Jesse Hills
dec6f04499 Bump version to 2023.6.1 2023-06-23 07:34:55 +12:00
Kamil Trzciński
a90d266017 display: fix white screen on binary displays (#4991) 2023-06-23 07:34:54 +12:00
Jimmy Hedman
df9fcf9850 Make ethernet_info work with esp-idf framework (#4976) 2023-06-23 07:34:54 +12:00
Kamil Trzciński
85608a8ab7 display: fix white screen on binary displays (#4991) 2023-06-22 14:18:29 -05:00
Jesse Hills
5ef9cd5f86 Merge pull request #4990 from esphome/bump-2023.6.0
2023.6.0
2023-06-22 17:15:13 +12:00
Jimmy Hedman
52d7d2cae7 Make ethernet_info work with esp-idf framework (#4976) 2023-06-22 16:09:00 +12:00
Jesse Hills
ceca91d1e7 Bump version to 2023.6.0 2023-06-22 13:39:10 +12:00
Jesse Hills
bc7c11be96 Merge pull request #4989 from esphome/bump-2023.6.0b7
2023.6.0b7
2023-06-22 13:31:03 +12:00
Jesse Hills
a2734330e1 Bump version to 2023.6.0b7 2023-06-22 11:50:46 +12:00
F.D.Castel
ef8180c8a8 dashboard: Adds "compressed=1" to /download.bin endpoint. (...) (#4966)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2023-06-22 11:50:45 +12:00
J. Nick Koston
cd773a1dec Migrate VOC sensors that use ppb to use volatile_organic_compounds_parts device class (#4982) 2023-06-22 11:50:45 +12:00
Kevin P. Fleming
244a212592 airthings_wave: refactor to eliminate code duplication (#4910) 2023-06-22 11:50:45 +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
98e3426769 Merge pull request #4987 from esphome/bump-2023.6.0b6
2023.6.0b6
2023-06-22 11:22:48 +12:00
Jesse Hills
7ef8d67831 Bump version to 2023.6.0b6 2023-06-22 10:31:23 +12:00
Jesse Hills
c455a5dd6a Update webserver and captive portal pages to 67c48ee9 (#4986) 2023-06-22 10:31:23 +12:00
Dion Hulse
74daca668e Add configuration option to disable the log UI. (#4419)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2023-06-22 10:31:23 +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
a40fa22055 Merge pull request #4984 from esphome/bump-2023.6.0b5
2023.6.0b5
2023-06-21 17:17:13 +12:00
Jesse Hills
2047bba4f7 Bump version to 2023.6.0b5 2023-06-21 16:39:52 +12:00
Jesse Hills
a064ab5c2b Fix pypi release (#4983) 2023-06-21 16:39:52 +12:00
Jesse Hills
e8ce7048d8 Fix pypi release (#4983) 2023-06-21 16:36:54 +12:00
Jesse Hills
0c37d06936 Merge pull request #4981 from esphome/bump-2023.6.0b4
2023.6.0b4
2023-06-21 15:49:32 +12:00
Jesse Hills
d919373853 Bump version to 2023.6.0b4 2023-06-21 14:32:53 +12:00
Jesse Hills
6c00be0a63 Bump esphome-dashboard to 20230621.0 (#4980) 2023-06-21 14:32:47 +12:00
Onne
0671287b80 Make growatt play nicer with other modbus components. (#4947)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2023-06-21 14:31:24 +12:00
Martin Murray
867b4719d1 Apply configured IIR filter setting in generated BMP280 code (#4975)
Co-authored-by: Martin Murray <murrayma@gmail.com>
2023-06-21 14:31:24 +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
417 changed files with 16862 additions and 4947 deletions

View File

@@ -5,9 +5,12 @@ Checks: >-
-altera-*,
-android-*,
-boost-*,
-bugprone-easily-swappable-parameters,
-bugprone-implicit-widening-of-multiplication-result,
-bugprone-narrowing-conversions,
-bugprone-signed-char-misuse,
-cert-dcl50-cpp,
-cert-err33-c,
-cert-err58-cpp,
-cert-oop57-cpp,
-cert-str34-c,
@@ -15,6 +18,7 @@ Checks: >-
-clang-analyzer-osx.*,
-clang-diagnostic-delete-abstract-non-virtual-dtor,
-clang-diagnostic-delete-non-abstract-non-virtual-dtor,
-clang-diagnostic-ignored-optimization-argument,
-clang-diagnostic-shadow-field,
-clang-diagnostic-unused-const-variable,
-clang-diagnostic-unused-parameter,
@@ -25,6 +29,7 @@ Checks: >-
-cppcoreguidelines-macro-usage,
-cppcoreguidelines-narrowing-conversions,
-cppcoreguidelines-non-private-member-variables-in-classes,
-cppcoreguidelines-prefer-member-initializer,
-cppcoreguidelines-pro-bounds-array-to-pointer-decay,
-cppcoreguidelines-pro-bounds-constant-array-index,
-cppcoreguidelines-pro-bounds-pointer-arithmetic,
@@ -36,6 +41,7 @@ Checks: >-
-cppcoreguidelines-pro-type-union-access,
-cppcoreguidelines-pro-type-vararg,
-cppcoreguidelines-special-member-functions,
-cppcoreguidelines-virtual-class-destructor,
-fuchsia-multiple-inheritance,
-fuchsia-overloaded-operator,
-fuchsia-statically-constructed-objects,
@@ -68,6 +74,7 @@ Checks: >-
-modernize-use-nodiscard,
-mpi-*,
-objc-*,
-readability-container-data-pointer,
-readability-convert-member-functions-to-static,
-readability-else-after-return,
-readability-function-cognitive-complexity,
@@ -82,8 +89,6 @@ WarningsAsErrors: '*'
AnalyzeTemporaryDtors: false
FormatStyle: google
CheckOptions:
- key: google-readability-braces-around-statements.ShortStatementLines
value: '1'
- key: google-readability-function-size.StatementThreshold
value: '800'
- key: google-runtime-int.TypeSuffix
@@ -158,3 +163,9 @@ CheckOptions:
value: ''
- key: readability-qualified-auto.AddConstToQualified
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:
name: Create common environment
runs-on: ubuntu-latest
outputs:
cache-key: ${{ steps.cache-key.outputs.key }}
steps:
- name: Check out code from GitHub
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 }}
id: python
uses: actions/setup-python@v4.6.0
with:
python-version: ${{ env.DEFAULT_PYTHON }}
@@ -39,7 +45,7 @@ jobs:
with:
path: venv
# 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
if: steps.cache-venv.outputs.cache-hit != 'true'
run: |
@@ -66,12 +72,11 @@ jobs:
steps:
- name: Check out code from GitHub
uses: actions/checkout@v3.5.2
- name: Restore Python virtual environment
uses: actions/cache/restore@v3.3.1
- name: Restore Python
uses: ./.github/actions/restore-python
with:
path: venv
# yamllint disable-line rule:line-length
key: ${{ runner.os }}-${{ env.DEFAULT_PYTHON }}-venv-${{ hashFiles('requirements.txt', 'requirements_optional.txt', 'requirements_test.txt') }}
python-version: ${{ env.DEFAULT_PYTHON }}
cache-key: ${{ needs.common.outputs.cache-key }}
- name: Run black
run: |
. venv/bin/activate
@@ -88,12 +93,11 @@ jobs:
steps:
- name: Check out code from GitHub
uses: actions/checkout@v3.5.2
- name: Restore Python virtual environment
uses: actions/cache/restore@v3.3.1
- name: Restore Python
uses: ./.github/actions/restore-python
with:
path: venv
# yamllint disable-line rule:line-length
key: ${{ runner.os }}-${{ env.DEFAULT_PYTHON }}-venv-${{ hashFiles('requirements.txt', 'requirements_optional.txt', 'requirements_test.txt') }}
python-version: ${{ env.DEFAULT_PYTHON }}
cache-key: ${{ needs.common.outputs.cache-key }}
- name: Run flake8
run: |
. venv/bin/activate
@@ -110,12 +114,11 @@ jobs:
steps:
- name: Check out code from GitHub
uses: actions/checkout@v3.5.2
- name: Restore Python virtual environment
uses: actions/cache/restore@v3.3.1
- name: Restore Python
uses: ./.github/actions/restore-python
with:
path: venv
# yamllint disable-line rule:line-length
key: ${{ runner.os }}-${{ env.DEFAULT_PYTHON }}-venv-${{ hashFiles('requirements.txt', 'requirements_optional.txt', 'requirements_test.txt') }}
python-version: ${{ env.DEFAULT_PYTHON }}
cache-key: ${{ needs.common.outputs.cache-key }}
- name: Run pylint
run: |
. venv/bin/activate
@@ -132,12 +135,11 @@ jobs:
steps:
- name: Check out code from GitHub
uses: actions/checkout@v3.5.2
- name: Restore Python virtual environment
uses: actions/cache/restore@v3.3.1
- name: Restore Python
uses: ./.github/actions/restore-python
with:
path: venv
# yamllint disable-line rule:line-length
key: ${{ runner.os }}-${{ env.DEFAULT_PYTHON }}-venv-${{ hashFiles('requirements.txt', 'requirements_optional.txt', 'requirements_test.txt') }}
python-version: ${{ env.DEFAULT_PYTHON }}
cache-key: ${{ needs.common.outputs.cache-key }}
- name: Run pyupgrade
run: |
. venv/bin/activate
@@ -154,12 +156,11 @@ jobs:
steps:
- name: Check out code from GitHub
uses: actions/checkout@v3.5.2
- name: Restore Python virtual environment
uses: actions/cache/restore@v3.3.1
- name: Restore Python
uses: ./.github/actions/restore-python
with:
path: venv
# yamllint disable-line rule:line-length
key: ${{ runner.os }}-${{ env.DEFAULT_PYTHON }}-venv-${{ hashFiles('requirements.txt', 'requirements_optional.txt', 'requirements_test.txt') }}
python-version: ${{ env.DEFAULT_PYTHON }}
cache-key: ${{ needs.common.outputs.cache-key }}
- name: Register matcher
run: echo "::add-matcher::.github/workflows/matchers/ci-custom.json"
- name: Run script/ci-custom
@@ -176,12 +177,11 @@ jobs:
steps:
- name: Check out code from GitHub
uses: actions/checkout@v3.5.2
- name: Restore Python virtual environment
uses: actions/cache/restore@v3.3.1
- name: Restore Python
uses: ./.github/actions/restore-python
with:
path: venv
# yamllint disable-line rule:line-length
key: ${{ runner.os }}-${{ env.DEFAULT_PYTHON }}-venv-${{ hashFiles('requirements.txt', 'requirements_optional.txt', 'requirements_test.txt') }}
python-version: ${{ env.DEFAULT_PYTHON }}
cache-key: ${{ needs.common.outputs.cache-key }}
- name: Register matcher
run: echo "::add-matcher::.github/workflows/matchers/pytest.json"
- name: Run pytest
@@ -197,12 +197,11 @@ jobs:
steps:
- name: Check out code from GitHub
uses: actions/checkout@v3.5.2
- name: Restore Python virtual environment
uses: actions/cache/restore@v3.3.1
- name: Restore Python
uses: ./.github/actions/restore-python
with:
path: venv
# yamllint disable-line rule:line-length
key: ${{ runner.os }}-${{ env.DEFAULT_PYTHON }}-venv-${{ hashFiles('requirements.txt', 'requirements_optional.txt', 'requirements_test.txt') }}
python-version: ${{ env.DEFAULT_PYTHON }}
cache-key: ${{ needs.common.outputs.cache-key }}
- name: Install clang-format
run: |
. venv/bin/activate
@@ -237,18 +236,11 @@ jobs:
steps:
- name: Check out code from GitHub
uses: actions/checkout@v3.5.2
- name: Restore Python virtual environment
uses: actions/cache/restore@v3.3.1
- name: Restore Python
uses: ./.github/actions/restore-python
with:
path: venv
# yamllint disable-line rule:line-length
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') }}
python-version: ${{ env.DEFAULT_PYTHON }}
cache-key: ${{ needs.common.outputs.cache-key }}
- name: Run esphome compile tests/test${{ matrix.file }}.yaml
run: |
. venv/bin/activate
@@ -300,13 +292,11 @@ jobs:
steps:
- name: Check out code from GitHub
uses: actions/checkout@v3.5.2
- name: Restore Python virtual environment
uses: actions/cache/restore@v3.3.1
- name: Restore Python
uses: ./.github/actions/restore-python
with:
path: venv
# yamllint disable-line rule:line-length
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
python-version: ${{ env.DEFAULT_PYTHON }}
cache-key: ${{ needs.common.outputs.cache-key }}
- name: Cache platformio
uses: actions/cache@v3.3.1
with:
@@ -315,7 +305,7 @@ jobs:
key: platformio-${{ matrix.pio_cache_key }}-${{ hashFiles('platformio.ini') }}
- name: Install clang-tidy
run: sudo apt-get install clang-tidy-11
run: sudo apt-get install clang-tidy-14
- name: Register problem matchers
run: |

View File

@@ -49,9 +49,11 @@ jobs:
with:
python-version: "3.x"
- name: Set up python environment
env:
ESPHOME_NO_VENV: 1
run: |
script/setup
pip install setuptools wheel twine
pip install twine
- name: Build
run: python setup.py sdist bdist_wheel
- name: Upload

View File

@@ -6,14 +6,12 @@ on:
schedule:
- cron: '45 6 * * *'
permissions:
contents: write
pull-requests: write
jobs:
sync:
name: Sync Device Classes
runs-on: ubuntu-latest
if: github.repository == 'esphome/esphome'
steps:
- name: Checkout
uses: actions/checkout@v3
@@ -38,15 +36,6 @@ jobs:
run: |
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
uses: peter-evans/create-pull-request@v5
with:
@@ -56,5 +45,5 @@ jobs:
branch: sync/device-classes
delete-branch: true
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 }}

4
.gitignore vendored
View File

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

View File

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

View File

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

View File

@@ -22,16 +22,23 @@ RUN \
python3=3.9.2-3 \
python3-pip=20.3.4-4+deb11u1 \
python3-setuptools=52.0.0-4 \
python3-pil=8.1.2+dfsg-0.3+deb11u1 \
python3-cryptography=3.3.2-1 \
python3-venv=3.9.2-3 \
iputils-ping=3:20210202-1 \
git=1:2.30.2-1+deb11u2 \
curl=7.74.0-1.3+deb11u7 \
openssh-client=1:8.4p1-5+deb11u1 \
libcairo2=1.16.0-5 \
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/* \
/var/{cache,log}/* \
/var/lib/apt/lists/*

View File

@@ -32,7 +32,7 @@ from esphome.const import (
SECRETS_FILES,
)
from esphome.core import CORE, EsphomeError, coroutine
from esphome.helpers import indent
from esphome.helpers import indent, is_ip_address
from esphome.util import (
run_external_command,
run_external_process,
@@ -308,8 +308,10 @@ def upload_program(config, args, host):
password = ota_conf.get(CONF_PASSWORD, "")
if (
get_port_type(host) == "MQTT" or config[CONF_MDNS][CONF_DISABLED]
) and CONF_MQTT in config:
not is_ip_address(CORE.address)
and (get_port_type(host) == "MQTT" or config[CONF_MDNS][CONF_DISABLED])
and CONF_MQTT in config
):
from esphome import mqtt
host = mqtt.get_esphome_device_ip(
@@ -363,10 +365,16 @@ def command_wizard(args):
def command_config(args, config):
_LOGGER.info("Configuration is valid!")
if not CORE.verbose:
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

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])
cg.add(var.set_dir_pin(dir_pin))
if CONF_SLEEP_PIN in config:
sleep_pin = await cg.gpio_pin_expression(config[CONF_SLEEP_PIN])
if sleep_pin_config := config.get(CONF_SLEEP_PIN):
sleep_pin = await cg.gpio_pin_expression(sleep_pin_config)
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")
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
# 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):
if str(value).upper() == "VCC":
@@ -89,11 +133,18 @@ def validate_adc_pin(value):
if CORE.is_esp32:
value = pins.internal_gpio_input_pin_number(value)
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")
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")
return pins.internal_gpio_input_pin_schema(value)
if CORE.is_esp8266:
@@ -104,7 +155,7 @@ def validate_adc_pin(value):
)
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(
{CONF_ANALOG: True, CONF_INPUT: True}, internal=True
)(value)
@@ -112,7 +163,7 @@ def validate_adc_pin(value):
if CORE.is_rp2040:
value = pins.internal_gpio_input_pin_number(value)
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)
raise NotImplementedError

View File

@@ -20,15 +20,15 @@ namespace 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
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
#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
static const int SOC_ADC_RTC_MAX_BITWIDTH = 12;
static const int32_t SOC_ADC_RTC_MAX_BITWIDTH = 12;
#endif
#endif
@@ -47,14 +47,21 @@ extern "C"
#endif
#ifdef USE_ESP32
adc1_config_width(ADC_WIDTH_MAX_SOC_BITS);
if (!autorange_) {
adc1_config_channel_atten(channel_, attenuation_);
if (channel1_ != ADC1_CHANNEL_MAX) {
adc1_config_width(ADC_WIDTH_MAX_SOC_BITS);
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
for (int i = 0; i < (int) ADC_ATTEN_MAX; i++) {
auto cal_value = esp_adc_cal_characterize(ADC_UNIT_1, (adc_atten_t) i, ADC_WIDTH_MAX_SOC_BITS,
for (int32_t i = 0; i <= ADC_ATTEN_DB_11; i++) {
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
&cal_characteristics_[i]);
switch (cal_value) {
@@ -136,9 +143,9 @@ void ADCSensor::update() {
#ifdef USE_ESP8266
float ADCSensor::sample() {
#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
int raw = analogRead(this->pin_->get_pin()); // NOLINT
int32_t raw = analogRead(this->pin_->get_pin()); // NOLINT
#endif
if (output_raw_) {
return raw;
@@ -150,29 +157,53 @@ float ADCSensor::sample() {
#ifdef USE_ESP32
float ADCSensor::sample() {
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) {
return NAN;
}
if (output_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;
}
int raw11, raw6 = ADC_MAX, raw2 = ADC_MAX, raw0 = ADC_MAX;
adc1_config_channel_atten(channel_, ADC_ATTEN_DB_11);
raw11 = adc1_get_raw(channel_);
if (raw11 < ADC_MAX) {
adc1_config_channel_atten(channel_, ADC_ATTEN_DB_6);
raw6 = adc1_get_raw(channel_);
if (raw6 < ADC_MAX) {
adc1_config_channel_atten(channel_, ADC_ATTEN_DB_2_5);
raw2 = adc1_get_raw(channel_);
if (raw2 < ADC_MAX) {
adc1_config_channel_atten(channel_, ADC_ATTEN_DB_0);
raw0 = adc1_get_raw(channel_);
int raw11 = ADC_MAX, raw6 = ADC_MAX, raw2 = ADC_MAX, raw0 = ADC_MAX;
if (channel1_ != ADC1_CHANNEL_MAX) {
adc1_config_channel_atten(channel1_, ADC_ATTEN_DB_11);
raw11 = adc1_get_raw(channel1_);
if (raw11 < ADC_MAX) {
adc1_config_channel_atten(channel1_, ADC_ATTEN_DB_6);
raw6 = adc1_get_raw(channel1_);
if (raw6 < ADC_MAX) {
adc1_config_channel_atten(channel1_, ADC_ATTEN_DB_2_5);
raw2 = adc1_get_raw(channel1_);
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;
}
uint32_t mv11 = esp_adc_cal_raw_to_voltage(raw11, &cal_characteristics_[(int) ADC_ATTEN_DB_11]);
uint32_t mv6 = esp_adc_cal_raw_to_voltage(raw6, &cal_characteristics_[(int) ADC_ATTEN_DB_6]);
uint32_t mv2 = esp_adc_cal_raw_to_voltage(raw2, &cal_characteristics_[(int) ADC_ATTEN_DB_2_5]);
uint32_t mv0 = esp_adc_cal_raw_to_voltage(raw0, &cal_characteristics_[(int) ADC_ATTEN_DB_0]);
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_[(int32_t) ADC_ATTEN_DB_6]);
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_[(int32_t) ADC_ATTEN_DB_0]);
// 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);
@@ -212,7 +243,7 @@ float ADCSensor::sample() {
adc_select_input(pin - 26);
}
int raw = adc_read();
int32_t raw = adc_read();
if (this->is_temperature_) {
adc_set_temp_sensor_enabled(false);
}

View File

@@ -19,16 +19,23 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage
#ifdef USE_ESP32
/// Set the attenuation for this pin. Only available on the ESP32.
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; }
#endif
/// Update adc values.
/// Update ADC values
void update() override;
/// Setup ADc
/// Setup ADC
void setup() override;
void dump_config() override;
/// `HARDWARE_LATE` setup priority.
/// `HARDWARE_LATE` setup priority
float get_setup_priority() const override;
void set_pin(InternalGPIOPin *pin) { this->pin_ = pin; }
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
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};
esp_adc_cal_characteristics_t cal_characteristics_[(int) ADC_ATTEN_MAX] = {};
esp_adc_cal_characteristics_t cal_characteristics_[ADC_ATTEN_MAX] = {};
#endif
};

View File

@@ -1,5 +1,7 @@
import esphome.codegen as cg
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.esp32 import get_esp32_variant
from esphome.const import (
@@ -8,15 +10,15 @@ from esphome.const import (
CONF_NUMBER,
CONF_PIN,
CONF_RAW,
CONF_WIFI,
DEVICE_CLASS_VOLTAGE,
STATE_CLASS_MEASUREMENT,
UNIT_VOLT,
)
from esphome.core import CORE
from . import (
ATTENUATION_MODES,
ESP32_VARIANT_ADC1_PIN_TO_CHANNEL,
ESP32_VARIANT_ADC2_PIN_TO_CHANNEL,
validate_adc_pin,
)
@@ -25,7 +27,23 @@ AUTO_LOAD = ["voltage_sampler"]
def validate_config(config):
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
@@ -55,6 +73,8 @@ CONFIG_SCHEMA = cv.All(
validate_config,
)
FINAL_VALIDATE_SCHEMA = final_validate_config
async def to_code(config):
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])
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 config[CONF_ATTENUATION] == "auto":
if attenuation := config.get(CONF_ATTENUATION):
if attenuation == "auto":
cg.add(var.set_autorange(cg.global_ns.true))
else:
cg.add(var.set_attenuation(config[CONF_ATTENUATION]))
cg.add(var.set_attenuation(attenuation))
if CORE.is_esp32:
variant = get_esp32_variant()
pin_num = config[CONF_PIN][CONF_NUMBER]
chan = ESP32_VARIANT_ADC1_PIN_TO_CHANNEL[variant][pin_num]
cg.add(var.set_channel(chan))
if (
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 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(
config[CONF_PIXEL_MAPPER],
pixel_mapper,
[(int, "x"), (int, "y")],
return_type=cg.int_,
)
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(
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_))

View File

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

View File

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

View File

@@ -1,5 +1,6 @@
#include "airthings_listener.h"
#include "esphome/core/log.h"
#include <cinttypes>
#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[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;
}
}

View File

@@ -0,0 +1,103 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import sensor, ble_client
from esphome.const import (
CONF_BATTERY_VOLTAGE,
CONF_HUMIDITY,
CONF_PRESSURE,
CONF_TEMPERATURE,
CONF_TVOC,
DEVICE_CLASS_VOLTAGE,
DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_PRESSURE,
DEVICE_CLASS_TEMPERATURE,
DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS_PARTS,
ENTITY_CATEGORY_DIAGNOSTIC,
STATE_CLASS_MEASUREMENT,
UNIT_CELSIUS,
UNIT_HECTOPASCAL,
UNIT_PARTS_PER_BILLION,
UNIT_PERCENT,
UNIT_VOLT,
)
CODEOWNERS = ["@ncareau", "@jeromelaban", "@kpfleming"]
DEPENDENCIES = ["ble_client"]
CONF_BATTERY_UPDATE_INTERVAL = "battery_update_interval"
airthings_wave_base_ns = cg.esphome_ns.namespace("airthings_wave_base")
AirthingsWaveBase = airthings_wave_base_ns.class_(
"AirthingsWaveBase", cg.PollingComponent, ble_client.BLEClientNode
)
BASE_SCHEMA = (
sensor.SENSOR_SCHEMA.extend(
{
cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
unit_of_measurement=UNIT_PERCENT,
accuracy_decimals=0,
device_class=DEVICE_CLASS_HUMIDITY,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
unit_of_measurement=UNIT_CELSIUS,
accuracy_decimals=2,
device_class=DEVICE_CLASS_TEMPERATURE,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_PRESSURE): sensor.sensor_schema(
unit_of_measurement=UNIT_HECTOPASCAL,
accuracy_decimals=1,
device_class=DEVICE_CLASS_PRESSURE,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_TVOC): sensor.sensor_schema(
unit_of_measurement=UNIT_PARTS_PER_BILLION,
accuracy_decimals=0,
device_class=DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS_PARTS,
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(ble_client.BLE_CLIENT_SCHEMA)
)
async def wave_base_to_code(var, config):
await cg.register_component(var, config)
await ble_client.register_ble_node(var, config)
if config_humidity := config.get(CONF_HUMIDITY):
sens = await sensor.new_sensor(config_humidity)
cg.add(var.set_humidity(sens))
if config_temperature := config.get(CONF_TEMPERATURE):
sens = await sensor.new_sensor(config_temperature)
cg.add(var.set_temperature(sens))
if config_pressure := config.get(CONF_PRESSURE):
sens = await sensor.new_sensor(config_pressure)
cg.add(var.set_pressure(sens))
if config_tvoc := config.get(CONF_TVOC):
sens = await sensor.new_sensor(config_tvoc)
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

@@ -0,0 +1,211 @@
#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
namespace esphome {
namespace airthings_wave_base {
static const char *const TAG = "airthings_wave_base";
void AirthingsWaveBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
esp_ble_gattc_cb_param_t *param) {
switch (event) {
case ESP_GATTC_OPEN_EVT: {
if (param->open.status == ESP_GATT_OK) {
ESP_LOGI(TAG, "Connected successfully!");
}
break;
}
case ESP_GATTC_DISCONNECT_EVT: {
this->handle_ = 0;
this->acp_handle_ = 0;
this->cccd_handle_ = 0;
ESP_LOGW(TAG, "Disconnected!");
break;
}
case ESP_GATTC_SEARCH_CMPL_EVT: {
if (this->request_read_values_()) {
if (!this->read_battery_next_update_) {
this->node_state = espbt::ClientState::ESTABLISHED;
} else {
// delay setting node_state to ESTABLISHED until confirmation of the notify registration
this->request_battery_();
}
}
// ensure that the client will be disconnected even if no responses arrive
this->set_response_timeout_();
break;
}
case ESP_GATTC_READ_CHAR_EVT: {
if (param->read.conn_id != this->parent()->get_conn_id())
break;
if (param->read.status != ESP_GATT_OK) {
ESP_LOGW(TAG, "Error reading char at handle %d, status=%d", param->read.handle, param->read.status);
break;
}
if (param->read.handle == this->handle_) {
this->read_sensors(param->read.value, param->read.value_len);
}
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:
break;
}
}
bool AirthingsWaveBase::is_valid_voc_value_(uint16_t voc) { return voc <= 16383; }
void AirthingsWaveBase::update() {
if (this->node_state != espbt::ClientState::ESTABLISHED) {
if (!this->parent()->enabled) {
ESP_LOGW(TAG, "Reconnecting to device");
this->parent()->set_enabled(true);
this->parent()->connect();
} else {
ESP_LOGW(TAG, "Connection in progress");
}
}
}
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_,
ESP_GATT_AUTH_REQ_NONE);
if (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 esphome
#endif // USE_ESP32

View File

@@ -0,0 +1,90 @@
#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
#include <esp_gattc_api.h>
#include <algorithm>
#include <iterator>
#include "esphome/components/ble_client/ble_client.h"
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/core/component.h"
#include "esphome/core/log.h"
namespace esphome {
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 {
public:
AirthingsWaveBase() = default;
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 set_temperature(sensor::Sensor *temperature) { temperature_sensor_ = temperature; }
void set_humidity(sensor::Sensor *humidity) { humidity_sensor_ = humidity; }
void set_pressure(sensor::Sensor *pressure) { pressure_sensor_ = pressure; }
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:
bool is_valid_voc_value_(uint16_t voc);
bool request_read_values_();
virtual void read_sensors(uint8_t *raw_value, uint16_t value_len) = 0;
sensor::Sensor *temperature_sensor_{nullptr};
sensor::Sensor *humidity_sensor_{nullptr};
sensor::Sensor *pressure_sensor_{nullptr};
sensor::Sensor *tvoc_sensor_{nullptr};
sensor::Sensor *battery_voltage_{nullptr};
uint16_t handle_;
espbt::ESPBTUUID service_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 esphome
#endif // USE_ESP32

View File

@@ -7,105 +7,47 @@ namespace airthings_wave_mini {
static const char *const TAG = "airthings_wave_mini";
void AirthingsWaveMini::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
esp_ble_gattc_cb_param_t *param) {
switch (event) {
case ESP_GATTC_OPEN_EVT: {
if (param->open.status == ESP_GATT_OK) {
ESP_LOGI(TAG, "Connected successfully!");
}
break;
}
case ESP_GATTC_DISCONNECT_EVT: {
ESP_LOGW(TAG, "Disconnected!");
break;
}
case ESP_GATTC_SEARCH_CMPL_EVT: {
this->handle_ = 0;
auto *chr = this->parent()->get_characteristic(service_uuid_, sensors_data_characteristic_uuid_);
if (chr == nullptr) {
ESP_LOGW(TAG, "No sensor characteristic found at service %s char %s", service_uuid_.to_string().c_str(),
sensors_data_characteristic_uuid_.to_string().c_str());
break;
}
this->handle_ = chr->handle;
this->node_state = esp32_ble_tracker::ClientState::ESTABLISHED;
request_read_values_();
break;
}
case ESP_GATTC_READ_CHAR_EVT: {
if (param->read.conn_id != this->parent()->get_conn_id())
break;
if (param->read.status != ESP_GATT_OK) {
ESP_LOGW(TAG, "Error reading char at handle %d, status=%d", param->read.handle, param->read.status);
break;
}
if (param->read.handle == this->handle_) {
read_sensors_(param->read.value, param->read.value_len);
}
break;
}
default:
break;
}
}
void AirthingsWaveMini::read_sensors_(uint8_t *raw_value, uint16_t value_len) {
void AirthingsWaveMini::read_sensors(uint8_t *raw_value, uint16_t value_len) {
auto *value = (WaveMiniReadings *) raw_value;
if (sizeof(WaveMiniReadings) <= value_len) {
this->humidity_sensor_->publish_state(value->humidity / 100.0f);
this->pressure_sensor_->publish_state(value->pressure / 50.0f);
this->temperature_sensor_->publish_state(value->temperature / 100.0f - 273.15f);
if (is_valid_voc_value_(value->voc)) {
if (this->humidity_sensor_ != nullptr) {
this->humidity_sensor_->publish_state(value->humidity / 100.0f);
}
if (this->pressure_sensor_ != nullptr) {
this->pressure_sensor_->publish_state(value->pressure / 50.0f);
}
if (this->temperature_sensor_ != nullptr) {
this->temperature_sensor_->publish_state(value->temperature / 100.0f - 273.15f);
}
if ((this->tvoc_sensor_ != nullptr) && this->is_valid_voc_value_(value->voc)) {
this->tvoc_sensor_->publish_state(value->voc);
}
// This instance must not stay connected
// so other clients can connect to it (e.g. the
// mobile app).
parent()->set_enabled(false);
}
}
bool AirthingsWaveMini::is_valid_voc_value_(uint16_t voc) { return 0 <= voc && voc <= 16383; }
void AirthingsWaveMini::update() {
if (this->node_state != esp32_ble_tracker::ClientState::ESTABLISHED) {
if (!parent()->enabled) {
ESP_LOGW(TAG, "Reconnecting to device");
parent()->set_enabled(true);
parent()->connect();
} else {
ESP_LOGW(TAG, "Connection in progress");
}
}
}
void AirthingsWaveMini::request_read_values_() {
auto status = esp_ble_gattc_read_char(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), this->handle_,
ESP_GATT_AUTH_REQ_NONE);
if (status) {
ESP_LOGW(TAG, "Error sending read request for sensor, status=%d", status);
}
this->response_received_();
}
void AirthingsWaveMini::dump_config() {
// these really don't belong here, but there doesn't seem to be a
// practical way to have the base class use LOG_SENSOR and include
// the TAG from this component
LOG_SENSOR(" ", "Humidity", this->humidity_sensor_);
LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
LOG_SENSOR(" ", "Pressure", this->pressure_sensor_);
LOG_SENSOR(" ", "TVOC", this->tvoc_sensor_);
LOG_SENSOR(" ", "Battery Voltage", this->battery_voltage_);
}
AirthingsWaveMini::AirthingsWaveMini()
: PollingComponent(10000),
service_uuid_(esp32_ble_tracker::ESPBTUUID::from_raw(SERVICE_UUID)),
sensors_data_characteristic_uuid_(esp32_ble_tracker::ESPBTUUID::from_raw(CHARACTERISTIC_UUID)) {}
AirthingsWaveMini::AirthingsWaveMini() {
this->service_uuid_ = espbt::ESPBTUUID::from_raw(SERVICE_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 esphome

View File

@@ -2,50 +2,25 @@
#ifdef USE_ESP32
#include <esp_gattc_api.h>
#include <algorithm>
#include <iterator>
#include "esphome/components/ble_client/ble_client.h"
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/core/component.h"
#include "esphome/core/log.h"
#include "esphome/components/airthings_wave_base/airthings_wave_base.h"
namespace esphome {
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 CHARACTERISTIC_UUID = "b42e3b98-ade7-11e4-89d3-123b93f75cba";
static const char *const ACCESS_CONTROL_POINT_CHARACTERISTIC_UUID = "b42e3ef4-ade7-11e4-89d3-123b93f75cba";
class AirthingsWaveMini : public PollingComponent, public ble_client::BLEClientNode {
class AirthingsWaveMini : public airthings_wave_base::AirthingsWaveBase {
public:
AirthingsWaveMini();
void dump_config() 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 set_temperature(sensor::Sensor *temperature) { temperature_sensor_ = temperature; }
void set_humidity(sensor::Sensor *humidity) { humidity_sensor_ = humidity; }
void set_pressure(sensor::Sensor *pressure) { pressure_sensor_ = pressure; }
void set_tvoc(sensor::Sensor *tvoc) { tvoc_sensor_ = tvoc; }
protected:
bool is_valid_voc_value_(uint16_t voc);
void read_sensors_(uint8_t *value, uint16_t value_len);
void request_read_values_();
sensor::Sensor *temperature_sensor_{nullptr};
sensor::Sensor *humidity_sensor_{nullptr};
sensor::Sensor *pressure_sensor_{nullptr};
sensor::Sensor *tvoc_sensor_{nullptr};
uint16_t handle_;
esp32_ble_tracker::ESPBTUUID service_uuid_;
esp32_ble_tracker::ESPBTUUID sensors_data_characteristic_uuid_;
void read_sensors(uint8_t *raw_value, uint16_t value_len) override;
struct WaveMiniReadings {
uint16_t unused01;

View File

@@ -1,82 +1,28 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import sensor, ble_client
from esphome.components import airthings_wave_base
from esphome.const import (
DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_TEMPERATURE,
DEVICE_CLASS_PRESSURE,
STATE_CLASS_MEASUREMENT,
UNIT_PERCENT,
UNIT_CELSIUS,
UNIT_HECTOPASCAL,
CONF_ID,
CONF_HUMIDITY,
CONF_TVOC,
CONF_PRESSURE,
CONF_TEMPERATURE,
UNIT_PARTS_PER_BILLION,
ICON_RADIATOR,
)
DEPENDENCIES = ["ble_client"]
DEPENDENCIES = airthings_wave_base.DEPENDENCIES
AUTO_LOAD = ["airthings_wave_base"]
airthings_wave_mini_ns = cg.esphome_ns.namespace("airthings_wave_mini")
AirthingsWaveMini = airthings_wave_mini_ns.class_(
"AirthingsWaveMini", cg.PollingComponent, ble_client.BLEClientNode
"AirthingsWaveMini", airthings_wave_base.AirthingsWaveBase
)
CONFIG_SCHEMA = cv.All(
cv.Schema(
{
cv.GenerateID(): cv.declare_id(AirthingsWaveMini),
cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
unit_of_measurement=UNIT_PERCENT,
device_class=DEVICE_CLASS_HUMIDITY,
state_class=STATE_CLASS_MEASUREMENT,
accuracy_decimals=2,
),
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
unit_of_measurement=UNIT_CELSIUS,
accuracy_decimals=2,
device_class=DEVICE_CLASS_TEMPERATURE,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_PRESSURE): sensor.sensor_schema(
unit_of_measurement=UNIT_HECTOPASCAL,
accuracy_decimals=2,
device_class=DEVICE_CLASS_PRESSURE,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_TVOC): sensor.sensor_schema(
unit_of_measurement=UNIT_PARTS_PER_BILLION,
icon=ICON_RADIATOR,
accuracy_decimals=0,
state_class=STATE_CLASS_MEASUREMENT,
),
}
)
.extend(cv.polling_component_schema("5min"))
.extend(ble_client.BLE_CLIENT_SCHEMA),
CONFIG_SCHEMA = airthings_wave_base.BASE_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(AirthingsWaveMini),
}
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await ble_client.register_ble_node(var, config)
if CONF_HUMIDITY in config:
sens = await sensor.new_sensor(config[CONF_HUMIDITY])
cg.add(var.set_humidity(sens))
if CONF_TEMPERATURE in config:
sens = await sensor.new_sensor(config[CONF_TEMPERATURE])
cg.add(var.set_temperature(sens))
if CONF_PRESSURE in config:
sens = await sensor.new_sensor(config[CONF_PRESSURE])
cg.add(var.set_pressure(sens))
if CONF_TVOC in config:
sens = await sensor.new_sensor(config[CONF_TVOC])
cg.add(var.set_tvoc(sens))
await airthings_wave_base.wave_base_to_code(var, config)

View File

@@ -7,55 +7,7 @@ namespace airthings_wave_plus {
static const char *const TAG = "airthings_wave_plus";
void AirthingsWavePlus::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
esp_ble_gattc_cb_param_t *param) {
switch (event) {
case ESP_GATTC_OPEN_EVT: {
if (param->open.status == ESP_GATT_OK) {
ESP_LOGI(TAG, "Connected successfully!");
}
break;
}
case ESP_GATTC_DISCONNECT_EVT: {
ESP_LOGW(TAG, "Disconnected!");
break;
}
case ESP_GATTC_SEARCH_CMPL_EVT: {
this->handle_ = 0;
auto *chr = this->parent()->get_characteristic(service_uuid_, sensors_data_characteristic_uuid_);
if (chr == nullptr) {
ESP_LOGW(TAG, "No sensor characteristic found at service %s char %s", service_uuid_.to_string().c_str(),
sensors_data_characteristic_uuid_.to_string().c_str());
break;
}
this->handle_ = chr->handle;
this->node_state = esp32_ble_tracker::ClientState::ESTABLISHED;
request_read_values_();
break;
}
case ESP_GATTC_READ_CHAR_EVT: {
if (param->read.conn_id != this->parent()->get_conn_id())
break;
if (param->read.status != ESP_GATT_OK) {
ESP_LOGW(TAG, "Error reading char at handle %d, status=%d", param->read.handle, param->read.status);
break;
}
if (param->read.handle == this->handle_) {
read_sensors_(param->read.value, param->read.value_len);
}
break;
}
default:
break;
}
}
void AirthingsWavePlus::read_sensors_(uint8_t *raw_value, uint16_t value_len) {
void AirthingsWavePlus::read_sensors(uint8_t *raw_value, uint16_t value_len) {
auto *value = (WavePlusReadings *) raw_value;
if (sizeof(WavePlusReadings) <= value_len) {
@@ -64,72 +16,66 @@ void AirthingsWavePlus::read_sensors_(uint8_t *raw_value, uint16_t value_len) {
if (value->version == 1) {
ESP_LOGD(TAG, "ambient light = %d", value->ambientLight);
this->humidity_sensor_->publish_state(value->humidity / 2.0f);
if (is_valid_radon_value_(value->radon)) {
this->radon_sensor_->publish_state(value->radon);
}
if (is_valid_radon_value_(value->radon_lt)) {
this->radon_long_term_sensor_->publish_state(value->radon_lt);
}
this->temperature_sensor_->publish_state(value->temperature / 100.0f);
this->pressure_sensor_->publish_state(value->pressure / 50.0f);
if (is_valid_co2_value_(value->co2)) {
this->co2_sensor_->publish_state(value->co2);
}
if (is_valid_voc_value_(value->voc)) {
this->tvoc_sensor_->publish_state(value->voc);
if (this->humidity_sensor_ != nullptr) {
this->humidity_sensor_->publish_state(value->humidity / 2.0f);
}
// This instance must not stay connected
// so other clients can connect to it (e.g. the
// mobile app).
parent()->set_enabled(false);
if ((this->radon_sensor_ != nullptr) && this->is_valid_radon_value_(value->radon)) {
this->radon_sensor_->publish_state(value->radon);
}
if ((this->radon_long_term_sensor_ != nullptr) && this->is_valid_radon_value_(value->radon_lt)) {
this->radon_long_term_sensor_->publish_state(value->radon_lt);
}
if (this->temperature_sensor_ != nullptr) {
this->temperature_sensor_->publish_state(value->temperature / 100.0f);
}
if (this->pressure_sensor_ != nullptr) {
this->pressure_sensor_->publish_state(value->pressure / 50.0f);
}
if ((this->co2_sensor_ != nullptr) && this->is_valid_co2_value_(value->co2)) {
this->co2_sensor_->publish_state(value->co2);
}
if ((this->tvoc_sensor_ != nullptr) && this->is_valid_voc_value_(value->voc)) {
this->tvoc_sensor_->publish_state(value->voc);
}
} else {
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_voc_value_(uint16_t voc) { return 0 <= voc && voc <= 16383; }
bool AirthingsWavePlus::is_valid_co2_value_(uint16_t co2) { return 0 <= co2 && co2 <= 16383; }
void AirthingsWavePlus::update() {
if (this->node_state != esp32_ble_tracker::ClientState::ESTABLISHED) {
if (!parent()->enabled) {
ESP_LOGW(TAG, "Reconnecting to device");
parent()->set_enabled(true);
parent()->connect();
} else {
ESP_LOGW(TAG, "Connection in progress");
}
}
}
void AirthingsWavePlus::request_read_values_() {
auto status = esp_ble_gattc_read_char(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), this->handle_,
ESP_GATT_AUTH_REQ_NONE);
if (status) {
ESP_LOGW(TAG, "Error sending read request for sensor, status=%d", status);
}
}
bool AirthingsWavePlus::is_valid_co2_value_(uint16_t co2) { return co2 <= 16383; }
void AirthingsWavePlus::dump_config() {
// these really don't belong here, but there doesn't seem to be a
// practical way to have the base class use LOG_SENSOR and include
// the TAG from this component
LOG_SENSOR(" ", "Humidity", this->humidity_sensor_);
LOG_SENSOR(" ", "Radon", this->radon_sensor_);
LOG_SENSOR(" ", "Radon Long Term", this->radon_long_term_sensor_);
LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
LOG_SENSOR(" ", "Pressure", this->pressure_sensor_);
LOG_SENSOR(" ", "CO2", this->co2_sensor_);
LOG_SENSOR(" ", "TVOC", this->tvoc_sensor_);
LOG_SENSOR(" ", "Battery Voltage", this->battery_voltage_);
LOG_SENSOR(" ", "Radon", this->radon_sensor_);
LOG_SENSOR(" ", "Radon Long Term", this->radon_long_term_sensor_);
LOG_SENSOR(" ", "CO2", this->co2_sensor_);
}
AirthingsWavePlus::AirthingsWavePlus()
: PollingComponent(10000),
service_uuid_(esp32_ble_tracker::ESPBTUUID::from_raw(SERVICE_UUID)),
sensors_data_characteristic_uuid_(esp32_ble_tracker::ESPBTUUID::from_raw(CHARACTERISTIC_UUID)) {}
AirthingsWavePlus::AirthingsWavePlus() {
this->service_uuid_ = espbt::ESPBTUUID::from_raw(SERVICE_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 esphome

View File

@@ -2,58 +2,36 @@
#ifdef USE_ESP32
#include <esp_gattc_api.h>
#include <algorithm>
#include <iterator>
#include "esphome/components/ble_client/ble_client.h"
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/core/component.h"
#include "esphome/core/log.h"
#include "esphome/components/airthings_wave_base/airthings_wave_base.h"
namespace esphome {
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 CHARACTERISTIC_UUID = "b42e2a68-ade7-11e4-89d3-123b93f75cba";
static const char *const ACCESS_CONTROL_POINT_CHARACTERISTIC_UUID = "b42e2d06-ade7-11e4-89d3-123b93f75cba";
class AirthingsWavePlus : public PollingComponent, public ble_client::BLEClientNode {
class AirthingsWavePlus : public airthings_wave_base::AirthingsWaveBase {
public:
AirthingsWavePlus();
void dump_config() 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 set_temperature(sensor::Sensor *temperature) { temperature_sensor_ = temperature; }
void set_radon(sensor::Sensor *radon) { radon_sensor_ = radon; }
void set_radon_long_term(sensor::Sensor *radon_long_term) { radon_long_term_sensor_ = radon_long_term; }
void set_humidity(sensor::Sensor *humidity) { humidity_sensor_ = humidity; }
void set_pressure(sensor::Sensor *pressure) { pressure_sensor_ = pressure; }
void set_co2(sensor::Sensor *co2) { co2_sensor_ = co2; }
void set_tvoc(sensor::Sensor *tvoc) { tvoc_sensor_ = tvoc; }
protected:
bool is_valid_radon_value_(uint16_t radon);
bool is_valid_voc_value_(uint16_t voc);
bool is_valid_co2_value_(uint16_t co2);
void read_sensors_(uint8_t *value, uint16_t value_len);
void request_read_values_();
void read_sensors(uint8_t *raw_value, uint16_t value_len) override;
sensor::Sensor *temperature_sensor_{nullptr};
sensor::Sensor *radon_sensor_{nullptr};
sensor::Sensor *radon_long_term_sensor_{nullptr};
sensor::Sensor *humidity_sensor_{nullptr};
sensor::Sensor *pressure_sensor_{nullptr};
sensor::Sensor *co2_sensor_{nullptr};
sensor::Sensor *tvoc_sensor_{nullptr};
uint16_t handle_;
esp32_ble_tracker::ESPBTUUID service_uuid_;
esp32_ble_tracker::ESPBTUUID sensors_data_characteristic_uuid_;
struct WavePlusReadings {
uint8_t version;

View File

@@ -1,116 +1,64 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import sensor, ble_client
from esphome.components import sensor, airthings_wave_base
from esphome.const import (
DEVICE_CLASS_CARBON_DIOXIDE,
DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_TEMPERATURE,
DEVICE_CLASS_PRESSURE,
STATE_CLASS_MEASUREMENT,
UNIT_PERCENT,
UNIT_CELSIUS,
UNIT_HECTOPASCAL,
ICON_RADIOACTIVE,
CONF_ID,
CONF_RADON,
CONF_RADON_LONG_TERM,
CONF_HUMIDITY,
CONF_TVOC,
CONF_CO2,
CONF_PRESSURE,
CONF_TEMPERATURE,
UNIT_BECQUEREL_PER_CUBIC_METER,
UNIT_PARTS_PER_MILLION,
UNIT_PARTS_PER_BILLION,
ICON_RADIATOR,
)
DEPENDENCIES = ["ble_client"]
DEPENDENCIES = airthings_wave_base.DEPENDENCIES
AUTO_LOAD = ["airthings_wave_base"]
airthings_wave_plus_ns = cg.esphome_ns.namespace("airthings_wave_plus")
AirthingsWavePlus = airthings_wave_plus_ns.class_(
"AirthingsWavePlus", cg.PollingComponent, ble_client.BLEClientNode
"AirthingsWavePlus", airthings_wave_base.AirthingsWaveBase
)
CONFIG_SCHEMA = cv.All(
cv.Schema(
{
cv.GenerateID(): cv.declare_id(AirthingsWavePlus),
cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
unit_of_measurement=UNIT_PERCENT,
device_class=DEVICE_CLASS_HUMIDITY,
state_class=STATE_CLASS_MEASUREMENT,
accuracy_decimals=0,
),
cv.Optional(CONF_RADON): sensor.sensor_schema(
unit_of_measurement=UNIT_BECQUEREL_PER_CUBIC_METER,
icon=ICON_RADIOACTIVE,
accuracy_decimals=0,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_RADON_LONG_TERM): sensor.sensor_schema(
unit_of_measurement=UNIT_BECQUEREL_PER_CUBIC_METER,
icon=ICON_RADIOACTIVE,
accuracy_decimals=0,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
unit_of_measurement=UNIT_CELSIUS,
accuracy_decimals=2,
device_class=DEVICE_CLASS_TEMPERATURE,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_PRESSURE): sensor.sensor_schema(
unit_of_measurement=UNIT_HECTOPASCAL,
accuracy_decimals=1,
device_class=DEVICE_CLASS_PRESSURE,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_CO2): sensor.sensor_schema(
unit_of_measurement=UNIT_PARTS_PER_MILLION,
accuracy_decimals=0,
device_class=DEVICE_CLASS_CARBON_DIOXIDE,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_TVOC): sensor.sensor_schema(
unit_of_measurement=UNIT_PARTS_PER_BILLION,
icon=ICON_RADIATOR,
accuracy_decimals=0,
state_class=STATE_CLASS_MEASUREMENT,
),
}
)
.extend(cv.polling_component_schema("5min"))
.extend(ble_client.BLE_CLIENT_SCHEMA),
CONFIG_SCHEMA = airthings_wave_base.BASE_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(AirthingsWavePlus),
cv.Optional(CONF_RADON): sensor.sensor_schema(
unit_of_measurement=UNIT_BECQUEREL_PER_CUBIC_METER,
icon=ICON_RADIOACTIVE,
accuracy_decimals=0,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_RADON_LONG_TERM): sensor.sensor_schema(
unit_of_measurement=UNIT_BECQUEREL_PER_CUBIC_METER,
icon=ICON_RADIOACTIVE,
accuracy_decimals=0,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_CO2): sensor.sensor_schema(
unit_of_measurement=UNIT_PARTS_PER_MILLION,
accuracy_decimals=0,
device_class=DEVICE_CLASS_CARBON_DIOXIDE,
state_class=STATE_CLASS_MEASUREMENT,
),
}
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await airthings_wave_base.wave_base_to_code(var, config)
await ble_client.register_ble_node(var, config)
if CONF_HUMIDITY in config:
sens = await sensor.new_sensor(config[CONF_HUMIDITY])
cg.add(var.set_humidity(sens))
if CONF_RADON in config:
sens = await sensor.new_sensor(config[CONF_RADON])
if config_radon := config.get(CONF_RADON):
sens = await sensor.new_sensor(config_radon)
cg.add(var.set_radon(sens))
if CONF_RADON_LONG_TERM in config:
sens = await sensor.new_sensor(config[CONF_RADON_LONG_TERM])
if config_radon_long_term := config.get(CONF_RADON_LONG_TERM):
sens = await sensor.new_sensor(config_radon_long_term)
cg.add(var.set_radon_long_term(sens))
if CONF_TEMPERATURE in config:
sens = await sensor.new_sensor(config[CONF_TEMPERATURE])
cg.add(var.set_temperature(sens))
if CONF_PRESSURE in config:
sens = await sensor.new_sensor(config[CONF_PRESSURE])
cg.add(var.set_pressure(sens))
if CONF_CO2 in config:
sens = await sensor.new_sensor(config[CONF_CO2])
if config_co2 := config.get(CONF_CO2):
sens = await sensor.new_sensor(config_co2)
cg.add(var.set_co2(sens))
if CONF_TVOC in config:
sens = await sensor.new_sensor(config[CONF_TVOC])
cg.add(var.set_tvoc(sens))

View File

@@ -16,6 +16,12 @@ IS_PLATFORM_COMPONENT = True
CONF_ON_TRIGGERED = "on_triggered"
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")
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", 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)
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)
PendingAction = alarm_control_panel_ns.class_("PendingAction", 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.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.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, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
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, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
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):
paren = await cg.get_variable(config[CONF_ID])
var = cg.new_Pvariable(action_id, template_arg, paren)
if CONF_CODE in config:
templatable_ = await cg.templatable(config[CONF_CODE], args, cg.std_string)
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
@@ -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
)
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])
var = cg.new_Pvariable(action_id, template_arg, paren)
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):
paren = await cg.get_variable(config[CONF_ID])
var = cg.new_Pvariable(action_id, template_arg, paren)
if CONF_CODE in config:
templatable_ = await cg.templatable(config[CONF_CODE], args, cg.std_string)
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

View File

@@ -36,7 +36,20 @@ void AlarmControlPanel::publish_state(AlarmControlPanelState state) {
this->state_callback_.call();
if (state == ACP_STATE_TRIGGERED) {
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) {
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));
}
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) {
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);
/** 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
*
* @param callback The callback function
@@ -128,6 +164,18 @@ class AlarmControlPanel : public EntityBase {
CallbackManager<void()> state_callback_{};
// trigger 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
CallbackManager<void()> cleared_callback_{};
};

View File

@@ -85,6 +85,11 @@ void AlarmControlPanelCall::validate_() {
this->state_.reset();
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:
return LOG_STR("ARMED_AWAY");
case ACP_STATE_ARMED_NIGHT:
return LOG_STR("NIGHT");
return LOG_STR("ARMED_NIGHT");
case ACP_STATE_ARMED_VACATION:
return LOG_STR("ARMED_VACATION");
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<> {
public:
explicit ClearedTrigger(AlarmControlPanel *alarm_control_panel) {
@@ -67,6 +109,26 @@ template<typename... Ts> class ArmHomeAction : public Action<Ts...> {
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...> {
public:
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 i2c.register_i2c_device(var, config)
if CONF_TEMPERATURE in config:
sens = await sensor.new_sensor(config[CONF_TEMPERATURE])
if temperature_config := config.get(CONF_TEMPERATURE):
sens = await sensor.new_sensor(temperature_config)
cg.add(var.set_temperature_sensor(sens))
if CONF_HUMIDITY in config:
sens = await sensor.new_sensor(config[CONF_HUMIDITY])
if humidity_config := config.get(CONF_HUMIDITY):
sens = await sensor.new_sensor(humidity_config)
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 ble_client.register_ble_node(var, config)
if CONF_BATTERY_LEVEL in config:
sens = await sensor.new_sensor(config[CONF_BATTERY_LEVEL])
if battery_level_config := config.get(CONF_BATTERY_LEVEL):
sens = await sensor.new_sensor(battery_level_config)
cg.add(var.set_battery(sens))
if CONF_ILLUMINANCE in config:
sens = await sensor.new_sensor(config[CONF_ILLUMINANCE])
if illuminance_config := config.get(CONF_ILLUMINANCE):
sens = await sensor.new_sensor(illuminance_config)
cg.add(var.set_illuminance(sens))

View File

@@ -1,7 +1,7 @@
import logging
from esphome import core
from esphome.components import display, font
from esphome import automation, core
from esphome.components import font
import esphome.components.image as espImage
from esphome.components.image import CONF_USE_TRANSPARENCY
import esphome.config_validation as cv
@@ -18,14 +18,30 @@ from esphome.core import CORE, HexInt
_LOGGER = logging.getLogger(__name__)
AUTO_LOAD = ["image"]
CODEOWNERS = ["@syndlex"]
DEPENDENCIES = ["display"]
MULTI_CONF = True
CONF_LOOP = "loop"
CONF_START_FRAME = "start_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):
@@ -74,7 +90,35 @@ ANIMATION_SCHEMA = cv.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):
@@ -245,8 +289,8 @@ async def to_code(config):
espImage.IMAGE_TYPE[config[CONF_TYPE]],
)
cg.add(var.set_transparency(transparent))
if CONF_LOOP in config:
start = config[CONF_LOOP][CONF_START_FRAME]
end = config[CONF_LOOP].get(CONF_END_FRAME, frames)
count = config[CONF_LOOP].get(CONF_REPEAT, -1)
if loop_config := config.get(CONF_LOOP):
start = loop_config[CONF_START_FRAME]
end = loop_config.get(CONF_END_FRAME, frames)
count = loop_config.get(CONF_REPEAT, -1)
cg.add(var.set_loop(start, end, count))

View File

@@ -3,9 +3,10 @@
#include "esphome/core/hal.h"
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),
animation_data_start_(data_start),
current_frame_(0),
@@ -65,5 +66,5 @@ void Animation::update_data_start_() {
this->data_start_ = this->animation_data_start_ + image_size * this->current_frame_;
}
} // namespace display
} // namespace animation
} // 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))
await automation.build_automation(trigger, func_args, conf)
if CONF_ENCRYPTION in config:
conf = config[CONF_ENCRYPTION]
decoded = base64.b64decode(conf[CONF_KEY])
if encryption_config := config.get(CONF_ENCRYPTION):
decoded = base64.b64decode(encryption_config[CONF_KEY])
cg.add(var.set_noise_psk(list(decoded)))
cg.add_define("USE_API_NOISE")
cg.add_library("esphome/noise-c", "0.1.4")

View File

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

View File

@@ -907,12 +907,13 @@ BluetoothConnectionsFreeResponse APIConnection::subscribe_bluetooth_connections_
#endif
#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_)
return false;
VoiceAssistantRequest msg;
msg.start = start;
msg.conversation_id = conversation_id;
msg.use_vad = use_vad;
return this->send_voice_assistant_request(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 {
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_event_response(const VoiceAssistantEventResponse &msg) override;
#endif

View File

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

View File

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

View File

@@ -323,16 +323,16 @@ void APIServer::on_shutdown() {
}
#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_) {
if (c->request_voice_assistant(true, conversation_id))
if (c->request_voice_assistant(true, conversation_id, use_vad))
return true;
}
return false;
}
void APIServer::stop_voice_assistant() {
for (auto &c : this->clients_) {
if (c->request_voice_assistant(false, ""))
if (c->request_voice_assistant(false, "", false))
return;
}
}

View File

@@ -81,7 +81,7 @@ class APIServer : public Component, public Controller {
#endif
#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();
#endif

View File

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

View File

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

View File

@@ -107,6 +107,6 @@ async def to_code(config):
cg.add(var.set_astep(config[CONF_ASTEP]))
for conf_id, set_sensor_func in SENSORS.items():
if conf_id in config:
sens = await sensor.new_sensor(config[conf_id])
if sens_config := config.get(conf_id):
sens = await sensor.new_sensor(sens_config)
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))
if CONF_TEMPERATURE in config:
sens = await sensor.new_sensor(config[CONF_TEMPERATURE])
if temperature_config := config.get(CONF_TEMPERATURE):
sens = await sensor.new_sensor(temperature_config)
cg.add(var.set_temperature(sens))
if CONF_HUMIDITY in config:
sens = await sensor.new_sensor(config[CONF_HUMIDITY])
if humidity_config := config.get(CONF_HUMIDITY):
sens = await sensor.new_sensor(humidity_config)
cg.add(var.set_humidity(sens))
if CONF_BATTERY_LEVEL in config:
sens = await sensor.new_sensor(config[CONF_BATTERY_LEVEL])
if battery_level_config := config.get(CONF_BATTERY_LEVEL):
sens = await sensor.new_sensor(battery_level_config)
cg.add(var.set_battery_level(sens))
if CONF_BATTERY_VOLTAGE in config:
sens = await sensor.new_sensor(config[CONF_BATTERY_VOLTAGE])
if battery_voltage_config := config.get(CONF_BATTERY_VOLTAGE):
sens = await sensor.new_sensor(battery_voltage_config)
cg.add(var.set_battery_voltage(sens))
if CONF_SIGNAL_STRENGTH in config:
sens = await sensor.new_sensor(config[CONF_SIGNAL_STRENGTH])
if signal_strength_config := config.get(CONF_SIGNAL_STRENGTH):
sens = await sensor.new_sensor(signal_strength_config)
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]
cg.add(var.set_volt_gain(i, conf[CONF_GAIN_VOLTAGE]))
cg.add(var.set_ct_gain(i, conf[CONF_GAIN_CT]))
if CONF_VOLTAGE in conf:
sens = await sensor.new_sensor(conf[CONF_VOLTAGE])
if voltage_config := conf.get(CONF_VOLTAGE):
sens = await sensor.new_sensor(voltage_config)
cg.add(var.set_voltage_sensor(i, sens))
if CONF_CURRENT in conf:
sens = await sensor.new_sensor(conf[CONF_CURRENT])
if current_config := conf.get(CONF_CURRENT):
sens = await sensor.new_sensor(current_config)
cg.add(var.set_current_sensor(i, sens))
if CONF_POWER in conf:
sens = await sensor.new_sensor(conf[CONF_POWER])
if power_config := conf.get(CONF_POWER):
sens = await sensor.new_sensor(power_config)
cg.add(var.set_power_sensor(i, sens))
if CONF_REACTIVE_POWER in conf:
sens = await sensor.new_sensor(conf[CONF_REACTIVE_POWER])
if reactive_power_config := conf.get(CONF_REACTIVE_POWER):
sens = await sensor.new_sensor(reactive_power_config)
cg.add(var.set_reactive_power_sensor(i, sens))
if CONF_POWER_FACTOR in conf:
sens = await sensor.new_sensor(conf[CONF_POWER_FACTOR])
if power_factor_config := conf.get(CONF_POWER_FACTOR):
sens = await sensor.new_sensor(power_factor_config)
cg.add(var.set_power_factor_sensor(i, sens))
if CONF_FORWARD_ACTIVE_ENERGY in conf:
sens = await sensor.new_sensor(conf[CONF_FORWARD_ACTIVE_ENERGY])
if forward_active_energy_config := conf.get(CONF_FORWARD_ACTIVE_ENERGY):
sens = await sensor.new_sensor(forward_active_energy_config)
cg.add(var.set_forward_active_energy_sensor(i, sens))
if CONF_REVERSE_ACTIVE_ENERGY in conf:
sens = await sensor.new_sensor(conf[CONF_REVERSE_ACTIVE_ENERGY])
if reverse_active_energy_config := conf.get(CONF_REVERSE_ACTIVE_ENERGY):
sens = await sensor.new_sensor(reverse_active_energy_config)
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))
if CONF_CHIP_TEMPERATURE in config:
sens = await sensor.new_sensor(config[CONF_CHIP_TEMPERATURE])
if chip_temperature_config := config.get(CONF_CHIP_TEMPERATURE):
sens = await sensor.new_sensor(chip_temperature_config)
cg.add(var.set_chip_temperature_sensor(sens))
cg.add(var.set_line_freq(config[CONF_LINE_FREQUENCY]))
cg.add(var.set_current_phases(config[CONF_CURRENT_PHASES]))
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_ILLUMINANCE, var.set_illuminance),
]:
if config_key in config:
sens = await sensor.new_sensor(config[config_key])
if sensor_config := config.get(config_key):
sens = await sensor.new_sensor(sensor_config)
cg.add(setter(sens))

View File

@@ -57,19 +57,18 @@ async def to_code(config):
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(
var.get_cool_trigger(), [], config[CONF_COOL_ACTION]
var.get_cool_trigger(), [], cool_action_config
)
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(
var.get_heat_trigger(), [], config[CONF_HEAT_ACTION]
var.get_heat_trigger(), [], heat_action_config
)
cg.add(var.set_supports_heat(True))
if CONF_AWAY_CONFIG in config:
away = config[CONF_AWAY_CONFIG]
if away := config.get(CONF_AWAY_CONFIG):
away_config = BangBangClimateTargetTempConfig(
away[CONF_DEFAULT_TARGET_TEMPERATURE_LOW],
away[CONF_DEFAULT_TARGET_TEMPERATURE_HIGH],

View File

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

View File

@@ -3,6 +3,7 @@
#include "bedjet_hub.h"
#include "bedjet_child.h"
#include "bedjet_const.h"
#include <cinttypes>
namespace esphome {
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_) {
// Set reentrant flag to prevent processing multiple packets.
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");
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());
} 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) {
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.
this->parent()->set_state(espbt::ClientState::CONNECTING);
this->parent()->set_enabled(false);

View File

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

View File

@@ -95,6 +95,14 @@ DEVICE_CLASSES = [
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")
BinarySensor = binary_sensor_ns.class_("BinarySensor", cg.EntityBase)
BinarySensorInitiallyOff = binary_sensor_ns.class_(
@@ -138,47 +146,75 @@ FILTER_REGISTRY = 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):
return cg.new_Pvariable(filter_id)
@FILTER_REGISTRY.register(
"delayed_on_off", DelayedOnOffFilter, cv.positive_time_period_milliseconds
@register_filter(
"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):
var = cg.new_Pvariable(filter_id, config)
var = cg.new_Pvariable(filter_id)
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
@FILTER_REGISTRY.register(
"delayed_on", DelayedOnFilter, cv.positive_time_period_milliseconds
@register_filter(
"delayed_on", DelayedOnFilter, cv.templatable(cv.positive_time_period_milliseconds)
)
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, {})
template_ = await cg.templatable(config, [], cg.uint32)
cg.add(var.set_delay(template_))
return var
@FILTER_REGISTRY.register(
"delayed_off", DelayedOffFilter, cv.positive_time_period_milliseconds
@register_filter(
"delayed_off",
DelayedOffFilter,
cv.templatable(cv.positive_time_period_milliseconds),
)
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, {})
template_ = await cg.templatable(config, [], cg.uint32)
cg.add(var.set_delay(template_))
return var
CONF_TIME_OFF = "time_off"
CONF_TIME_ON = "time_on"
DEFAULT_DELAY = "1s"
DEFAULT_TIME_OFF = "100ms"
DEFAULT_TIME_ON = "900ms"
@FILTER_REGISTRY.register(
@register_filter(
"autorepeat",
AutorepeatFilter,
cv.All(
@@ -215,7 +251,7 @@ async def autorepeat_filter_to_code(config, filter_id):
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):
lambda_ = await cg.process_lambda(
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="_")
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(
{
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.Optional(CONF_ON_CLICK): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ClickTrigger),
cv.Optional(
CONF_MIN_LENGTH, default="50ms"
): cv.positive_time_period_milliseconds,
cv.Optional(
CONF_MAX_LENGTH, default="350ms"
): cv.positive_time_period_milliseconds,
}
cv.Optional(CONF_ON_CLICK): cv.All(
automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ClickTrigger),
cv.Optional(
CONF_MIN_LENGTH, default="50ms"
): cv.positive_time_period_milliseconds,
cv.Optional(
CONF_MAX_LENGTH, default="350ms"
): cv.positive_time_period_milliseconds,
}
),
validate_click_timing,
),
cv.Optional(CONF_ON_DOUBLE_CLICK): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(DoubleClickTrigger),
cv.Optional(
CONF_MIN_LENGTH, default="50ms"
): cv.positive_time_period_milliseconds,
cv.Optional(
CONF_MAX_LENGTH, default="350ms"
): cv.positive_time_period_milliseconds,
}
cv.Optional(CONF_ON_DOUBLE_CLICK): cv.All(
automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(DoubleClickTrigger),
cv.Optional(
CONF_MIN_LENGTH, default="50ms"
): cv.positive_time_period_milliseconds,
cv.Optional(
CONF_MAX_LENGTH, default="350ms"
): cv.positive_time_period_milliseconds,
}
),
validate_click_timing,
),
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):
await setup_entity(var, config)
if CONF_DEVICE_CLASS in config:
cg.add(var.set_device_class(config[CONF_DEVICE_CLASS]))
if CONF_PUBLISH_INITIAL_STATE in config:
cg.add(var.set_publish_initial_state(config[CONF_PUBLISH_INITIAL_STATE]))
if CONF_INVERTED in config:
cg.add(var.set_inverted(config[CONF_INVERTED]))
if CONF_FILTERS in config:
filters = await cg.build_registry_list(FILTER_REGISTRY, config[CONF_FILTERS])
if (device_class := config.get(CONF_DEVICE_CLASS)) is not None:
cg.add(var.set_device_class(device_class))
if publish_initial_state := config.get(CONF_PUBLISH_INITIAL_STATE):
cg.add(var.set_publish_initial_state(publish_initial_state))
if inverted := config.get(CONF_INVERTED):
cg.add(var.set_inverted(inverted))
if filters_config := config.get(CONF_FILTERS):
filters = await cg.build_registry_list(FILTER_REGISTRY, filters_config)
cg.add(var.add_filters(filters))
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)
await automation.build_automation(trigger, [(bool, "x")], conf)
if CONF_MQTT_ID in config:
mqtt_ = cg.new_Pvariable(config[CONF_MQTT_ID], var)
if mqtt_id := config.get(CONF_MQTT_ID):
mqtt_ = cg.new_Pvariable(mqtt_id, var)
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) {
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 {
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 {};
}
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) {
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 {};
} else {
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; }
DelayedOffFilter::DelayedOffFilter(uint32_t delay) : delay_(delay) {}
optional<bool> DelayedOffFilter::new_value(bool value, bool is_initial) {
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 {};
} else {
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> 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 esphome

View File

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

View File

@@ -93,35 +93,27 @@ async def to_code(config):
await cg.register_component(var, config)
await uart.register_uart_device(var, config)
if CONF_VOLTAGE in config:
conf = config[CONF_VOLTAGE]
sens = await sensor.new_sensor(conf)
if voltage_config := config.get(CONF_VOLTAGE):
sens = await sensor.new_sensor(voltage_config)
cg.add(var.set_voltage_sensor(sens))
if CONF_CURRENT_1 in config:
conf = config[CONF_CURRENT_1]
sens = await sensor.new_sensor(conf)
if current_1_config := config.get(CONF_CURRENT_1):
sens = await sensor.new_sensor(current_1_config)
cg.add(var.set_current_sensor_1(sens))
if CONF_CURRENT_2 in config:
conf = config[CONF_CURRENT_2]
sens = await sensor.new_sensor(conf)
if current_2_config := config.get(CONF_CURRENT_2):
sens = await sensor.new_sensor(current_2_config)
cg.add(var.set_current_sensor_2(sens))
if CONF_ACTIVE_POWER_1 in config:
conf = config[CONF_ACTIVE_POWER_1]
sens = await sensor.new_sensor(conf)
if active_power_1_config := config.get(CONF_ACTIVE_POWER_1):
sens = await sensor.new_sensor(active_power_1_config)
cg.add(var.set_power_sensor_1(sens))
if CONF_ACTIVE_POWER_2 in config:
conf = config[CONF_ACTIVE_POWER_2]
sens = await sensor.new_sensor(conf)
if active_power_2_config := config.get(CONF_ACTIVE_POWER_2):
sens = await sensor.new_sensor(active_power_2_config)
cg.add(var.set_power_sensor_2(sens))
if CONF_ENERGY_1 in config:
conf = config[CONF_ENERGY_1]
sens = await sensor.new_sensor(conf)
if energy_1_config := config.get(CONF_ENERGY_1):
sens = await sensor.new_sensor(energy_1_config)
cg.add(var.set_energy_sensor_1(sens))
if CONF_ENERGY_2 in config:
conf = config[CONF_ENERGY_2]
sens = await sensor.new_sensor(conf)
if energy_2_config := config.get(CONF_ENERGY_2):
sens = await sensor.new_sensor(energy_2_config)
cg.add(var.set_energy_sensor_2(sens))
if CONF_ENERGY_TOTAL in config:
conf = config[CONF_ENERGY_TOTAL]
sens = await sensor.new_sensor(conf)
if energy_total_config := config.get(CONF_ENERGY_TOTAL):
sens = await sensor.new_sensor(energy_total_config)
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 uart.register_uart_device(var, config)
if CONF_VOLTAGE in config:
conf = config[CONF_VOLTAGE]
sens = await sensor.new_sensor(conf)
if voltage_config := config.get(CONF_VOLTAGE):
sens = await sensor.new_sensor(voltage_config)
cg.add(var.set_voltage_sensor(sens))
if CONF_CURRENT in config:
conf = config[CONF_CURRENT]
sens = await sensor.new_sensor(conf)
if current_config := config.get(CONF_CURRENT):
sens = await sensor.new_sensor(current_config)
cg.add(var.set_current_sensor(sens))
if CONF_POWER in config:
conf = config[CONF_POWER]
sens = await sensor.new_sensor(conf)
if power_config := config.get(CONF_POWER):
sens = await sensor.new_sensor(power_config)
cg.add(var.set_power_sensor(sens))
if CONF_ENERGY in config:
conf = config[CONF_ENERGY]
sens = await sensor.new_sensor(conf)
if energy_config := config.get(CONF_ENERGY):
sens = await sensor.new_sensor(energy_config)
cg.add(var.set_energy_sensor(sens))
if CONF_INTERNAL_TEMPERATURE in config:
conf = config[CONF_INTERNAL_TEMPERATURE]
sens = await sensor.new_sensor(conf)
if internal_temperature_config := config.get(CONF_INTERNAL_TEMPERATURE):
sens = await sensor.new_sensor(internal_temperature_config)
cg.add(var.set_internal_temperature_sensor(sens))
if CONF_EXTERNAL_TEMPERATURE in config:
conf = config[CONF_EXTERNAL_TEMPERATURE]
sens = await sensor.new_sensor(conf)
if external_temperature_config := config.get(CONF_EXTERNAL_TEMPERATURE):
sens = await sensor.new_sensor(external_temperature_config)
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 uart.register_uart_device(var, config)
if CONF_VOLTAGE in config:
conf = config[CONF_VOLTAGE]
sens = await sensor.new_sensor(conf)
if voltage_config := config.get(CONF_VOLTAGE):
sens = await sensor.new_sensor(voltage_config)
cg.add(var.set_voltage_sensor(sens))
if CONF_CURRENT in config:
conf = config[CONF_CURRENT]
sens = await sensor.new_sensor(conf)
if current_config := config.get(CONF_CURRENT):
sens = await sensor.new_sensor(current_config)
cg.add(var.set_current_sensor(sens))
if CONF_POWER in config:
conf = config[CONF_POWER]
sens = await sensor.new_sensor(conf)
if power_config := config.get(CONF_POWER):
sens = await sensor.new_sensor(power_config)
cg.add(var.set_power_sensor(sens))
if CONF_ENERGY in config:
conf = config[CONF_ENERGY]
sens = await sensor.new_sensor(conf)
if energy_config := config.get(CONF_ENERGY):
sens = await sensor.new_sensor(energy_config)
cg.add(var.set_energy_sensor(sens))
if CONF_FREQUENCY in config:
conf = config[CONF_FREQUENCY]
sens = await sensor.new_sensor(conf)
if frequency_config := config.get(CONF_FREQUENCY):
sens = await sensor.new_sensor(frequency_config)
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))
if CONF_DESCRIPTOR_UUID in config:
if len(config[CONF_DESCRIPTOR_UUID]) == len(esp32_ble_tracker.bt_uuid16_format):
cg.add(
var.set_descr_uuid16(
esp32_ble_tracker.as_hex(config[CONF_DESCRIPTOR_UUID])
)
)
elif len(config[CONF_DESCRIPTOR_UUID]) == len(
esp32_ble_tracker.bt_uuid32_format
):
cg.add(
var.set_descr_uuid32(
esp32_ble_tracker.as_hex(config[CONF_DESCRIPTOR_UUID])
)
)
elif len(config[CONF_DESCRIPTOR_UUID]) == len(
esp32_ble_tracker.bt_uuid128_format
):
uuid128 = esp32_ble_tracker.as_reversed_hex_array(
config[CONF_DESCRIPTOR_UUID]
)
if descriptor_uuid := config.get(CONF_DESCRIPTOR_UUID):
if len(descriptor_uuid) == len(esp32_ble_tracker.bt_uuid16_format):
cg.add(var.set_descr_uuid16(esp32_ble_tracker.as_hex(descriptor_uuid)))
elif len(descriptor_uuid) == len(esp32_ble_tracker.bt_uuid32_format):
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)
cg.add(var.set_descr_uuid128(uuid128))
if CONF_LAMBDA in config:
if lambda_config := config.get(CONF_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_))

View File

@@ -88,27 +88,13 @@ async def to_code(config):
)
cg.add(var.set_char_uuid128(uuid128))
if CONF_DESCRIPTOR_UUID in config:
if len(config[CONF_DESCRIPTOR_UUID]) == len(esp32_ble_tracker.bt_uuid16_format):
cg.add(
var.set_descr_uuid16(
esp32_ble_tracker.as_hex(config[CONF_DESCRIPTOR_UUID])
)
)
elif len(config[CONF_DESCRIPTOR_UUID]) == len(
esp32_ble_tracker.bt_uuid32_format
):
cg.add(
var.set_descr_uuid32(
esp32_ble_tracker.as_hex(config[CONF_DESCRIPTOR_UUID])
)
)
elif len(config[CONF_DESCRIPTOR_UUID]) == len(
esp32_ble_tracker.bt_uuid128_format
):
uuid128 = esp32_ble_tracker.as_reversed_hex_array(
config[CONF_DESCRIPTOR_UUID]
)
if descriptor_uuid := config:
if len(descriptor_uuid) == len(esp32_ble_tracker.bt_uuid16_format):
cg.add(var.set_descr_uuid16(esp32_ble_tracker.as_hex(descriptor_uuid)))
elif len(descriptor_uuid) == len(esp32_ble_tracker.bt_uuid32_format):
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)
cg.add(var.set_descr_uuid128(uuid128))
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_UUID): cv.uuid,
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 esp32_ble_tracker.register_ble_device(var, config)
if CONF_MIN_RSSI in config:
cg.add(var.set_minimum_rssi(config[CONF_MIN_RSSI]))
if min_rssi := config.get(CONF_MIN_RSSI):
cg.add(var.set_minimum_rssi(min_rssi))
if CONF_MAC_ADDRESS in config:
cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex))
if mac_address := config.get(CONF_MAC_ADDRESS):
cg.add(var.set_address(mac_address.as_hex))
if CONF_SERVICE_UUID in config:
if len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid16_format):
cg.add(
var.set_service_uuid16(
esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID])
)
)
elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid32_format):
cg.add(
var.set_service_uuid32(
esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID])
)
)
elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid128_format):
uuid128 = esp32_ble_tracker.as_reversed_hex_array(config[CONF_SERVICE_UUID])
if service_uuid := config.get(CONF_SERVICE_UUID):
if len(service_uuid) == len(esp32_ble_tracker.bt_uuid16_format):
cg.add(var.set_service_uuid16(esp32_ble_tracker.as_hex(service_uuid)))
elif len(service_uuid) == len(esp32_ble_tracker.bt_uuid32_format):
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)
cg.add(var.set_service_uuid128(uuid128))
if CONF_IBEACON_UUID in config:
ibeacon_uuid = esp32_ble_tracker.as_hex_array(str(config[CONF_IBEACON_UUID]))
if ibeacon_uuid := config.get(CONF_IBEACON_UUID):
ibeacon_uuid = esp32_ble_tracker.as_hex_array(str(ibeacon_uuid))
cg.add(var.set_ibeacon_uuid(ibeacon_uuid))
if CONF_IBEACON_MAJOR in config:
cg.add(var.set_ibeacon_major(config[CONF_IBEACON_MAJOR]))
if (ibeacon_major := config.get(CONF_IBEACON_MAJOR)) is not None:
cg.add(var.set_ibeacon_major(ibeacon_major))
if CONF_IBEACON_MINOR in config:
cg.add(var.set_ibeacon_minor(config[CONF_IBEACON_MINOR]))
if (ibeacon_minor := config.get(CONF_IBEACON_MINOR)) is not None:
cg.add(var.set_ibeacon_minor(ibeacon_minor))

View File

@@ -51,7 +51,7 @@ class BLEPresenceDevice : public binary_sensor::BinarySensorInitiallyOff,
this->found_ = false;
}
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;
}
switch (this->match_by_) {

View File

@@ -57,32 +57,24 @@ async def to_code(config):
await cg.register_component(var, config)
await esp32_ble_tracker.register_ble_device(var, config)
if CONF_MAC_ADDRESS in config:
cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex))
if mac_address := config.get(CONF_MAC_ADDRESS):
cg.add(var.set_address(mac_address.as_hex))
if CONF_SERVICE_UUID in config:
if len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid16_format):
cg.add(
var.set_service_uuid16(
esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID])
)
)
elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid32_format):
cg.add(
var.set_service_uuid32(
esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID])
)
)
elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid128_format):
uuid128 = esp32_ble_tracker.as_reversed_hex_array(config[CONF_SERVICE_UUID])
if service_uuid := config.get(CONF_SERVICE_UUID):
if len(service_uuid) == len(esp32_ble_tracker.bt_uuid16_format):
cg.add(var.set_service_uuid16(esp32_ble_tracker.as_hex(service_uuid)))
elif len(service_uuid) == len(esp32_ble_tracker.bt_uuid32_format):
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)
cg.add(var.set_service_uuid128(uuid128))
if CONF_IBEACON_UUID in config:
ibeacon_uuid = esp32_ble_tracker.as_hex_array(str(config[CONF_IBEACON_UUID]))
if ibeacon_uuid := config.get(CONF_IBEACON_UUID):
ibeacon_uuid = esp32_ble_tracker.as_hex_array(str(ibeacon_uuid))
cg.add(var.set_ibeacon_uuid(ibeacon_uuid))
if CONF_IBEACON_MAJOR in config:
cg.add(var.set_ibeacon_major(config[CONF_IBEACON_MAJOR]))
if (ibeacon_major := config.get(CONF_IBEACON_MAJOR)) is not None:
cg.add(var.set_ibeacon_major(ibeacon_major))
if CONF_IBEACON_MINOR in config:
cg.add(var.set_ibeacon_minor(config[CONF_IBEACON_MINOR]))
if (ibeacon_minor := config.get(CONF_IBEACON_MINOR)) is not None:
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;
}
esp32_ble_tracker::AdvertisementParserType BluetoothConnection::get_advertisement_parser_type() {
return this->proxy_->get_advertisement_parser_type();
}
} // namespace bluetooth_proxy
} // 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,
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;
esp32_ble_tracker::AdvertisementParserType get_advertisement_parser_type() override;
esp_err_t read_characteristic(uint16_t handle);
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) {
for (auto *connection : this->connections_) {
if (connection->get_address() == address)
@@ -435,6 +441,7 @@ void BluetoothProxy::subscribe_api_connection(api::APIConnection *api_connection
}
this->api_connection_ = api_connection;
this->raw_advertisements_ = flags & BluetoothProxySubscriptionFlag::SUBSCRIPTION_RAW_ADVERTISEMENTS;
this->parent_->recalculate_advertisement_parser_types();
}
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->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) {

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;
void dump_config() override;
void loop() override;
esp32_ble_tracker::AdvertisementParserType get_advertisement_parser_type() override;
void register_connection(BluetoothConnection *connection) {
this->connections_.push_back(connection);

View File

@@ -98,22 +98,19 @@ async def to_code(config):
await cg.register_component(var, config)
await i2c.register_i2c_device(var, config)
if CONF_TEMPERATURE in config:
conf = config[CONF_TEMPERATURE]
sens = await sensor.new_sensor(conf)
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(conf[CONF_OVERSAMPLING]))
cg.add(var.set_temperature_oversampling(temperature_config[CONF_OVERSAMPLING]))
if CONF_PRESSURE in config:
conf = config[CONF_PRESSURE]
sens = await sensor.new_sensor(conf)
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(conf[CONF_OVERSAMPLING]))
cg.add(var.set_pressure_oversampling(pressure_config[CONF_OVERSAMPLING]))
if CONF_HUMIDITY in config:
conf = config[CONF_HUMIDITY]
sens = await sensor.new_sensor(conf)
if humidity_config := config.get(CONF_HUMIDITY):
sens = await sensor.new_sensor(humidity_config)
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]))

View File

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

View File

@@ -21,9 +21,8 @@ CONFIG_SCHEMA = cv.Schema(
async def setup_conf(config, key, hub):
if key in config:
conf = config[key]
sens = await text_sensor.new_text_sensor(conf)
if sensor_config := config.get(key):
sens = await text_sensor.new_text_sensor(sensor_config)
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 i2c.register_i2c_device(var, config)
if CONF_TEMPERATURE in config:
conf = config[CONF_TEMPERATURE]
sens = await sensor.new_sensor(conf)
if temperature_config := config.get(CONF_TEMPERATURE):
sens = await sensor.new_sensor(temperature_config)
cg.add(var.set_temperature(sens))
if CONF_PRESSURE in config:
conf = config[CONF_PRESSURE]
sens = await sensor.new_sensor(conf)
if pressure_config := config.get(CONF_PRESSURE):
sens = await sensor.new_sensor(pressure_config)
cg.add(var.set_pressure(sens))

View File

@@ -83,14 +83,14 @@ async def to_code(config):
await cg.register_component(var, config)
await i2c.register_i2c_device(var, config)
if CONF_TEMPERATURE in config:
conf = config[CONF_TEMPERATURE]
sens = await sensor.new_sensor(conf)
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(conf[CONF_OVERSAMPLING]))
cg.add(var.set_temperature_oversampling(temperature_config[CONF_OVERSAMPLING]))
if CONF_PRESSURE in config:
conf = config[CONF_PRESSURE]
sens = await sensor.new_sensor(conf)
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(conf[CONF_OVERSAMPLING]))
cg.add(var.set_pressure_oversampling(pressure_config[CONF_OVERSAMPLING]))
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 i2c.register_i2c_device(var, config)
cg.add(var.set_iir_filter_config(config[CONF_IIR_FILTER]))
if CONF_TEMPERATURE in config:
conf = config[CONF_TEMPERATURE]
sens = await sensor.new_sensor(conf)
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(conf[CONF_OVERSAMPLING]))
cg.add(
var.set_temperature_oversampling_config(
temperature_config[CONF_OVERSAMPLING]
)
)
if CONF_PRESSURE in config:
conf = config[CONF_PRESSURE]
sens = await sensor.new_sensor(conf)
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(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

@@ -12,6 +12,7 @@ from esphome.const import (
CONF_TRIGGER_ID,
CONF_MQTT_ID,
DEVICE_CLASS_EMPTY,
DEVICE_CLASS_IDENTIFY,
DEVICE_CLASS_RESTART,
DEVICE_CLASS_UPDATE,
)
@@ -24,6 +25,7 @@ IS_PLATFORM_COMPONENT = True
DEVICE_CLASSES = [
DEVICE_CLASS_EMPTY,
DEVICE_CLASS_IDENTIFY,
DEVICE_CLASS_RESTART,
DEVICE_CLASS_UPDATE,
]
@@ -83,11 +85,11 @@ async def setup_button_core_(var, config):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)
if CONF_DEVICE_CLASS in config:
cg.add(var.set_device_class(config[CONF_DEVICE_CLASS]))
if device_class := config.get(CONF_DEVICE_CLASS):
cg.add(var.set_device_class(device_class))
if CONF_MQTT_ID in config:
mqtt_ = cg.new_Pvariable(config[CONF_MQTT_ID], var)
if mqtt_id := config.get(CONF_MQTT_ID):
mqtt_ = cg.new_Pvariable(mqtt_id, var)
await mqtt.register_mqtt_component(mqtt_, config)

View File

@@ -17,12 +17,11 @@ CONF_ON_FRAME = "on_frame"
def validate_id(config):
if CONF_CAN_ID in config:
id_value = config[CONF_CAN_ID]
id_ext = config[CONF_USE_EXTENDED_ID]
if not id_ext:
if id_value > 0x7FF:
raise cv.Invalid("Standard IDs must be 11 Bit (0x000-0x7ff / 0-2047)")
can_id = config[CONF_CAN_ID]
id_ext = config[CONF_USE_EXTENDED_ID]
if not id_ext:
if can_id > 0x7FF:
raise cv.Invalid("Standard IDs must be 11 Bit (0x000-0x7ff / 0-2047)")
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)
await cg.register_parented(var, config[CONF_CANBUS_ID])
if CONF_CAN_ID in config:
can_id = await cg.templatable(config[CONF_CAN_ID], args, cg.uint32)
if can_id := config.get(CONF_CAN_ID):
can_id = await cg.templatable(can_id, args, cg.uint32)
cg.add(var.set_can_id(can_id))
use_extended_id = await cg.templatable(
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) {
ESP_LOGD(TAG, "send extended id=0x%08x rtr=%s size=%d", can_id, TRUEFALSE(remote_transmission_request), size);
} 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)
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_allow_multiple_touches(config[CONF_ALLOW_MULTIPLE_TOUCHES]))
if CONF_RESET_PIN in config:
pin = await cg.gpio_pin_expression(config[CONF_RESET_PIN])
if reset_pin_config := config.get(CONF_RESET_PIN):
pin = await cg.gpio_pin_expression(reset_pin_config)
cg.add(var.set_reset_pin(pin))
await cg.register_component(var, config)

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