Compare commits

...

257 Commits

Author SHA1 Message Date
Jesse Hills
dc0ed8857f Merge pull request #3198 from esphome/bump-2022.2.0b2
2022.2.0b2
2022-02-15 12:48:34 +13:00
Jesse Hills
bb6b77bd98 Bump version to 2022.2.0b2 2022-02-15 12:00:12 +13:00
Jesse Hills
dcc80f9032 Allow framework version validator to be maximum version (#3197) 2022-02-15 12:00:12 +13:00
Otto Winter
dd554bcdf4 Make generating combined binary output verbose (#3127) 2022-02-15 12:00:12 +13:00
Jesse Hills
f376a39e55 Clamp rotary_encoder restored value to min and max (#3184) 2022-02-15 12:00:12 +13:00
dependabot[bot]
8dcc9d6b66 Bump aioesphomeapi from 10.8.1 to 10.8.2 (#3182) 2022-02-15 12:00:11 +13:00
Jesse Hills
a576c9f21f Merge pull request #3180 from esphome/bump-2022.2.0b1
2022.2.0b1
2022-02-10 00:20:46 +13:00
Jesse Hills
71a438e2cb Bump version to 2022.2.0b1 2022-02-09 23:47:36 +13:00
Jesse Hills
272d6f2a8b Merge branch 'dev' into bump-2022.2.0b1 2022-02-09 23:47:36 +13:00
Jesse Hills
5c22065135 Change most references from hassio to ha-addon (#3178) 2022-02-09 23:46:20 +13:00
Borys Pierov
e7dd6c52ac Allow to set manufacturer data for BLEAdvertising (#3179) 2022-02-09 23:29:32 +13:00
dependabot[bot]
3bf042dce9 Bump pytest-asyncio from 0.17.2 to 0.18.0 (#3168)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-02-09 12:57:12 +13:00
dependabot[bot]
64f798d4b2 Bump pytest from 6.2.5 to 7.0.0 (#3163)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-02-09 11:42:44 +13:00
Jesse Hills
f43e04e15a Try fix canbus config validation (#3173) 2022-02-09 11:42:00 +13:00
Jesse Hills
88d72f8c9a Fix files CI after merging (#3175) 2022-02-09 08:04:44 +13:00
Andrej Komelj
9826726a72 Implement MQTT discovery object_id generator (#3114) 2022-02-08 22:58:38 +13:00
Jesse Hills
c66d0550e8 Inkplate 6 PLUS (#3013) 2022-02-08 22:56:56 +13:00
mckaymatthew
4aeacfd16e Add max9611 High Side Current Shunt ADC (#2705)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2022-02-08 22:56:40 +13:00
stegm
58fa63ad88 Add Select for modbus (#3032)
Co-authored-by: Oxan van Leeuwen <oxan@oxanvanleeuwen.nl>
2022-02-08 22:27:22 +13:00
Jesse Hills
94f944dc9c Add Lilygo t5 4.7 Touchscreen (#3084) 2022-02-08 21:50:25 +13:00
Ashton Kemerling
116ddbdd01 Add require response option for BLE binary output (#3091) 2022-02-08 21:30:31 +13:00
Jesse Hills
1c0697b5d4 Dont warn on nonnull comparisons (#3123) 2022-02-08 21:28:12 +13:00
Otto Winter
434ca47ea0 Enable mDNS during OTA safe mode (#3146) 2022-02-08 21:21:52 +13:00
functionpointer
397ef72b16 MLX90393 three-axis magnetometer (#2770)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2022-02-08 20:42:11 +13:00
Jonas Bergler
7ca9245735 wifi_info, reduce polling interval (#3165)
Co-authored-by: Jonas Bergler <jbergler@meraki.com>
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2022-02-08 20:27:39 +13:00
Jesse Hills
69856286e8 Text sensor schema generator similar to sensor (#3172) 2022-02-08 17:23:45 +13:00
Jeff Eberl
ad43d6a5bc Added RadonEye RD200 Component (#3119)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2022-02-08 16:32:37 +13:00
mknjc
1e5004f495 [debug] Refactor debug sensors to use the normal sensor model. (#3162) 2022-02-08 12:45:27 +13:00
Otto Winter
253161d3d0 Fix copy_file_if_changed src permissions copied too (#3161) 2022-02-07 21:26:16 +01:00
Jesse Hills
ab47e201c7 Bump improv library to 1.2.1 (#3160) 2022-02-04 19:15:00 +13:00
Samuel Sieb
42984fa72a Handle Tuya multi-datapoint messages (#3159)
Co-authored-by: Samuel Sieb <samuel@sieb.net>
2022-02-04 15:50:42 +13:00
Franck Nijhof
e7864a28a1 Add device class support to Switch (#3012)
Co-authored-by: Oxan van Leeuwen <oxan@oxanvanleeuwen.nl>
2022-02-04 09:04:48 +13:00
Keilin Bickar
21803607e7 Add new Lock core component (#2958)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2022-02-04 07:24:31 +13:00
Jesse Hills
5dc776e55f Merge pull request #3068 from esphome/bump-2022.1.0b4
2022.1.0b4
2022-01-19 07:40:37 +13:00
Jesse Hills
72d60f30f7 Bump version to 2022.1.0b4 2022-01-18 15:49:31 +13:00
Oxan van Leeuwen
869743a742 Fail hard if no random bytes available for encryption (#3067) 2022-01-18 15:49:31 +13:00
Martin
7b03e07908 [modbus_controller] add missing skip_updates (#3063) 2022-01-18 15:49:31 +13:00
Paulus Schoutsen
348f880e15 bump dashboard to 20220116.0 (#3061) 2022-01-18 15:49:31 +13:00
Jesse Hills
ead597d0fb Merge pull request #3060 from esphome/bump-2022.1.0b3
2022.1.0b3
2022-01-17 13:13:40 +13:00
Jesse Hills
afbf989715 Bump version to 2022.1.0b3 2022-01-17 12:40:07 +13:00
Jesse Hills
01b62a16c3 Add number setting to web_server/rest_api (#3055) 2022-01-17 12:40:07 +13:00
Oxan van Leeuwen
c5eba04517 Remove deprecated attribute from virtual entity methods (#3056) 2022-01-17 12:40:07 +13:00
Oxan van Leeuwen
282313ab52 Rename post_build scripts to fix codeowners script (#3057) 2022-01-17 12:40:07 +13:00
Ohad Lutzky
d274545e77 Disable caching for binary download (#3054) 2022-01-17 12:40:07 +13:00
Jesse Hills
d3fda37615 Merge pull request #3042 from esphome/bump-2022.1.0b2
2022.1.0b2
2022-01-13 22:21:45 +13:00
Jesse Hills
cbe3092404 Bump version to 2022.1.0b2 2022-01-13 21:28:45 +13:00
Paulus Schoutsen
6dfe3039d0 Bump dashboard to 20220113.2 (#3041) 2022-01-13 21:28:45 +13:00
Paulus Schoutsen
d6009453df Add factory to download name (#3040) 2022-01-13 21:28:45 +13:00
Jesse Hills
c81323ef91 Merge pull request #3039 from esphome/bump-2022.1.0b1
2022.1.0b1
2022-01-13 12:12:07 +13:00
Jesse Hills
961c27f1c2 Bump version to 2022.1.0b1 2022-01-13 11:02:07 +13:00
Jesse Hills
fe4a14e6cc Merge branch 'dev' into bump-2022.1.0b1 2022-01-13 11:02:07 +13:00
Jesse Hills
50848c2f4d Merge pull request #2966 from esphome/bump-2021.12.3
2021.12.3
2021-12-30 14:54:49 +13:00
Jesse Hills
d32633b3c7 Update curl package version in docker (#2939) 2021-12-30 14:32:29 +13:00
Jesse Hills
b37739eec2 Bump version to 2021.12.3 2021-12-30 13:58:47 +13:00
Jesse Hills
28f87dc804 Remove -e for hassio images (#2964) 2021-12-30 13:58:47 +13:00
Jesse Hills
41879e41e6 Workaround installing as editable package not working (#2936) 2021-12-30 13:58:47 +13:00
Jesse Hills
fc0a6546a2 Only allow internal pins for dht sensor (#2940) 2021-12-30 13:58:47 +13:00
Jesse Hills
ffd4280d6c Require arduino in webserver for better validation (#2941) 2021-12-30 13:58:46 +13:00
Jesse Hills
db3b955b0f Merge pull request #2932 from esphome/bump-2021.12.2
2021.12.2
2021-12-21 12:45:27 +13:00
Jesse Hills
5516f65971 Bump version to 2021.12.2 2021-12-21 08:24:08 +13:00
Oxan van Leeuwen
9471df0a1b Fix MQTT button press action (#2917) 2021-12-21 08:24:07 +13:00
Oxan van Leeuwen
6d39f64be7 Don't disable idle task WDT when it's not enabled (#2856) 2021-12-21 08:24:07 +13:00
Jesse Hills
b89d0a9a73 Merge pull request #2915 from esphome/bump-2021.12.1
2021.12.1
2021-12-15 16:36:39 +13:00
Jesse Hills
4bb779d9a5 Bump version to 2021.12.1 2021-12-15 14:57:32 +13:00
wilberforce
386a5b6362 Allow button POST on press from web server (#2913) 2021-12-15 14:57:32 +13:00
Oxan van Leeuwen
e32a999cd0 Set text sensor state property to filter output (#2893) 2021-12-15 14:57:32 +13:00
Jesse Hills
bfbc6a4bad Merge pull request #2907 from esphome/bump-2021.12.0
2021.12.0
2021-12-12 07:59:37 +13:00
Jesse Hills
8c9e0e552d Bump version to 2021.12.0 2021-12-12 07:10:51 +13:00
Jesse Hills
8aaf9fd83f Merge pull request #2905 from esphome/bump-2021.12.0b6
2021.12.0b6
2021-12-11 21:54:49 +13:00
Jesse Hills
08057720b8 Bump version to 2021.12.0b6 2021-12-11 21:07:07 +13:00
Jesse Hills
bfaa648837 Bump esphome-dashboard to 20211211.0 (#2904) 2021-12-11 21:07:07 +13:00
Keith Burzinski
d504daef91 Fix for two points setting when fan_only_cooling is disabled (#2903)
Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>
Co-authored-by: Keith Burzinski <kburzinski@kbx-mbp2021.ad.kbx81.net>
2021-12-11 21:07:07 +13:00
Jesse Hills
b8d3ef2f49 Merge pull request #2899 from esphome/bump-2021.12.0b5
2021.12.0b5
2021-12-10 10:55:55 +13:00
Jesse Hills
3bf6320030 Bump version to 2021.12.0b5 2021-12-10 09:55:48 +13:00
Guillermo Ruffino
708b928c73 Modbus number/output use write single (#2896)
Co-authored-by: Martin <25747549+martgras@users.noreply.github.com>
2021-12-10 09:55:48 +13:00
Jesse Hills
649366ff44 Fix published state for modbus number (#2894) 2021-12-10 09:55:47 +13:00
Jesse Hills
e5c9e87fad Merge pull request #2890 from esphome/bump-2021.12.0b4
2021.12.0b4
2021-12-10 09:50:29 +13:00
Jesse Hills
f3d9d707b6 Bump version to 2021.12.0b4 2021-12-08 12:58:14 +13:00
Jesse Hills
090e10730c Bump esphome-dashboard to 20211208.0 (#2887) 2021-12-08 12:58:14 +13:00
Jesse Hills
fbc84861c7 Use new platform component config blocks for wizard (#2885) 2021-12-08 12:58:14 +13:00
Carlos Garcia Saura
e763469af8 Feed watchdog while setting up OTA (#2876) 2021-12-08 12:58:14 +13:00
Jesse Hills
3c0c514e44 Merge pull request #2880 from esphome/bump-2021.12.0b3
2021.12.0b3
2021-12-07 15:27:08 +13:00
Jesse Hills
ed5e2dd332 Bump version to 2021.12.0b3 2021-12-07 07:47:48 +13:00
Jesse Hills
09b7c6f550 Bump esphome-dashboard to 20211207.0 (#2877) 2021-12-07 07:47:48 +13:00
Oxan van Leeuwen
df315a1f51 Feed watchdog when no component loops (#2857) 2021-12-07 07:47:48 +13:00
Jesse Hills
7ee4bb621c Allow wizard to specify secrets (#2875) 2021-12-07 07:47:48 +13:00
Jesse Hills
24874f4c3c Adopt using wifi secrets that should exist at this point (#2874) 2021-12-07 07:47:48 +13:00
Jesse Hills
c128880033 Add endpoint to fetch secrets keys (#2873) 2021-12-07 07:47:48 +13:00
Massimiliano Ravelli
a66e94a0b0 Ignore already stopped dhcp for ethernet (#2862)
Co-authored-by: Oxan van Leeuwen <oxan@oxanvanleeuwen.nl>
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2021-12-07 07:47:48 +13:00
Oxan van Leeuwen
56870ed4a8 Fix MCP23x17 not disabling pullup after config change (#2855) 2021-12-07 07:47:48 +13:00
Martin
3ac720df47 SPS30 : fix i2c read size (#2866) 2021-12-07 07:47:48 +13:00
Carlos Garcia Saura
1bc757ad06 ADC: Turn verbose the debugging "got voltage" (#2863) 2021-12-07 07:47:48 +13:00
Martin
f72abc6f3d tlc59208f : fix compilation error (#2867) 2021-12-07 07:47:48 +13:00
Jesse Hills
5ac88de985 Bump esphome-dashboard to 20211206.0 (#2870) 2021-12-07 07:47:48 +13:00
Jesse Hills
0826b367d6 Merge pull request #2853 from esphome/bump-2021.12.0b2
2021.12.0b2
2021-12-03 08:07:30 +13:00
Jesse Hills
329bf861d6 Bump version to 2021.12.0b2 2021-12-03 07:54:34 +13:00
Oxan van Leeuwen
9dcd3d18a0 Update ota_component.cpp (#2852) 2021-12-03 07:54:34 +13:00
Jesse Hills
db66cd88b6 Merge pull request #2851 from esphome/bump-2021.12.0b1
2021.12.0b1
2021-12-02 21:32:43 +13:00
Jesse Hills
86c205fe43 Remove blank line 2021-12-02 21:08:11 +13:00
Jesse Hills
c6414138c7 Bump version to 2021.12.0b1 2021-12-02 19:38:49 +13:00
Jesse Hills
36b355eb82 Merge branch 'dev' into bump-2021.12.0b1 2021-12-02 19:38:44 +13:00
Jesse Hills
7be9291b13 Merge pull request #2821 from esphome/bump-2021.11.4
2021.11.4
2021-11-29 13:23:47 +13:00
Jesse Hills
ea9e75039b Bump version to 2021.11.4 2021-11-29 10:18:49 +13:00
Conclusio
a5fb036011 Add delay to improve stability (#2793) 2021-11-29 10:18:48 +13:00
Dave T
e55506f9db Correct bitmask for third color (blue) scaling. (#2817) 2021-11-29 10:18:48 +13:00
Carlos Garcia Saura
50ec1d0445 Fix compilation error for WPA enterprise in ESP-IDF (#2815) 2021-11-29 10:18:48 +13:00
Oxan van Leeuwen
3d5e1d8d91 Fix parsing of multiple values in EZO sensor (#2814)
Co-authored-by: Lydia Sevelt <LydiaSevelt@gmail.com>
2021-11-29 10:18:48 +13:00
Oxan van Leeuwen
db2128a344 Fix parsing numbers in Anova (#2816) 2021-11-29 10:18:48 +13:00
Jesse Hills
21db43db06 Merge pull request #2808 from esphome/bump-2021.11.3
2021.11.3
2021-11-28 00:01:16 +13:00
Jesse Hills
5009b3029f Bump version to 2021.11.3 2021-11-27 21:13:01 +13:00
Maurice Makaay
57a029189c Add missing nvs_flash_init() to ESP32 preferences code (#2805)
Co-authored-by: Maurice Makaay <mmakaay1@xs4all.net>
2021-11-27 21:13:01 +13:00
Jesse Hills
0cb715bb76 Merge pull request #2799 from esphome/bump-2021.11.2
2021.11.2
2021-11-26 09:25:37 +13:00
Jesse Hills
7d03823afd Bump version to 2021.11.2 2021-11-26 09:02:54 +13:00
Oxan van Leeuwen
8e1c9f5042 Fix parsing numbers from null-terminated buffers (#2755) 2021-11-26 09:02:54 +13:00
Samuel Sieb
980b7cda8f Remove floating point ops from the ISR (#2751)
Co-authored-by: Samuel Sieb <samuel@sieb.net>
2021-11-26 09:02:53 +13:00
Kamil Trzciński
3a72dd5cb6 esp32_camera_web_server: Improve support for MotionEye (#2777) 2021-11-26 09:02:53 +13:00
Dave T
3178243811 Fix frame scaling for animated gifs (#2750) 2021-11-26 09:02:53 +13:00
Maurice Makaay
d30e2f2a4f Allow UART debug configuration with no after: definition (#2753) 2021-11-26 09:02:53 +13:00
Jesse Hills
6226dae05c Merge pull request #2744 from esphome/bump-2021.11.1
2021.11.1
2021-11-17 23:45:43 +13:00
Jesse Hills
9c6a475a6e Bump version to 2021.11.1 2021-11-17 23:31:38 +13:00
Franck Nijhof
8294d10d5b Re-instate device class update for binary sensors (#2743) 2021-11-17 23:31:38 +13:00
Evgeny
67558bec47 Fix HM3301 AQI index calculator (#2739) 2021-11-17 23:31:38 +13:00
Jesse Hills
84873d4074 Merge pull request #2742 from esphome/bump-2021.11.0
2021.11.0
2021-11-17 22:18:29 +13:00
Jesse Hills
58a0b28a39 Bump version to 2021.11.0 2021-11-17 21:58:30 +13:00
Jesse Hills
b37d3a66cc Merge pull request #2738 from esphome/bump-2021.11.0b9
2021.11.0b9
2021-11-17 10:27:41 +13:00
Jesse Hills
7e495a5e27 Bump version to 2021.11.0b9 2021-11-17 08:00:26 +13:00
rotarykite
c41547fd4a Fix senseair component uart read timeout (#2658)
Co-authored-by: DAVe3283 <DAVe3283+GitHub@gmail.com>
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
Co-authored-by: Chua Jun Chieh <junchieh.chua@softspace.com.my>
2021-11-17 08:00:26 +13:00
Ryan Hoffman
0d47d41c85 Use as_reversed_hex_array in ble_sensor to fix UUID parsing (#2737)
#1627 renamed as_hex_array to as_reversed_hex_array but forgot to rename these users.
2021-11-17 08:00:26 +13:00
Jesse Hills
41a3a17456 Merge pull request #2734 from esphome/bump-2021.11.0b8
2021.11.0b8
2021-11-16 13:50:10 +13:00
Jesse Hills
cbbafbcca2 Bump version to 2021.11.0b8 2021-11-16 12:53:56 +13:00
Jesse Hills
c75566b374 Fix zeroconf time comparisons (#2733)
Co-authored-by: J. Nick Koston <nick@koston.org>
2021-11-16 12:53:56 +13:00
Jesse Hills
7279f1fcc1 Merge pull request #2732 from esphome/bump-2021.11.0b7
2021.11.0b7
2021-11-16 12:19:07 +13:00
Jesse Hills
d7432f7c10 Bump version to 2021.11.0b7 2021-11-16 11:05:51 +13:00
Jesse Hills
b0a0a153f3 Improv serial/checksum changes (#2731)
Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
2021-11-16 11:05:51 +13:00
Jesse Hills
024632dbd0 Merge pull request #2730 from esphome/bump-2021.11.0b6
2021.11.0b6
2021-11-16 10:53:11 +13:00
Jesse Hills
0a545a28b9 Bump version to 2021.11.0b6 2021-11-16 09:59:00 +13:00
Jesse Hills
0f2df59998 Add zeroconf as a direct dependency and lock the version (#2729) 2021-11-16 09:58:59 +13:00
Jesse Hills
29a7d32f77 Merge pull request #2725 from esphome/bump-2021.11.0b5
2021.11.0b5
2021-11-15 13:42:59 +13:00
Jesse Hills
687a7e9b2f Bump version to 2021.11.0b5 2021-11-15 12:02:18 +13:00
Alexandre-Jacques St-Jacques
09e8782318 Remove unnecessary duplicate touch_pad_filter_start (#2724) 2021-11-15 12:02:18 +13:00
Jesse Hills
f2aea02210 Merge pull request #2723 from esphome/bump-2021.11.0b4
2021.11.0b4
2021-11-15 11:42:59 +13:00
Jesse Hills
194f922312 Bump version to 2021.11.0b4 2021-11-15 11:03:40 +13:00
Jesse Hills
fea3c48098 Fix indentation of write_lambda for modbus_controller number (#2722) 2021-11-15 11:03:39 +13:00
Sergey V. DUDANOV
c2f57baec2 RemoteTransmitter fix. Bug from version 2021.10. Some changes. (#2706) 2021-11-15 11:03:39 +13:00
Oxan van Leeuwen
f4a140e126 Feed WDT between doing ESP32 touchpad measurements (#2720) 2021-11-15 11:03:39 +13:00
Oxan van Leeuwen
ab506b09fe Restore InterruptLock on wifi-less ESP8266 (#2712) 2021-11-15 11:03:39 +13:00
Krzysztof Białek
87e1cdeedb Allow setting custom command_topic for Select and Number components (#2714) 2021-11-15 11:03:39 +13:00
Jesse Hills
81a36146ef Bump ESPAsyncWebServer to 2.1.0 (#2686) 2021-11-15 11:03:39 +13:00
Jesse Hills
7fa4a68a27 Merge pull request #2704 from esphome/bump-2021.11.0b3
2021.11.0b3
2021-11-12 17:21:58 +13:00
Jesse Hills
f1c5e2ef81 Bump version to 2021.11.0b3 2021-11-12 16:12:31 +13:00
lcavalli
b526155cce Update device classes for binary sensors (#2703) 2021-11-12 16:12:31 +13:00
Jesse Hills
62c3f301e7 Only allow prometheus when using arduino (#2697) 2021-11-12 16:12:31 +13:00
Jesse Hills
38cb988809 Remove my.ha links from improv (#2695) 2021-11-12 16:12:31 +13:00
Jesse Hills
b976ac54c8 Merge pull request #2693 from esphome/bump-2021.11.0b2
2021.11.0b2
2021-11-11 12:52:35 +13:00
Jesse Hills
78026e766f Bump version to 2021.11.0b2 2021-11-11 12:25:41 +13:00
Oxan van Leeuwen
b4cd8d21a5 Enable addressable light power supply based on raw values (#2690) 2021-11-11 12:25:41 +13:00
Maurice Makaay
7552893311 Uart debugging support (#2478)
Co-authored-by: Maurice Makaay <mmakaay1@xs4all.net>
Co-authored-by: Maurice Makaay <account-github@makaay.nl>
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2021-11-11 12:25:41 +13:00
Carlos Garcia Saura
21c896d8f8 [remote_transmitter] accurate pulse timing for ESP8266 (#2476) 2021-11-11 12:25:40 +13:00
Jesse Hills
4b7fe202ec Fix template number initial value being NaN (#2692) 2021-11-11 12:25:40 +13:00
Jesse Hills
9f4519210f Merge pull request #2691 from esphome/bump-2021.11.0b1
2021.11.0b1
2021-11-11 11:05:45 +13:00
Jesse Hills
b0506afa5b Merge branch 'beta' into bump-2021.11.0b1 2021-11-11 10:48:23 +13:00
Jesse Hills
8cbb379898 Remove import (not sure how it got there) 2021-11-11 10:35:18 +13:00
Jesse Hills
b226215593 Bump version to 2021.11.0b1 2021-11-11 10:10:05 +13:00
Jesse Hills
19970729a9 Merge branch 'dev' into bump-2021.11.0b1 2021-11-11 10:10:04 +13:00
Jesse Hills
d2ebfd2833 Merge pull request #2634 from esphome/bump-2021.10.3
2021.10.3
2021-10-27 11:21:08 +13:00
Jesse Hills
bd782fc828 Bump version to 2021.10.3 2021-10-27 10:49:11 +13:00
Jesse Hills
23560e608c Fix select.set using lambda (#2633) 2021-10-27 10:49:10 +13:00
Jan Čermák
f1377b560e Fix pin number validation for sn74hc595 (#2621) 2021-10-27 10:49:10 +13:00
Martin
72108684ea fix modbus output (#2630) 2021-10-27 10:49:10 +13:00
Jesse Hills
c6adaaea97 Remove power and energy from sensors that are not true power (#2628) 2021-10-27 10:49:10 +13:00
Otto Winter
91999a38ca Fix glue code missing micros() (#2623) 2021-10-27 10:49:10 +13:00
0hax
b34eed125d Teleinfo ptec (#2599)
* teleinfo: handle historical mode correctly.

In historical mode, tags like PTEC leads to an issue where we detect a
timestamp wheras this is not possible in historical mode.

PTEC teleinfo tag looks like:
    PTEC HP..
Instead of the usual format
    IINST1 001 I

This make our data parsing fails.

While at here, make sure we continue parsing other tags even if parsing
one of the tag fails.

Signed-off-by: 0hax <0hax@protonmail.com>

* teleinfo: fix compilation with loglevel set to debug.

Signed-off-by: 0hax <0hax@protonmail.com>
2021-10-27 10:49:10 +13:00
Stefan Agner
2abe09529a Autodetect flash size (#2615) 2021-10-27 10:49:10 +13:00
Otto Winter
9aaaf4dd4b Bump platform-espressif8266 from 2.6.2 to 2.6.3 (#2620) 2021-10-27 10:49:09 +13:00
Andreas Hergert
cbfbcf7f1b fixed dependency for pca9685 component (#2614)
Co-authored-by: Otto Winter <otto@otto-winter.com>
Co-authored-by: Andreas <andreas.hergert@otrs.com>
2021-10-27 10:49:09 +13:00
Otto Winter
c7651dc40d Merge pull request #2613 from esphome/bump-2021.10.2
2021.10.2
2021-10-22 18:35:28 +02:00
Otto winter
eda1c471ad Bump version to 2021.10.2 2021-10-22 18:26:24 +02:00
Andreas Hergert
c7ef18fbc4 Bugfix tca9548a and idf refactor anh (#2612)
Co-authored-by: Andreas Hergert <andreas.hergert@otrs.com>
2021-10-22 18:26:23 +02:00
Otto Winter
901ec918b1 Fix ESP8266 OTA compression only starting framework v2.7.0 (#2610) 2021-10-22 18:26:23 +02:00
Otto Winter
6bdae55ee1 Fix compiler warnings and update platformio line filter (#2607) 2021-10-22 18:26:23 +02:00
Otto Winter
dfb96e4b7f Add owner to all libraries used (#2604) 2021-10-22 18:26:22 +02:00
Otto Winter
ff2c316b18 Re-raise keyboardinterrupt (#2603) 2021-10-22 18:26:22 +02:00
Otto Winter
5be52f71f9 Add OTA upload compression for ESP8266 (#2601) 2021-10-22 18:26:22 +02:00
Otto Winter
42873dd37c Bump noise-c from 0.1.3 to 0.1.4 (#2602) 2021-10-22 18:26:21 +02:00
Otto Winter
f93e7d4e3a Fix socket connection closed not detected (#2587) 2021-10-22 18:26:21 +02:00
Oxan van Leeuwen
bbcd523967 Fix validation of addressable light IDs (#2588) 2021-10-22 18:26:21 +02:00
dependabot[bot]
68cbe58d00 Bump esphome-dashboard from 20211021.0 to 20211021.1 (#2594)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-10-22 18:26:12 +02:00
Oxan van Leeuwen
115bca98f1 Fix old-style arduino_version on ESP8266 and with magic values (#2591) 2021-10-22 18:24:48 +02:00
Oxan van Leeuwen
ed0b34b2fe Fix pin/component switchup in SX1509 pin configuration (#2593) 2021-10-22 18:24:48 +02:00
Oxan van Leeuwen
ab34401421 Fix PlatformIO version for latest Arduino framework (#2590) 2021-10-22 18:24:48 +02:00
Otto Winter
eed0c18d65 Fix HeatpumpIR pin (#2585) 2021-10-22 18:24:47 +02:00
Otto Winter
e5a38ce748 Merge pull request #2580 from esphome/bump-2021.10.1
2021.10.1
2021-10-21 15:31:26 +02:00
Otto Winter
7d9d9fcf36 Fix platformio_install_deps no longer installing all lib_deps (#2584) 2021-10-21 15:29:53 +02:00
Otto Winter
f0aba6ceb2 Fix platformio version in Dockerfile doesn't match requirements (#2582) 2021-10-21 14:53:22 +02:00
Otto Winter
ab07ee57c6 Fix ESP8266 dallas GPIO16 INPUT_PULLUP (#2581) 2021-10-21 14:39:55 +02:00
Otto winter
eae3d72a4d Bump version to 2021.10.1 2021-10-21 14:23:08 +02:00
Otto Winter
7b8d826704 Fix ESP8266 OTA adds unnecessary Update library (#2579) 2021-10-21 14:23:07 +02:00
Otto Winter
e7baa42e63 Arduino global delay/millis/... symbols workaround (#2575) 2021-10-21 14:23:07 +02:00
Otto Winter
2f32833a22 Fix wifi ble coexistence check (#2573) 2021-10-21 14:23:07 +02:00
Otto Winter
f6935a4b4b Fix ESP8266 GPIO0 Pullup Validation (#2572) 2021-10-21 14:23:06 +02:00
Maurice Makaay
332c9e891b Fix MDNS for ESP8266 devices (#2571)
Co-authored-by: Maurice Makaay <account-github@makaay.nl>
Co-authored-by: Otto winter <otto@otto-winter.com>
Co-authored-by: Maurice Makaay <mmakaay1@xs4all.net>
2021-10-21 14:23:06 +02:00
Jesse Hills
b91ee4847f Merge pull request #2570 from esphome/bump-2021.10.0
2021.10.0
2021-10-21 08:37:26 +13:00
Jesse Hills
625463d871 Bump version to 2021.10.0 2021-10-21 07:57:10 +13:00
Jesse Hills
6433a01e07 Merge pull request #2567 from esphome/bump-2021.10.0b11
2021.10.0b11
2021-10-21 07:47:22 +13:00
Jesse Hills
56cc31e8e7 Bump version to 2021.10.0b11 2021-10-21 07:32:27 +13:00
Otto Winter
3af297aa76 Revert nextion clang-tidy changes (#2566) 2021-10-21 07:32:27 +13:00
Otto Winter
996ec59d28 Move running process log line to debug level (#2565) 2021-10-21 07:32:26 +13:00
Jesse Hills
95593eeeab Bump esphome-dashboard to 20211021.0 (#2564) 2021-10-21 07:32:20 +13:00
Jesse Hills
dad244fb7a A few esp32_ble_server/improv fixes (#2562) 2021-10-21 07:31:55 +13:00
Jesse Hills
adb5d27d95 Merge pull request #2561 from esphome/bump-2021.10.0b10
2021.10.0b10
2021-10-20 17:28:37 +13:00
Jesse Hills
8456a8cecb Bump version to 2021.10.0b10 2021-10-20 16:39:24 +13:00
Jesse Hills
b9f66373c1 Bump esphome-dashboard to 20211020.1 (#2559) 2021-10-20 16:39:16 +13:00
Jesse Hills
9ac365feef Fix HA addon so it does not have logout button (#2558) 2021-10-20 16:38:46 +13:00
Jesse Hills
43bbd58a44 Merge pull request #2557 from esphome/bump-2021.10.0b9
2021.10.0b9
2021-10-20 11:18:40 +13:00
Jesse Hills
7feffa64f3 Bump version to 2021.10.0b9 2021-10-20 11:00:02 +13:00
Jesse Hills
ea0977abb4 Bump dashboard to 20211020.0 (#2556) 2021-10-20 10:59:53 +13:00
Jesse Hills
4c83dc7c28 Merge pull request #2555 from esphome/bump-2021.10.0b8
2021.10.0b8
2021-10-20 10:30:59 +13:00
Jesse Hills
e10ab1da78 Bump version to 2021.10.0b8 2021-10-20 10:14:30 +13:00
Martin
1b0e60374b ignore exception when not waiting for a response (#2552) 2021-10-20 10:14:30 +13:00
Oxan van Leeuwen
3a760fbb44 Fix ADC pin validation on ESP32-C3 (#2551) 2021-10-20 10:14:30 +13:00
Jesse Hills
6ef57a2973 Merge pull request #2550 from esphome/bump-2021.10.0b7
2021.10.0b7
2021-10-19 16:22:29 +13:00
Jesse Hills
3e9c7f2e9f Bump version to 2021.10.0b7 2021-10-19 15:47:31 +13:00
Jesse Hills
430598b7a1 Bump dashboard to 20211019.0 (#2549) 2021-10-19 15:47:26 +13:00
Jesse Hills
91611b09b4 Merge pull request #2545 from esphome/bump-2021.10.0b6
2021.10.0b6
2021-10-18 21:41:22 +13:00
Jesse Hills
ecd115851f Bump version to 2021.10.0b6 2021-10-18 21:26:36 +13:00
Maurice Makaay
4a1e50fed1 OTA firmware MD5 check + password support for esp-idf (#2507)
Co-authored-by: Maurice Makaay <account-github@makaay.nl>
2021-10-18 21:26:36 +13:00
Jesse Hills
d6d037047b Merge pull request #2544 from esphome/bump-2021.10.0b5
2021.10.0b5
2021-10-18 16:37:43 +13:00
Jesse Hills
b5734c2b20 Bump version to 2021.10.0b5 2021-10-18 15:31:01 +13:00
Oxan van Leeuwen
723fb7eaac Autodetect ESP32 variant (#2530)
Co-authored-by: Otto winter <otto@otto-winter.com>
2021-10-18 15:31:01 +13:00
Jesse Hills
63a9acaa19 Fix Bluetooth setup_priorities (#2458)
Co-authored-by: Otto Winter <otto@otto-winter.com>
2021-10-18 15:31:00 +13:00
Jesse Hills
0524f8c677 Fix const used for IDF recommended version (#2542) 2021-10-18 15:31:00 +13:00
Otto Winter
70b62f272e Only show timestamp for dashboard access logs (#2540) 2021-10-18 15:31:00 +13:00
Jesse Hills
f0089b7940 Merge pull request #2539 from esphome/bump-2021.10.0b4
2021.10.0b4
2021-10-17 21:17:54 +13:00
Jesse Hills
4b44280d53 Bump version to 2021.10.0b4 2021-10-17 20:34:07 +13:00
Paulus Schoutsen
f045382d20 Bump dashboard to 20211015.0 (#2525)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2021-10-17 20:34:03 +13:00
Jesse Hills
db3fa1ade7 Allow downloading all bin files from backend in dashboard (#2514)
Co-authored-by: Otto Winter <otto@otto-winter.com>
2021-10-17 20:28:43 +13:00
Oxan van Leeuwen
f83950fd75 Fix bitshift on read in ADE7953 (#2537) 2021-10-17 20:28:43 +13:00
Oxan van Leeuwen
4dd1bf920d Replace framework version_hint with source option (#2529) 2021-10-17 20:28:43 +13:00
Martin
98755f3621 Fix bug in register name definition (#2526) 2021-10-17 20:28:43 +13:00
Keith Burzinski
c3a8a044b9 Fix Nextion HTTPClient error for ESP32 (#2524) 2021-10-17 20:28:42 +13:00
Martin
15b5ea43a7 Add pressure compensation during runtime (#2493)
Co-authored-by: Oxan van Leeuwen <oxan@oxanvanleeuwen.nl>
2021-10-17 20:28:42 +13:00
Jesse Hills
ec683fc227 Merge pull request #2523 from esphome/bump-2021.10.0b3
2021.10.0b3
2021-10-15 10:15:26 +13:00
Jesse Hills
d4e65eb82a Bump version to 2021.10.0b3 2021-10-15 09:42:44 +13:00
Paul Monigatti
10c6601b0a Revert "Added test for bme680_bsec" (#2518)
This reverts commit 7f6a50d291b14935b17802b4dce52135fad1e49e due to BSEC library license restrictions.
2021-10-15 09:42:44 +13:00
Oxan van Leeuwen
73940bc1bd Don't define UART_SELECTION_UART2 when UART2 is unavailable (#2512) 2021-10-15 09:42:43 +13:00
Paul Monigatti
9b7fb829f9 Fix: Color modes not being correctly used in light partitions (#2513) 2021-10-15 09:42:43 +13:00
Dmitriy Lopatko
c51d8c9021 add missing include in sgp30 (#2517) 2021-10-15 09:42:43 +13:00
Paul Monigatti
d8a6dfe5ce Fix BME680_BSEC compilation issue with ESP32 (#2516) 2021-10-15 09:42:43 +13:00
Oxan van Leeuwen
5f7cef0b06 Disallow using UART2 for logger on ESP-32 variants that lack it (#2510) 2021-10-15 09:42:43 +13:00
Paul Monigatti
48ff2ffc68 Fix: Light flash not restoring previous LightState (#2383)
* Update light state when transformer has finished

* Revert writing direct to output

* Correct handling of zero-length light transformers

* Allow transformers to handle zero-length transitions, and check more boundary conditions when transitioning back to start state

* Removed log.h

* Fixed race condition between LightFlashTransformer.apply() and is_finished()

* clang-format

* Step progress from 0.0f to 1.0f at t=start_time for zero-length transforms to avoid divide-by-zero
2021-10-15 09:42:43 +13:00
Oxan van Leeuwen
b3b9ccd314 Fix light state remaining on after turn off with transition (#2509) 2021-10-15 09:42:43 +13:00
Jesse Hills
e63c7b483b Merge pull request #2505 from esphome/bump-2021.10.0b2
2021.10.0b2
2021-10-13 22:45:29 +13:00
Jesse Hills
f57980b069 Bump version to 2021.10.0b2 2021-10-13 22:30:29 +13:00
Jesse Hills
7006aa0d2a Merge branch 'dev' into beta 2021-10-13 22:11:53 +13:00
Jesse Hills
8051c1ca99 Merge pull request #2504 from esphome/bump-2021.10.0b1
2021.10.0b1
2021-10-13 21:38:41 +13:00
Jesse Hills
a779592414 Bump version to 2021.10.0b1 2021-10-13 16:40:46 +13:00
Jesse Hills
112215848d Merge branch 'dev' into bump-2021.10.0b1 2021-10-13 16:40:46 +13:00
158 changed files with 4046 additions and 829 deletions

View File

@@ -137,7 +137,7 @@ jobs:
--build-type "${{ matrix.build_type }}" \
manifest
deploy-hassio-repo:
deploy-ha-addon-repo:
if: github.repository == 'esphome/esphome' && github.event_name == 'release'
runs-on: ubuntu-latest
needs: [deploy-docker]
@@ -150,5 +150,5 @@ jobs:
-u ":$TOKEN" \
-X POST \
-H "Accept: application/vnd.github.v3+json" \
https://api.github.com/repos/esphome/hassio/actions/workflows/bump-version.yml/dispatches \
https://api.github.com/repos/esphome/home-assistant-addon/actions/workflows/bump-version.yml/dispatches \
-d "{\"ref\":\"main\",\"inputs\":{\"version\":\"$TAG\"}}"

View File

@@ -89,9 +89,12 @@ esphome/components/json/* @OttoWinter
esphome/components/kalman_combinator/* @Cat-Ion
esphome/components/ledc/* @OttoWinter
esphome/components/light/* @esphome/core
esphome/components/lilygo_t5_47/touchscreen/* @jesserockz
esphome/components/lock/* @esphome/core
esphome/components/logger/* @esphome/core
esphome/components/ltr390/* @sjtrny
esphome/components/max7219digit/* @rspaargaren
esphome/components/max9611/* @mckaymatthew
esphome/components/mcp23008/* @jesserockz
esphome/components/mcp23017/* @jesserockz
esphome/components/mcp23s08/* @SenexCrenshaw @jesserockz
@@ -108,10 +111,12 @@ esphome/components/mdns/* @esphome/core
esphome/components/midea/* @dudanov
esphome/components/midea_ir/* @dudanov
esphome/components/mitsubishi/* @RubyBailey
esphome/components/mlx90393/* @functionpointer
esphome/components/modbus_controller/* @martgras
esphome/components/modbus_controller/binary_sensor/* @martgras
esphome/components/modbus_controller/number/* @martgras
esphome/components/modbus_controller/output/* @martgras
esphome/components/modbus_controller/select/* @martgras @stegm
esphome/components/modbus_controller/sensor/* @martgras
esphome/components/modbus_controller/switch/* @martgras
esphome/components/modbus_controller/text_sensor/* @martgras
@@ -138,6 +143,8 @@ esphome/components/psram/* @esphome/core
esphome/components/pulse_meter/* @stevebaxter
esphome/components/pvvx_mithermometer/* @pasiz
esphome/components/qr_code/* @wjtje
esphome/components/radon_eye_ble/* @jeffeb3
esphome/components/radon_eye_rd200/* @jeffeb3
esphome/components/rc522/* @glmnet
esphome/components/rc522_i2c/* @glmnet
esphome/components/rc522_spi/* @glmnet

View File

@@ -102,7 +102,7 @@ RUN \
ARG BUILD_VERSION=dev
# Copy root filesystem
COPY docker/hassio-rootfs/ /
COPY docker/ha-addon-rootfs/ /
# First install requirements to leverage caching when requirements don't change
COPY requirements.txt requirements_optional.txt docker/platformio_install_deps.py platformio.ini /

View File

@@ -10,7 +10,7 @@ server {
ssl_certificate_key /ssl/%%keyfile%%;
# Clear Hass.io Ingress header
proxy_set_header X-Hassio-Ingress "";
proxy_set_header X-HA-Ingress "";
# Redirect http requests to https on the same port.
# https://rageagainstshell.com/2016/11/redirect-http-to-https-on-the-same-port-in-nginx/

View File

@@ -4,7 +4,7 @@ server {
include /etc/nginx/includes/server_params.conf;
include /etc/nginx/includes/proxy_params.conf;
# Clear Hass.io Ingress header
proxy_set_header X-Hassio-Ingress "";
proxy_set_header X-HA-Ingress "";
location / {
proxy_pass http://esphome;

View File

@@ -3,8 +3,8 @@ server {
include /etc/nginx/includes/server_params.conf;
include /etc/nginx/includes/proxy_params.conf;
# Set Hass.io Ingress header
proxy_set_header X-Hassio-Ingress "YES";
# Set Home Assistant Ingress header
proxy_set_header X-HA-Ingress "YES";
location / {
# Only allow from Hass.io supervisor

View File

@@ -4,7 +4,7 @@
# Runs the ESPHome dashboard
# ==============================================================================
export ESPHOME_IS_HASSIO=true
export ESPHOME_IS_HA_ADDON=true
if bashio::config.true 'leave_front_door_open'; then
export DISABLE_HA_AUTHENTICATION=true
@@ -32,4 +32,4 @@ export PLATFORMIO_CACHE_DIR="${pio_cache_base}/cache"
export PLATFORMIO_GLOBALLIB_DIR=/piolibs
bashio::log.info "Starting ESPHome dashboard..."
exec esphome dashboard /config/esphome --socket /var/run/esphome.sock --hassio
exec esphome dashboard /config/esphome --socket /var/run/esphome.sock --ha-addon

View File

@@ -661,7 +661,7 @@ def parse_args(argv):
"--open-ui", help="Open the dashboard UI in a browser.", action="store_true"
)
parser_dashboard.add_argument(
"--hassio", help=argparse.SUPPRESS, action="store_true"
"--ha-addon", help=argparse.SUPPRESS, action="store_true"
)
parser_dashboard.add_argument(
"--socket", help="Make the dashboard serve under a unix socket", type=str

View File

@@ -63,6 +63,7 @@ from esphome.cpp_types import ( # noqa
uint32,
uint64,
int32,
int64,
const_char_ptr,
NAN,
esphome_ns,
@@ -81,4 +82,5 @@ from esphome.cpp_types import ( # noqa
InternalGPIOPin,
gpio_Flags,
EntityCategory,
Parented,
)

View File

@@ -76,6 +76,8 @@ async def to_code(config):
pos = 0
for frameIndex in range(frames):
image.seek(frameIndex)
if CONF_RESIZE in config:
image.thumbnail(config[CONF_RESIZE])
frame = image.convert("RGB")
if CONF_RESIZE in config:
frame = frame.resize([width, height])

View File

@@ -41,6 +41,7 @@ service APIConnection {
rpc number_command (NumberCommandRequest) returns (void) {}
rpc select_command (SelectCommandRequest) returns (void) {}
rpc button_command (ButtonCommandRequest) returns (void) {}
rpc lock_command (LockCommandRequest) returns (void) {}
}
@@ -528,6 +529,7 @@ message ListEntitiesSwitchResponse {
bool assumed_state = 6;
bool disabled_by_default = 7;
EntityCategory entity_category = 8;
string device_class = 9;
}
message SwitchStateResponse {
option (id) = 26;
@@ -956,6 +958,63 @@ message SelectCommandRequest {
string state = 2;
}
// ==================== LOCK ====================
enum LockState {
LOCK_STATE_NONE = 0;
LOCK_STATE_LOCKED = 1;
LOCK_STATE_UNLOCKED = 2;
LOCK_STATE_JAMMED = 3;
LOCK_STATE_LOCKING = 4;
LOCK_STATE_UNLOCKING = 5;
}
enum LockCommand {
LOCK_UNLOCK = 0;
LOCK_LOCK = 1;
LOCK_OPEN = 2;
}
message ListEntitiesLockResponse {
option (id) = 58;
option (source) = SOURCE_SERVER;
option (ifdef) = "USE_LOCK";
string object_id = 1;
fixed32 key = 2;
string name = 3;
string unique_id = 4;
string icon = 5;
bool disabled_by_default = 6;
EntityCategory entity_category = 7;
bool assumed_state = 8;
bool supports_open = 9;
bool requires_code = 10;
# Not yet implemented:
string code_format = 11;
}
message LockStateResponse {
option (id) = 59;
option (source) = SOURCE_SERVER;
option (ifdef) = "USE_LOCK";
option (no_delay) = true;
fixed32 key = 1;
LockState state = 2;
}
message LockCommandRequest {
option (id) = 60;
option (source) = SOURCE_CLIENT;
option (ifdef) = "USE_LOCK";
option (no_delay) = true;
fixed32 key = 1;
LockCommand command = 2;
# Not yet implemented:
bool has_code = 3;
string code = 4;
}
// ==================== BUTTON ====================
message ListEntitiesButtonResponse {
option (id) = 61;
@@ -980,3 +1039,4 @@ message ButtonCommandRequest {
fixed32 key = 1;
}

View File

@@ -462,6 +462,7 @@ bool APIConnection::send_switch_info(switch_::Switch *a_switch) {
msg.assumed_state = a_switch->assumed_state();
msg.disabled_by_default = a_switch->is_disabled_by_default();
msg.entity_category = static_cast<enums::EntityCategory>(a_switch->get_entity_category());
msg.device_class = a_switch->get_device_class();
return this->send_list_entities_switch_response(msg);
}
void APIConnection::switch_command(const SwitchCommandRequest &msg) {
@@ -700,6 +701,49 @@ void APIConnection::button_command(const ButtonCommandRequest &msg) {
}
#endif
#ifdef USE_LOCK
bool APIConnection::send_lock_state(lock::Lock *a_lock, lock::LockState state) {
if (!this->state_subscription_)
return false;
LockStateResponse resp{};
resp.key = a_lock->get_object_id_hash();
resp.state = static_cast<enums::LockState>(state);
return this->send_lock_state_response(resp);
}
bool APIConnection::send_lock_info(lock::Lock *a_lock) {
ListEntitiesLockResponse msg;
msg.key = a_lock->get_object_id_hash();
msg.object_id = a_lock->get_object_id();
msg.name = a_lock->get_name();
msg.unique_id = get_default_unique_id("lock", a_lock);
msg.icon = a_lock->get_icon();
msg.assumed_state = a_lock->traits.get_assumed_state();
msg.disabled_by_default = a_lock->is_disabled_by_default();
msg.entity_category = static_cast<enums::EntityCategory>(a_lock->get_entity_category());
msg.supports_open = a_lock->traits.get_supports_open();
msg.requires_code = a_lock->traits.get_requires_code();
return this->send_list_entities_lock_response(msg);
}
void APIConnection::lock_command(const LockCommandRequest &msg) {
lock::Lock *a_lock = App.get_lock_by_key(msg.key);
if (a_lock == nullptr)
return;
switch (msg.command) {
case enums::LOCK_UNLOCK:
a_lock->unlock();
break;
case enums::LOCK_LOCK:
a_lock->lock();
break;
case enums::LOCK_OPEN:
a_lock->open();
break;
}
}
#endif
#ifdef USE_ESP32_CAMERA
void APIConnection::send_camera_state(std::shared_ptr<esp32_camera::CameraImage> image) {
if (!this->state_subscription_)

View File

@@ -77,6 +77,11 @@ class APIConnection : public APIServerConnection {
#ifdef USE_BUTTON
bool send_button_info(button::Button *button);
void button_command(const ButtonCommandRequest &msg) override;
#endif
#ifdef USE_LOCK
bool send_lock_state(lock::Lock *a_lock, lock::LockState state);
bool send_lock_info(lock::Lock *a_lock);
void lock_command(const LockCommandRequest &msg) override;
#endif
bool send_log_message(int level, const char *tag, const char *line);
void send_homeassistant_service_call(const HomeassistantServiceResponse &call) {

View File

@@ -278,6 +278,36 @@ template<> const char *proto_enum_to_string<enums::NumberMode>(enums::NumberMode
return "UNKNOWN";
}
}
template<> const char *proto_enum_to_string<enums::LockState>(enums::LockState value) {
switch (value) {
case enums::LOCK_STATE_NONE:
return "LOCK_STATE_NONE";
case enums::LOCK_STATE_LOCKED:
return "LOCK_STATE_LOCKED";
case enums::LOCK_STATE_UNLOCKED:
return "LOCK_STATE_UNLOCKED";
case enums::LOCK_STATE_JAMMED:
return "LOCK_STATE_JAMMED";
case enums::LOCK_STATE_LOCKING:
return "LOCK_STATE_LOCKING";
case enums::LOCK_STATE_UNLOCKING:
return "LOCK_STATE_UNLOCKING";
default:
return "UNKNOWN";
}
}
template<> const char *proto_enum_to_string<enums::LockCommand>(enums::LockCommand value) {
switch (value) {
case enums::LOCK_UNLOCK:
return "LOCK_UNLOCK";
case enums::LOCK_LOCK:
return "LOCK_LOCK";
case enums::LOCK_OPEN:
return "LOCK_OPEN";
default:
return "UNKNOWN";
}
}
bool HelloRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 1: {
@@ -2147,6 +2177,10 @@ bool ListEntitiesSwitchResponse::decode_length(uint32_t field_id, ProtoLengthDel
this->icon = value.as_string();
return true;
}
case 9: {
this->device_class = value.as_string();
return true;
}
default:
return false;
}
@@ -2170,6 +2204,7 @@ void ListEntitiesSwitchResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_bool(6, this->assumed_state);
buffer.encode_bool(7, this->disabled_by_default);
buffer.encode_enum<enums::EntityCategory>(8, this->entity_category);
buffer.encode_string(9, this->device_class);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesSwitchResponse::dump_to(std::string &out) const {
@@ -2207,6 +2242,10 @@ void ListEntitiesSwitchResponse::dump_to(std::string &out) const {
out.append(" entity_category: ");
out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
out.append("\n");
out.append(" device_class: ");
out.append("'").append(this->device_class).append("'");
out.append("\n");
out.append("}");
}
#endif
@@ -4186,6 +4225,234 @@ void SelectCommandRequest::dump_to(std::string &out) const {
out.append("}");
}
#endif
bool ListEntitiesLockResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 6: {
this->disabled_by_default = value.as_bool();
return true;
}
case 7: {
this->entity_category = value.as_enum<enums::EntityCategory>();
return true;
}
case 8: {
this->assumed_state = value.as_bool();
return true;
}
case 9: {
this->supports_open = value.as_bool();
return true;
}
case 10: {
this->requires_code = value.as_bool();
return true;
}
default:
return false;
}
}
bool ListEntitiesLockResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 1: {
this->object_id = value.as_string();
return true;
}
case 3: {
this->name = value.as_string();
return true;
}
case 4: {
this->unique_id = value.as_string();
return true;
}
case 5: {
this->icon = value.as_string();
return true;
}
case 11: {
this->code_format = value.as_string();
return true;
}
default:
return false;
}
}
bool ListEntitiesLockResponse::decode_32bit(uint32_t field_id, Proto32Bit value) {
switch (field_id) {
case 2: {
this->key = value.as_fixed32();
return true;
}
default:
return false;
}
}
void ListEntitiesLockResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(1, this->object_id);
buffer.encode_fixed32(2, this->key);
buffer.encode_string(3, this->name);
buffer.encode_string(4, this->unique_id);
buffer.encode_string(5, this->icon);
buffer.encode_bool(6, this->disabled_by_default);
buffer.encode_enum<enums::EntityCategory>(7, this->entity_category);
buffer.encode_bool(8, this->assumed_state);
buffer.encode_bool(9, this->supports_open);
buffer.encode_bool(10, this->requires_code);
buffer.encode_string(11, this->code_format);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesLockResponse::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
out.append("ListEntitiesLockResponse {\n");
out.append(" object_id: ");
out.append("'").append(this->object_id).append("'");
out.append("\n");
out.append(" key: ");
sprintf(buffer, "%u", this->key);
out.append(buffer);
out.append("\n");
out.append(" name: ");
out.append("'").append(this->name).append("'");
out.append("\n");
out.append(" unique_id: ");
out.append("'").append(this->unique_id).append("'");
out.append("\n");
out.append(" icon: ");
out.append("'").append(this->icon).append("'");
out.append("\n");
out.append(" disabled_by_default: ");
out.append(YESNO(this->disabled_by_default));
out.append("\n");
out.append(" entity_category: ");
out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
out.append("\n");
out.append(" assumed_state: ");
out.append(YESNO(this->assumed_state));
out.append("\n");
out.append(" supports_open: ");
out.append(YESNO(this->supports_open));
out.append("\n");
out.append(" requires_code: ");
out.append(YESNO(this->requires_code));
out.append("\n");
out.append(" code_format: ");
out.append("'").append(this->code_format).append("'");
out.append("\n");
out.append("}");
}
#endif
bool LockStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 2: {
this->state = value.as_enum<enums::LockState>();
return true;
}
default:
return false;
}
}
bool LockStateResponse::decode_32bit(uint32_t field_id, Proto32Bit value) {
switch (field_id) {
case 1: {
this->key = value.as_fixed32();
return true;
}
default:
return false;
}
}
void LockStateResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_fixed32(1, this->key);
buffer.encode_enum<enums::LockState>(2, this->state);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void LockStateResponse::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
out.append("LockStateResponse {\n");
out.append(" key: ");
sprintf(buffer, "%u", this->key);
out.append(buffer);
out.append("\n");
out.append(" state: ");
out.append(proto_enum_to_string<enums::LockState>(this->state));
out.append("\n");
out.append("}");
}
#endif
bool LockCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 2: {
this->command = value.as_enum<enums::LockCommand>();
return true;
}
case 3: {
this->has_code = value.as_bool();
return true;
}
default:
return false;
}
}
bool LockCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 4: {
this->code = value.as_string();
return true;
}
default:
return false;
}
}
bool LockCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
switch (field_id) {
case 1: {
this->key = value.as_fixed32();
return true;
}
default:
return false;
}
}
void LockCommandRequest::encode(ProtoWriteBuffer buffer) const {
buffer.encode_fixed32(1, this->key);
buffer.encode_enum<enums::LockCommand>(2, this->command);
buffer.encode_bool(3, this->has_code);
buffer.encode_string(4, this->code);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void LockCommandRequest::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
out.append("LockCommandRequest {\n");
out.append(" key: ");
sprintf(buffer, "%u", this->key);
out.append(buffer);
out.append("\n");
out.append(" command: ");
out.append(proto_enum_to_string<enums::LockCommand>(this->command));
out.append("\n");
out.append(" has_code: ");
out.append(YESNO(this->has_code));
out.append("\n");
out.append(" code: ");
out.append("'").append(this->code).append("'");
out.append("\n");
out.append("}");
}
#endif
bool ListEntitiesButtonResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 6: {
@@ -4248,7 +4515,7 @@ void ListEntitiesButtonResponse::encode(ProtoWriteBuffer buffer) const {
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesButtonResponse::dump_to(std::string &out) const {
char buffer[64];
__attribute__((unused)) char buffer[64];
out.append("ListEntitiesButtonResponse {\n");
out.append(" object_id: ");
out.append("'").append(this->object_id).append("'");
@@ -4298,7 +4565,7 @@ bool ButtonCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
void ButtonCommandRequest::encode(ProtoWriteBuffer buffer) const { buffer.encode_fixed32(1, this->key); }
#ifdef HAS_PROTO_MESSAGE_DUMP
void ButtonCommandRequest::dump_to(std::string &out) const {
char buffer[64];
__attribute__((unused)) char buffer[64];
out.append("ButtonCommandRequest {\n");
out.append(" key: ");
sprintf(buffer, "%u", this->key);

View File

@@ -128,6 +128,19 @@ enum NumberMode : uint32_t {
NUMBER_MODE_BOX = 1,
NUMBER_MODE_SLIDER = 2,
};
enum LockState : uint32_t {
LOCK_STATE_NONE = 0,
LOCK_STATE_LOCKED = 1,
LOCK_STATE_UNLOCKED = 2,
LOCK_STATE_JAMMED = 3,
LOCK_STATE_LOCKING = 4,
LOCK_STATE_UNLOCKING = 5,
};
enum LockCommand : uint32_t {
LOCK_UNLOCK = 0,
LOCK_LOCK = 1,
LOCK_OPEN = 2,
};
} // namespace enums
@@ -567,6 +580,7 @@ class ListEntitiesSwitchResponse : public ProtoMessage {
bool assumed_state{false};
bool disabled_by_default{false};
enums::EntityCategory entity_category{};
std::string device_class{};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
@@ -1049,6 +1063,58 @@ class SelectCommandRequest : public ProtoMessage {
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
};
class ListEntitiesLockResponse : public ProtoMessage {
public:
std::string object_id{};
uint32_t key{0};
std::string name{};
std::string unique_id{};
std::string icon{};
bool disabled_by_default{false};
enums::EntityCategory entity_category{};
bool assumed_state{false};
bool supports_open{false};
bool requires_code{false};
std::string code_format{};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class LockStateResponse : public ProtoMessage {
public:
uint32_t key{0};
enums::LockState state{};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class LockCommandRequest : public ProtoMessage {
public:
uint32_t key{0};
enums::LockCommand command{};
bool has_code{false};
std::string code{};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class ListEntitiesButtonResponse : public ProtoMessage {
public:
std::string object_id{};

View File

@@ -282,6 +282,24 @@ bool APIServerConnectionBase::send_select_state_response(const SelectStateRespon
#endif
#ifdef USE_SELECT
#endif
#ifdef USE_LOCK
bool APIServerConnectionBase::send_list_entities_lock_response(const ListEntitiesLockResponse &msg) {
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "send_list_entities_lock_response: %s", msg.dump().c_str());
#endif
return this->send_message_<ListEntitiesLockResponse>(msg, 58);
}
#endif
#ifdef USE_LOCK
bool APIServerConnectionBase::send_lock_state_response(const LockStateResponse &msg) {
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "send_lock_state_response: %s", msg.dump().c_str());
#endif
return this->send_message_<LockStateResponse>(msg, 59);
}
#endif
#ifdef USE_LOCK
#endif
#ifdef USE_BUTTON
bool APIServerConnectionBase::send_list_entities_button_response(const ListEntitiesButtonResponse &msg) {
#ifdef HAS_PROTO_MESSAGE_DUMP
@@ -523,6 +541,17 @@ bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
ESP_LOGVV(TAG, "on_select_command_request: %s", msg.dump().c_str());
#endif
this->on_select_command_request(msg);
#endif
break;
}
case 60: {
#ifdef USE_LOCK
LockCommandRequest msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_lock_command_request: %s", msg.dump().c_str());
#endif
this->on_lock_command_request(msg);
#endif
break;
}
@@ -771,6 +800,19 @@ void APIServerConnection::on_button_command_request(const ButtonCommandRequest &
this->button_command(msg);
}
#endif
#ifdef USE_LOCK
void APIServerConnection::on_lock_command_request(const LockCommandRequest &msg) {
if (!this->is_connection_setup()) {
this->on_no_setup_connection();
return;
}
if (!this->is_authenticated()) {
this->on_unauthenticated_access();
return;
}
this->lock_command(msg);
}
#endif
} // namespace api
} // namespace esphome

View File

@@ -130,6 +130,15 @@ class APIServerConnectionBase : public ProtoService {
#ifdef USE_SELECT
virtual void on_select_command_request(const SelectCommandRequest &value){};
#endif
#ifdef USE_LOCK
bool send_list_entities_lock_response(const ListEntitiesLockResponse &msg);
#endif
#ifdef USE_LOCK
bool send_lock_state_response(const LockStateResponse &msg);
#endif
#ifdef USE_LOCK
virtual void on_lock_command_request(const LockCommandRequest &value){};
#endif
#ifdef USE_BUTTON
bool send_list_entities_button_response(const ListEntitiesButtonResponse &msg);
#endif
@@ -180,6 +189,9 @@ class APIServerConnection : public APIServerConnectionBase {
#endif
#ifdef USE_BUTTON
virtual void button_command(const ButtonCommandRequest &msg) = 0;
#endif
#ifdef USE_LOCK
virtual void lock_command(const LockCommandRequest &msg) = 0;
#endif
protected:
void on_hello_request(const HelloRequest &msg) override;
@@ -221,6 +233,9 @@ class APIServerConnection : public APIServerConnectionBase {
#ifdef USE_BUTTON
void on_button_command_request(const ButtonCommandRequest &msg) override;
#endif
#ifdef USE_LOCK
void on_lock_command_request(const LockCommandRequest &msg) override;
#endif
};
} // namespace api

View File

@@ -263,6 +263,15 @@ void APIServer::on_select_update(select::Select *obj, const std::string &state)
}
#endif
#ifdef USE_LOCK
void APIServer::on_lock_update(lock::Lock *obj) {
if (obj->is_internal())
return;
for (auto &c : this->clients_)
c->send_lock_state(obj, obj->state);
}
#endif
float APIServer::get_setup_priority() const { return setup_priority::AFTER_WIFI; }
void APIServer::set_port(uint16_t port) { this->port_ = port; }
APIServer *global_api_server = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)

View File

@@ -66,6 +66,9 @@ class APIServer : public Component, public Controller {
#endif
#ifdef USE_SELECT
void on_select_update(select::Select *obj, const std::string &state) override;
#endif
#ifdef USE_LOCK
void on_lock_update(lock::Lock *obj) override;
#endif
void send_homeassistant_service_call(const HomeassistantServiceResponse &call);
void register_user_service(UserServiceDescriptor *descriptor) { this->user_services_.push_back(descriptor); }

View File

@@ -35,6 +35,9 @@ bool ListEntitiesIterator::on_text_sensor(text_sensor::TextSensor *text_sensor)
return this->client_->send_text_sensor_info(text_sensor);
}
#endif
#ifdef USE_LOCK
bool ListEntitiesIterator::on_lock(lock::Lock *a_lock) { return this->client_->send_lock_info(a_lock); }
#endif
bool ListEntitiesIterator::on_end() { return this->client_->send_list_info_done(); }
ListEntitiesIterator::ListEntitiesIterator(APIServer *server, APIConnection *client)

View File

@@ -48,6 +48,9 @@ class ListEntitiesIterator : public ComponentIterator {
#endif
#ifdef USE_SELECT
bool on_select(select::Select *select) override;
#endif
#ifdef USE_LOCK
bool on_lock(lock::Lock *a_lock) override;
#endif
bool on_end() override;

View File

@@ -47,6 +47,9 @@ bool InitialStateIterator::on_select(select::Select *select) {
return this->client_->send_select_state(select, select->state);
}
#endif
#ifdef USE_LOCK
bool InitialStateIterator::on_lock(lock::Lock *a_lock) { return this->client_->send_lock_state(a_lock, a_lock->state); }
#endif
InitialStateIterator::InitialStateIterator(APIServer *server, APIConnection *client)
: ComponentIterator(server), client_(client) {}

View File

@@ -45,6 +45,9 @@ class InitialStateIterator : public ComponentIterator {
#endif
#ifdef USE_SELECT
bool on_select(select::Select *select) override;
#endif
#ifdef USE_LOCK
bool on_lock(lock::Lock *a_lock) override;
#endif
protected:
APIConnection *client_;

View File

@@ -212,6 +212,21 @@ void ComponentIterator::advance() {
}
}
break;
#endif
#ifdef USE_LOCK
case IteratorState::LOCK:
if (this->at_ >= App.get_locks().size()) {
advance_platform = true;
} else {
auto *a_lock = App.get_locks()[this->at_];
if (a_lock->is_internal()) {
success = true;
break;
} else {
success = this->on_lock(a_lock);
}
}
break;
#endif
case IteratorState::MAX:
if (this->on_end()) {

View File

@@ -56,6 +56,9 @@ class ComponentIterator {
#endif
#ifdef USE_SELECT
virtual bool on_select(select::Select *select) = 0;
#endif
#ifdef USE_LOCK
virtual bool on_lock(lock::Lock *a_lock) = 0;
#endif
virtual bool on_end();
@@ -99,6 +102,9 @@ class ComponentIterator {
#endif
#ifdef USE_SELECT
SELECT,
#endif
#ifdef USE_LOCK
LOCK,
#endif
MAX,
} state_{IteratorState::NONE};

View File

@@ -395,15 +395,19 @@ BLEDescriptor *BLECharacteristic::get_descriptor(uint16_t uuid) {
return this->get_descriptor(espbt::ESPBTUUID::from_uint16(uuid));
}
void BLECharacteristic::write_value(uint8_t *new_val, int16_t new_val_size) {
void BLECharacteristic::write_value(uint8_t *new_val, int16_t new_val_size, esp_gatt_write_type_t write_type) {
auto *client = this->service->client;
auto status = esp_ble_gattc_write_char(client->gattc_if, client->conn_id, this->handle, new_val_size, new_val,
ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
write_type, ESP_GATT_AUTH_REQ_NONE);
if (status) {
ESP_LOGW(TAG, "Error sending write value to BLE gattc server, status=%d", status);
}
}
void BLECharacteristic::write_value(uint8_t *new_val, int16_t new_val_size) {
write_value(new_val, new_val_size, ESP_GATT_WRITE_TYPE_NO_RSP);
}
} // namespace ble_client
} // namespace esphome

View File

@@ -60,6 +60,7 @@ class BLECharacteristic {
BLEDescriptor *get_descriptor(espbt::ESPBTUUID uuid);
BLEDescriptor *get_descriptor(uint16_t uuid);
void write_value(uint8_t *new_val, int16_t new_val_size);
void write_value(uint8_t *new_val, int16_t new_val_size, esp_gatt_write_type_t write_type);
BLEService *service;
};

View File

@@ -1,13 +1,14 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import output, ble_client, esp32_ble_tracker
from esphome.components import ble_client, esp32_ble_tracker, output
from esphome.const import CONF_ID, CONF_SERVICE_UUID
from .. import ble_client_ns
from .. import ble_client_ns
DEPENDENCIES = ["ble_client"]
CONF_CHARACTERISTIC_UUID = "characteristic_uuid"
CONF_REQUIRE_RESPONSE = "require_response"
BLEBinaryOutput = ble_client_ns.class_(
"BLEBinaryOutput", output.BinaryOutput, ble_client.BLEClientNode, cg.Component
@@ -19,6 +20,7 @@ CONFIG_SCHEMA = cv.All(
cv.Required(CONF_ID): cv.declare_id(BLEBinaryOutput),
cv.Required(CONF_SERVICE_UUID): esp32_ble_tracker.bt_uuid,
cv.Required(CONF_CHARACTERISTIC_UUID): esp32_ble_tracker.bt_uuid,
cv.Optional(CONF_REQUIRE_RESPONSE, default=False): cv.boolean,
}
)
.extend(cv.COMPONENT_SCHEMA)
@@ -61,7 +63,7 @@ def to_code(config):
config[CONF_CHARACTERISTIC_UUID]
)
cg.add(var.set_char_uuid128(uuid128))
cg.add(var.set_require_response(config[CONF_REQUIRE_RESPONSE]))
yield output.register_output(var, config)
yield ble_client.register_ble_node(var, config)
yield cg.register_component(var, config)

View File

@@ -63,7 +63,11 @@ void BLEBinaryOutput::write_state(bool state) {
uint8_t state_as_uint = (uint8_t) state;
ESP_LOGV(TAG, "[%s] Write State: %d", this->char_uuid_.to_string().c_str(), state_as_uint);
chr->write_value(&state_as_uint, sizeof(state_as_uint));
if (this->require_response_) {
chr->write_value(&state_as_uint, sizeof(state_as_uint), ESP_GATT_WRITE_TYPE_RSP);
} else {
chr->write_value(&state_as_uint, sizeof(state_as_uint), ESP_GATT_WRITE_TYPE_NO_RSP);
}
}
} // namespace ble_client

View File

@@ -25,9 +25,11 @@ class BLEBinaryOutput : public output::BinaryOutput, public BLEClientNode, publi
void set_char_uuid128(uint8_t *uuid) { this->char_uuid_ = espbt::ESPBTUUID::from_raw(uuid); }
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_require_response(bool response) { this->require_response_ = response; }
protected:
void write_state(bool state) override;
bool require_response_;
espbt::ESPBTUUID service_uuid_;
espbt::ESPBTUUID char_uuid_;
espbt::ClientState client_state_;

View File

@@ -1,7 +1,6 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import text_sensor, esp32_ble_tracker
from esphome.const import CONF_ID
DEPENDENCIES = ["esp32_ble_tracker"]
@@ -14,18 +13,13 @@ BLEScanner = ble_scanner_ns.class_(
)
CONFIG_SCHEMA = cv.All(
text_sensor.TEXT_SENSOR_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(BLEScanner),
}
)
text_sensor.text_sensor_schema(klass=BLEScanner)
.extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA)
.extend(cv.COMPONENT_SCHEMA)
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
var = await text_sensor.new_text_sensor(config)
await cg.register_component(var, config)
await esp32_ble_tracker.register_ble_device(var, config)
await text_sensor.register_text_sensor(var, config)

View File

@@ -1,7 +1,6 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import text_sensor
from esphome.const import CONF_ID, CONF_ICON
from . import BME680BSECComponent, CONF_BME680_BSEC_ID
DEPENDENCIES = ["bme680_bsec"]
@@ -14,11 +13,8 @@ TYPES = [CONF_IAQ_ACCURACY]
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(CONF_BME680_BSEC_ID): cv.use_id(BME680BSECComponent),
cv.Optional(CONF_IAQ_ACCURACY): text_sensor.TEXT_SENSOR_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(text_sensor.TextSensor),
cv.Optional(CONF_ICON, default=ICON_ACCURACY): cv.icon,
}
cv.Optional(CONF_IAQ_ACCURACY): text_sensor.text_sensor_schema(
icon=ICON_ACCURACY
),
}
)
@@ -27,8 +23,7 @@ CONFIG_SCHEMA = cv.Schema(
async def setup_conf(config, key, hub):
if key in config:
conf = config[key]
sens = cg.new_Pvariable(conf[CONF_ID])
await text_sensor.register_text_sensor(sens, conf)
sens = await text_sensor.new_text_sensor(conf)
cg.add(getattr(hub, f"set_{key}_text_sensor")(sens))

View File

@@ -14,10 +14,14 @@ CONF_BIT_RATE = "bit_rate"
CONF_ON_FRAME = "on_frame"
def validate_id(id_value, id_ext):
if not id_ext:
if id_value > 0x7FF:
raise cv.Invalid("Standard IDs must be 11 Bit (0x000-0x7ff / 0-2047)")
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)")
return config
def validate_raw_data(value):
@@ -67,23 +71,18 @@ CANBUS_SCHEMA = cv.Schema(
cv.Optional(CONF_ON_FRAME): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(CanbusTrigger),
cv.GenerateID(CONF_CAN_ID): cv.int_range(min=0, max=0x1FFFFFFF),
cv.Required(CONF_CAN_ID): cv.int_range(min=0, max=0x1FFFFFFF),
cv.Optional(CONF_USE_EXTENDED_ID, default=False): cv.boolean,
cv.Optional(CONF_ON_FRAME): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(CanbusTrigger),
cv.GenerateID(CONF_CAN_ID): cv.int_range(min=0, max=0x1FFFFFFF),
cv.Optional(CONF_USE_EXTENDED_ID, default=False): cv.boolean,
}
),
}
},
validate_id,
),
}
},
).extend(cv.COMPONENT_SCHEMA)
CANBUS_SCHEMA.add_extra(validate_id)
async def setup_canbus_core_(var, config):
validate_id(config[CONF_CAN_ID], config[CONF_USE_EXTENDED_ID])
await cg.register_component(var, config)
cg.add(var.set_can_id([config[CONF_CAN_ID]]))
cg.add(var.set_use_extended_id([config[CONF_USE_EXTENDED_ID]]))
@@ -92,7 +91,6 @@ async def setup_canbus_core_(var, config):
for conf in config.get(CONF_ON_FRAME, []):
can_id = conf[CONF_CAN_ID]
ext_id = conf[CONF_USE_EXTENDED_ID]
validate_id(can_id, ext_id)
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var, can_id, ext_id)
await cg.register_component(trigger, conf)
await automation.build_automation(
@@ -117,11 +115,11 @@ async def register_canbus(var, config):
cv.Optional(CONF_USE_EXTENDED_ID, default=False): cv.boolean,
cv.Required(CONF_DATA): cv.templatable(validate_raw_data),
},
validate_id,
key=CONF_DATA,
),
)
async def canbus_action_to_code(config, action_id, template_arg, args):
validate_id(config[CONF_CAN_ID], config[CONF_USE_EXTENDED_ID])
var = cg.new_Pvariable(action_id, template_arg)
await cg.register_parented(var, config[CONF_CANBUS_ID])

View File

@@ -2,7 +2,6 @@ import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import i2c, sensor, text_sensor
from esphome.const import (
CONF_ICON,
CONF_ID,
ICON_RADIATOR,
ICON_RESTART,
@@ -47,11 +46,8 @@ CONFIG_SCHEMA = (
device_class=DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_VERSION): text_sensor.TEXT_SENSOR_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(text_sensor.TextSensor),
cv.Optional(CONF_ICON, default=ICON_RESTART): cv.icon,
}
cv.Optional(CONF_VERSION): text_sensor.text_sensor_schema(
icon=ICON_RESTART
),
cv.Optional(CONF_BASELINE): cv.hex_uint16_t,
cv.Optional(CONF_TEMPERATURE): cv.use_id(sensor.Sensor),
@@ -74,8 +70,7 @@ async def to_code(config):
cg.add(var.set_tvoc(sens))
if CONF_VERSION in config:
sens = cg.new_Pvariable(config[CONF_VERSION][CONF_ID])
await text_sensor.register_text_sensor(sens, config[CONF_VERSION])
sens = await text_sensor.new_text_sensor(config[CONF_VERSION])
cg.add(var.set_version(sens))
if CONF_BASELINE in config:

View File

@@ -7,6 +7,7 @@ from esphome.const import (
CONF_ID,
CONF_DEVICE_CLASS,
CONF_STATE,
CONF_ON_OPEN,
CONF_POSITION,
CONF_POSITION_COMMAND_TOPIC,
CONF_POSITION_STATE_TOPIC,
@@ -74,7 +75,6 @@ CoverClosedTrigger = cover_ns.class_(
"CoverClosedTrigger", automation.Trigger.template()
)
CONF_ON_OPEN = "on_open"
CONF_ON_CLOSED = "on_closed"
COVER_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend(

View File

@@ -11,11 +11,7 @@ CONFIG_SCHEMA = cv.Schema(
cv.GenerateID(): cv.declare_id(CustomTextSensorConstructor),
cv.Required(CONF_LAMBDA): cv.returning_lambda,
cv.Required(CONF_TEXT_SENSORS): cv.ensure_list(
text_sensor.TEXT_SENSOR_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(text_sensor.TextSensor),
}
)
text_sensor.text_sensor_schema()
),
}
)

View File

@@ -1,7 +1,7 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import text_sensor
from esphome.const import CONF_ICON, CONF_ID, CONF_STATUS
from esphome.const import CONF_STATUS
from . import DalyBmsComponent, CONF_BMS_DALY_ID
ICON_CAR_BATTERY = "mdi:car-battery"
@@ -14,11 +14,8 @@ CONFIG_SCHEMA = cv.All(
cv.Schema(
{
cv.GenerateID(CONF_BMS_DALY_ID): cv.use_id(DalyBmsComponent),
cv.Optional(CONF_STATUS): text_sensor.TEXT_SENSOR_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(text_sensor.TextSensor),
cv.Optional(CONF_ICON, default=ICON_CAR_BATTERY): cv.icon,
}
cv.Optional(CONF_STATUS): text_sensor.text_sensor_schema(
icon=ICON_CAR_BATTERY
),
}
).extend(cv.COMPONENT_SCHEMA)
@@ -28,8 +25,7 @@ CONFIG_SCHEMA = cv.All(
async def setup_conf(config, key, hub):
if key in config:
conf = config[key]
sens = cg.new_Pvariable(conf[CONF_ID])
await text_sensor.register_text_sensor(sens, conf)
sens = await text_sensor.new_text_sensor(conf)
cg.add(getattr(hub, f"set_{key}_text_sensor")(sens))

View File

@@ -1,6 +1,5 @@
import esphome.config_validation as cv
import esphome.codegen as cg
from esphome.components import sensor, text_sensor
from esphome.const import (
CONF_ID,
CONF_DEVICE,
@@ -8,16 +7,12 @@ from esphome.const import (
CONF_FRAGMENTATION,
CONF_BLOCK,
CONF_LOOP_TIME,
UNIT_MILLISECOND,
UNIT_PERCENT,
UNIT_BYTES,
ICON_COUNTER,
ICON_TIMER,
)
CODEOWNERS = ["@OttoWinter"]
DEPENDENCIES = ["logger"]
CONF_DEBUG_ID = "debug_id"
debug_ns = cg.esphome_ns.namespace("debug")
DebugComponent = debug_ns.class_("DebugComponent", cg.PollingComponent)
@@ -25,18 +20,20 @@ DebugComponent = debug_ns.class_("DebugComponent", cg.PollingComponent)
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(): cv.declare_id(DebugComponent),
cv.Optional(CONF_DEVICE): text_sensor.TEXT_SENSOR_SCHEMA.extend(
{cv.GenerateID(): cv.declare_id(text_sensor.TextSensor)}
cv.Optional(CONF_DEVICE): cv.invalid(
"The 'device' option has been moved to the 'debug' text_sensor component"
),
cv.Optional(CONF_FREE): sensor.sensor_schema(UNIT_BYTES, ICON_COUNTER, 0),
cv.Optional(CONF_BLOCK): sensor.sensor_schema(UNIT_BYTES, ICON_COUNTER, 0),
cv.Optional(CONF_FRAGMENTATION): cv.All(
cv.only_on_esp8266,
cv.require_framework_version(esp8266_arduino=cv.Version(2, 5, 2)),
sensor.sensor_schema(UNIT_PERCENT, ICON_COUNTER, 1),
cv.Optional(CONF_FREE): cv.invalid(
"The 'free' option has been moved to the 'debug' sensor component"
),
cv.Optional(CONF_LOOP_TIME): sensor.sensor_schema(
UNIT_MILLISECOND, ICON_TIMER, 1
cv.Optional(CONF_BLOCK): cv.invalid(
"The 'block' option has been moved to the 'debug' sensor component"
),
cv.Optional(CONF_FRAGMENTATION): cv.invalid(
"The 'fragmentation' option has been moved to the 'debug' sensor component"
),
cv.Optional(CONF_LOOP_TIME): cv.invalid(
"The 'loop_time' option has been moved to the 'debug' sensor component"
),
}
).extend(cv.polling_component_schema("60s"))
@@ -45,24 +42,3 @@ CONFIG_SCHEMA = cv.Schema(
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
if CONF_DEVICE in config:
sens = cg.new_Pvariable(config[CONF_DEVICE][CONF_ID])
await text_sensor.register_text_sensor(sens, config[CONF_DEVICE])
cg.add(var.set_device_info_sensor(sens))
if CONF_FREE in config:
sens = await sensor.new_sensor(config[CONF_FREE])
cg.add(var.set_free_sensor(sens))
if CONF_BLOCK in config:
sens = await sensor.new_sensor(config[CONF_BLOCK])
cg.add(var.set_block_sensor(sens))
if CONF_FRAGMENTATION in config:
sens = await sensor.new_sensor(config[CONF_FRAGMENTATION])
cg.add(var.set_fragmentation_sensor(sens))
if CONF_LOOP_TIME in config:
sens = await sensor.new_sensor(config[CONF_LOOP_TIME])
cg.add(var.set_loop_time_sensor(sens))

View File

@@ -47,12 +47,16 @@ void DebugComponent::dump_config() {
#endif
ESP_LOGCONFIG(TAG, "Debug component:");
#ifdef USE_TEXT_SENSOR
LOG_TEXT_SENSOR(" ", "Device info", this->device_info_);
#endif // USE_TEXT_SENSOR
#ifdef USE_SENSOR
LOG_SENSOR(" ", "Free space on heap", this->free_sensor_);
LOG_SENSOR(" ", "Largest free heap block", this->block_sensor_);
#if defined(USE_ESP8266) && ARDUINO_VERSION_CODE >= VERSION_CODE(2, 5, 2)
LOG_SENSOR(" ", "Heap fragmentation", this->fragmentation_sensor_);
#endif
#endif // defined(USE_ESP8266) && ARDUINO_VERSION_CODE >= VERSION_CODE(2, 5, 2)
#endif // USE_SENSOR
ESP_LOGD(TAG, "ESPHome version %s", ESPHOME_VERSION);
device_info += ESPHOME_VERSION;
@@ -268,11 +272,13 @@ void DebugComponent::dump_config() {
device_info += ESP.getResetInfo().c_str();
#endif
#ifdef USE_TEXT_SENSOR
if (this->device_info_ != nullptr) {
if (device_info.length() > 255)
device_info.resize(255);
this->device_info_->publish_state(device_info);
}
#endif // USE_TEXT_SENSOR
}
void DebugComponent::loop() {
@@ -284,6 +290,7 @@ void DebugComponent::loop() {
this->status_momentary_warning("heap", 1000);
}
#ifdef USE_SENSOR
// calculate loop time - from last call to this one
if (this->loop_time_sensor_ != nullptr) {
uint32_t now = millis();
@@ -291,9 +298,11 @@ void DebugComponent::loop() {
this->max_loop_time_ = std::max(this->max_loop_time_, loop_time);
this->last_loop_timetag_ = now;
}
#endif // USE_SENSOR
}
void DebugComponent::update() {
#ifdef USE_SENSOR
if (this->free_sensor_ != nullptr) {
this->free_sensor_->publish_state(get_free_heap());
}
@@ -318,6 +327,7 @@ void DebugComponent::update() {
this->loop_time_sensor_->publish_state(this->max_loop_time_);
this->max_loop_time_ = 0;
}
#endif // USE_SENSOR
}
float DebugComponent::get_setup_priority() const { return setup_priority::LATE; }

View File

@@ -1,10 +1,16 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/defines.h"
#include "esphome/core/macros.h"
#include "esphome/core/helpers.h"
#include "esphome/components/text_sensor/text_sensor.h"
#ifdef USE_SENSOR
#include "esphome/components/sensor/sensor.h"
#endif
#ifdef USE_TEXT_SENSOR
#include "esphome/components/text_sensor/text_sensor.h"
#endif
namespace esphome {
namespace debug {
@@ -16,27 +22,35 @@ class DebugComponent : public PollingComponent {
float get_setup_priority() const override;
void dump_config() override;
#ifdef USE_TEXT_SENSOR
void set_device_info_sensor(text_sensor::TextSensor *device_info) { device_info_ = device_info; }
#endif // USE_TEXT_SENSOR
#ifdef USE_SENSOR
void set_free_sensor(sensor::Sensor *free_sensor) { free_sensor_ = free_sensor; }
void set_block_sensor(sensor::Sensor *block_sensor) { block_sensor_ = block_sensor; }
#if defined(USE_ESP8266) && ARDUINO_VERSION_CODE >= VERSION_CODE(2, 5, 2)
void set_fragmentation_sensor(sensor::Sensor *fragmentation_sensor) { fragmentation_sensor_ = fragmentation_sensor; }
#endif
void set_loop_time_sensor(sensor::Sensor *loop_time_sensor) { loop_time_sensor_ = loop_time_sensor; }
#endif // USE_SENSOR
protected:
uint32_t free_heap_{};
#ifdef USE_SENSOR
uint32_t last_loop_timetag_{0};
uint32_t max_loop_time_{0};
text_sensor::TextSensor *device_info_{nullptr};
sensor::Sensor *free_sensor_{nullptr};
sensor::Sensor *block_sensor_{nullptr};
#if defined(USE_ESP8266) && ARDUINO_VERSION_CODE >= VERSION_CODE(2, 5, 2)
sensor::Sensor *fragmentation_sensor_{nullptr};
#endif
sensor::Sensor *loop_time_sensor_{nullptr};
#endif // USE_SENSOR
#ifdef USE_TEXT_SENSOR
text_sensor::TextSensor *device_info_{nullptr};
#endif // USE_TEXT_SENSOR
};
} // namespace debug

View File

@@ -0,0 +1,49 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import sensor
from esphome.const import (
CONF_FREE,
CONF_FRAGMENTATION,
CONF_BLOCK,
CONF_LOOP_TIME,
UNIT_MILLISECOND,
UNIT_PERCENT,
UNIT_BYTES,
ICON_COUNTER,
ICON_TIMER,
)
from . import CONF_DEBUG_ID, DebugComponent
DEPENDENCIES = ["debug"]
CONFIG_SCHEMA = {
cv.GenerateID(CONF_DEBUG_ID): cv.use_id(DebugComponent),
cv.Optional(CONF_FREE): sensor.sensor_schema(UNIT_BYTES, ICON_COUNTER, 0),
cv.Optional(CONF_BLOCK): sensor.sensor_schema(UNIT_BYTES, ICON_COUNTER, 0),
cv.Optional(CONF_FRAGMENTATION): cv.All(
cv.only_on_esp8266,
cv.require_framework_version(esp8266_arduino=cv.Version(2, 5, 2)),
sensor.sensor_schema(UNIT_PERCENT, ICON_COUNTER, 1),
),
cv.Optional(CONF_LOOP_TIME): sensor.sensor_schema(UNIT_MILLISECOND, ICON_TIMER, 0),
}
async def to_code(config):
debug_component = await cg.get_variable(config[CONF_DEBUG_ID])
if CONF_FREE in config:
sens = await sensor.new_sensor(config[CONF_FREE])
cg.add(debug_component.set_free_sensor(sens))
if CONF_BLOCK in config:
sens = await sensor.new_sensor(config[CONF_BLOCK])
cg.add(debug_component.set_block_sensor(sens))
if CONF_FRAGMENTATION in config:
sens = await sensor.new_sensor(config[CONF_FRAGMENTATION])
cg.add(debug_component.set_fragmentation_sensor(sens))
if CONF_LOOP_TIME in config:
sens = await sensor.new_sensor(config[CONF_LOOP_TIME])
cg.add(debug_component.set_loop_time_sensor(sens))

View File

@@ -0,0 +1,24 @@
from esphome.components import text_sensor
import esphome.config_validation as cv
import esphome.codegen as cg
from esphome.const import CONF_DEVICE
from . import CONF_DEBUG_ID, DebugComponent
DEPENDENCIES = ["debug"]
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(CONF_DEBUG_ID): cv.use_id(DebugComponent),
cv.Optional(CONF_DEVICE): text_sensor.text_sensor_schema(),
}
)
async def to_code(config):
debug_component = await cg.get_variable(config[CONF_DEBUG_ID])
if CONF_DEVICE in config:
sens = await text_sensor.new_text_sensor(config[CONF_DEVICE])
cg.add(debug_component.set_device_info_sensor(sens))

View File

@@ -37,12 +37,10 @@ from esphome.const import (
DEVICE_CLASS_TEMPERATURE,
ICON_BLUETOOTH,
ICON_BLUR,
ICON_EMPTY,
ICON_THERMOMETER,
STATE_CLASS_MEASUREMENT,
STATE_CLASS_TOTAL_INCREASING,
UNIT_CELSIUS,
UNIT_EMPTY,
UNIT_PERCENT,
UNIT_WATT_HOURS,
)
@@ -339,7 +337,7 @@ CONFIG_SCHEMA = cv.Schema(
},
],
): [
sensor.sensor_schema(UNIT_EMPTY, ICON_EMPTY, 0)
sensor.sensor_schema(accuracy_decimals=0)
.extend(cv.polling_component_schema("60s"))
.extend(
{
@@ -378,12 +376,8 @@ CONFIG_SCHEMA = cv.Schema(
},
],
): [
text_sensor.TEXT_SENSOR_SCHEMA.extend(
text_sensor.text_sensor_schema(klass=DemoTextSensor).extend(
cv.polling_component_schema("60s")
).extend(
{
cv.GenerateID(): cv.declare_id(DemoTextSensor),
}
)
],
}
@@ -443,6 +437,5 @@ async def to_code(config):
await switch.register_switch(var, conf)
for conf in config[CONF_TEXT_SENSORS]:
var = cg.new_Pvariable(conf[CONF_ID])
var = await text_sensor.new_text_sensor(conf)
await cg.register_component(var, conf)
await text_sensor.register_text_sensor(var, conf)

View File

@@ -1,9 +1,7 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import text_sensor
from esphome.const import (
CONF_ID,
)
from . import Dsmr, CONF_DSMR_ID
AUTO_LOAD = ["dsmr"]
@@ -11,71 +9,19 @@ AUTO_LOAD = ["dsmr"]
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(CONF_DSMR_ID): cv.use_id(Dsmr),
cv.Optional("identification"): text_sensor.TEXT_SENSOR_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(text_sensor.TextSensor),
}
),
cv.Optional("p1_version"): text_sensor.TEXT_SENSOR_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(text_sensor.TextSensor),
}
),
cv.Optional("p1_version_be"): text_sensor.TEXT_SENSOR_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(text_sensor.TextSensor),
}
),
cv.Optional("timestamp"): text_sensor.TEXT_SENSOR_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(text_sensor.TextSensor),
}
),
cv.Optional("electricity_tariff"): text_sensor.TEXT_SENSOR_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(text_sensor.TextSensor),
}
),
cv.Optional("electricity_failure_log"): text_sensor.TEXT_SENSOR_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(text_sensor.TextSensor),
}
),
cv.Optional("message_short"): text_sensor.TEXT_SENSOR_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(text_sensor.TextSensor),
}
),
cv.Optional("message_long"): text_sensor.TEXT_SENSOR_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(text_sensor.TextSensor),
}
),
cv.Optional("gas_equipment_id"): text_sensor.TEXT_SENSOR_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(text_sensor.TextSensor),
}
),
cv.Optional("thermal_equipment_id"): text_sensor.TEXT_SENSOR_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(text_sensor.TextSensor),
}
),
cv.Optional("water_equipment_id"): text_sensor.TEXT_SENSOR_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(text_sensor.TextSensor),
}
),
cv.Optional("sub_equipment_id"): text_sensor.TEXT_SENSOR_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(text_sensor.TextSensor),
}
),
cv.Optional("gas_delivered_text"): text_sensor.TEXT_SENSOR_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(text_sensor.TextSensor),
}
),
cv.Optional("identification"): text_sensor.text_sensor_schema(),
cv.Optional("p1_version"): text_sensor.text_sensor_schema(),
cv.Optional("p1_version_be"): text_sensor.text_sensor_schema(),
cv.Optional("timestamp"): text_sensor.text_sensor_schema(),
cv.Optional("electricity_tariff"): text_sensor.text_sensor_schema(),
cv.Optional("electricity_failure_log"): text_sensor.text_sensor_schema(),
cv.Optional("message_short"): text_sensor.text_sensor_schema(),
cv.Optional("message_long"): text_sensor.text_sensor_schema(),
cv.Optional("gas_equipment_id"): text_sensor.text_sensor_schema(),
cv.Optional("thermal_equipment_id"): text_sensor.text_sensor_schema(),
cv.Optional("water_equipment_id"): text_sensor.text_sensor_schema(),
cv.Optional("sub_equipment_id"): text_sensor.text_sensor_schema(),
cv.Optional("gas_delivered_text"): text_sensor.text_sensor_schema(),
}
).extend(cv.COMPONENT_SCHEMA)
@@ -89,8 +35,7 @@ async def to_code(config):
continue
id = conf.get("id")
if id and id.type == text_sensor.TextSensor:
var = cg.new_Pvariable(conf[CONF_ID])
await text_sensor.register_text_sensor(var, conf)
var = await text_sensor.new_text_sensor(conf)
cg.add(getattr(hub, f"set_{key}")(var))
text_sensors.append(f"F({key})")

View File

@@ -1,13 +1,16 @@
# Source https://github.com/letscontrolit/ESPEasy/pull/3845#issuecomment-1005864664
import esptool
from SCons.Script import ARGUMENTS
# pylint: disable=E0602
Import("env") # noqa
def esp32_create_combined_bin(source, target, env):
print("Generating combined binary for serial flashing")
verbose = bool(int(ARGUMENTS.get("PIOVERBOSE", "0")))
if verbose:
print("Generating combined binary for serial flashing")
app_offset = 0x10000
new_file_name = env.subst("$BUILD_DIR/${PROGNAME}-factory.bin")
@@ -24,18 +27,21 @@ def esp32_create_combined_bin(source, target, env):
"--flash_size",
flash_size,
]
print(" Offset | File")
if verbose:
print(" Offset | File")
for section in sections:
sect_adr, sect_file = section.split(" ", 1)
print(f" - {sect_adr} | {sect_file}")
if verbose:
print(f" - {sect_adr} | {sect_file}")
cmd += [sect_adr, sect_file]
print(f" - {hex(app_offset)} | {firmware_name}")
cmd += [hex(app_offset), firmware_name]
print()
print(f"Using esptool.py arguments: {' '.join(cmd)}")
print()
if verbose:
print(f" - {hex(app_offset)} | {firmware_name}")
print()
print(f"Using esptool.py arguments: {' '.join(cmd)}")
print()
esptool.main(cmd)

View File

@@ -42,6 +42,11 @@ void BLEAdvertising::remove_service_uuid(ESPBTUUID uuid) {
this->advertising_uuids_.end());
}
void BLEAdvertising::set_manufacturer_data(uint8_t *data, uint16_t size) {
this->advertising_data_.p_manufacturer_data = data;
this->advertising_data_.manufacturer_len = size;
}
void BLEAdvertising::start() {
int num_services = this->advertising_uuids_.size();
if (num_services == 0) {

View File

@@ -20,6 +20,7 @@ class BLEAdvertising {
void remove_service_uuid(ESPBTUUID uuid);
void set_scan_response(bool scan_response) { this->scan_response_ = scan_response; }
void set_min_preferred_interval(uint16_t interval) { this->advertising_data_.min_interval = interval; }
void set_manufacturer_data(uint8_t *data, uint16_t size);
void start();
void stop();

View File

@@ -56,7 +56,7 @@ async def to_code(config):
cg.add(ble_server.register_service_component(var))
cg.add_define("USE_IMPROV")
cg.add_library("esphome/Improv", "1.2.0")
cg.add_library("esphome/Improv", "1.2.1")
cg.add(var.set_identify_duration(config[CONF_IDENTIFY_DURATION]))
cg.add(var.set_authorized_duration(config[CONF_AUTHORIZED_DURATION]))

View File

@@ -166,6 +166,7 @@ async def to_code(config):
cg.add_platformio_option("framework", "arduino")
cg.add_build_flag("-DUSE_ARDUINO")
cg.add_build_flag("-DUSE_ESP8266_FRAMEWORK_ARDUINO")
cg.add_build_flag("-Wno-nonnull-compare")
cg.add_platformio_option("platform", conf[CONF_PLATFORM_VERSION])
cg.add_platformio_option(
"platform_packages",

View File

@@ -49,7 +49,12 @@ CONFIG_SCHEMA = cv.All(
}
),
_validate,
cv.only_with_arduino,
cv.require_framework_version(
esp8266_arduino=cv.Version(2, 7, 4),
esp32_arduino=cv.Version(99, 0, 0),
max_version=True,
extra_message="Please see note on documentation for FastLED",
),
)

View File

@@ -33,7 +33,12 @@ CONFIG_SCHEMA = cv.All(
cv.Optional(CONF_DATA_RATE): cv.frequency,
}
),
cv.only_with_arduino,
cv.require_framework_version(
esp8266_arduino=cv.Version(2, 7, 4),
esp32_arduino=cv.Version(99, 0, 0),
max_version=True,
extra_message="Please see note on documentation for FastLED",
),
)

View File

@@ -1,7 +1,8 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import text_sensor
from esphome.const import CONF_ATTRIBUTE, CONF_ENTITY_ID, CONF_ID
from esphome.const import CONF_ATTRIBUTE, CONF_ENTITY_ID
from .. import homeassistant_ns
DEPENDENCIES = ["api"]
@@ -10,7 +11,7 @@ HomeassistantTextSensor = homeassistant_ns.class_(
"HomeassistantTextSensor", text_sensor.TextSensor, cg.Component
)
CONFIG_SCHEMA = text_sensor.TEXT_SENSOR_SCHEMA.extend(
CONFIG_SCHEMA = text_sensor.text_sensor_schema().extend(
{
cv.GenerateID(): cv.declare_id(HomeassistantTextSensor),
cv.Required(CONF_ENTITY_ID): cv.entity_id,
@@ -20,9 +21,8 @@ CONFIG_SCHEMA = text_sensor.TEXT_SENSOR_SCHEMA.extend(
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
var = await text_sensor.new_text_sensor(config)
await cg.register_component(var, config)
await text_sensor.register_text_sensor(var, config)
cg.add(var.set_entity_id(config[CONF_ENTITY_ID]))
if CONF_ATTRIBUTE in config:

View File

@@ -30,4 +30,4 @@ FINAL_VALIDATE_SCHEMA = validate_logger_baud_rate
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
cg.add_library("esphome/Improv", "1.2.0")
cg.add_library("esphome/Improv", "1.2.1")

View File

@@ -47,6 +47,7 @@ InkplateModel = inkplate6_ns.enum("InkplateModel")
MODELS = {
"inkplate_6": InkplateModel.INKPLATE_6,
"inkplate_10": InkplateModel.INKPLATE_10,
"inkplate_6_plus": InkplateModel.INKPLATE_6_PLUS,
}
CONFIG_SCHEMA = cv.All(

View File

@@ -13,6 +13,11 @@ namespace inkplate6 {
static const char *const TAG = "inkplate";
void Inkplate6::setup() {
for (uint32_t i = 0; i < 256; i++) {
this->pin_lut_[i] = ((i & 0b00000011) << 4) | (((i & 0b00001100) >> 2) << 18) | (((i & 0b00010000) >> 4) << 23) |
(((i & 0b11100000) >> 5) << 25);
}
this->initialize_();
this->vcom_pin_->setup();
@@ -38,11 +43,21 @@ void Inkplate6::setup() {
this->display_data_6_pin_->setup();
this->display_data_7_pin_->setup();
this->clean();
this->display();
this->wakeup_pin_->digital_write(true);
delay(1);
this->write_bytes(0x09, {
0b00011011, // Power up seq.
0b00000000, // Power up delay (3mS per rail)
0b00011011, // Power down seq.
0b00000000, // Power down delay (6mS per rail)
});
delay(1);
this->wakeup_pin_->digital_write(false);
}
void Inkplate6::initialize_() {
ExternalRAMAllocator<uint8_t> allocator(ExternalRAMAllocator<uint8_t>::ALLOW_FAILURE);
ExternalRAMAllocator<uint32_t> allocator32(ExternalRAMAllocator<uint32_t>::ALLOW_FAILURE);
uint32_t buffer_size = this->get_buffer_length_();
if (buffer_size == 0)
return;
@@ -53,6 +68,10 @@ void Inkplate6::initialize_() {
allocator.deallocate(this->partial_buffer_2_, buffer_size * 2);
if (this->buffer_ != nullptr)
allocator.deallocate(this->buffer_, buffer_size);
if (this->glut_ != nullptr)
allocator32.deallocate(this->glut_, 256 * (this->model_ == INKPLATE_6_PLUS ? 9 : 8));
if (this->glut2_ != nullptr)
allocator32.deallocate(this->glut2_, 256 * (this->model_ == INKPLATE_6_PLUS ? 9 : 8));
this->buffer_ = allocator.allocate(buffer_size);
if (this->buffer_ == nullptr) {
@@ -60,7 +79,34 @@ void Inkplate6::initialize_() {
this->mark_failed();
return;
}
if (!this->greyscale_) {
if (this->greyscale_) {
uint8_t glut_size = (this->model_ == INKPLATE_6_PLUS ? 9 : 8);
this->glut_ = allocator32.allocate(256 * glut_size);
if (this->glut_ == nullptr) {
ESP_LOGE(TAG, "Could not allocate glut!");
this->mark_failed();
return;
}
this->glut2_ = allocator32.allocate(256 * glut_size);
if (this->glut2_ == nullptr) {
ESP_LOGE(TAG, "Could not allocate glut2!");
this->mark_failed();
return;
}
for (int i = 0; i < glut_size; i++) {
for (uint32_t j = 0; j < 256; j++) {
uint8_t z = (waveform3Bit[j & 0x07][i] << 2) | (waveform3Bit[(j >> 4) & 0x07][i]);
this->glut_[i * 256 + j] = ((z & 0b00000011) << 4) | (((z & 0b00001100) >> 2) << 18) |
(((z & 0b00010000) >> 4) << 23) | (((z & 0b11100000) >> 5) << 25);
z = ((waveform3Bit[j & 0x07][i] << 2) | (waveform3Bit[(j >> 4) & 0x07][i])) << 4;
this->glut2_[i * 256 + j] = ((z & 0b00000011) << 4) | (((z & 0b00001100) >> 2) << 18) |
(((z & 0b00010000) >> 4) << 23) | (((z & 0b11100000) >> 5) << 25);
}
}
} else {
this->partial_buffer_ = allocator.allocate(buffer_size);
if (this->partial_buffer_ == nullptr) {
ESP_LOGE(TAG, "Could not allocate partial buffer for display!");
@@ -73,13 +119,16 @@ void Inkplate6::initialize_() {
this->mark_failed();
return;
}
memset(this->partial_buffer_, 0, buffer_size);
memset(this->partial_buffer_2_, 0, buffer_size * 2);
}
memset(this->buffer_, 0, buffer_size);
}
float Inkplate6::get_setup_priority() const { return setup_priority::PROCESSOR; }
size_t Inkplate6::get_buffer_length_() {
if (this->greyscale_) {
return size_t(this->get_width_internal()) * size_t(this->get_height_internal()) / 2u;
@@ -87,6 +136,7 @@ size_t Inkplate6::get_buffer_length_() {
return size_t(this->get_width_internal()) * size_t(this->get_height_internal()) / 8u;
}
}
void Inkplate6::update() {
this->do_update_();
@@ -96,6 +146,7 @@ void Inkplate6::update() {
this->display();
}
void HOT Inkplate6::draw_absolute_pixel_internal(int x, int y, Color color) {
if (x >= this->get_width_internal() || y >= this->get_height_internal() || x < 0 || y < 0)
return;
@@ -121,6 +172,7 @@ void HOT Inkplate6::draw_absolute_pixel_internal(int x, int y, Color color) {
this->partial_buffer_[pos] = (~pixelMaskLUT[x_sub] & current) | (color.is_on() ? 0 : pixelMaskLUT[x_sub]);
}
}
void Inkplate6::dump_config() {
LOG_DISPLAY("", "Inkplate", this);
ESP_LOGCONFIG(TAG, " Greyscale: %s", YESNO(this->greyscale_));
@@ -150,44 +202,51 @@ void Inkplate6::dump_config() {
LOG_UPDATE_INTERVAL(this);
}
void Inkplate6::eink_off_() {
ESP_LOGV(TAG, "Eink off called");
if (panel_on_ == 0)
if (!panel_on_)
return;
panel_on_ = 0;
this->gmod_pin_->digital_write(false);
panel_on_ = false;
this->oe_pin_->digital_write(false);
this->gmod_pin_->digital_write(false);
GPIO.out &= ~(get_data_pin_mask_() | (1 << this->cl_pin_->get_pin()) | (1 << this->le_pin_->get_pin()));
GPIO.out &= ~(this->get_data_pin_mask_() | (1 << this->cl_pin_->get_pin()) | (1 << this->le_pin_->get_pin()));
this->ckv_pin_->digital_write(false);
this->sph_pin_->digital_write(false);
this->spv_pin_->digital_write(false);
this->powerup_pin_->digital_write(false);
this->wakeup_pin_->digital_write(false);
this->vcom_pin_->digital_write(false);
this->write_byte(0x01, 0x6F); // Put TPS65186 into standby mode
delay(100); // NOLINT
this->write_byte(0x01, 0x4f); // Disable 3V3 to the panel
if (this->model_ != INKPLATE_6_PLUS)
this->wakeup_pin_->digital_write(false);
pins_z_state_();
}
void Inkplate6::eink_on_() {
ESP_LOGV(TAG, "Eink on called");
if (panel_on_ == 1)
if (panel_on_)
return;
panel_on_ = 1;
pins_as_outputs_();
this->panel_on_ = true;
this->pins_as_outputs_();
this->wakeup_pin_->digital_write(true);
this->powerup_pin_->digital_write(true);
this->vcom_pin_->digital_write(true);
this->write_byte(0x01, 0x3F);
delay(40);
this->write_byte(0x0D, 0x80);
delay(2);
this->read_register(0x00, nullptr, 0);
this->write_byte(0x01, 0b00101111); // Enable all rails
delay(1);
this->write_byte(0x01, 0b10101111); // Switch TPS65186 into active mode
this->le_pin_->digital_write(false);
this->oe_pin_->digital_write(false);
@@ -196,8 +255,33 @@ void Inkplate6::eink_on_() {
this->gmod_pin_->digital_write(true);
this->spv_pin_->digital_write(true);
this->ckv_pin_->digital_write(false);
this->oe_pin_->digital_write(false);
uint32_t timer = millis();
do {
delay(1);
} while (!this->read_power_status_() && ((millis() - timer) < 250));
if ((millis() - timer) >= 250) {
ESP_LOGW(TAG, "Power supply not detected");
this->wakeup_pin_->digital_write(false);
this->vcom_pin_->digital_write(false);
this->powerup_pin_->digital_write(false);
this->panel_on_ = false;
return;
}
this->oe_pin_->digital_write(true);
}
bool Inkplate6::read_power_status_() {
uint8_t data;
auto err = this->read_register(0x0F, &data, 1);
if (err == i2c::ERROR_OK) {
return data == 0b11111010;
}
return false;
}
void Inkplate6::fill(Color color) {
ESP_LOGV(TAG, "Fill called");
uint32_t start_time = millis();
@@ -212,6 +296,7 @@ void Inkplate6::fill(Color color) {
ESP_LOGV(TAG, "Fill finished (%ums)", millis() - start_time);
}
void Inkplate6::display() {
ESP_LOGV(TAG, "Display called");
uint32_t start_time = millis();
@@ -227,201 +312,185 @@ void Inkplate6::display() {
}
ESP_LOGV(TAG, "Display finished (full) (%ums)", millis() - start_time);
}
void Inkplate6::display1b_() {
ESP_LOGV(TAG, "Display1b called");
uint32_t start_time = millis();
memcpy(this->buffer_, this->partial_buffer_, this->get_buffer_length_());
uint32_t send;
uint8_t data;
uint8_t buffer_value;
const uint8_t *buffer_ptr;
eink_on_();
clean_fast_(0, 1);
clean_fast_(1, 5);
clean_fast_(2, 1);
clean_fast_(0, 5);
clean_fast_(2, 1);
clean_fast_(1, 12);
clean_fast_(2, 1);
clean_fast_(0, 11);
if (this->model_ == INKPLATE_6_PLUS) {
clean_fast_(0, 1);
clean_fast_(1, 15);
clean_fast_(2, 1);
clean_fast_(0, 5);
clean_fast_(2, 1);
clean_fast_(1, 15);
} else {
clean_fast_(0, 1);
clean_fast_(1, 21);
clean_fast_(2, 1);
clean_fast_(0, 12);
clean_fast_(2, 1);
clean_fast_(1, 21);
clean_fast_(2, 1);
clean_fast_(0, 12);
}
uint32_t clock = (1 << this->cl_pin_->get_pin());
uint32_t data_mask = this->get_data_pin_mask_();
ESP_LOGV(TAG, "Display1b start loops (%ums)", millis() - start_time);
for (int k = 0; k < 3; k++) {
for (int k = 0; k < 4; k++) {
buffer_ptr = &this->buffer_[this->get_buffer_length_() - 1];
vscan_start_();
for (int i = 0; i < this->get_height_internal(); i++) {
for (int i = 0, im = this->get_height_internal(); i < im; i++) {
buffer_value = *(buffer_ptr--);
data = LUTB[(buffer_value >> 4) & 0x0F];
send = ((data & 0b00000011) << 4) | (((data & 0b00001100) >> 2) << 18) | (((data & 0b00010000) >> 4) << 23) |
(((data & 0b11100000) >> 5) << 25);
hscan_start_(send);
data = LUTB[buffer_value & 0x0F];
send = ((data & 0b00000011) << 4) | (((data & 0b00001100) >> 2) << 18) | (((data & 0b00010000) >> 4) << 23) |
(((data & 0b11100000) >> 5) << 25) | clock;
GPIO.out_w1ts = send;
GPIO.out_w1tc = send;
data = this->model_ == INKPLATE_6_PLUS ? LUTW[(~buffer_value >> 4) & 0x0F] : LUTB[(buffer_value >> 4) & 0x0F];
hscan_start_(this->pin_lut_[data]);
data = this->model_ == INKPLATE_6_PLUS ? LUTW[(~buffer_value) & 0x0F] : LUTB[buffer_value & 0x0F];
GPIO.out_w1ts = this->pin_lut_[data] | clock;
GPIO.out_w1tc = data_mask | clock;
for (int j = 0, jm = (this->get_width_internal() / 8) - 1; j < jm; j++) {
buffer_value = *(buffer_ptr--);
data = LUTB[(buffer_value >> 4) & 0x0F];
send = ((data & 0b00000011) << 4) | (((data & 0b00001100) >> 2) << 18) | (((data & 0b00010000) >> 4) << 23) |
(((data & 0b11100000) >> 5) << 25) | clock;
GPIO.out_w1ts = send;
GPIO.out_w1tc = send;
data = LUTB[buffer_value & 0x0F];
send = ((data & 0b00000011) << 4) | (((data & 0b00001100) >> 2) << 18) | (((data & 0b00010000) >> 4) << 23) |
(((data & 0b11100000) >> 5) << 25) | clock;
GPIO.out_w1ts = send;
GPIO.out_w1tc = send;
data = this->model_ == INKPLATE_6_PLUS ? LUTW[(~buffer_value >> 4) & 0x0F] : LUTB[(buffer_value >> 4) & 0x0F];
GPIO.out_w1ts = this->pin_lut_[data] | clock;
GPIO.out_w1tc = data_mask | clock;
data = this->model_ == INKPLATE_6_PLUS ? LUTW[(~buffer_value) & 0x0F] : LUTB[buffer_value & 0x0F];
GPIO.out_w1ts = this->pin_lut_[data] | clock;
GPIO.out_w1tc = data_mask | clock;
}
GPIO.out_w1ts = send;
GPIO.out_w1tc = get_data_pin_mask_() | clock;
GPIO.out_w1ts = clock;
GPIO.out_w1tc = data_mask | clock;
vscan_end_();
}
delayMicroseconds(230);
}
ESP_LOGV(TAG, "Display1b first loop x %d (%ums)", 3, millis() - start_time);
ESP_LOGV(TAG, "Display1b first loop x %d (%ums)", 4, millis() - start_time);
buffer_ptr = &this->buffer_[this->get_buffer_length_() - 1];
vscan_start_();
for (int i = 0; i < this->get_height_internal(); i++) {
for (int i = 0, im = this->get_height_internal(); i < im; i++) {
buffer_value = *(buffer_ptr--);
data = LUT2[(buffer_value >> 4) & 0x0F];
send = ((data & 0b00000011) << 4) | (((data & 0b00001100) >> 2) << 18) | (((data & 0b00010000) >> 4) << 23) |
(((data & 0b11100000) >> 5) << 25);
hscan_start_(send);
data = LUT2[buffer_value & 0x0F];
send = ((data & 0b00000011) << 4) | (((data & 0b00001100) >> 2) << 18) | (((data & 0b00010000) >> 4) << 23) |
(((data & 0b11100000) >> 5) << 25) | clock;
GPIO.out_w1ts = send;
GPIO.out_w1tc = send;
data = this->model_ == INKPLATE_6_PLUS ? LUTB[(buffer_value >> 4) & 0x0F] : LUT2[(buffer_value >> 4) & 0x0F];
hscan_start_(this->pin_lut_[data] | clock);
data = this->model_ == INKPLATE_6_PLUS ? LUTB[buffer_value & 0x0F] : LUT2[buffer_value & 0x0F];
GPIO.out_w1ts = this->pin_lut_[data] | clock;
GPIO.out_w1tc = data_mask | clock;
for (int j = 0, jm = (this->get_width_internal() / 8) - 1; j < jm; j++) {
buffer_value = *(buffer_ptr--);
data = LUT2[(buffer_value >> 4) & 0x0F];
send = ((data & 0b00000011) << 4) | (((data & 0b00001100) >> 2) << 18) | (((data & 0b00010000) >> 4) << 23) |
(((data & 0b11100000) >> 5) << 25) | clock;
GPIO.out_w1ts = send;
GPIO.out_w1tc = send;
data = LUT2[buffer_value & 0x0F];
send = ((data & 0b00000011) << 4) | (((data & 0b00001100) >> 2) << 18) | (((data & 0b00010000) >> 4) << 23) |
(((data & 0b11100000) >> 5) << 25) | clock;
GPIO.out_w1ts = send;
GPIO.out_w1tc = send;
data = this->model_ == INKPLATE_6_PLUS ? LUTB[(buffer_value >> 4) & 0x0F] : LUT2[(buffer_value >> 4) & 0x0F];
GPIO.out_w1ts = this->pin_lut_[data] | clock;
GPIO.out_w1tc = data_mask | clock;
data = this->model_ == INKPLATE_6_PLUS ? LUTB[buffer_value & 0x0F] : LUT2[buffer_value & 0x0F];
GPIO.out_w1ts = this->pin_lut_[data] | clock;
GPIO.out_w1tc = data_mask | clock;
}
GPIO.out_w1ts = send;
GPIO.out_w1tc = get_data_pin_mask_() | clock;
GPIO.out_w1ts = clock;
GPIO.out_w1tc = data_mask | clock;
vscan_end_();
}
delayMicroseconds(230);
ESP_LOGV(TAG, "Display1b second loop (%ums)", millis() - start_time);
vscan_start_();
for (int i = 0; i < this->get_height_internal(); i++) {
data = 0b00000000;
send = ((data & 0b00000011) << 4) | (((data & 0b00001100) >> 2) << 18) | (((data & 0b00010000) >> 4) << 23) |
(((data & 0b11100000) >> 5) << 25);
hscan_start_(send);
send |= clock;
GPIO.out_w1ts = send;
GPIO.out_w1tc = send;
for (int j = 0; j < (this->get_width_internal() / 8) - 1; j++) {
GPIO.out_w1ts = send;
GPIO.out_w1tc = send;
GPIO.out_w1ts = send;
GPIO.out_w1tc = send;
if (this->model_ == INKPLATE_6_PLUS) {
clean_fast_(2, 2);
clean_fast_(3, 1);
} else {
uint32_t send = this->pin_lut_[0];
vscan_start_();
for (int i = 0, im = this->get_height_internal(); i < im; i++) {
hscan_start_(send);
GPIO.out_w1ts = send | clock;
GPIO.out_w1tc = data_mask | clock;
for (int j = 0, jm = (this->get_width_internal() / 8) - 1; j < jm; j++) {
GPIO.out_w1ts = send | clock;
GPIO.out_w1tc = data_mask | clock;
GPIO.out_w1ts = send | clock;
GPIO.out_w1tc = data_mask | clock;
}
GPIO.out_w1ts = send | clock;
GPIO.out_w1tc = data_mask | clock;
vscan_end_();
}
GPIO.out_w1ts = clock;
GPIO.out_w1tc = get_data_pin_mask_() | clock;
vscan_end_();
delayMicroseconds(230);
ESP_LOGV(TAG, "Display1b third loop (%ums)", millis() - start_time);
}
delayMicroseconds(230);
ESP_LOGV(TAG, "Display1b third loop (%ums)", millis() - start_time);
vscan_start_();
eink_off_();
this->block_partial_ = false;
this->partial_updates_ = 0;
ESP_LOGV(TAG, "Display1b finished (%ums)", millis() - start_time);
}
void Inkplate6::display3b_() {
ESP_LOGV(TAG, "Display3b called");
uint32_t start_time = millis();
eink_on_();
clean_fast_(0, 1);
clean_fast_(1, 12);
clean_fast_(2, 1);
clean_fast_(0, 11);
clean_fast_(2, 1);
clean_fast_(1, 12);
clean_fast_(2, 1);
clean_fast_(0, 11);
if (this->model_ == INKPLATE_6_PLUS) {
clean_fast_(0, 1);
clean_fast_(1, 15);
clean_fast_(2, 1);
clean_fast_(0, 5);
clean_fast_(2, 1);
clean_fast_(1, 15);
} else {
clean_fast_(0, 1);
clean_fast_(1, 21);
clean_fast_(2, 1);
clean_fast_(0, 12);
clean_fast_(2, 1);
clean_fast_(1, 21);
clean_fast_(2, 1);
clean_fast_(0, 12);
}
uint32_t clock = (1 << this->cl_pin_->get_pin());
for (int k = 0; k < 8; k++) {
const uint8_t *buffer_ptr = &this->buffer_[this->get_buffer_length_() - 1];
uint32_t send;
uint8_t pix1;
uint8_t pix2;
uint8_t pix3;
uint8_t pix4;
uint8_t pixel;
uint8_t pixel2;
uint32_t data_mask = this->get_data_pin_mask_();
uint32_t pos;
uint32_t data;
uint8_t glut_size = this->model_ == INKPLATE_6_PLUS ? 9 : 8;
for (int k = 0; k < glut_size; k++) {
pos = this->get_buffer_length_();
vscan_start_();
for (int i = 0; i < this->get_height_internal(); i++) {
pix1 = (*buffer_ptr--);
pix2 = (*buffer_ptr--);
pix3 = (*buffer_ptr--);
pix4 = (*buffer_ptr--);
pixel = (waveform3Bit[pix1 & 0x07][k] << 6) | (waveform3Bit[(pix1 >> 4) & 0x07][k] << 4) |
(waveform3Bit[pix2 & 0x07][k] << 2) | (waveform3Bit[(pix2 >> 4) & 0x07][k] << 0);
pixel2 = (waveform3Bit[pix3 & 0x07][k] << 6) | (waveform3Bit[(pix3 >> 4) & 0x07][k] << 4) |
(waveform3Bit[pix4 & 0x07][k] << 2) | (waveform3Bit[(pix4 >> 4) & 0x07][k] << 0);
data = this->glut2_[k * 256 + this->buffer_[--pos]];
data |= this->glut_[k * 256 + this->buffer_[--pos]];
hscan_start_(data);
data = this->glut2_[k * 256 + this->buffer_[--pos]];
data |= this->glut_[k * 256 + this->buffer_[--pos]];
GPIO.out_w1ts = data | clock;
GPIO.out_w1tc = data_mask | clock;
send = ((pixel & 0b00000011) << 4) | (((pixel & 0b00001100) >> 2) << 18) | (((pixel & 0b00010000) >> 4) << 23) |
(((pixel & 0b11100000) >> 5) << 25);
hscan_start_(send);
send = ((pixel2 & 0b00000011) << 4) | (((pixel2 & 0b00001100) >> 2) << 18) |
(((pixel2 & 0b00010000) >> 4) << 23) | (((pixel2 & 0b11100000) >> 5) << 25) | clock;
GPIO.out_w1ts = send;
GPIO.out_w1tc = send;
for (int j = 0, jm = (this->get_width_internal() / 8) - 1; j < jm; j++) {
pix1 = (*buffer_ptr--);
pix2 = (*buffer_ptr--);
pix3 = (*buffer_ptr--);
pix4 = (*buffer_ptr--);
pixel = (waveform3Bit[pix1 & 0x07][k] << 6) | (waveform3Bit[(pix1 >> 4) & 0x07][k] << 4) |
(waveform3Bit[pix2 & 0x07][k] << 2) | (waveform3Bit[(pix2 >> 4) & 0x07][k] << 0);
pixel2 = (waveform3Bit[pix3 & 0x07][k] << 6) | (waveform3Bit[(pix3 >> 4) & 0x07][k] << 4) |
(waveform3Bit[pix4 & 0x07][k] << 2) | (waveform3Bit[(pix4 >> 4) & 0x07][k] << 0);
send = ((pixel & 0b00000011) << 4) | (((pixel & 0b00001100) >> 2) << 18) | (((pixel & 0b00010000) >> 4) << 23) |
(((pixel & 0b11100000) >> 5) << 25) | clock;
GPIO.out_w1ts = send;
GPIO.out_w1tc = send;
send = ((pixel2 & 0b00000011) << 4) | (((pixel2 & 0b00001100) >> 2) << 18) |
(((pixel2 & 0b00010000) >> 4) << 23) | (((pixel2 & 0b11100000) >> 5) << 25) | clock;
GPIO.out_w1ts = send;
GPIO.out_w1tc = send;
for (int j = 0; j < (this->get_width_internal() / 8) - 1; j++) {
data = this->glut2_[k * 256 + this->buffer_[--pos]];
data |= this->glut_[k * 256 + this->buffer_[--pos]];
GPIO.out_w1ts = data | clock;
GPIO.out_w1tc = data_mask | clock;
data = this->glut2_[k * 256 + this->buffer_[--pos]];
data |= this->glut_[k * 256 + this->buffer_[--pos]];
GPIO.out_w1ts = data | clock;
GPIO.out_w1tc = data_mask | clock;
}
GPIO.out_w1ts = send;
GPIO.out_w1tc = get_data_pin_mask_() | clock;
GPIO.out_w1ts = clock;
GPIO.out_w1tc = data_mask | clock;
vscan_end_();
}
delayMicroseconds(230);
}
clean_fast_(2, 1);
clean_fast_(3, 1);
vscan_start_();
eink_off_();
ESP_LOGV(TAG, "Display3b finished (%ums)", millis() - start_time);
}
bool Inkplate6::partial_update_() {
ESP_LOGV(TAG, "Partial update called");
uint32_t start_time = millis();
@@ -432,16 +501,15 @@ bool Inkplate6::partial_update_() {
this->partial_updates_++;
uint16_t pos = this->get_buffer_length_() - 1;
uint32_t send;
uint32_t pos = this->get_buffer_length_() - 1;
uint8_t data;
uint8_t diffw, diffb;
uint32_t n = (this->get_buffer_length_() * 2) - 1;
for (int i = 0, im = this->get_height_internal(); i < im; i++) {
for (int j = 0, jm = (this->get_width_internal() / 8); j < jm; j++) {
diffw = (this->buffer_[pos] ^ this->partial_buffer_[pos]) & ~(this->partial_buffer_[pos]);
diffb = (this->buffer_[pos] ^ this->partial_buffer_[pos]) & this->partial_buffer_[pos];
diffw = this->buffer_[pos] & ~(this->partial_buffer_[pos]);
diffb = ~(this->buffer_[pos]) & this->partial_buffer_[pos];
pos--;
this->partial_buffer_2_[n--] = LUTW[diffw >> 4] & LUTB[diffb >> 4];
this->partial_buffer_2_[n--] = LUTW[diffw & 0x0F] & LUTB[diffb & 0x0F];
@@ -451,23 +519,20 @@ bool Inkplate6::partial_update_() {
eink_on_();
uint32_t clock = (1 << this->cl_pin_->get_pin());
uint32_t data_mask = this->get_data_pin_mask_();
for (int k = 0; k < 5; k++) {
vscan_start_();
const uint8_t *data_ptr = &this->partial_buffer_2_[(this->get_buffer_length_() * 2) - 1];
for (int i = 0; i < this->get_height_internal(); i++) {
data = *(data_ptr--);
send = ((data & 0b00000011) << 4) | (((data & 0b00001100) >> 2) << 18) | (((data & 0b00010000) >> 4) << 23) |
(((data & 0b11100000) >> 5) << 25);
hscan_start_(send);
hscan_start_(this->pin_lut_[data]);
for (int j = 0, jm = (this->get_width_internal() / 4) - 1; j < jm; j++) {
data = *(data_ptr--);
send = ((data & 0b00000011) << 4) | (((data & 0b00001100) >> 2) << 18) | (((data & 0b00010000) >> 4) << 23) |
(((data & 0b11100000) >> 5) << 25) | clock;
GPIO.out_w1ts = send;
GPIO.out_w1tc = send;
GPIO.out_w1ts = this->pin_lut_[data] | clock;
GPIO.out_w1tc = data_mask | clock;
}
GPIO.out_w1ts = send;
GPIO.out_w1tc = get_data_pin_mask_() | clock;
GPIO.out_w1ts = clock;
GPIO.out_w1tc = data_mask | clock;
vscan_end_();
}
delayMicroseconds(230);
@@ -482,6 +547,7 @@ bool Inkplate6::partial_update_() {
ESP_LOGV(TAG, "Partial update finished (%ums)", millis() - start_time);
return true;
}
void Inkplate6::vscan_start_() {
this->ckv_pin_->digital_write(true);
delayMicroseconds(7);
@@ -505,30 +571,23 @@ void Inkplate6::vscan_start_() {
delayMicroseconds(0);
this->ckv_pin_->digital_write(true);
}
void Inkplate6::vscan_write_() {
this->ckv_pin_->digital_write(false);
this->le_pin_->digital_write(true);
this->le_pin_->digital_write(false);
delayMicroseconds(0);
void Inkplate6::hscan_start_(uint32_t d) {
uint8_t clock = (1 << this->cl_pin_->get_pin());
this->sph_pin_->digital_write(false);
this->cl_pin_->digital_write(true);
this->cl_pin_->digital_write(false);
GPIO.out_w1ts = d | clock;
GPIO.out_w1tc = this->get_data_pin_mask_() | clock;
this->sph_pin_->digital_write(true);
this->ckv_pin_->digital_write(true);
}
void Inkplate6::hscan_start_(uint32_t d) {
this->sph_pin_->digital_write(false);
GPIO.out_w1ts = (d) | (1 << this->cl_pin_->get_pin());
GPIO.out_w1tc = get_data_pin_mask_() | (1 << this->cl_pin_->get_pin());
this->sph_pin_->digital_write(true);
}
void Inkplate6::vscan_end_() {
this->ckv_pin_->digital_write(false);
this->le_pin_->digital_write(true);
this->le_pin_->digital_write(false);
delayMicroseconds(1);
this->ckv_pin_->digital_write(true);
delayMicroseconds(0);
}
void Inkplate6::clean() {
ESP_LOGV(TAG, "Clean called");
uint32_t start_time = millis();
@@ -542,6 +601,7 @@ void Inkplate6::clean() {
clean_fast_(1, 10); // White to White
ESP_LOGV(TAG, "Clean finished (%ums)", millis() - start_time);
}
void Inkplate6::clean_fast_(uint8_t c, uint8_t rep) {
ESP_LOGV(TAG, "Clean fast called with: (%d, %d)", c, rep);
uint32_t start_time = millis();
@@ -568,14 +628,14 @@ void Inkplate6::clean_fast_(uint8_t c, uint8_t rep) {
hscan_start_(send);
GPIO.out_w1ts = send | clock;
GPIO.out_w1tc = clock;
for (int j = 0, jm = this->get_width_internal() / 8; j < jm; j++) {
for (int j = 0; j < (this->get_width_internal() / 8) - 1; j++) {
GPIO.out_w1ts = clock;
GPIO.out_w1tc = clock;
GPIO.out_w1ts = clock;
GPIO.out_w1tc = clock;
}
GPIO.out_w1ts = clock;
GPIO.out_w1tc = get_data_pin_mask_() | clock;
GPIO.out_w1ts = send | clock;
GPIO.out_w1tc = clock;
vscan_end_();
}
delayMicroseconds(230);
@@ -583,7 +643,10 @@ void Inkplate6::clean_fast_(uint8_t c, uint8_t rep) {
}
ESP_LOGV(TAG, "Clean fast finished (%ums)", millis() - start_time);
}
void Inkplate6::pins_z_state_() {
this->cl_pin_->pin_mode(gpio::FLAG_INPUT);
this->le_pin_->pin_mode(gpio::FLAG_INPUT);
this->ckv_pin_->pin_mode(gpio::FLAG_INPUT);
this->sph_pin_->pin_mode(gpio::FLAG_INPUT);
@@ -600,7 +663,10 @@ void Inkplate6::pins_z_state_() {
this->display_data_6_pin_->pin_mode(gpio::FLAG_INPUT);
this->display_data_7_pin_->pin_mode(gpio::FLAG_INPUT);
}
void Inkplate6::pins_as_outputs_() {
this->cl_pin_->pin_mode(gpio::FLAG_OUTPUT);
this->le_pin_->pin_mode(gpio::FLAG_OUTPUT);
this->ckv_pin_->pin_mode(gpio::FLAG_OUTPUT);
this->sph_pin_->pin_mode(gpio::FLAG_OUTPUT);

View File

@@ -13,32 +13,28 @@ namespace inkplate6 {
enum InkplateModel : uint8_t {
INKPLATE_6 = 0,
INKPLATE_10 = 1,
INKPLATE_6_PLUS = 2,
};
class Inkplate6 : public PollingComponent, public display::DisplayBuffer, public i2c::I2CDevice {
public:
const uint8_t LUT2[16] = {0b10101010, 0b10101001, 0b10100110, 0b10100101, 0b10011010, 0b10011001,
0b10010110, 0b10010101, 0b01101010, 0b01101001, 0b01100110, 0b01100101,
0b01011010, 0b01011001, 0b01010110, 0b01010101};
const uint8_t LUTW[16] = {0b11111111, 0b11111110, 0b11111011, 0b11111010, 0b11101111, 0b11101110,
0b11101011, 0b11101010, 0b10111111, 0b10111110, 0b10111011, 0b10111010,
0b10101111, 0b10101110, 0b10101011, 0b10101010};
const uint8_t LUTB[16] = {0b11111111, 0b11111101, 0b11110111, 0b11110101, 0b11011111, 0b11011101,
0b11010111, 0b11010101, 0b01111111, 0b01111101, 0b01110111, 0b01110101,
0b01011111, 0b01011101, 0b01010111, 0b01010101};
const uint8_t pixelMaskLUT[8] = {0b00000001, 0b00000010, 0b00000100, 0b00001000,
0b00010000, 0b00100000, 0b01000000, 0b10000000};
const uint8_t pixelMaskGLUT[2] = {0b00001111, 0b11110000};
const uint8_t waveform3Bit[8][8] = {{0, 0, 0, 0, 1, 1, 1, 0}, {1, 2, 2, 2, 1, 1, 1, 0}, {0, 1, 2, 1, 1, 2, 1, 0},
{0, 2, 1, 2, 1, 2, 1, 0}, {0, 0, 0, 1, 1, 1, 2, 0}, {2, 1, 1, 1, 2, 1, 2, 0},
const uint8_t LUT2[16] = {0xAA, 0xA9, 0xA6, 0xA5, 0x9A, 0x99, 0x96, 0x95,
0x6A, 0x69, 0x66, 0x65, 0x5A, 0x59, 0x56, 0x55};
const uint8_t LUTW[16] = {0xFF, 0xFE, 0xFB, 0xFA, 0xEF, 0xEE, 0xEB, 0xEA,
0xBF, 0xBE, 0xBB, 0xBA, 0xAF, 0xAE, 0xAB, 0xAA};
const uint8_t LUTB[16] = {0xFF, 0xFD, 0xF7, 0xF5, 0xDF, 0xDD, 0xD7, 0xD5,
0x7F, 0x7D, 0x77, 0x75, 0x5F, 0x5D, 0x57, 0x55};
const uint8_t pixelMaskLUT[8] = {0x1, 0x2, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80};
const uint8_t pixelMaskGLUT[2] = {0x0F, 0xF0};
const uint8_t waveform3Bit[8][8] = {{0, 1, 1, 0, 0, 1, 1, 0}, {0, 1, 2, 1, 1, 2, 1, 0}, {1, 1, 1, 2, 2, 1, 0, 0},
{0, 0, 0, 1, 1, 1, 2, 0}, {2, 1, 1, 1, 2, 1, 2, 0}, {2, 2, 1, 1, 2, 1, 2, 0},
{1, 1, 1, 2, 1, 2, 2, 0}, {0, 0, 0, 0, 0, 0, 2, 0}};
const uint32_t waveform[50] = {
0x00000008, 0x00000008, 0x00200408, 0x80281888, 0x60a81898, 0x60a8a8a8, 0x60a8a8a8, 0x6068a868, 0x6868a868,
0x6868a868, 0x68686868, 0x6a686868, 0x5a686868, 0x5a686868, 0x5a586a68, 0x5a5a6a68, 0x5a5a6a68, 0x55566a68,
0x55565a64, 0x55555654, 0x55555556, 0x55555556, 0x55555556, 0x55555516, 0x55555596, 0x15555595, 0x95955595,
0x95959595, 0x95949495, 0x94949495, 0x94949495, 0xa4949494, 0x9494a4a4, 0x84a49494, 0x84948484, 0x84848484,
0x84848484, 0x84848484, 0xa5a48484, 0xa9a4a4a8, 0xa9a8a8a8, 0xa5a9a9a4, 0xa5a5a5a4, 0xa1a5a5a1, 0xa9a9a9a9,
0xa9a9a9a9, 0xa9a9a9a9, 0xa9a9a9a9, 0x15151515, 0x11111111};
const uint8_t waveform3Bit6Plus[8][9] = {{0, 0, 0, 0, 0, 2, 1, 1, 0}, {0, 0, 2, 1, 1, 1, 2, 1, 0},
{0, 2, 2, 2, 1, 1, 2, 1, 0}, {0, 0, 2, 2, 2, 1, 2, 1, 0},
{0, 0, 0, 0, 2, 2, 2, 1, 0}, {0, 0, 2, 1, 2, 1, 1, 2, 0},
{0, 0, 2, 2, 2, 1, 1, 2, 0}, {0, 0, 0, 0, 2, 2, 2, 2, 0}};
void set_greyscale(bool greyscale) {
this->greyscale_ = greyscale;
@@ -88,6 +84,8 @@ class Inkplate6 : public PollingComponent, public display::DisplayBuffer, public
bool get_partial_updating() { return this->partial_updating_; }
uint8_t get_temperature() { return this->temperature_; }
void block_partial() { this->block_partial_ = true; }
protected:
void draw_absolute_pixel_internal(int x, int y, Color color) override;
void display1b_();
@@ -99,10 +97,10 @@ class Inkplate6 : public PollingComponent, public display::DisplayBuffer, public
void hscan_start_(uint32_t d);
void vscan_end_();
void vscan_start_();
void vscan_write_();
void eink_off_();
void eink_on_();
bool read_power_status_();
void setup_pins_();
void pins_z_state_();
@@ -113,6 +111,8 @@ class Inkplate6 : public PollingComponent, public display::DisplayBuffer, public
return 800;
} else if (this->model_ == INKPLATE_10) {
return 1200;
} else if (this->model_ == INKPLATE_6_PLUS) {
return 1024;
}
return 0;
}
@@ -122,6 +122,8 @@ class Inkplate6 : public PollingComponent, public display::DisplayBuffer, public
return 600;
} else if (this->model_ == INKPLATE_10) {
return 825;
} else if (this->model_ == INKPLATE_6_PLUS) {
return 758;
}
return 0;
}
@@ -141,16 +143,20 @@ class Inkplate6 : public PollingComponent, public display::DisplayBuffer, public
return data;
}
uint8_t panel_on_ = 0;
bool panel_on_{false};
uint8_t temperature_;
uint8_t *partial_buffer_{nullptr};
uint8_t *partial_buffer_2_{nullptr};
uint32_t *glut_{nullptr};
uint32_t *glut2_{nullptr};
uint32_t pin_lut_[256];
uint32_t full_update_every_;
uint32_t partial_updates_{0};
bool block_partial_;
bool block_partial_{true};
bool greyscale_;
bool partial_updating_;

View File

@@ -0,0 +1,3 @@
import esphome.codegen as cg
lilygo_t5_47_ns = cg.esphome_ns.namespace("lilygo_t5_47")

View File

@@ -0,0 +1,45 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import pins
from esphome.components import i2c, touchscreen
from esphome.const import CONF_ID
from .. import lilygo_t5_47_ns
CODEOWNERS = ["@jesserockz"]
DEPENDENCIES = ["i2c"]
LilygoT547Touchscreen = lilygo_t5_47_ns.class_(
"LilygoT547Touchscreen",
touchscreen.Touchscreen,
cg.Component,
i2c.I2CDevice,
)
CONF_LILYGO_T5_47_TOUCHSCREEN_ID = "lilygo_t5_47_touchscreen_id"
CONF_INTERRUPT_PIN = "interrupt_pin"
CONFIG_SCHEMA = touchscreen.TOUCHSCREEN_SCHEMA.extend(
cv.Schema(
{
cv.GenerateID(): cv.declare_id(LilygoT547Touchscreen),
cv.Required(CONF_INTERRUPT_PIN): cv.All(
pins.internal_gpio_input_pin_schema
),
}
)
.extend(i2c.i2c_device_schema(0x5A))
.extend(cv.COMPONENT_SCHEMA)
)
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)
await touchscreen.register_touchscreen(var, config)
interrupt_pin = await cg.gpio_pin_expression(config[CONF_INTERRUPT_PIN])
cg.add(var.set_interrupt_pin(interrupt_pin))

View File

@@ -0,0 +1,141 @@
#include "lilygo_t5_47_touchscreen.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
namespace esphome {
namespace lilygo_t5_47 {
static const char *const TAG = "lilygo_t5_47.touchscreen";
static const uint8_t POWER_REGISTER = 0xD6;
static const uint8_t TOUCH_REGISTER = 0xD0;
static const uint8_t WAKEUP_CMD[1] = {0x06};
static const uint8_t READ_FLAGS[1] = {0x00};
static const uint8_t CLEAR_FLAGS[2] = {0x00, 0xAB};
static const uint8_t READ_TOUCH[1] = {0x07};
#define ERROR_CHECK(err) \
if ((err) != i2c::ERROR_OK) { \
ESP_LOGE(TAG, "Failed to communicate!"); \
this->status_set_warning(); \
return; \
}
void Store::gpio_intr(Store *store) { store->touch = true; }
void LilygoT547Touchscreen::setup() {
ESP_LOGCONFIG(TAG, "Setting up Lilygo T5 4.7 Touchscreen...");
this->interrupt_pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
this->interrupt_pin_->setup();
this->store_.pin = this->interrupt_pin_->to_isr();
this->interrupt_pin_->attach_interrupt(Store::gpio_intr, &this->store_, gpio::INTERRUPT_FALLING_EDGE);
if (this->write(nullptr, 0) != i2c::ERROR_OK) {
ESP_LOGE(TAG, "Failed to communicate!");
this->interrupt_pin_->detach_interrupt();
this->mark_failed();
return;
}
this->write_register(POWER_REGISTER, WAKEUP_CMD, 1);
}
void LilygoT547Touchscreen::loop() {
if (!this->store_.touch) {
for (auto *listener : this->touch_listeners_)
listener->release();
return;
}
this->store_.touch = false;
uint8_t point = 0;
uint8_t buffer[40] = {0};
uint32_t sum_l = 0, sum_h = 0;
i2c::ErrorCode err;
err = this->write_register(TOUCH_REGISTER, READ_FLAGS, 1);
ERROR_CHECK(err);
err = this->read(buffer, 7);
ERROR_CHECK(err);
if (buffer[0] == 0xAB) {
this->write_register(TOUCH_REGISTER, CLEAR_FLAGS, 2);
return;
}
point = buffer[5] & 0xF;
if (point == 0) {
for (auto *listener : this->touch_listeners_)
listener->release();
return;
} else if (point == 1) {
err = this->write_register(TOUCH_REGISTER, READ_TOUCH, 1);
ERROR_CHECK(err);
err = this->read(&buffer[5], 2);
ERROR_CHECK(err);
sum_l = buffer[5] << 8 | buffer[6];
} else if (point > 1) {
err = this->write_register(TOUCH_REGISTER, READ_TOUCH, 1);
ERROR_CHECK(err);
err = this->read(&buffer[5], 5 * (point - 1) + 3);
ERROR_CHECK(err);
sum_l = buffer[5 * point + 1] << 8 | buffer[5 * point + 2];
}
this->write_register(TOUCH_REGISTER, CLEAR_FLAGS, 2);
for (int i = 0; i < 5 * point; i++)
sum_h += buffer[i];
if (sum_l != sum_h)
point = 0;
if (point) {
uint8_t offset;
for (int i = 0; i < point; i++) {
if (i == 0) {
offset = 0;
} else {
offset = 4;
}
TouchPoint tp;
tp.id = (buffer[i * 5 + offset] >> 4) & 0x0F;
tp.state = buffer[i * 5 + offset] & 0x0F;
if (tp.state == 0x06)
tp.state = 0x07;
tp.y = (uint16_t)((buffer[i * 5 + 1 + offset] << 4) | ((buffer[i * 5 + 3 + offset] >> 4) & 0x0F));
tp.x = (uint16_t)((buffer[i * 5 + 2 + offset] << 4) | (buffer[i * 5 + 3 + offset] & 0x0F));
this->defer([this, tp]() { this->send_touch_(tp); });
}
} else {
TouchPoint tp;
tp.id = (buffer[0] >> 4) & 0x0F;
tp.state = 0x06;
tp.y = (uint16_t)((buffer[0 * 5 + 1] << 4) | ((buffer[0 * 5 + 3] >> 4) & 0x0F));
tp.x = (uint16_t)((buffer[0 * 5 + 2] << 4) | (buffer[0 * 5 + 3] & 0x0F));
this->defer([this, tp]() { this->send_touch_(tp); });
}
this->status_clear_warning();
}
void LilygoT547Touchscreen::dump_config() {
ESP_LOGCONFIG(TAG, "Lilygo T5 47 Touchscreen:");
LOG_I2C_DEVICE(this);
LOG_PIN(" Interrupt Pin: ", this->interrupt_pin_);
}
} // namespace lilygo_t5_47
} // namespace esphome

View File

@@ -0,0 +1,35 @@
#pragma once
#include "esphome/components/i2c/i2c.h"
#include "esphome/components/touchscreen/touchscreen.h"
#include "esphome/core/automation.h"
#include "esphome/core/component.h"
#include "esphome/core/hal.h"
namespace esphome {
namespace lilygo_t5_47 {
struct Store {
volatile bool touch;
ISRInternalGPIOPin pin;
static void gpio_intr(Store *store);
};
using namespace touchscreen;
class LilygoT547Touchscreen : public Touchscreen, public Component, public i2c::I2CDevice {
public:
void setup() override;
void loop() override;
void dump_config() override;
void set_interrupt_pin(InternalGPIOPin *pin) { this->interrupt_pin_ = pin; }
protected:
InternalGPIOPin *interrupt_pin_;
Store store_;
};
} // namespace lilygo_t5_47
} // namespace esphome

View File

@@ -0,0 +1,102 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import automation
from esphome.automation import Condition, maybe_simple_id
from esphome.components import mqtt
from esphome.const import (
CONF_ID,
CONF_ON_LOCK,
CONF_ON_UNLOCK,
CONF_TRIGGER_ID,
CONF_MQTT_ID,
)
from esphome.core import CORE, coroutine_with_priority
from esphome.cpp_helpers import setup_entity
CODEOWNERS = ["@esphome/core"]
IS_PLATFORM_COMPONENT = True
lock_ns = cg.esphome_ns.namespace("lock")
Lock = lock_ns.class_("Lock", cg.EntityBase)
LockPtr = Lock.operator("ptr")
LockCall = lock_ns.class_("LockCall")
UnlockAction = lock_ns.class_("UnlockAction", automation.Action)
LockAction = lock_ns.class_("LockAction", automation.Action)
OpenAction = lock_ns.class_("OpenAction", automation.Action)
LockPublishAction = lock_ns.class_("LockPublishAction", automation.Action)
LockCondition = lock_ns.class_("LockCondition", Condition)
LockLockTrigger = lock_ns.class_("LockLockTrigger", automation.Trigger.template())
LockUnlockTrigger = lock_ns.class_("LockUnlockTrigger", automation.Trigger.template())
LOCK_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend(
{
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTLockComponent),
cv.Optional(CONF_ON_LOCK): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(LockLockTrigger),
}
),
cv.Optional(CONF_ON_UNLOCK): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(LockUnlockTrigger),
}
),
}
)
async def setup_lock_core_(var, config):
await setup_entity(var, config)
for conf in config.get(CONF_ON_LOCK, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)
for conf in config.get(CONF_ON_UNLOCK, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)
if CONF_MQTT_ID in config:
mqtt_ = cg.new_Pvariable(config[CONF_MQTT_ID], var)
await mqtt.register_mqtt_component(mqtt_, config)
async def register_lock(var, config):
if not CORE.has_id(config[CONF_ID]):
var = cg.Pvariable(config[CONF_ID], var)
cg.add(cg.App.register_lock(var))
await setup_lock_core_(var, config)
LOCK_ACTION_SCHEMA = maybe_simple_id(
{
cv.Required(CONF_ID): cv.use_id(Lock),
}
)
@automation.register_action("lock.unlock", UnlockAction, LOCK_ACTION_SCHEMA)
@automation.register_action("lock.lock", LockAction, LOCK_ACTION_SCHEMA)
@automation.register_action("lock.open", OpenAction, LOCK_ACTION_SCHEMA)
async def lock_action_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID])
return cg.new_Pvariable(action_id, template_arg, paren)
@automation.register_condition("lock.is_locked", LockCondition, LOCK_ACTION_SCHEMA)
async def lock_is_on_to_code(config, condition_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID])
return cg.new_Pvariable(condition_id, template_arg, paren, True)
@automation.register_condition("lock.is_unlocked", LockCondition, LOCK_ACTION_SCHEMA)
async def lock_is_off_to_code(config, condition_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID])
return cg.new_Pvariable(condition_id, template_arg, paren, False)
@coroutine_with_priority(100.0)
async def to_code(config):
cg.add_global(lock_ns.using)
cg.add_define("USE_LOCK")

View File

@@ -0,0 +1,87 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/automation.h"
#include "esphome/components/lock/lock.h"
namespace esphome {
namespace lock {
template<typename... Ts> class LockAction : public Action<Ts...> {
public:
explicit LockAction(Lock *a_lock) : lock_(a_lock) {}
void play(Ts... x) override { this->lock_->lock(); }
protected:
Lock *lock_;
};
template<typename... Ts> class UnlockAction : public Action<Ts...> {
public:
explicit UnlockAction(Lock *a_lock) : lock_(a_lock) {}
void play(Ts... x) override { this->lock_->unlock(); }
protected:
Lock *lock_;
};
template<typename... Ts> class OpenAction : public Action<Ts...> {
public:
explicit OpenAction(Lock *a_lock) : lock_(a_lock) {}
void play(Ts... x) override { this->lock_->open(); }
protected:
Lock *lock_;
};
template<typename... Ts> class LockCondition : public Condition<Ts...> {
public:
LockCondition(Lock *parent, bool state) : parent_(parent), state_(state) {}
bool check(Ts... x) override {
auto check_state = this->state_ ? LockState::LOCK_STATE_LOCKED : LockState::LOCK_STATE_UNLOCKED;
return this->parent_->state == check_state;
}
protected:
Lock *parent_;
bool state_;
};
class LockLockTrigger : public Trigger<> {
public:
LockLockTrigger(Lock *a_lock) {
a_lock->add_on_state_callback([this, a_lock]() {
if (a_lock->state == LockState::LOCK_STATE_LOCKED) {
this->trigger();
}
});
}
};
class LockUnlockTrigger : public Trigger<> {
public:
LockUnlockTrigger(Lock *a_lock) {
a_lock->add_on_state_callback([this, a_lock]() {
if (a_lock->state == LockState::LOCK_STATE_UNLOCKED) {
this->trigger();
}
});
}
};
template<typename... Ts> class LockPublishAction : public Action<Ts...> {
public:
LockPublishAction(Lock *a_lock) : lock_(a_lock) {}
TEMPLATABLE_VALUE(LockState, state)
void play(Ts... x) override { this->lock_->publish_state(this->state_.value(x...)); }
protected:
Lock *lock_;
};
} // namespace lock
} // namespace esphome

View File

@@ -0,0 +1,109 @@
#include "lock.h"
#include "esphome/core/log.h"
namespace esphome {
namespace lock {
static const char *const TAG = "lock";
const char *lock_state_to_string(LockState state) {
switch (state) {
case LOCK_STATE_LOCKED:
return "LOCKED";
case LOCK_STATE_UNLOCKED:
return "UNLOCKED";
case LOCK_STATE_JAMMED:
return "JAMMED";
case LOCK_STATE_LOCKING:
return "LOCKING";
case LOCK_STATE_UNLOCKING:
return "UNLOCKING";
case LOCK_STATE_NONE:
default:
return "UNKNOWN";
}
}
Lock::Lock(const std::string &name) : EntityBase(name), state(LOCK_STATE_NONE) {}
Lock::Lock() : Lock("") {}
LockCall Lock::make_call() { return LockCall(this); }
void Lock::lock() {
auto call = this->make_call();
call.set_state(LOCK_STATE_LOCKED);
this->control(call);
}
void Lock::unlock() {
auto call = this->make_call();
call.set_state(LOCK_STATE_UNLOCKED);
this->control(call);
}
void Lock::open() {
if (traits.get_supports_open()) {
ESP_LOGD(TAG, "'%s' Opening.", this->get_name().c_str());
this->open_latch();
} else {
ESP_LOGW(TAG, "'%s' Does not support Open.", this->get_name().c_str());
}
}
void Lock::publish_state(LockState state) {
if (!this->publish_dedup_.next(state))
return;
this->state = state;
this->rtc_.save(&this->state);
ESP_LOGD(TAG, "'%s': Sending state %s", this->name_.c_str(), lock_state_to_string(state));
this->state_callback_.call();
}
void Lock::add_on_state_callback(std::function<void()> &&callback) { this->state_callback_.add(std::move(callback)); }
uint32_t Lock::hash_base() { return 856245656UL; }
void LockCall::perform() {
ESP_LOGD(TAG, "'%s' - Setting", this->parent_->get_name().c_str());
this->validate_();
if (this->state_.has_value()) {
const char *state_s = lock_state_to_string(*this->state_);
ESP_LOGD(TAG, " State: %s", state_s);
}
this->parent_->control(*this);
}
void LockCall::validate_() {
if (this->state_.has_value()) {
auto state = *this->state_;
if (!this->parent_->traits.supports_state(state)) {
ESP_LOGW(TAG, " State %s is not supported by this device!", lock_state_to_string(*this->state_));
this->state_.reset();
}
}
}
LockCall &LockCall::set_state(LockState state) {
this->state_ = state;
return *this;
}
LockCall &LockCall::set_state(optional<LockState> state) {
this->state_ = state;
return *this;
}
LockCall &LockCall::set_state(const std::string &state) {
if (str_equals_case_insensitive(state, "LOCKED")) {
this->set_state(LOCK_STATE_LOCKED);
} else if (str_equals_case_insensitive(state, "UNLOCKED")) {
this->set_state(LOCK_STATE_UNLOCKED);
} else if (str_equals_case_insensitive(state, "JAMMED")) {
this->set_state(LOCK_STATE_JAMMED);
} else if (str_equals_case_insensitive(state, "LOCKING")) {
this->set_state(LOCK_STATE_LOCKING);
} else if (str_equals_case_insensitive(state, "UNLOCKING")) {
this->set_state(LOCK_STATE_UNLOCKING);
} else if (str_equals_case_insensitive(state, "NONE")) {
this->set_state(LOCK_STATE_NONE);
} else {
ESP_LOGW(TAG, "'%s' - Unrecognized state %s", this->parent_->get_name().c_str(), state.c_str());
}
return *this;
}
const optional<LockState> &LockCall::get_state() const { return this->state_; }
} // namespace lock
} // namespace esphome

View File

@@ -0,0 +1,178 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/entity_base.h"
#include "esphome/core/preferences.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#include <set>
namespace esphome {
namespace lock {
class Lock;
#define LOG_LOCK(prefix, type, obj) \
if ((obj) != nullptr) { \
ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, LOG_STR_LITERAL(type), (obj)->get_name().c_str()); \
if (!(obj)->get_icon().empty()) { \
ESP_LOGCONFIG(TAG, "%s Icon: '%s'", prefix, (obj)->get_icon().c_str()); \
} \
if ((obj)->traits.get_assumed_state()) { \
ESP_LOGCONFIG(TAG, "%s Assumed State: YES", prefix); \
} \
}
/// Enum for all states a lock can be in.
enum LockState : uint8_t {
LOCK_STATE_NONE = 0,
LOCK_STATE_LOCKED = 1,
LOCK_STATE_UNLOCKED = 2,
LOCK_STATE_JAMMED = 3,
LOCK_STATE_LOCKING = 4,
LOCK_STATE_UNLOCKING = 5
};
const char *lock_state_to_string(LockState state);
class LockTraits {
public:
LockTraits() = default;
bool get_supports_open() const { return this->supports_open_; }
void set_supports_open(bool supports_open) { this->supports_open_ = supports_open; }
bool get_requires_code() const { return this->requires_code_; }
void set_requires_code(bool requires_code) { this->requires_code_ = requires_code; }
bool get_assumed_state() const { return this->assumed_state_; }
void set_assumed_state(bool assumed_state) { this->assumed_state_ = assumed_state; }
bool supports_state(LockState state) const { return supported_states_.count(state); }
std::set<LockState> get_supported_states() const { return supported_states_; }
void set_supported_states(std::set<LockState> states) { supported_states_ = std::move(states); }
void add_supported_state(LockState state) { supported_states_.insert(state); }
protected:
bool supports_open_{false};
bool requires_code_{false};
bool assumed_state_{false};
std::set<LockState> supported_states_ = {LOCK_STATE_NONE, LOCK_STATE_LOCKED, LOCK_STATE_UNLOCKED};
};
/** This class is used to encode all control actions on a lock device.
*
* It is supposed to be used by all code that wishes to control a lock device (mqtt, api, lambda etc).
* Create an instance of this class by calling `id(lock_device).make_call();`. Then set all attributes
* with the `set_x` methods. Finally, to apply the changes call `.perform();`.
*
* The integration that implements the lock device receives this instance with the `control` method.
* It should check all the properties it implements and apply them as needed. It should do so by
* getting all properties it controls with the getter methods in this class. If the optional value is
* set (check with `.has_value()`) that means the user wants to control this property. Get the value
* of the optional with the star operator (`*call.get_state()`) and apply it.
*/
class LockCall {
public:
LockCall(Lock *parent) : parent_(parent) {}
/// Set the state of the lock device.
LockCall &set_state(LockState state);
/// Set the state of the lock device.
LockCall &set_state(optional<LockState> state);
/// Set the state of the lock device based on a string.
LockCall &set_state(const std::string &state);
void perform();
const optional<LockState> &get_state() const;
protected:
void validate_();
Lock *const parent_;
optional<LockState> state_;
};
/** Base class for all locks.
*
* A lock is basically a switch with a combination of a binary sensor (for reporting lock values)
* and a write_state method that writes a state to the hardware. Locks can also have an "open"
* method to unlatch.
*
* For integrations: Integrations must implement the method control().
* Control will be called with the arguments supplied by the user and should be used
* to control all values of the lock.
*/
class Lock : public EntityBase {
public:
explicit Lock();
explicit Lock(const std::string &name);
/** Make a lock device control call, this is used to control the lock device, see the LockCall description
* for more info.
* @return A new LockCall instance targeting this lock device.
*/
LockCall make_call();
/** Publish a state to the front-end from the back-end.
*
* Then the internal value member is set and finally the callbacks are called.
*
* @param state The new state.
*/
void publish_state(LockState state);
/// The current reported state of the lock.
LockState state{LOCK_STATE_NONE};
LockTraits traits;
/** Turn this lock on. This is called by the front-end.
*
* For implementing locks, please override control.
*/
void lock();
/** Turn this lock off. This is called by the front-end.
*
* For implementing locks, please override control.
*/
void unlock();
/** Open (unlatch) this lock. This is called by the front-end.
*
* For implementing locks, please override control.
*/
void open();
/** Set callback for state changes.
*
* @param callback The void(bool) callback.
*/
void add_on_state_callback(std::function<void()> &&callback);
protected:
friend LockCall;
/** Perform the open latch action with hardware. This method is optional to implement
* when creating a new lock.
*
* In the implementation of this method, it is recommended you also call
* publish_state with "unlock" to acknowledge that the state was written to the hardware.
*/
virtual void open_latch() { unlock(); };
/** Control the lock device, this is a virtual method that each lock integration must implement.
*
* See more info in LockCall. The integration should check all of its values in this method and
* set them accordingly. At the end of the call, the integration must call `publish_state()` to
* notify the frontend of a changed state.
*
* @param call The LockCall instance encoding all attribute changes.
*/
virtual void control(const LockCall &call) = 0;
uint32_t hash_base() override;
CallbackManager<void()> state_callback_{};
Deduplicator<LockState> publish_dedup_;
ESPPreferenceObject rtc_;
};
} // namespace lock
} // namespace esphome

View File

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

View File

@@ -0,0 +1,93 @@
#include "max9611.h"
#include "esphome/core/log.h"
#include "esphome/components/i2c/i2c_bus.h"
namespace esphome {
namespace max9611 {
using namespace esphome::i2c;
// Sign extend
// http://graphics.stanford.edu/~seander/bithacks.html#FixedSignExtend
template<typename T, unsigned B> inline T signextend(const T x) {
struct {
T x : B;
} s;
return s.x = x;
}
// Map the gain register to in uV/LSB
float gain_to_lsb(MAX9611Multiplexer gain) {
float lsb = 0.0;
if (gain == MAX9611_MULTIPLEXER_CSA_GAIN1) {
lsb = 107.50;
} else if (gain == MAX9611_MULTIPLEXER_CSA_GAIN4) {
lsb = 26.88;
} else if (gain == MAX9611_MULTIPLEXER_CSA_GAIN8) {
lsb = 13.44;
}
return lsb;
}
static const char *const TAG = "max9611";
static const uint8_t SETUP_DELAY = 4; // Wait 2 integration periods.
static const float VOUT_LSB = 14.0 / 1000.0; // 14mV/LSB
static const float TEMP_LSB = 0.48; // 0.48C/LSB
static const float MICRO_VOLTS_PER_VOLT = 1000000.0;
void MAX9611Component::setup() {
ESP_LOGCONFIG(TAG, "Setting up max9611...");
// Perform dummy-read
uint8_t value;
this->read(&value, 1);
// Configuration Stage.
// First send an integration request with the specified gain
const uint8_t setup_dat[] = {CONTROL_REGISTER_1_ADRR, static_cast<uint8_t>(gain_)};
// Then send a request that samples all channels as fast as possible, using the last provided gain
const uint8_t fast_mode_dat[] = {CONTROL_REGISTER_1_ADRR, MAX9611Multiplexer::MAX9611_MULTIPLEXER_FAST_MODE};
if (this->write(reinterpret_cast<const uint8_t *>(&setup_dat), sizeof(setup_dat)) != ErrorCode::ERROR_OK) {
ESP_LOGE(TAG, "Failed to setup Max9611 during GAIN SET");
return;
}
delay(SETUP_DELAY);
if (this->write(reinterpret_cast<const uint8_t *>(&fast_mode_dat), sizeof(fast_mode_dat)) != ErrorCode::ERROR_OK) {
ESP_LOGE(TAG, "Failed to setup Max9611 during FAST MODE SET");
return;
}
}
void MAX9611Component::dump_config() {
ESP_LOGCONFIG(TAG, "Dump Config max9611...");
ESP_LOGCONFIG(TAG, " CSA Gain Register: %x", gain_);
LOG_I2C_DEVICE(this);
}
void MAX9611Component::update() {
// Setup read from 0x0 register base
const uint8_t reg_base = 0x0;
const ErrorCode write_result = this->write(&reg_base, 1);
// Just read the entire register map in a bulk read, faster than individually querying register.
const ErrorCode read_result = this->read(register_map_, sizeof(register_map_));
if (write_result != ErrorCode::ERROR_OK || read_result != ErrorCode::ERROR_OK) {
ESP_LOGW(TAG, "MAX9611 Update FAILED!");
return;
}
uint16_t csa_register = ((register_map_[CSA_DATA_BYTE_MSB_ADRR] << 8) | (register_map_[CSA_DATA_BYTE_LSB_ADRR])) >> 4;
uint16_t rs_register = ((register_map_[RS_DATA_BYTE_MSB_ADRR] << 8) | (register_map_[RS_DATA_BYTE_LSB_ADRR])) >> 4;
uint16_t t_register = ((register_map_[TEMP_DATA_BYTE_MSB_ADRR] << 8) | (register_map_[TEMP_DATA_BYTE_LSB_ADRR])) >> 7;
float voltage = rs_register * VOUT_LSB;
float shunt_voltage = (csa_register * gain_to_lsb(gain_)) / MICRO_VOLTS_PER_VOLT;
float temp = signextend<signed int, 9>(t_register) * TEMP_LSB;
float amps = shunt_voltage / current_resistor_;
float watts = amps * voltage;
if (voltage_sensor_ != nullptr) {
voltage_sensor_->publish_state(voltage);
}
if (current_sensor_ != nullptr) {
current_sensor_->publish_state(amps);
}
if (watt_sensor_ != nullptr) {
watt_sensor_->publish_state(watts);
}
if (temperature_sensor_ != nullptr) {
temperature_sensor_->publish_state(temp);
}
ESP_LOGD(TAG, "V: %f, A: %f, W: %f, Deg C: %f", voltage, amps, watts, temp);
}
} // namespace max9611
} // namespace esphome

View File

@@ -0,0 +1,62 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/i2c/i2c.h"
#include "esphome/core/hal.h"
namespace esphome {
namespace max9611 {
enum MAX9611Multiplexer {
MAX9611_MULTIPLEXER_CSA_GAIN1 = 0b000,
MAX9611_MULTIPLEXER_CSA_GAIN4 = 0b001,
MAX9611_MULTIPLEXER_CSA_GAIN8 = 0b010,
MAX9611_MULTIPLEXER_RS = 0b011,
MAX9611_MULTIPLEXER_OUT = 0b100,
MAX9611_MULTIPLEXER_SET = 0b101,
MAX9611_MULTIPLEXER_TEMP = 0b110,
MAX9611_MULTIPLEXER_FAST_MODE = 0b111,
};
enum MAX9611RegisterMap {
CSA_DATA_BYTE_MSB_ADRR = 0x00,
CSA_DATA_BYTE_LSB_ADRR = 0x01,
RS_DATA_BYTE_MSB_ADRR = 0x02,
RS_DATA_BYTE_LSB_ADRR = 0x03,
OUT_DATA_BYTE_MSB_ADRR = 0x04, // Unused Op-Amp
OUT_DATA_BYTE_LSB_ADRR = 0x05, // Unused Op-Amp
SET_DATA_BYTE_MSB_ADRR = 0x06, // Unused Op-Amp
SET_DATA_BYTE_LSB_ADRR = 0x07, // Unused Op-Amp
TEMP_DATA_BYTE_MSB_ADRR = 0x08,
TEMP_DATA_BYTE_LSB_ADRR = 0x09,
CONTROL_REGISTER_1_ADRR = 0x0A,
CONTROL_REGISTER_2_ADRR = 0x0B,
};
class MAX9611Component : public PollingComponent, public i2c::I2CDevice {
public:
void setup() override;
void dump_config() override;
float get_setup_priority() const override { return setup_priority::DATA; }
void update() override;
void set_voltage_sensor(sensor::Sensor *vs) { voltage_sensor_ = vs; }
void set_current_sensor(sensor::Sensor *cs) { current_sensor_ = cs; }
void set_watt_sensor(sensor::Sensor *ws) { watt_sensor_ = ws; }
void set_temp_sensor(sensor::Sensor *ts) { temperature_sensor_ = ts; }
void set_current_resistor(float r) { current_resistor_ = r; }
void set_gain(MAX9611Multiplexer g) { gain_ = g; }
protected:
sensor::Sensor *voltage_sensor_{nullptr};
sensor::Sensor *current_sensor_{nullptr};
sensor::Sensor *watt_sensor_{nullptr};
sensor::Sensor *temperature_sensor_{nullptr};
float current_resistor_;
uint8_t register_map_[0x0C];
MAX9611Multiplexer gain_;
};
} // namespace max9611
} // namespace esphome

View File

@@ -0,0 +1,92 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import i2c, sensor
from esphome.const import (
CONF_ID,
CONF_SHUNT_RESISTANCE,
CONF_GAIN,
CONF_VOLTAGE,
CONF_CURRENT,
CONF_POWER,
CONF_TEMPERATURE,
UNIT_VOLT,
UNIT_AMPERE,
UNIT_WATT,
UNIT_CELSIUS,
DEVICE_CLASS_VOLTAGE,
DEVICE_CLASS_CURRENT,
DEVICE_CLASS_POWER,
DEVICE_CLASS_TEMPERATURE,
STATE_CLASS_MEASUREMENT,
)
DEPENDENCIES = ["i2c"]
max9611_ns = cg.esphome_ns.namespace("max9611")
max9611Gain = max9611_ns.enum("MAX9611Multiplexer")
MAX9611_GAIN = {
"8X": max9611Gain.MAX9611_MULTIPLEXER_CSA_GAIN8,
"4X": max9611Gain.MAX9611_MULTIPLEXER_CSA_GAIN4,
"1X": max9611Gain.MAX9611_MULTIPLEXER_CSA_GAIN1,
}
MAX9611Component = max9611_ns.class_(
"MAX9611Component", cg.PollingComponent, i2c.I2CDevice
)
CONFIG_SCHEMA = (
cv.Schema(
{
cv.GenerateID(): cv.declare_id(MAX9611Component),
cv.Required(CONF_SHUNT_RESISTANCE): cv.resistance,
cv.Required(CONF_GAIN): cv.enum(MAX9611_GAIN, upper=True),
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_TEMPERATURE): sensor.sensor_schema(
unit_of_measurement=UNIT_CELSIUS,
accuracy_decimals=2,
device_class=DEVICE_CLASS_TEMPERATURE,
state_class=STATE_CLASS_MEASUREMENT,
),
}
)
.extend(cv.polling_component_schema("60s"))
.extend(i2c.i2c_device_schema(0x70))
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await i2c.register_i2c_device(var, config)
cg.add(var.set_current_resistor(config[CONF_SHUNT_RESISTANCE]))
cg.add(var.set_gain(config[CONF_GAIN]))
if CONF_VOLTAGE in config:
conf = config[CONF_VOLTAGE]
sens = await sensor.new_sensor(conf)
cg.add(var.set_voltage_sensor(sens))
if CONF_CURRENT in config:
conf = config[CONF_CURRENT]
sens = await sensor.new_sensor(conf)
cg.add(var.set_current_sensor(sens))
if CONF_POWER in config:
conf = config[CONF_POWER]
sens = await sensor.new_sensor(conf)
cg.add(var.set_watt_sensor(sens))
if CONF_TEMPERATURE in config:
conf = config[CONF_TEMPERATURE]
sens = await sensor.new_sensor(conf)
cg.add(var.set_temp_sensor(sens))

View File

@@ -1,7 +1,7 @@
from esphome.const import CONF_ID
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.core import CORE
from esphome.core import CORE, coroutine_with_priority
CODEOWNERS = ["@esphome/core"]
DEPENDENCIES = ["network"]
@@ -29,6 +29,7 @@ CONFIG_SCHEMA = cv.All(
)
@coroutine_with_priority(55.0)
async def to_code(config):
if CORE.using_arduino:
if CORE.is_esp32:

View File

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

View File

@@ -0,0 +1,135 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import i2c, sensor
from esphome.const import (
CONF_ID,
UNIT_MICROTESLA,
UNIT_CELSIUS,
STATE_CLASS_MEASUREMENT,
ICON_MAGNET,
ICON_THERMOMETER,
CONF_GAIN,
CONF_RESOLUTION,
CONF_OVERSAMPLING,
CONF_FILTER,
CONF_TEMPERATURE,
)
from esphome import pins
CODEOWNERS = ["@functionpointer"]
DEPENDENCIES = ["i2c"]
mlx90393_ns = cg.esphome_ns.namespace("mlx90393")
MLX90393Component = mlx90393_ns.class_(
"MLX90393Cls", cg.PollingComponent, i2c.I2CDevice
)
GAIN = {
"1X": 7,
"1_33X": 6,
"1_67X": 5,
"2X": 4,
"2_5X": 3,
"3X": 2,
"4X": 1,
"5X": 0,
}
RESOLUTION = {
"16BIT": 0,
"17BIT": 1,
"18BIT": 2,
"19BIT": 3,
}
CONF_X_AXIS = "x_axis"
CONF_Y_AXIS = "y_axis"
CONF_Z_AXIS = "z_axis"
CONF_DRDY_PIN = "drdy_pin"
def mlx90393_axis_schema(default_resolution: str):
return sensor.sensor_schema(
unit_of_measurement=UNIT_MICROTESLA,
accuracy_decimals=0,
icon=ICON_MAGNET,
state_class=STATE_CLASS_MEASUREMENT,
).extend(
cv.Schema(
{
cv.Optional(CONF_RESOLUTION, default=default_resolution): cv.enum(
RESOLUTION, upper=True, space="_"
)
}
)
)
CONFIG_SCHEMA = (
cv.Schema(
{
cv.GenerateID(): cv.declare_id(MLX90393Component),
cv.Optional(CONF_GAIN, default="2_5X"): cv.enum(
GAIN, upper=True, space="_"
),
cv.Optional(CONF_DRDY_PIN): pins.gpio_input_pin_schema,
cv.Optional(CONF_OVERSAMPLING, default=2): cv.int_range(min=0, max=3),
cv.Optional(CONF_FILTER, default=6): cv.int_range(min=0, max=7),
cv.Optional(CONF_X_AXIS): mlx90393_axis_schema("19BIT"),
cv.Optional(CONF_Y_AXIS): mlx90393_axis_schema("19BIT"),
cv.Optional(CONF_Z_AXIS): mlx90393_axis_schema("16BIT"),
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
unit_of_measurement=UNIT_CELSIUS,
accuracy_decimals=1,
icon=ICON_THERMOMETER,
state_class=STATE_CLASS_MEASUREMENT,
).extend(
cv.Schema(
{
cv.Optional(CONF_OVERSAMPLING, default=0): cv.int_range(
min=0, max=3
),
}
)
),
},
)
.extend(cv.polling_component_schema("60s"))
.extend(i2c.i2c_device_schema(0x0C))
)
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 CONF_DRDY_PIN in config:
pin = await cg.gpio_pin_expression(config[CONF_DRDY_PIN])
cg.add(var.set_drdy_pin(pin))
cg.add(var.set_gain(GAIN[config[CONF_GAIN]]))
cg.add(var.set_oversampling(config[CONF_OVERSAMPLING]))
cg.add(var.set_filter(config[CONF_FILTER]))
if CONF_X_AXIS in config:
sens = await sensor.new_sensor(config[CONF_X_AXIS])
cg.add(var.set_x_sensor(sens))
cg.add(var.set_resolution(0, RESOLUTION[config[CONF_X_AXIS][CONF_RESOLUTION]]))
if CONF_Y_AXIS in config:
sens = await sensor.new_sensor(config[CONF_Y_AXIS])
cg.add(var.set_y_sensor(sens))
cg.add(var.set_resolution(1, RESOLUTION[config[CONF_Y_AXIS][CONF_RESOLUTION]]))
if CONF_Z_AXIS in config:
sens = await sensor.new_sensor(config[CONF_Z_AXIS])
cg.add(var.set_z_sensor(sens))
cg.add(var.set_resolution(2, RESOLUTION[config[CONF_Z_AXIS][CONF_RESOLUTION]]))
if CONF_TEMPERATURE in config:
sens = await sensor.new_sensor(config[CONF_TEMPERATURE])
cg.add(var.set_t_sensor(sens))
cg.add(var.set_t_oversampling(config[CONF_TEMPERATURE][CONF_OVERSAMPLING]))
if CONF_DRDY_PIN in config:
pin = await cg.gpio_pin_expression(config[CONF_DRDY_PIN])
cg.add(var.set_drdy_gpio(pin))
cg.add_library("functionpointer/arduino-MLX90393", "1.0.0")

View File

@@ -0,0 +1,91 @@
#include "sensor_mlx90393.h"
#include "esphome/core/log.h"
namespace esphome {
namespace mlx90393 {
static const char *const TAG = "mlx90393";
bool MLX90393Cls::transceive(const uint8_t *request, size_t request_size, uint8_t *response, size_t response_size) {
i2c::ErrorCode e = this->write(request, request_size);
if (e != i2c::ErrorCode::ERROR_OK) {
return false;
}
e = this->read(response, response_size);
return e == i2c::ErrorCode::ERROR_OK;
}
bool MLX90393Cls::has_drdy_pin() { return this->drdy_pin_ != nullptr; }
bool MLX90393Cls::read_drdy_pin() {
if (this->drdy_pin_ == nullptr) {
return false;
} else {
return this->drdy_pin_->digital_read();
}
}
void MLX90393Cls::sleep_millis(uint32_t millis) { delay(millis); }
void MLX90393Cls::sleep_micros(uint32_t micros) { delayMicroseconds(micros); }
void MLX90393Cls::setup() {
ESP_LOGCONFIG(TAG, "Setting up MLX90393...");
// note the two arguments A0 and A1 which are used to construct an i2c address
// we can hard-code these because we never actually use the constructed address
// see the transceive function above, which uses the address from I2CComponent
this->mlx_.begin_with_hal(this, 0, 0);
this->mlx_.setGainSel(this->gain_);
this->mlx_.setResolution(this->resolutions_[0], this->resolutions_[1], this->resolutions_[2]);
this->mlx_.setOverSampling(this->oversampling_);
this->mlx_.setDigitalFiltering(this->filter_);
this->mlx_.setTemperatureOverSampling(this->temperature_oversampling_);
}
void MLX90393Cls::dump_config() {
ESP_LOGCONFIG(TAG, "MLX90393:");
LOG_I2C_DEVICE(this);
if (this->is_failed()) {
ESP_LOGE(TAG, "Communication with MLX90393 failed!");
return;
}
LOG_UPDATE_INTERVAL(this);
LOG_SENSOR(" ", "X Axis", this->x_sensor_);
LOG_SENSOR(" ", "Y Axis", this->y_sensor_);
LOG_SENSOR(" ", "Z Axis", this->z_sensor_);
LOG_SENSOR(" ", "Temperature", this->t_sensor_);
}
float MLX90393Cls::get_setup_priority() const { return setup_priority::DATA; }
void MLX90393Cls::update() {
MLX90393::txyz data;
if (this->mlx_.readData(data) == MLX90393::STATUS_OK) {
ESP_LOGD(TAG, "received %f %f %f", data.x, data.y, data.z);
if (this->x_sensor_ != nullptr) {
this->x_sensor_->publish_state(data.x);
}
if (this->y_sensor_ != nullptr) {
this->y_sensor_->publish_state(data.y);
}
if (this->z_sensor_ != nullptr) {
this->z_sensor_->publish_state(data.z);
}
if (this->t_sensor_ != nullptr) {
this->t_sensor_->publish_state(data.t);
}
this->status_clear_warning();
} else {
ESP_LOGE(TAG, "failed to read data");
this->status_set_warning();
}
}
} // namespace mlx90393
} // namespace esphome

View File

@@ -0,0 +1,59 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/i2c/i2c.h"
#include "esphome/core/hal.h"
#include <MLX90393.h>
#include <MLX90393Hal.h>
namespace esphome {
namespace mlx90393 {
class MLX90393Cls : public PollingComponent, public i2c::I2CDevice, public MLX90393Hal {
public:
void setup() override;
void dump_config() override;
float get_setup_priority() const override;
void update() override;
void set_drdy_gpio(GPIOPin *pin) { drdy_pin_ = pin; }
void set_x_sensor(sensor::Sensor *x_sensor) { x_sensor_ = x_sensor; }
void set_y_sensor(sensor::Sensor *y_sensor) { y_sensor_ = y_sensor; }
void set_z_sensor(sensor::Sensor *z_sensor) { z_sensor_ = z_sensor; }
void set_t_sensor(sensor::Sensor *t_sensor) { t_sensor_ = t_sensor; }
void set_oversampling(uint8_t osr) { oversampling_ = osr; }
void set_t_oversampling(uint8_t osr2) { temperature_oversampling_ = osr2; }
void set_resolution(uint8_t xyz, uint8_t res) { resolutions_[xyz] = res; }
void set_filter(uint8_t filter) { filter_ = filter; }
void set_gain(uint8_t gain_sel) { gain_ = gain_sel; }
// overrides for MLX library
// disable lint because it keeps suggesting const uint8_t *response.
// this->read() writes data into response, so it can't be const
bool transceive(const uint8_t *request, size_t request_size, uint8_t *response,
size_t response_size) override; // NOLINT
bool has_drdy_pin() override;
bool read_drdy_pin() override;
void sleep_millis(uint32_t millis) override;
void sleep_micros(uint32_t micros) override;
protected:
MLX90393 mlx_;
sensor::Sensor *x_sensor_{nullptr};
sensor::Sensor *y_sensor_{nullptr};
sensor::Sensor *z_sensor_{nullptr};
sensor::Sensor *t_sensor_{nullptr};
uint8_t gain_;
uint8_t oversampling_;
uint8_t temperature_oversampling_ = 0;
uint8_t filter_;
uint8_t resolutions_[3] = {0};
GPIOPin *drdy_pin_ = nullptr;
};
} // namespace mlx90393
} // namespace esphome

View File

@@ -110,7 +110,8 @@ void ModbusController::queue_command(const ModbusCommandItem &command) {
for (auto &item : command_queue_) {
if (item->register_address == command.register_address && item->register_count == command.register_count &&
item->register_type == command.register_type && item->function_code == command.function_code) {
ESP_LOGW(TAG, "Duplicate modbus command found");
ESP_LOGW(TAG, "Duplicate modbus command found: type=0x%x address=%u count=%u",
static_cast<uint8_t>(command.register_type), command.register_address, command.register_count);
// update the payload of the queued command
// replaces a previous command
item->payload = command.payload;
@@ -360,8 +361,9 @@ ModbusCommandItem ModbusCommandItem::create_write_multiple_command(ModbusControl
modbusdevice->on_write_register_response(cmd.register_type, start_address, data);
};
for (auto v : values) {
cmd.payload.push_back((v / 256) & 0xFF);
cmd.payload.push_back(v & 0xFF);
auto decoded_value = decode_value(v);
cmd.payload.push_back(decoded_value[0]);
cmd.payload.push_back(decoded_value[1]);
}
return cmd;
}
@@ -416,7 +418,7 @@ ModbusCommandItem ModbusCommandItem::create_write_multiple_coils(ModbusControlle
}
ModbusCommandItem ModbusCommandItem::create_write_single_command(ModbusController *modbusdevice, uint16_t start_address,
int16_t value) {
uint16_t value) {
ModbusCommandItem cmd;
cmd.modbusdevice = modbusdevice;
cmd.register_type = ModbusRegisterType::HOLDING;
@@ -427,8 +429,10 @@ ModbusCommandItem ModbusCommandItem::create_write_single_command(ModbusControlle
const std::vector<uint8_t> &data) {
modbusdevice->on_write_register_response(cmd.register_type, start_address, data);
};
cmd.payload.push_back((value / 256) & 0xFF);
cmd.payload.push_back((value % 256) & 0xFF);
auto decoded_value = decode_value(value);
cmd.payload.push_back(decoded_value[0]);
cmd.payload.push_back(decoded_value[1]);
return cmd;
}
@@ -463,84 +467,70 @@ bool ModbusCommandItem::send() {
return true;
}
std::vector<uint16_t> float_to_payload(float value, SensorValueType value_type) {
union {
float float_value;
uint32_t raw;
} raw_to_float;
std::vector<uint16_t> data;
int32_t val;
void number_to_payload(std::vector<uint16_t> &data, int64_t value, SensorValueType value_type) {
switch (value_type) {
case SensorValueType::U_WORD:
case SensorValueType::S_WORD:
// cast truncates the float do some rounding here
data.push_back(lroundf(value) & 0xFFFF);
data.push_back(value & 0xFFFF);
break;
case SensorValueType::U_DWORD:
case SensorValueType::S_DWORD:
val = lroundf(value);
data.push_back((val & 0xFFFF0000) >> 16);
data.push_back(val & 0xFFFF);
case SensorValueType::FP32:
case SensorValueType::FP32_R:
data.push_back((value & 0xFFFF0000) >> 16);
data.push_back(value & 0xFFFF);
break;
case SensorValueType::U_DWORD_R:
case SensorValueType::S_DWORD_R:
val = lroundf(value);
data.push_back(val & 0xFFFF);
data.push_back((val & 0xFFFF0000) >> 16);
data.push_back(value & 0xFFFF);
data.push_back((value & 0xFFFF0000) >> 16);
break;
case SensorValueType::FP32:
raw_to_float.float_value = value;
data.push_back((raw_to_float.raw & 0xFFFF0000) >> 16);
data.push_back(raw_to_float.raw & 0xFFFF);
case SensorValueType::U_QWORD:
case SensorValueType::S_QWORD:
data.push_back((value & 0xFFFF000000000000) >> 48);
data.push_back((value & 0xFFFF00000000) >> 32);
data.push_back((value & 0xFFFF0000) >> 16);
data.push_back(value & 0xFFFF);
break;
case SensorValueType::FP32_R:
raw_to_float.float_value = value;
data.push_back(raw_to_float.raw & 0xFFFF);
data.push_back((raw_to_float.raw & 0xFFFF0000) >> 16);
case SensorValueType::U_QWORD_R:
case SensorValueType::S_QWORD_R:
data.push_back(value & 0xFFFF);
data.push_back((value & 0xFFFF0000) >> 16);
data.push_back((value & 0xFFFF00000000) >> 32);
data.push_back((value & 0xFFFF000000000000) >> 48);
break;
default:
ESP_LOGE(TAG, "Invalid data type for modbus float to payload conversation");
ESP_LOGE(TAG, "Invalid data type for modbus number to payload conversation: %d",
static_cast<uint16_t>(value_type));
break;
}
return data;
}
float payload_to_float(const std::vector<uint8_t> &data, SensorValueType sensor_value_type, uint8_t offset,
uint32_t bitmask) {
union {
float float_value;
uint32_t raw;
} raw_to_float;
int64_t payload_to_number(const std::vector<uint8_t> &data, SensorValueType sensor_value_type, uint8_t offset,
uint32_t bitmask) {
int64_t value = 0; // int64_t because it can hold signed and unsigned 32 bits
float result = NAN;
switch (sensor_value_type) {
case SensorValueType::U_WORD:
value = mask_and_shift_by_rightbit(get_data<uint16_t>(data, offset), bitmask); // default is 0xFFFF ;
result = static_cast<float>(value);
break;
case SensorValueType::U_DWORD:
case SensorValueType::FP32:
value = get_data<uint32_t>(data, offset);
value = mask_and_shift_by_rightbit((uint32_t) value, bitmask);
result = static_cast<float>(value);
break;
case SensorValueType::U_DWORD_R:
case SensorValueType::FP32_R:
value = get_data<uint32_t>(data, offset);
value = static_cast<uint32_t>(value & 0xFFFF) << 16 | (value & 0xFFFF0000) >> 16;
value = mask_and_shift_by_rightbit((uint32_t) value, bitmask);
result = static_cast<float>(value);
break;
case SensorValueType::S_WORD:
value = mask_and_shift_by_rightbit(get_data<int16_t>(data, offset),
bitmask); // default is 0xFFFF ;
result = static_cast<float>(value);
break;
case SensorValueType::S_DWORD:
value = mask_and_shift_by_rightbit(get_data<int32_t>(data, offset), bitmask);
result = static_cast<float>(value);
break;
case SensorValueType::S_DWORD_R: {
value = get_data<uint32_t>(data, offset);
@@ -549,18 +539,14 @@ float payload_to_float(const std::vector<uint8_t> &data, SensorValueType sensor_
uint32_t sign_bit = (value & 0x8000) << 16;
value = mask_and_shift_by_rightbit(
static_cast<int32_t>(((value & 0x7FFF) << 16 | (value & 0xFFFF0000) >> 16) | sign_bit), bitmask);
result = static_cast<float>(value);
} break;
case SensorValueType::U_QWORD:
// Ignore bitmask for U_QWORD
value = get_data<uint64_t>(data, offset);
result = static_cast<float>(value);
break;
case SensorValueType::S_QWORD:
// Ignore bitmask for S_QWORD
value = get_data<int64_t>(data, offset);
result = static_cast<float>(value);
break;
case SensorValueType::U_QWORD_R:
// Ignore bitmask for U_QWORD
@@ -568,32 +554,16 @@ float payload_to_float(const std::vector<uint8_t> &data, SensorValueType sensor_
value = static_cast<uint64_t>(value & 0xFFFF) << 48 | (value & 0xFFFF000000000000) >> 48 |
static_cast<uint64_t>(value & 0xFFFF0000) << 32 | (value & 0x0000FFFF00000000) >> 32 |
static_cast<uint64_t>(value & 0xFFFF00000000) << 16 | (value & 0x00000000FFFF0000) >> 16;
result = static_cast<float>(value);
break;
case SensorValueType::S_QWORD_R:
// Ignore bitmask for S_QWORD
value = get_data<int64_t>(data, offset);
result = static_cast<float>(value);
break;
case SensorValueType::FP32:
raw_to_float.raw = get_data<uint32_t>(data, offset);
ESP_LOGD(TAG, "FP32 = 0x%08X => %f", raw_to_float.raw, raw_to_float.float_value);
result = raw_to_float.float_value;
break;
case SensorValueType::FP32_R: {
auto tmp = get_data<uint32_t>(data, offset);
raw_to_float.raw = static_cast<uint32_t>(tmp & 0xFFFF) << 16 | (tmp & 0xFFFF0000) >> 16;
ESP_LOGD(TAG, "FP32_R = 0x%08X => %f", raw_to_float.raw, raw_to_float.float_value);
result = raw_to_float.float_value;
} break;
case SensorValueType::RAW:
result = NAN;
break;
default:
break;
}
return result;
return value;
}
} // namespace modbus_controller

View File

@@ -195,7 +195,7 @@ inline bool coil_from_vector(int coil, const std::vector<uint8_t> &data) {
*/
template<typename N> N mask_and_shift_by_rightbit(N data, uint32_t mask) {
auto result = (mask & data);
if (result == 0) {
if (result == 0 || mask == 0xFFFFFFFF) {
return result;
}
for (size_t pos = 0; pos < sizeof(N) << 3; pos++) {
@@ -205,22 +205,23 @@ template<typename N> N mask_and_shift_by_rightbit(N data, uint32_t mask) {
return 0;
}
/** convert float value to vector<uint16_t> suitable for sending
* @param value float value to cconvert
/** Convert float value to vector<uint16_t> suitable for sending
* @param data target for payload
* @param value float value to convert
* @param value_type defines if 16/32 or FP32 is used
* @return vector containing the modbus register words in correct order
*/
std::vector<uint16_t> float_to_payload(float value, SensorValueType value_type);
void number_to_payload(std::vector<uint16_t> &data, int64_t value, SensorValueType value_type);
/** convert vector<uint8_t> response payload to float
* @param value float value to cconvert
/** Convert vector<uint8_t> response payload to number.
* @param data payload with the data to convert
* @param sensor_value_type defines if 16/32/64 bits or FP32 is used
* @param offset offset to the data in data
* @param bitmask bitmask used for masking and shifting
* @return float version of the input
* @return 64-bit number of the payload
*/
float payload_to_float(const std::vector<uint8_t> &data, SensorValueType sensor_value_type, uint8_t offset,
uint32_t bitmask);
int64_t payload_to_number(const std::vector<uint8_t> &data, SensorValueType sensor_value_type, uint8_t offset,
uint32_t bitmask);
class ModbusController;
@@ -348,11 +349,11 @@ class ModbusCommandItem {
* @param modbusdevice pointer to the device to execute the command
* @param start_address modbus address of the first register to read
* @param register_count number of registers to read
* @param values uint16_t array to be written to the registers
* @param value uint16_t single register value to write
* @return ModbusCommandItem with the prepared command
*/
static ModbusCommandItem create_write_single_command(ModbusController *modbusdevice, uint16_t start_address,
int16_t value);
uint16_t value);
/** Create modbus write single registers command
* Function 05 (05hex) Write Single Coil
* @param modbusdevice pointer to the device to execute the command
@@ -446,13 +447,36 @@ class ModbusController : public PollingComponent, public modbus::ModbusDevice {
uint16_t command_throttle_;
};
/** convert vector<uint8_t> response payload to float
* @param value float value to cconvert
/** Convert vector<uint8_t> response payload to float.
* @param data payload with data
* @param item SensorItem object
* @return float version of the input
* @return float value of data
*/
inline float payload_to_float(const std::vector<uint8_t> &data, const SensorItem &item) {
return payload_to_float(data, item.sensor_value_type, item.offset, item.bitmask);
int64_t number = payload_to_number(data, item.sensor_value_type, item.offset, item.bitmask);
float float_value;
if (item.sensor_value_type == SensorValueType::FP32 || item.sensor_value_type == SensorValueType::FP32_R) {
float_value = bit_cast<float>(static_cast<uint32_t>(number));
} else {
float_value = static_cast<float>(number);
}
return float_value;
}
inline std::vector<uint16_t> float_to_payload(float value, SensorValueType value_type) {
int64_t val;
if (value_type == SensorValueType::FP32 || value_type == SensorValueType::FP32_R) {
val = bit_cast<uint32_t>(value);
} else {
val = llroundf(value);
}
std::vector<uint16_t> data;
number_to_payload(data, val, value_type);
return data;
}
} // namespace modbus_controller

View File

@@ -0,0 +1,142 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import select
from esphome.const import CONF_ADDRESS, CONF_ID, CONF_LAMBDA
from esphome.jsonschema import jschema_composite
from .. import (
SENSOR_VALUE_TYPE,
TYPE_REGISTER_MAP,
ModbusController,
SensorItem,
modbus_controller_ns,
)
from ..const import (
CONF_FORCE_NEW_RANGE,
CONF_MODBUS_CONTROLLER_ID,
CONF_REGISTER_COUNT,
CONF_SKIP_UPDATES,
CONF_USE_WRITE_MULTIPLE,
CONF_VALUE_TYPE,
CONF_WRITE_LAMBDA,
)
DEPENDENCIES = ["modbus_controller"]
CODEOWNERS = ["@martgras", "@stegm"]
CONF_OPTIONSMAP = "optionsmap"
ModbusSelect = modbus_controller_ns.class_(
"ModbusSelect", cg.Component, select.Select, SensorItem
)
@jschema_composite
def ensure_option_map():
def validator(value):
cv.check_not_templatable(value)
option = cv.All(cv.string_strict)
mapping = cv.All(cv.int_range(-(2**63), 2**63 - 1))
options_map_schema = cv.Schema({option: mapping})
value = options_map_schema(value)
all_values = list(value.values())
unique_values = set(value.values())
if len(all_values) != len(unique_values):
raise cv.Invalid("Mapping values must be unique.")
return value
return validator
def register_count_value_type_min(value):
reg_count = value.get(CONF_REGISTER_COUNT)
if reg_count is not None:
value_type = value[CONF_VALUE_TYPE]
min_register_count = TYPE_REGISTER_MAP[value_type]
if min_register_count > reg_count:
raise cv.Invalid(
f"Value type {value_type} needs at least {min_register_count} registers"
)
return value
INTEGER_SENSOR_VALUE_TYPE = {
key: value for key, value in SENSOR_VALUE_TYPE.items() if not key.startswith("FP")
}
CONFIG_SCHEMA = cv.All(
select.SELECT_SCHEMA.extend(cv.COMPONENT_SCHEMA).extend(
{
cv.GenerateID(): cv.declare_id(ModbusSelect),
cv.GenerateID(CONF_MODBUS_CONTROLLER_ID): cv.use_id(ModbusController),
cv.Required(CONF_ADDRESS): cv.positive_int,
cv.Optional(CONF_VALUE_TYPE, default="U_WORD"): cv.enum(
INTEGER_SENSOR_VALUE_TYPE
),
cv.Optional(CONF_REGISTER_COUNT): cv.positive_int,
cv.Optional(CONF_SKIP_UPDATES, default=0): cv.positive_int,
cv.Optional(CONF_FORCE_NEW_RANGE, default=False): cv.boolean,
cv.Required(CONF_OPTIONSMAP): ensure_option_map(),
cv.Optional(CONF_USE_WRITE_MULTIPLE, default=False): cv.boolean,
cv.Optional(CONF_LAMBDA): cv.returning_lambda,
cv.Optional(CONF_WRITE_LAMBDA): cv.returning_lambda,
},
),
register_count_value_type_min,
)
async def to_code(config):
value_type = config[CONF_VALUE_TYPE]
reg_count = config.get(CONF_REGISTER_COUNT)
if reg_count is None:
reg_count = TYPE_REGISTER_MAP[value_type]
options_map = config[CONF_OPTIONSMAP]
var = cg.new_Pvariable(
config[CONF_ID],
value_type,
config[CONF_ADDRESS],
reg_count,
config[CONF_SKIP_UPDATES],
config[CONF_FORCE_NEW_RANGE],
list(options_map.values()),
)
await cg.register_component(var, config)
await select.register_select(var, config, options=list(options_map.keys()))
parent = await cg.get_variable(config[CONF_MODBUS_CONTROLLER_ID])
cg.add(parent.add_sensor_item(var))
cg.add(var.set_parent(parent))
cg.add(var.set_use_write_mutiple(config[CONF_USE_WRITE_MULTIPLE]))
if CONF_LAMBDA in config:
template_ = await cg.process_lambda(
config[CONF_LAMBDA],
[
(ModbusSelect.operator("const_ptr"), "item"),
(cg.int64, "x"),
(
cg.std_vector.template(cg.uint8).operator("const").operator("ref"),
"data",
),
],
return_type=cg.optional.template(cg.std_string),
)
cg.add(var.set_template(template_))
if CONF_WRITE_LAMBDA in config:
template_ = await cg.process_lambda(
config[CONF_WRITE_LAMBDA],
[
(ModbusSelect.operator("const_ptr"), "item"),
(cg.std_string.operator("const").operator("ref"), "x"),
(cg.int64, "value"),
(cg.std_vector.template(cg.uint16).operator("ref"), "payload"),
],
return_type=cg.optional.template(cg.int64),
)
cg.add(var.set_write_template(template_))

View File

@@ -0,0 +1,86 @@
#include "modbus_select.h"
#include "esphome/core/log.h"
namespace esphome {
namespace modbus_controller {
static const char *const TAG = "modbus_controller.select";
void ModbusSelect::dump_config() { LOG_SELECT(TAG, "Modbus Controller Select", this); }
void ModbusSelect::parse_and_publish(const std::vector<uint8_t> &data) {
int64_t value = payload_to_number(data, this->sensor_value_type, this->offset, this->bitmask);
ESP_LOGD(TAG, "New select value %lld from payload", value);
optional<std::string> new_state;
if (this->transform_func_.has_value()) {
auto val = (*this->transform_func_)(this, value, data);
if (val.has_value()) {
new_state = *val;
ESP_LOGV(TAG, "lambda returned option %s", new_state->c_str());
}
}
if (!new_state.has_value()) {
auto map_it = std::find(this->mapping_.cbegin(), this->mapping_.cend(), value);
if (map_it != this->mapping_.cend()) {
size_t idx = std::distance(this->mapping_.cbegin(), map_it);
new_state = this->traits.get_options()[idx];
ESP_LOGV(TAG, "Found option %s for value %lld", new_state->c_str(), value);
} else {
ESP_LOGE(TAG, "No option found for mapping %lld", value);
}
}
if (new_state.has_value()) {
this->publish_state(new_state.value());
}
}
void ModbusSelect::control(const std::string &value) {
auto options = this->traits.get_options();
auto opt_it = std::find(options.cbegin(), options.cend(), value);
size_t idx = std::distance(options.cbegin(), opt_it);
optional<int64_t> mapval = this->mapping_[idx];
ESP_LOGD(TAG, "Found value %lld for option '%s'", *mapval, value.c_str());
std::vector<uint16_t> data;
if (this->write_transform_func_.has_value()) {
auto val = (*this->write_transform_func_)(this, value, *mapval, data);
if (val.has_value()) {
mapval = *val;
ESP_LOGV(TAG, "write_lambda returned mapping value %lld", *mapval);
} else {
ESP_LOGD(TAG, "Communication handled by write_lambda - exiting control");
return;
}
}
if (data.empty()) {
number_to_payload(data, *mapval, this->sensor_value_type);
} else {
ESP_LOGV(TAG, "Using payload from write lambda");
}
if (data.empty()) {
ESP_LOGW(TAG, "No payload was created for updating select");
return;
}
const uint16_t write_address = this->start_address + this->offset / 2;
ModbusCommandItem write_cmd;
if ((this->register_count == 1) && (!this->use_write_multiple_)) {
write_cmd = ModbusCommandItem::create_write_single_command(parent_, write_address, data[0]);
} else {
write_cmd = ModbusCommandItem::create_write_multiple_command(parent_, write_address, this->register_count, data);
}
parent_->queue_command(write_cmd);
}
} // namespace modbus_controller
} // namespace esphome

View File

@@ -0,0 +1,51 @@
#pragma once
#include <utility>
#include "esphome/components/modbus_controller/modbus_controller.h"
#include "esphome/components/select/select.h"
#include "esphome/core/component.h"
namespace esphome {
namespace modbus_controller {
class ModbusSelect : public Component, public select::Select, public SensorItem {
public:
ModbusSelect(SensorValueType sensor_value_type, uint16_t start_address, uint8_t register_count, uint8_t skip_updates,
bool force_new_range, std::vector<int64_t> mapping) {
this->register_type = ModbusRegisterType::HOLDING; // not configurable
this->sensor_value_type = sensor_value_type;
this->start_address = start_address;
this->offset = 0; // not configurable
this->bitmask = 0xFFFFFFFF; // not configurable
this->register_count = register_count;
this->response_bytes = 0; // not configurable
this->skip_updates = skip_updates;
this->force_new_range = force_new_range;
this->mapping_ = std::move(mapping);
}
using transform_func_t =
std::function<optional<std::string>(ModbusSelect *const, int64_t, const std::vector<uint8_t> &)>;
using write_transform_func_t =
std::function<optional<int64_t>(ModbusSelect *const, const std::string &, int64_t, std::vector<uint16_t> &)>;
void set_parent(ModbusController *const parent) { this->parent_ = parent; }
void set_use_write_mutiple(bool use_write_multiple) { this->use_write_multiple_ = use_write_multiple; }
void set_template(transform_func_t &&f) { this->transform_func_ = f; }
void set_write_template(write_transform_func_t &&f) { this->write_transform_func_ = f; }
void dump_config() override;
void parse_and_publish(const std::vector<uint8_t> &data) override;
void control(const std::string &value) override;
protected:
std::vector<int64_t> mapping_;
ModbusController *parent_;
bool use_write_multiple_{false};
optional<transform_func_t> transform_func_;
optional<write_transform_func_t> write_transform_func_;
};
} // namespace modbus_controller
} // namespace esphome

View File

@@ -40,7 +40,8 @@ RAW_ENCODING = {
}
CONFIG_SCHEMA = cv.All(
text_sensor.TEXT_SENSOR_SCHEMA.extend(cv.COMPONENT_SCHEMA)
text_sensor.text_sensor_schema()
.extend(cv.COMPONENT_SCHEMA)
.extend(ModbusItemBaseSchema)
.extend(
{

View File

@@ -16,6 +16,7 @@ from esphome.const import (
CONF_DISCOVERY_PREFIX,
CONF_DISCOVERY_RETAIN,
CONF_DISCOVERY_UNIQUE_ID_GENERATOR,
CONF_DISCOVERY_OBJECT_ID_GENERATOR,
CONF_ID,
CONF_KEEPALIVE,
CONF_LEVEL,
@@ -97,6 +98,7 @@ MQTTTextSensor = mqtt_ns.class_("MQTTTextSensor", MQTTComponent)
MQTTNumberComponent = mqtt_ns.class_("MQTTNumberComponent", MQTTComponent)
MQTTSelectComponent = mqtt_ns.class_("MQTTSelectComponent", MQTTComponent)
MQTTButtonComponent = mqtt_ns.class_("MQTTButtonComponent", MQTTComponent)
MQTTLockComponent = mqtt_ns.class_("MQTTLockComponent", MQTTComponent)
MQTTDiscoveryUniqueIdGenerator = mqtt_ns.enum("MQTTDiscoveryUniqueIdGenerator")
MQTT_DISCOVERY_UNIQUE_ID_GENERATOR_OPTIONS = {
@@ -104,6 +106,12 @@ MQTT_DISCOVERY_UNIQUE_ID_GENERATOR_OPTIONS = {
"mac": MQTTDiscoveryUniqueIdGenerator.MQTT_MAC_ADDRESS_UNIQUE_ID_GENERATOR,
}
MQTTDiscoveryObjectIdGenerator = mqtt_ns.enum("MQTTDiscoveryObjectIdGenerator")
MQTT_DISCOVERY_OBJECT_ID_GENERATOR_OPTIONS = {
"none": MQTTDiscoveryObjectIdGenerator.MQTT_NONE_OBJECT_ID_GENERATOR,
"device_name": MQTTDiscoveryObjectIdGenerator.MQTT_DEVICE_NAME_OBJECT_ID_GENERATOR,
}
def validate_config(value):
# Populate default fields
@@ -165,6 +173,9 @@ CONFIG_SCHEMA = cv.All(
cv.Optional(CONF_DISCOVERY_UNIQUE_ID_GENERATOR, default="legacy"): cv.enum(
MQTT_DISCOVERY_UNIQUE_ID_GENERATOR_OPTIONS
),
cv.Optional(CONF_DISCOVERY_OBJECT_ID_GENERATOR, default="none"): cv.enum(
MQTT_DISCOVERY_OBJECT_ID_GENERATOR_OPTIONS
),
cv.Optional(CONF_USE_ABBREVIATIONS, default=True): cv.boolean,
cv.Optional(CONF_BIRTH_MESSAGE): MQTT_MESSAGE_SCHEMA,
cv.Optional(CONF_WILL_MESSAGE): MQTT_MESSAGE_SCHEMA,
@@ -244,19 +255,27 @@ async def to_code(config):
discovery_retain = config[CONF_DISCOVERY_RETAIN]
discovery_prefix = config[CONF_DISCOVERY_PREFIX]
discovery_unique_id_generator = config[CONF_DISCOVERY_UNIQUE_ID_GENERATOR]
discovery_object_id_generator = config[CONF_DISCOVERY_OBJECT_ID_GENERATOR]
if not discovery:
cg.add(var.disable_discovery())
elif discovery == "CLEAN":
cg.add(
var.set_discovery_info(
discovery_prefix, discovery_unique_id_generator, discovery_retain, True
discovery_prefix,
discovery_unique_id_generator,
discovery_object_id_generator,
discovery_retain,
True,
)
)
elif CONF_DISCOVERY_RETAIN in config or CONF_DISCOVERY_PREFIX in config:
cg.add(
var.set_discovery_info(
discovery_prefix, discovery_unique_id_generator, discovery_retain
discovery_prefix,
discovery_unique_id_generator,
discovery_object_id_generator,
discovery_retain,
)
)

View File

@@ -538,9 +538,11 @@ void MQTTClientComponent::set_birth_message(MQTTMessage &&message) {
void MQTTClientComponent::set_shutdown_message(MQTTMessage &&message) { this->shutdown_message_ = std::move(message); }
void MQTTClientComponent::set_discovery_info(std::string &&prefix, MQTTDiscoveryUniqueIdGenerator unique_id_generator,
bool retain, bool clean) {
MQTTDiscoveryObjectIdGenerator object_id_generator, bool retain,
bool clean) {
this->discovery_info_.prefix = std::move(prefix);
this->discovery_info_.unique_id_generator = unique_id_generator;
this->discovery_info_.object_id_generator = object_id_generator;
this->discovery_info_.retain = retain;
this->discovery_info_.clean = clean;
}

View File

@@ -61,6 +61,12 @@ enum MQTTDiscoveryUniqueIdGenerator {
MQTT_MAC_ADDRESS_UNIQUE_ID_GENERATOR,
};
/// available discovery object_id generators
enum MQTTDiscoveryObjectIdGenerator {
MQTT_NONE_OBJECT_ID_GENERATOR = 0,
MQTT_DEVICE_NAME_OBJECT_ID_GENERATOR,
};
/** Internal struct for MQTT Home Assistant discovery
*
* See <a href="https://www.home-assistant.io/docs/mqtt/discovery/">MQTT Discovery</a>.
@@ -70,6 +76,7 @@ struct MQTTDiscoveryInfo {
bool retain; ///< Whether to retain discovery messages.
bool clean;
MQTTDiscoveryUniqueIdGenerator unique_id_generator;
MQTTDiscoveryObjectIdGenerator object_id_generator;
};
enum MQTTClientState {
@@ -106,10 +113,11 @@ class MQTTClientComponent : public Component {
* See <a href="https://www.home-assistant.io/docs/mqtt/discovery/">MQTT Discovery</a>.
* @param prefix The Home Assistant discovery prefix.
* @param unique_id_generator Controls how UniqueId is generated.
* @param object_id_generator Controls how ObjectId is generated.
* @param retain Whether to retain discovery messages.
*/
void set_discovery_info(std::string &&prefix, MQTTDiscoveryUniqueIdGenerator unique_id_generator, bool retain,
bool clean = false);
void set_discovery_info(std::string &&prefix, MQTTDiscoveryUniqueIdGenerator unique_id_generator,
MQTTDiscoveryObjectIdGenerator object_id_generator, bool retain, bool clean = false);
/// Get Home Assistant discovery info.
const MQTTDiscoveryInfo &get_discovery_info() const;
/// Globally disable Home Assistant discovery.

View File

@@ -111,12 +111,11 @@ bool MQTTComponent::send_discovery_() {
root[MQTT_PAYLOAD_NOT_AVAILABLE] = this->availability_->payload_not_available;
}
const std::string &node_name = App.get_name();
std::string unique_id = this->unique_id();
const MQTTDiscoveryInfo &discovery_info = global_mqtt_client->get_discovery_info();
if (!unique_id.empty()) {
root[MQTT_UNIQUE_ID] = unique_id;
} else {
const MQTTDiscoveryInfo &discovery_info = global_mqtt_client->get_discovery_info();
if (discovery_info.unique_id_generator == MQTT_MAC_ADDRESS_UNIQUE_ID_GENERATOR) {
char friendly_name_hash[9];
sprintf(friendly_name_hash, "%08x", fnv1_hash(this->friendly_name()));
@@ -129,6 +128,10 @@ bool MQTTComponent::send_discovery_() {
}
}
const std::string &node_name = App.get_name();
if (discovery_info.object_id_generator == MQTT_DEVICE_NAME_OBJECT_ID_GENERATOR)
root[MQTT_OBJECT_ID] = node_name + "_" + this->get_default_object_id_();
JsonObject device_info = root.createNestedObject(MQTT_DEVICE);
device_info[MQTT_DEVICE_IDENTIFIERS] = get_mac_address();
device_info[MQTT_DEVICE_NAME] = node_name;

View File

@@ -107,6 +107,7 @@ constexpr const char *const MQTT_MODE_STATE_TOPIC = "mode_stat_t";
constexpr const char *const MQTT_MODE_STATE_TEMPLATE = "mode_stat_tpl";
constexpr const char *const MQTT_MODES = "modes";
constexpr const char *const MQTT_NAME = "name";
constexpr const char *const MQTT_OBJECT_ID = "obj_id";
constexpr const char *const MQTT_OFF_DELAY = "off_dly";
constexpr const char *const MQTT_ON_COMMAND_TYPE = "on_cmd_type";
constexpr const char *const MQTT_OPTIONS = "ops";
@@ -360,6 +361,7 @@ constexpr const char *const MQTT_MODE_STATE_TOPIC = "mode_state_topic";
constexpr const char *const MQTT_MODE_STATE_TEMPLATE = "mode_state_template";
constexpr const char *const MQTT_MODES = "modes";
constexpr const char *const MQTT_NAME = "name";
constexpr const char *const MQTT_OBJECT_ID = "object_id";
constexpr const char *const MQTT_OFF_DELAY = "off_delay";
constexpr const char *const MQTT_ON_COMMAND_TYPE = "on_command_type";
constexpr const char *const MQTT_OPTIONS = "options";

View File

@@ -0,0 +1,55 @@
#include "mqtt_lock.h"
#include "esphome/core/log.h"
#include "mqtt_const.h"
#ifdef USE_MQTT
#ifdef USE_LOCK
namespace esphome {
namespace mqtt {
static const char *const TAG = "mqtt.lock";
using namespace esphome::lock;
MQTTLockComponent::MQTTLockComponent(lock::Lock *a_lock) : lock_(a_lock) {}
void MQTTLockComponent::setup() {
this->subscribe(this->get_command_topic_(), [this](const std::string &topic, const std::string &payload) {
if (strcasecmp(payload.c_str(), "LOCK") == 0) {
this->lock_->lock();
} else if (strcasecmp(payload.c_str(), "UNLOCK") == 0) {
this->lock_->unlock();
} else if (strcasecmp(payload.c_str(), "OPEN") == 0) {
this->lock_->open();
} else {
ESP_LOGW(TAG, "'%s': Received unknown status payload: %s", this->friendly_name().c_str(), payload.c_str());
this->status_momentary_warning("state", 5000);
}
});
this->lock_->add_on_state_callback([this]() { this->defer("send", [this]() { this->publish_state(); }); });
}
void MQTTLockComponent::dump_config() {
ESP_LOGCONFIG(TAG, "MQTT Lock '%s': ", this->lock_->get_name().c_str());
LOG_MQTT_COMPONENT(true, true);
}
std::string MQTTLockComponent::component_type() const { return "lock"; }
const EntityBase *MQTTLockComponent::get_entity() const { return this->lock_; }
void MQTTLockComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) {
if (this->lock_->traits.get_assumed_state())
root[MQTT_OPTIMISTIC] = true;
}
bool MQTTLockComponent::send_initial_state() { return this->publish_state(); }
bool MQTTLockComponent::publish_state() {
std::string payload = lock_state_to_string(this->lock_->state);
return this->publish(this->get_state_topic_(), payload);
}
} // namespace mqtt
} // namespace esphome
#endif
#endif // USE_MQTT

View File

@@ -0,0 +1,41 @@
#pragma once
#include "esphome/core/defines.h"
#ifdef USE_MQTT
#ifdef USE_LOCK
#include "esphome/components/lock/lock.h"
#include "mqtt_component.h"
namespace esphome {
namespace mqtt {
class MQTTLockComponent : public mqtt::MQTTComponent {
public:
explicit MQTTLockComponent(lock::Lock *a_lock);
// ========== INTERNAL METHODS ==========
// (In most use cases you won't need these)
void setup() override;
void dump_config() override;
void send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) override;
bool send_initial_state() override;
bool publish_state();
protected:
/// "lock" component type.
std::string component_type() const override;
const EntityBase *get_entity() const override;
lock::Lock *lock_;
};
} // namespace mqtt
} // namespace esphome
#endif
#endif // USE_MQTT

View File

@@ -1,7 +1,8 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import text_sensor, mqtt
from esphome.const import CONF_ID, CONF_QOS, CONF_TOPIC
from esphome.const import CONF_QOS, CONF_TOPIC
from .. import mqtt_subscribe_ns
DEPENDENCIES = ["mqtt"]
@@ -11,20 +12,23 @@ MQTTSubscribeTextSensor = mqtt_subscribe_ns.class_(
"MQTTSubscribeTextSensor", text_sensor.TextSensor, cg.Component
)
CONFIG_SCHEMA = text_sensor.TEXT_SENSOR_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(MQTTSubscribeTextSensor),
cv.GenerateID(CONF_MQTT_PARENT_ID): cv.use_id(mqtt.MQTTClientComponent),
cv.Required(CONF_TOPIC): cv.subscribe_topic,
cv.Optional(CONF_QOS, default=0): cv.mqtt_qos,
}
).extend(cv.COMPONENT_SCHEMA)
CONFIG_SCHEMA = (
text_sensor.text_sensor_schema()
.extend(
{
cv.GenerateID(): cv.declare_id(MQTTSubscribeTextSensor),
cv.GenerateID(CONF_MQTT_PARENT_ID): cv.use_id(mqtt.MQTTClientComponent),
cv.Required(CONF_TOPIC): cv.subscribe_topic,
cv.Optional(CONF_QOS, default=0): cv.mqtt_qos,
}
)
.extend(cv.COMPONENT_SCHEMA)
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
var = await text_sensor.new_text_sensor(config)
await cg.register_component(var, config)
await text_sensor.register_text_sensor(var, config)
parent = await cg.get_variable(config[CONF_MQTT_PARENT_ID])
cg.add(var.set_parent(parent))

View File

@@ -17,11 +17,7 @@ NextionTextSensor = nextion_ns.class_(
)
CONFIG_SCHEMA = (
text_sensor.TEXT_SENSOR_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(NextionTextSensor),
}
)
text_sensor.text_sensor_schema(klass=NextionTextSensor)
.extend(CONFIG_TEXT_COMPONENT_SCHEMA)
.extend(cv.polling_component_schema("never"))
)
@@ -30,8 +26,8 @@ CONFIG_SCHEMA = (
async def to_code(config):
hub = await cg.get_variable(config[CONF_NEXTION_ID])
var = cg.new_Pvariable(config[CONF_ID], hub)
await cg.register_component(var, config)
await text_sensor.register_text_sensor(var, config)
await cg.register_component(var, config)
cg.add(hub.register_textsensor_component(var))

View File

@@ -0,0 +1,23 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import output, lock
from esphome.const import CONF_ID, CONF_OUTPUT
from .. import output_ns
OutputLock = output_ns.class_("OutputLock", lock.Lock, cg.Component)
CONFIG_SCHEMA = lock.LOCK_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(OutputLock),
cv.Required(CONF_OUTPUT): cv.use_id(output.BinaryOutput),
}
).extend(cv.COMPONENT_SCHEMA)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await lock.register_lock(var, config)
output_ = await cg.get_variable(config[CONF_OUTPUT])
cg.add(var.set_output(output_))

View File

@@ -0,0 +1,22 @@
#include "output_lock.h"
#include "esphome/core/log.h"
namespace esphome {
namespace output {
static const char *const TAG = "output.lock";
void OutputLock::dump_config() { LOG_LOCK("", "Output Lock", this); }
void OutputLock::control(const lock::LockCall &call) {
auto state = *call.get_state();
if (state == lock::LOCK_STATE_LOCKED) {
this->output_->turn_on();
} else if (state == lock::LOCK_STATE_UNLOCKED) {
this->output_->turn_off();
}
this->publish_state(state);
}
} // namespace output
} // namespace esphome

View File

@@ -0,0 +1,24 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/lock/lock.h"
#include "esphome/components/output/binary_output.h"
namespace esphome {
namespace output {
class OutputLock : public lock::Lock, public Component {
public:
void set_output(BinaryOutput *output) { output_ = output; }
float get_setup_priority() const override { return setup_priority::HARDWARE - 1.0f; }
void dump_config() override;
protected:
void control(const lock::LockCall &call) override;
output::BinaryOutput *output_;
};
} // namespace output
} // namespace esphome

View File

@@ -13,7 +13,7 @@ CONF_PIPSOLAR_ID = "pipsolar_id"
pipsolar_ns = cg.esphome_ns.namespace("pipsolar")
PipsolarComponent = pipsolar_ns.class_("Pipsolar", cg.Component)
PIPSOLAR_COMPONENT_SCHEMA = cv.COMPONENT_SCHEMA.extend(
PIPSOLAR_COMPONENT_SCHEMA = cv.Schema(
{
cv.Required(CONF_PIPSOLAR_ID): cv.use_id(PipsolarComponent),
}

View File

@@ -1,8 +1,7 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import text_sensor
from esphome.const import CONF_ID
from .. import CONF_PIPSOLAR_ID, PIPSOLAR_COMPONENT_SCHEMA, pipsolar_ns
from .. import CONF_PIPSOLAR_ID, PIPSOLAR_COMPONENT_SCHEMA
DEPENDENCIES = ["uart"]
@@ -15,10 +14,6 @@ CONF_LAST_QPIWS = "last_qpiws"
CONF_LAST_QT = "last_qt"
CONF_LAST_QMN = "last_qmn"
PipsolarTextSensor = pipsolar_ns.class_(
"PipsolarTextSensor", text_sensor.TextSensor, cg.Component
)
TYPES = [
CONF_DEVICE_MODE,
CONF_LAST_QPIGS,
@@ -31,12 +26,7 @@ TYPES = [
]
CONFIG_SCHEMA = PIPSOLAR_COMPONENT_SCHEMA.extend(
{
cv.Optional(type): text_sensor.TEXT_SENSOR_SCHEMA.extend(
{cv.GenerateID(): cv.declare_id(PipsolarTextSensor)}
)
for type in TYPES
}
{cv.Optional(type): text_sensor.text_sensor_schema() for type in TYPES}
)
@@ -46,7 +36,5 @@ async def to_code(config):
for type in TYPES:
if type in config:
conf = config[type]
var = cg.new_Pvariable(conf[CONF_ID])
await text_sensor.register_text_sensor(var, conf)
await cg.register_component(var, conf)
var = await text_sensor.new_text_sensor(conf)
cg.add(getattr(paren, f"set_{type}")(var))

View File

@@ -1,13 +0,0 @@
#include "pipsolar_textsensor.h"
#include "esphome/core/log.h"
#include "esphome/core/application.h"
namespace esphome {
namespace pipsolar {
static const char *const TAG = "pipsolar.text_sensor";
void PipsolarTextSensor::dump_config() { LOG_TEXT_SENSOR("", "Pipsolar TextSensor", this); }
} // namespace pipsolar
} // namespace esphome

View File

@@ -1,20 +0,0 @@
#pragma once
#include "../pipsolar.h"
#include "esphome/components/text_sensor/text_sensor.h"
#include "esphome/core/component.h"
namespace esphome {
namespace pipsolar {
class Pipsolar;
class PipsolarTextSensor : public Component, public text_sensor::TextSensor {
public:
void set_parent(Pipsolar *parent) { this->parent_ = parent; };
void dump_config() override;
protected:
Pipsolar *parent_;
};
} // namespace pipsolar
} // namespace esphome

View File

@@ -45,6 +45,12 @@ void PrometheusHandler::handleRequest(AsyncWebServerRequest *req) {
this->switch_row_(stream, obj);
#endif
#ifdef USE_LOCK
this->lock_type_(stream);
for (auto *obj : App.get_locks())
this->lock_row_(stream, obj);
#endif
req->send(stream);
}
@@ -310,6 +316,30 @@ void PrometheusHandler::switch_row_(AsyncResponseStream *stream, switch_::Switch
}
#endif
#ifdef USE_LOCK
void PrometheusHandler::lock_type_(AsyncResponseStream *stream) {
stream->print(F("#TYPE esphome_lock_value GAUGE\n"));
stream->print(F("#TYPE esphome_lock_failed GAUGE\n"));
}
void PrometheusHandler::lock_row_(AsyncResponseStream *stream, lock::Lock *obj) {
if (obj->is_internal())
return;
stream->print(F("esphome_lock_failed{id=\""));
stream->print(obj->get_object_id().c_str());
stream->print(F("\",name=\""));
stream->print(obj->get_name().c_str());
stream->print(F("\"} 0\n"));
// Data itself
stream->print(F("esphome_lock_value{id=\""));
stream->print(obj->get_object_id().c_str());
stream->print(F("\",name=\""));
stream->print(obj->get_name().c_str());
stream->print(F("\"} "));
stream->print(obj->state);
stream->print('\n');
}
#endif
} // namespace prometheus
} // namespace esphome

View File

@@ -76,6 +76,13 @@ class PrometheusHandler : public AsyncWebHandler, public Component {
void switch_row_(AsyncResponseStream *stream, switch_::Switch *obj);
#endif
#ifdef USE_LOCK
/// Return the type for prometheus
void lock_type_(AsyncResponseStream *stream);
/// Return the lock Values state as prometheus data point
void lock_row_(AsyncResponseStream *stream, lock::Lock *obj);
#endif
web_server_base::WebServerBase *base_;
};

View File

@@ -0,0 +1,23 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import esp32_ble_tracker
from esphome.const import CONF_ID
DEPENDENCIES = ["esp32_ble_tracker"]
CODEOWNERS = ["@jeffeb3"]
radon_eye_ble_ns = cg.esphome_ns.namespace("radon_eye_ble")
RadonEyeListener = radon_eye_ble_ns.class_(
"RadonEyeListener", esp32_ble_tracker.ESPBTDeviceListener
)
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(): cv.declare_id(RadonEyeListener),
}
).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA)
def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
yield esp32_ble_tracker.register_ble_device(var, config)

View File

@@ -0,0 +1,25 @@
#include "radon_eye_listener.h"
#include "esphome/core/log.h"
#ifdef USE_ESP32
namespace esphome {
namespace radon_eye_ble {
static const char *const TAG = "radon_eye_ble";
bool RadonEyeListener::parse_device(const esp32_ble_tracker::ESPBTDevice &device) {
if (not device.get_name().empty()) {
if (device.get_name().rfind("FR:R20:SN", 0) == 0) {
// This is an RD200, I think
ESP_LOGD(TAG, "Found Radon Eye RD200 device Name: %s (MAC: %s)", device.get_name().c_str(),
device.address_str().c_str());
}
}
return false;
}
} // namespace radon_eye_ble
} // namespace esphome
#endif

View File

@@ -0,0 +1,19 @@
#pragma once
#ifdef USE_ESP32
#include "esphome/core/component.h"
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
namespace esphome {
namespace radon_eye_ble {
class RadonEyeListener : public esp32_ble_tracker::ESPBTDeviceListener {
public:
bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override;
};
} // namespace radon_eye_ble
} // namespace esphome
#endif

View File

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

View File

@@ -0,0 +1,179 @@
#include "radon_eye_rd200.h"
#ifdef USE_ESP32
namespace esphome {
namespace radon_eye_rd200 {
static const char *const TAG = "radon_eye_rd200";
void RadonEyeRD200::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->read_handle_ = 0;
auto *chr = this->parent()->get_characteristic(service_uuid_, sensors_read_characteristic_uuid_);
if (chr == nullptr) {
ESP_LOGW(TAG, "No sensor read characteristic found at service %s char %s", service_uuid_.to_string().c_str(),
sensors_read_characteristic_uuid_.to_string().c_str());
break;
}
this->read_handle_ = chr->handle;
// Write a 0x50 to the write characteristic.
auto *write_chr = this->parent()->get_characteristic(service_uuid_, sensors_write_characteristic_uuid_);
if (write_chr == nullptr) {
ESP_LOGW(TAG, "No sensor write characteristic found at service %s char %s", service_uuid_.to_string().c_str(),
sensors_read_characteristic_uuid_.to_string().c_str());
break;
}
this->write_handle_ = write_chr->handle;
this->node_state = esp32_ble_tracker::ClientState::ESTABLISHED;
write_query_message_();
request_read_values_();
break;
}
case ESP_GATTC_READ_CHAR_EVT: {
if (param->read.conn_id != this->parent()->conn_id)
break;
if (param->read.status != ESP_GATT_OK) {
ESP_LOGW(TAG, "Error reading char at handle %d, status=%d", param->read.handle, param->read.status);
break;
}
if (param->read.handle == this->read_handle_) {
read_sensors_(param->read.value, param->read.value_len);
}
break;
}
default:
break;
}
}
void RadonEyeRD200::read_sensors_(uint8_t *value, uint16_t value_len) {
if (value_len < 20) {
ESP_LOGD(TAG, "Invalid read");
return;
}
// Example data
// [13:08:47][D][radon_eye_rd200:107]: result bytes: 5010 85EBB940 00000000 00000000 2200 2500 0000
ESP_LOGV(TAG, "result bytes: %02X%02X %02X%02X%02X%02X %02X%02X%02X%02X %02X%02X%02X%02X %02X%02X %02X%02X %02X%02X",
value[0], value[1], value[2], value[3], value[4], value[5], value[6], value[7], value[8], value[9],
value[10], value[11], value[12], value[13], value[14], value[15], value[16], value[17], value[18],
value[19]);
if (value[0] != 0x50) {
// This isn't a sensor reading.
return;
}
// Convert from pCi/L to Bq/m³
constexpr float convert_to_bwpm3 = 37.0;
RadonValue radon_value;
radon_value.chars[0] = value[2];
radon_value.chars[1] = value[3];
radon_value.chars[2] = value[4];
radon_value.chars[3] = value[5];
float radon_now = radon_value.number * convert_to_bwpm3;
if (is_valid_radon_value_(radon_now)) {
radon_sensor_->publish_state(radon_now);
}
radon_value.chars[0] = value[6];
radon_value.chars[1] = value[7];
radon_value.chars[2] = value[8];
radon_value.chars[3] = value[9];
float radon_day = radon_value.number * convert_to_bwpm3;
radon_value.chars[0] = value[10];
radon_value.chars[1] = value[11];
radon_value.chars[2] = value[12];
radon_value.chars[3] = value[13];
float radon_month = radon_value.number * convert_to_bwpm3;
if (is_valid_radon_value_(radon_month)) {
ESP_LOGV(TAG, "Radon Long Term based on month");
radon_long_term_sensor_->publish_state(radon_month);
} else if (is_valid_radon_value_(radon_day)) {
ESP_LOGV(TAG, "Radon Long Term based on day");
radon_long_term_sensor_->publish_state(radon_day);
}
ESP_LOGV(TAG, " Measurements (Bq/m³) now: %0.03f, day: %0.03f, month: %0.03f", radon_now, radon_day, radon_month);
ESP_LOGV(TAG, " Measurements (pCi/L) now: %0.03f, day: %0.03f, month: %0.03f", radon_now / convert_to_bwpm3,
radon_day / convert_to_bwpm3, radon_month / convert_to_bwpm3);
// This instance must not stay connected
// so other clients can connect to it (e.g. the
// mobile app).
parent()->set_enabled(false);
}
bool RadonEyeRD200::is_valid_radon_value_(float radon) { return radon > 0.0 and radon < 37000; }
void RadonEyeRD200::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 RadonEyeRD200::write_query_message_() {
ESP_LOGV(TAG, "writing 0x50 to write service");
int request = 0x50;
auto status = esp_ble_gattc_write_char_descr(this->parent()->gattc_if, this->parent()->conn_id, this->write_handle_,
sizeof(request), (uint8_t *) &request, ESP_GATT_WRITE_TYPE_NO_RSP,
ESP_GATT_AUTH_REQ_NONE);
if (status) {
ESP_LOGW(TAG, "Error sending write request for sensor, status=%d", status);
}
}
void RadonEyeRD200::request_read_values_() {
auto status = esp_ble_gattc_read_char(this->parent()->gattc_if, this->parent()->conn_id, this->read_handle_,
ESP_GATT_AUTH_REQ_NONE);
if (status) {
ESP_LOGW(TAG, "Error sending read request for sensor, status=%d", status);
}
}
void RadonEyeRD200::dump_config() {
LOG_SENSOR(" ", "Radon", this->radon_sensor_);
LOG_SENSOR(" ", "Radon Long Term", this->radon_long_term_sensor_);
}
RadonEyeRD200::RadonEyeRD200()
: PollingComponent(10000),
service_uuid_(esp32_ble_tracker::ESPBTUUID::from_raw(SERVICE_UUID)),
sensors_write_characteristic_uuid_(esp32_ble_tracker::ESPBTUUID::from_raw(WRITE_CHARACTERISTIC_UUID)),
sensors_read_characteristic_uuid_(esp32_ble_tracker::ESPBTUUID::from_raw(READ_CHARACTERISTIC_UUID)) {}
} // namespace radon_eye_rd200
} // namespace esphome
#endif // USE_ESP32

View File

@@ -0,0 +1,59 @@
#pragma once
#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 radon_eye_rd200 {
static const char *const SERVICE_UUID = "00001523-1212-efde-1523-785feabcd123";
static const char *const WRITE_CHARACTERISTIC_UUID = "00001524-1212-efde-1523-785feabcd123";
static const char *const READ_CHARACTERISTIC_UUID = "00001525-1212-efde-1523-785feabcd123";
class RadonEyeRD200 : public PollingComponent, public ble_client::BLEClientNode {
public:
RadonEyeRD200();
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_radon(sensor::Sensor *radon) { radon_sensor_ = radon; }
void set_radon_long_term(sensor::Sensor *radon_long_term) { radon_long_term_sensor_ = radon_long_term; }
protected:
bool is_valid_radon_value_(float radon);
void read_sensors_(uint8_t *value, uint16_t value_len);
void write_query_message_();
void request_read_values_();
sensor::Sensor *radon_sensor_{nullptr};
sensor::Sensor *radon_long_term_sensor_{nullptr};
uint16_t read_handle_;
uint16_t write_handle_;
esp32_ble_tracker::ESPBTUUID service_uuid_;
esp32_ble_tracker::ESPBTUUID sensors_write_characteristic_uuid_;
esp32_ble_tracker::ESPBTUUID sensors_read_characteristic_uuid_;
union RadonValue {
char chars[4];
float number;
};
};
} // namespace radon_eye_rd200
} // namespace esphome
#endif // USE_ESP32

View File

@@ -0,0 +1,55 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import sensor, ble_client
from esphome.const import (
STATE_CLASS_MEASUREMENT,
UNIT_BECQUEREL_PER_CUBIC_METER,
CONF_ID,
CONF_RADON,
CONF_RADON_LONG_TERM,
ICON_RADIOACTIVE,
)
DEPENDENCIES = ["ble_client"]
radon_eye_rd200_ns = cg.esphome_ns.namespace("radon_eye_rd200")
RadonEyeRD200 = radon_eye_rd200_ns.class_(
"RadonEyeRD200", cg.PollingComponent, ble_client.BLEClientNode
)
CONFIG_SCHEMA = cv.All(
cv.Schema(
{
cv.GenerateID(): cv.declare_id(RadonEyeRD200),
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,
),
}
)
.extend(cv.polling_component_schema("5min"))
.extend(ble_client.BLE_CLIENT_SCHEMA),
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await ble_client.register_ble_node(var, config)
if CONF_RADON in config:
sens = await sensor.new_sensor(config[CONF_RADON])
cg.add(var.set_radon(sens))
if CONF_RADON_LONG_TERM in config:
sens = await sensor.new_sensor(config[CONF_RADON_LONG_TERM])
cg.add(var.set_radon_long_term(sens))

View File

@@ -138,6 +138,8 @@ void RotaryEncoderSensor::setup() {
initial_value = 0;
break;
}
initial_value = clamp(initial_value, this->store_.min_value, this->store_.max_value);
this->store_.counter = initial_value;
this->store_.last_read = initial_value;

View File

@@ -6,7 +6,6 @@ from esphome.const import (
ICON_WEATHER_SUNSET_DOWN,
ICON_WEATHER_SUNSET_UP,
CONF_TYPE,
CONF_ID,
CONF_FORMAT,
)
from .. import sun_ns, CONF_SUN_ID, Sun, CONF_ELEVATION, elevation, DEFAULT_ELEVATION
@@ -33,7 +32,8 @@ def validate_optional_icon(config):
CONFIG_SCHEMA = cv.All(
text_sensor.TEXT_SENSOR_SCHEMA.extend(
text_sensor.text_sensor_schema()
.extend(
{
cv.GenerateID(): cv.declare_id(SunTextSensor),
cv.GenerateID(CONF_SUN_ID): cv.use_id(Sun),
@@ -41,15 +41,15 @@ CONFIG_SCHEMA = cv.All(
cv.Optional(CONF_ELEVATION, default=DEFAULT_ELEVATION): elevation,
cv.Optional(CONF_FORMAT, default="%X"): cv.string_strict,
}
).extend(cv.polling_component_schema("60s")),
)
.extend(cv.polling_component_schema("60s")),
validate_optional_icon,
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
var = await text_sensor.new_text_sensor(config)
await cg.register_component(var, config)
await text_sensor.register_text_sensor(var, config)
paren = await cg.get_variable(config[CONF_SUN_ID])
cg.add(var.set_parent(paren))

View File

@@ -4,18 +4,27 @@ from esphome import automation
from esphome.automation import Condition, maybe_simple_id
from esphome.components import mqtt
from esphome.const import (
CONF_DEVICE_CLASS,
CONF_ID,
CONF_INVERTED,
CONF_MQTT_ID,
CONF_ON_TURN_OFF,
CONF_ON_TURN_ON,
CONF_TRIGGER_ID,
CONF_MQTT_ID,
DEVICE_CLASS_EMPTY,
DEVICE_CLASS_OUTLET,
DEVICE_CLASS_SWITCH,
)
from esphome.core import CORE, coroutine_with_priority
from esphome.cpp_helpers import setup_entity
CODEOWNERS = ["@esphome/core"]
IS_PLATFORM_COMPONENT = True
DEVICE_CLASSES = [
DEVICE_CLASS_EMPTY,
DEVICE_CLASS_OUTLET,
DEVICE_CLASS_SWITCH,
]
switch_ns = cg.esphome_ns.namespace("switch_")
Switch = switch_ns.class_("Switch", cg.EntityBase)
@@ -51,6 +60,7 @@ SWITCH_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).e
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(SwitchTurnOffTrigger),
}
),
cv.Optional(CONF_DEVICE_CLASS): cv.one_of(*DEVICE_CLASSES, lower=True),
}
)
@@ -71,6 +81,9 @@ async def setup_switch_core_(var, config):
mqtt_ = cg.new_Pvariable(config[CONF_MQTT_ID], var)
await mqtt.register_mqtt_component(mqtt_, config)
if CONF_DEVICE_CLASS in config:
cg.add(var.set_device_class(config[CONF_DEVICE_CLASS]))
async def register_switch(var, config):
if not CORE.has_id(config[CONF_ID]):

View File

@@ -46,5 +46,12 @@ void Switch::set_inverted(bool inverted) { this->inverted_ = inverted; }
uint32_t Switch::hash_base() { return 3129890955UL; }
bool Switch::is_inverted() const { return this->inverted_; }
std::string Switch::get_device_class() {
if (this->device_class_.has_value())
return *this->device_class_;
return "";
}
void Switch::set_device_class(const std::string &device_class) { this->device_class_ = device_class; }
} // namespace switch_
} // namespace esphome

View File

@@ -20,6 +20,9 @@ namespace switch_ {
if ((obj)->is_inverted()) { \
ESP_LOGCONFIG(TAG, "%s Inverted: YES", prefix); \
} \
if (!(obj)->get_device_class().empty()) { \
ESP_LOGCONFIG(TAG, "%s Device Class: '%s'", prefix, (obj)->get_device_class().c_str()); \
} \
}
/** Base class for all switches.
@@ -88,6 +91,11 @@ class Switch : public EntityBase {
bool is_inverted() const;
/// Get the device class for this switch.
std::string get_device_class();
/// Set the Home Assistant device class for this switch.
void set_device_class(const std::string &device_class);
protected:
/** Write the given state to hardware. You should implement this
* abstract method if you want to create your own switch.
@@ -105,6 +113,7 @@ class Switch : public EntityBase {
bool inverted_{false};
Deduplicator<bool> publish_dedup_;
ESPPreferenceObject rtc_;
optional<std::string> device_class_;
};
} // namespace switch_

View File

@@ -7,9 +7,20 @@ CODEOWNERS = ["@0hax"]
teleinfo_ns = cg.esphome_ns.namespace("teleinfo")
TeleInfo = teleinfo_ns.class_("TeleInfo", cg.PollingComponent, uart.UARTDevice)
CONF_TELEINFO_ID = "teleinfo_id"
CONF_TELEINFO_ID = "teleinfo_id"
CONF_TAG_NAME = "tag_name"
CONF_HISTORICAL_MODE = "historical_mode"
TELEINFO_LISTENER_SCHEMA = cv.Schema(
{
cv.GenerateID(CONF_TELEINFO_ID): cv.use_id(TeleInfo),
cv.Required(CONF_TAG_NAME): cv.string,
}
)
CONFIG_SCHEMA = (
cv.Schema(
{

View File

@@ -3,20 +3,22 @@ import esphome.config_validation as cv
from esphome.components import sensor
from esphome.const import CONF_ID, ICON_FLASH, UNIT_WATT_HOURS
from .. import teleinfo_ns, TeleInfo, CONF_TELEINFO_ID
from .. import (
CONF_TAG_NAME,
TELEINFO_LISTENER_SCHEMA,
teleinfo_ns,
CONF_TELEINFO_ID,
)
CONF_TAG_NAME = "tag_name"
TeleInfoSensor = teleinfo_ns.class_("TeleInfoSensor", sensor.Sensor, cg.Component)
CONFIG_SCHEMA = sensor.sensor_schema(
unit_of_measurement=UNIT_WATT_HOURS, icon=ICON_FLASH, accuracy_decimals=0
).extend(
{
cv.GenerateID(): cv.declare_id(TeleInfoSensor),
cv.GenerateID(CONF_TELEINFO_ID): cv.use_id(TeleInfo),
cv.Required(CONF_TAG_NAME): cv.string,
}
CONFIG_SCHEMA = (
sensor.sensor_schema(
unit_of_measurement=UNIT_WATT_HOURS, icon=ICON_FLASH, accuracy_decimals=0
)
.extend({cv.GenerateID(): cv.declare_id(TeleInfoSensor)})
.extend(TELEINFO_LISTENER_SCHEMA)
)

View File

@@ -1,22 +1,15 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import text_sensor
from esphome.const import CONF_ID
from .. import teleinfo_ns, TeleInfo, CONF_TELEINFO_ID
CONF_TAG_NAME = "tag_name"
from .. import CONF_TAG_NAME, TELEINFO_LISTENER_SCHEMA, teleinfo_ns, CONF_TELEINFO_ID
TeleInfoTextSensor = teleinfo_ns.class_(
"TeleInfoTextSensor", text_sensor.TextSensor, cg.Component
)
CONFIG_SCHEMA = text_sensor.TEXT_SENSOR_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(TeleInfoTextSensor),
cv.GenerateID(CONF_TELEINFO_ID): cv.use_id(TeleInfo),
cv.Required(CONF_TAG_NAME): cv.string,
}
CONFIG_SCHEMA = text_sensor.text_sensor_schema(klass=TeleInfoTextSensor).extend(
TELEINFO_LISTENER_SCHEMA
)

View File

@@ -0,0 +1,103 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import automation
from esphome.components import lock
from esphome.const import (
CONF_ASSUMED_STATE,
CONF_ID,
CONF_LAMBDA,
CONF_LOCK_ACTION,
CONF_OPEN_ACTION,
CONF_OPTIMISTIC,
CONF_STATE,
CONF_UNLOCK_ACTION,
)
from .. import template_ns
TemplateLock = template_ns.class_("TemplateLock", lock.Lock, cg.Component)
LockState = lock.lock_ns.enum("LockState")
LOCK_STATES = {
"LOCKED": LockState.LOCK_STATE_LOCKED,
"UNLOCKED": LockState.LOCK_STATE_UNLOCKED,
"JAMMED": LockState.LOCK_STATE_JAMMED,
"LOCKING": LockState.LOCK_STATE_LOCKING,
"UNLOCKING": LockState.LOCK_STATE_UNLOCKING,
}
validate_lock_state = cv.enum(LOCK_STATES, upper=True)
def validate(config):
if not config[CONF_OPTIMISTIC] and (
CONF_LOCK_ACTION not in config or CONF_UNLOCK_ACTION not in config
):
raise cv.Invalid(
"Either optimistic mode must be enabled, or lock_action and unlock_action must be set, "
"to handle the lock being changed."
)
return config
CONFIG_SCHEMA = cv.All(
lock.LOCK_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(TemplateLock),
cv.Optional(CONF_LAMBDA): cv.returning_lambda,
cv.Optional(CONF_OPTIMISTIC, default=False): cv.boolean,
cv.Optional(CONF_ASSUMED_STATE, default=False): cv.boolean,
cv.Optional(CONF_UNLOCK_ACTION): automation.validate_automation(
single=True
),
cv.Optional(CONF_LOCK_ACTION): automation.validate_automation(single=True),
cv.Optional(CONF_OPEN_ACTION): automation.validate_automation(single=True),
}
).extend(cv.COMPONENT_SCHEMA),
validate,
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await lock.register_lock(var, config)
if CONF_LAMBDA in config:
template_ = await cg.process_lambda(
config[CONF_LAMBDA], [], return_type=cg.optional.template(LockState)
)
cg.add(var.set_state_lambda(template_))
if CONF_UNLOCK_ACTION in config:
await automation.build_automation(
var.get_unlock_trigger(), [], config[CONF_UNLOCK_ACTION]
)
if CONF_LOCK_ACTION in config:
await automation.build_automation(
var.get_lock_trigger(), [], config[CONF_LOCK_ACTION]
)
if CONF_OPEN_ACTION in config:
await automation.build_automation(
var.get_open_trigger(), [], config[CONF_OPEN_ACTION]
)
cg.add(var.traits.set_supports_open(CONF_OPEN_ACTION in config))
cg.add(var.traits.set_assumed_state(config[CONF_ASSUMED_STATE]))
cg.add(var.set_optimistic(config[CONF_OPTIMISTIC]))
@automation.register_action(
"lock.template.publish",
lock.LockPublishAction,
cv.Schema(
{
cv.Required(CONF_ID): cv.use_id(lock.Lock),
cv.Required(CONF_STATE): cv.templatable(validate_lock_state),
}
),
)
async def lock_template_publish_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)
template_ = await cg.templatable(config[CONF_STATE], args, LockState)
cg.add(var.set_state(template_))
return var

View File

@@ -0,0 +1,59 @@
#include "template_lock.h"
#include "esphome/core/log.h"
namespace esphome {
namespace template_ {
using namespace esphome::lock;
static const char *const TAG = "template.lock";
TemplateLock::TemplateLock()
: lock_trigger_(new Trigger<>()), unlock_trigger_(new Trigger<>()), open_trigger_(new Trigger<>()) {}
void TemplateLock::loop() {
if (!this->f_.has_value())
return;
auto val = (*this->f_)();
if (!val.has_value())
return;
this->publish_state(*val);
}
void TemplateLock::control(const lock::LockCall &call) {
if (this->prev_trigger_ != nullptr) {
this->prev_trigger_->stop_action();
}
auto state = *call.get_state();
if (state == LOCK_STATE_LOCKED) {
this->prev_trigger_ = this->lock_trigger_;
this->lock_trigger_->trigger();
} else if (state == LOCK_STATE_UNLOCKED) {
this->prev_trigger_ = this->unlock_trigger_;
this->unlock_trigger_->trigger();
}
if (this->optimistic_)
this->publish_state(state);
}
void TemplateLock::open_latch() {
if (this->prev_trigger_ != nullptr) {
this->prev_trigger_->stop_action();
}
this->prev_trigger_ = this->open_trigger_;
this->open_trigger_->trigger();
}
void TemplateLock::set_optimistic(bool optimistic) { this->optimistic_ = optimistic; }
void TemplateLock::set_state_lambda(std::function<optional<lock::LockState>()> &&f) { this->f_ = f; }
float TemplateLock::get_setup_priority() const { return setup_priority::HARDWARE; }
Trigger<> *TemplateLock::get_lock_trigger() const { return this->lock_trigger_; }
Trigger<> *TemplateLock::get_unlock_trigger() const { return this->unlock_trigger_; }
Trigger<> *TemplateLock::get_open_trigger() const { return this->open_trigger_; }
void TemplateLock::dump_config() {
LOG_LOCK("", "Template Lock", this);
ESP_LOGCONFIG(TAG, " Optimistic: %s", YESNO(this->optimistic_));
}
} // namespace template_
} // namespace esphome

View File

@@ -0,0 +1,38 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/automation.h"
#include "esphome/components/lock/lock.h"
namespace esphome {
namespace template_ {
class TemplateLock : public lock::Lock, public Component {
public:
TemplateLock();
void dump_config() override;
void set_state_lambda(std::function<optional<lock::LockState>()> &&f);
Trigger<> *get_lock_trigger() const;
Trigger<> *get_unlock_trigger() const;
Trigger<> *get_open_trigger() const;
void set_optimistic(bool optimistic);
void loop() override;
float get_setup_priority() const override;
protected:
void control(const lock::LockCall &call) override;
void open_latch() override;
optional<std::function<optional<lock::LockState>()>> f_;
bool optimistic_{false};
Trigger<> *lock_trigger_;
Trigger<> *unlock_trigger_;
Trigger<> *open_trigger_;
Trigger<> *prev_trigger_{nullptr};
};
} // namespace template_
} // namespace esphome

View File

@@ -10,18 +10,21 @@ TemplateTextSensor = template_ns.class_(
"TemplateTextSensor", text_sensor.TextSensor, cg.PollingComponent
)
CONFIG_SCHEMA = text_sensor.TEXT_SENSOR_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(TemplateTextSensor),
cv.Optional(CONF_LAMBDA): cv.returning_lambda,
}
).extend(cv.polling_component_schema("60s"))
CONFIG_SCHEMA = (
text_sensor.text_sensor_schema()
.extend(
{
cv.GenerateID(): cv.declare_id(TemplateTextSensor),
cv.Optional(CONF_LAMBDA): cv.returning_lambda,
}
)
.extend(cv.polling_component_schema("60s"))
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
var = await text_sensor.new_text_sensor(config)
await cg.register_component(var, config)
await text_sensor.register_text_sensor(var, config)
if CONF_LAMBDA in config:
template_ = await cg.process_lambda(

View File

@@ -3,7 +3,9 @@ import esphome.config_validation as cv
from esphome import automation
from esphome.components import mqtt
from esphome.const import (
CONF_ENTITY_CATEGORY,
CONF_FILTERS,
CONF_ICON,
CONF_ID,
CONF_ON_VALUE,
CONF_ON_RAW_VALUE,
@@ -14,6 +16,7 @@ from esphome.const import (
CONF_TO,
)
from esphome.core import CORE, coroutine_with_priority
from esphome.cpp_generator import MockObjClass
from esphome.cpp_helpers import setup_entity
from esphome.util import Registry
@@ -110,12 +113,10 @@ async def map_filter_to_code(config, filter_id):
)
icon = cv.icon
TEXT_SENSOR_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend(
{
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTTextSensor),
cv.GenerateID(): cv.declare_id(TextSensor),
cv.Optional(CONF_FILTERS): validate_filters,
cv.Optional(CONF_ON_VALUE): automation.validate_automation(
{
@@ -132,6 +133,29 @@ TEXT_SENSOR_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).exte
}
)
_UNDEF = object()
def text_sensor_schema(
klass: MockObjClass = _UNDEF,
icon: str = _UNDEF,
entity_category: str = _UNDEF,
) -> cv.Schema:
schema = TEXT_SENSOR_SCHEMA
if klass is not _UNDEF:
schema = schema.extend({cv.GenerateID(): cv.declare_id(klass)})
if icon is not _UNDEF:
schema = schema.extend({cv.Optional(CONF_ICON, default=icon): cv.icon})
if entity_category is not _UNDEF:
schema = schema.extend(
{
cv.Optional(
CONF_ENTITY_CATEGORY, default=entity_category
): cv.entity_category
}
)
return schema
async def build_filters(config):
return await cg.build_registry_list(FILTER_REGISTRY, config)
@@ -164,6 +188,12 @@ async def register_text_sensor(var, config):
await setup_text_sensor_core_(var, config)
async def new_text_sensor(config):
var = cg.new_Pvariable(config[CONF_ID])
await register_text_sensor(var, config)
return var
@coroutine_with_priority(100.0)
async def to_code(config):
cg.add_define("USE_TEXT_SENSOR")

View File

@@ -9,7 +9,11 @@ from .. import touchscreen_ns, CONF_TOUCHSCREEN_ID, Touchscreen, TouchListener
DEPENDENCIES = ["touchscreen"]
TouchscreenBinarySensor = touchscreen_ns.class_(
"TouchscreenBinarySensor", binary_sensor.BinarySensor, TouchListener
"TouchscreenBinarySensor",
binary_sensor.BinarySensor,
cg.Component,
TouchListener,
cg.Parented.template(Touchscreen),
)
CONF_X_MIN = "x_min"
@@ -39,7 +43,7 @@ CONFIG_SCHEMA = cv.All(
cv.Required(CONF_Y_MIN): cv.int_range(min=0, max=2000),
cv.Required(CONF_Y_MAX): cv.int_range(min=0, max=2000),
}
),
).extend(cv.COMPONENT_SCHEMA),
validate_coords,
)
@@ -47,7 +51,9 @@ CONFIG_SCHEMA = cv.All(
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await binary_sensor.register_binary_sensor(var, config)
hub = await cg.get_variable(config[CONF_TOUCHSCREEN_ID])
await cg.register_component(var, config)
await cg.register_parented(var, config[CONF_TOUCHSCREEN_ID])
cg.add(
var.set_area(
config[CONF_X_MIN],
@@ -56,4 +62,3 @@ async def to_code(config):
config[CONF_Y_MAX],
)
)
cg.add(hub.register_listener(var))

View File

@@ -1,13 +1,20 @@
#pragma once
#include "esphome/components/touchscreen/touchscreen.h"
#include "esphome/components/binary_sensor/binary_sensor.h"
#include "esphome/components/touchscreen/touchscreen.h"
#include "esphome/core/component.h"
#include "esphome/core/helpers.h"
namespace esphome {
namespace touchscreen {
class TouchscreenBinarySensor : public binary_sensor::BinarySensor, public TouchListener {
class TouchscreenBinarySensor : public binary_sensor::BinarySensor,
public Component,
public TouchListener,
public Parented<Touchscreen> {
public:
void setup() override { this->parent_->register_listener(this); }
/// Set the touch screen area where the button will detect the touch.
void set_area(int16_t x_min, int16_t x_max, int16_t y_min, int16_t y_max) {
this->x_min_ = x_min;

View File

@@ -1,7 +1,7 @@
from esphome.components import text_sensor
import esphome.config_validation as cv
import esphome.codegen as cg
from esphome.const import CONF_ID, CONF_SENSOR_DATAPOINT
from esphome.const import CONF_SENSOR_DATAPOINT
from .. import tuya_ns, CONF_TUYA_ID, Tuya
DEPENDENCIES = ["tuya"]
@@ -9,19 +9,22 @@ CODEOWNERS = ["@dentra"]
TuyaTextSensor = tuya_ns.class_("TuyaTextSensor", text_sensor.TextSensor, cg.Component)
CONFIG_SCHEMA = text_sensor.TEXT_SENSOR_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(TuyaTextSensor),
cv.GenerateID(CONF_TUYA_ID): cv.use_id(Tuya),
cv.Required(CONF_SENSOR_DATAPOINT): cv.uint8_t,
}
).extend(cv.COMPONENT_SCHEMA)
CONFIG_SCHEMA = (
text_sensor.text_sensor_schema()
.extend(
{
cv.GenerateID(): cv.declare_id(TuyaTextSensor),
cv.GenerateID(CONF_TUYA_ID): cv.use_id(Tuya),
cv.Required(CONF_SENSOR_DATAPOINT): cv.uint8_t,
}
)
.extend(cv.COMPONENT_SCHEMA)
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
var = await text_sensor.new_text_sensor(config)
await cg.register_component(var, config)
await text_sensor.register_text_sensor(var, config)
paren = await cg.get_variable(config[CONF_TUYA_ID])
cg.add(var.set_tuya_parent(paren))

View File

@@ -199,7 +199,7 @@ void Tuya::handle_command_(uint8_t command, uint8_t version, const uint8_t *buff
this->set_timeout("datapoint_dump", 1000, [this] { this->dump_config(); });
this->initialized_callback_.call();
}
this->handle_datapoint_(buffer, len);
this->handle_datapoints_(buffer, len);
break;
case TuyaCommandType::DATAPOINT_QUERY:
break;
@@ -224,105 +224,110 @@ void Tuya::handle_command_(uint8_t command, uint8_t version, const uint8_t *buff
}
}
void Tuya::handle_datapoint_(const uint8_t *buffer, size_t len) {
if (len < 2)
return;
void Tuya::handle_datapoints_(const uint8_t *buffer, size_t len) {
while (len >= 4) {
TuyaDatapoint datapoint{};
datapoint.id = buffer[0];
datapoint.type = (TuyaDatapointType) buffer[1];
datapoint.value_uint = 0;
TuyaDatapoint datapoint{};
datapoint.id = buffer[0];
datapoint.type = (TuyaDatapointType) buffer[1];
datapoint.value_uint = 0;
// Drop update if datapoint is in ignore_mcu_datapoint_update list
for (uint8_t i : this->ignore_mcu_update_on_datapoints_) {
if (datapoint.id == i) {
ESP_LOGV(TAG, "Datapoint %u found in ignore_mcu_update_on_datapoints list, dropping MCU update", datapoint.id);
size_t data_size = (buffer[2] << 8) + buffer[3];
const uint8_t *data = buffer + 4;
size_t data_len = len - 4;
if (data_size > data_len) {
ESP_LOGW(TAG, "Datapoint %u is truncated and cannot be parsed (%zu > %zu)", datapoint.id, data_size, data_len);
return;
}
}
size_t data_size = (buffer[2] << 8) + buffer[3];
const uint8_t *data = buffer + 4;
size_t data_len = len - 4;
if (data_size > data_len) {
ESP_LOGW(TAG, "Datapoint %u has extra bytes that will be ignored (%zu > %zu)", datapoint.id, data_size, data_len);
} else if (data_size < data_len) {
ESP_LOGW(TAG, "Datapoint %u is truncated and cannot be parsed (%zu < %zu)", datapoint.id, data_size, data_len);
return;
}
datapoint.len = data_len;
datapoint.len = data_size;
switch (datapoint.type) {
case TuyaDatapointType::RAW:
datapoint.value_raw = std::vector<uint8_t>(data, data + data_len);
ESP_LOGD(TAG, "Datapoint %u update to %s", datapoint.id, format_hex_pretty(datapoint.value_raw).c_str());
break;
case TuyaDatapointType::BOOLEAN:
if (data_len != 1) {
ESP_LOGW(TAG, "Datapoint %u has bad boolean len %zu", datapoint.id, data_len);
return;
}
datapoint.value_bool = data[0];
ESP_LOGD(TAG, "Datapoint %u update to %s", datapoint.id, ONOFF(datapoint.value_bool));
break;
case TuyaDatapointType::INTEGER:
if (data_len != 4) {
ESP_LOGW(TAG, "Datapoint %u has bad integer len %zu", datapoint.id, data_len);
return;
}
datapoint.value_uint = encode_uint32(data[0], data[1], data[2], data[3]);
ESP_LOGD(TAG, "Datapoint %u update to %d", datapoint.id, datapoint.value_int);
break;
case TuyaDatapointType::STRING:
datapoint.value_string = std::string(reinterpret_cast<const char *>(data), data_len);
ESP_LOGD(TAG, "Datapoint %u update to %s", datapoint.id, datapoint.value_string.c_str());
break;
case TuyaDatapointType::ENUM:
if (data_len != 1) {
ESP_LOGW(TAG, "Datapoint %u has bad enum len %zu", datapoint.id, data_len);
return;
}
datapoint.value_enum = data[0];
ESP_LOGD(TAG, "Datapoint %u update to %d", datapoint.id, datapoint.value_enum);
break;
case TuyaDatapointType::BITMASK:
switch (data_len) {
case 1:
datapoint.value_bitmask = encode_uint32(0, 0, 0, data[0]);
break;
case 2:
datapoint.value_bitmask = encode_uint32(0, 0, data[0], data[1]);
break;
case 4:
datapoint.value_bitmask = encode_uint32(data[0], data[1], data[2], data[3]);
break;
default:
ESP_LOGW(TAG, "Datapoint %u has bad bitmask len %zu", datapoint.id, data_len);
switch (datapoint.type) {
case TuyaDatapointType::RAW:
datapoint.value_raw = std::vector<uint8_t>(data, data + data_size);
ESP_LOGD(TAG, "Datapoint %u update to %s", datapoint.id, format_hex_pretty(datapoint.value_raw).c_str());
break;
case TuyaDatapointType::BOOLEAN:
if (data_size != 1) {
ESP_LOGW(TAG, "Datapoint %u has bad boolean len %zu", datapoint.id, data_size);
return;
}
ESP_LOGD(TAG, "Datapoint %u update to %#08X", datapoint.id, datapoint.value_bitmask);
break;
default:
ESP_LOGW(TAG, "Datapoint %u has unknown type %#02hhX", datapoint.id, static_cast<uint8_t>(datapoint.type));
return;
}
// Update internal datapoints
bool found = false;
for (auto &other : this->datapoints_) {
if (other.id == datapoint.id) {
other = datapoint;
found = true;
}
datapoint.value_bool = data[0];
ESP_LOGD(TAG, "Datapoint %u update to %s", datapoint.id, ONOFF(datapoint.value_bool));
break;
case TuyaDatapointType::INTEGER:
if (data_size != 4) {
ESP_LOGW(TAG, "Datapoint %u has bad integer len %zu", datapoint.id, data_size);
return;
}
datapoint.value_uint = encode_uint32(data[0], data[1], data[2], data[3]);
ESP_LOGD(TAG, "Datapoint %u update to %d", datapoint.id, datapoint.value_int);
break;
case TuyaDatapointType::STRING:
datapoint.value_string = std::string(reinterpret_cast<const char *>(data), data_size);
ESP_LOGD(TAG, "Datapoint %u update to %s", datapoint.id, datapoint.value_string.c_str());
break;
case TuyaDatapointType::ENUM:
if (data_size != 1) {
ESP_LOGW(TAG, "Datapoint %u has bad enum len %zu", datapoint.id, data_size);
return;
}
datapoint.value_enum = data[0];
ESP_LOGD(TAG, "Datapoint %u update to %d", datapoint.id, datapoint.value_enum);
break;
case TuyaDatapointType::BITMASK:
switch (data_size) {
case 1:
datapoint.value_bitmask = encode_uint32(0, 0, 0, data[0]);
break;
case 2:
datapoint.value_bitmask = encode_uint32(0, 0, data[0], data[1]);
break;
case 4:
datapoint.value_bitmask = encode_uint32(data[0], data[1], data[2], data[3]);
break;
default:
ESP_LOGW(TAG, "Datapoint %u has bad bitmask len %zu", datapoint.id, data_size);
return;
}
ESP_LOGD(TAG, "Datapoint %u update to %#08X", datapoint.id, datapoint.value_bitmask);
break;
default:
ESP_LOGW(TAG, "Datapoint %u has unknown type %#02hhX", datapoint.id, static_cast<uint8_t>(datapoint.type));
return;
}
}
if (!found) {
this->datapoints_.push_back(datapoint);
}
// Run through listeners
for (auto &listener : this->listeners_) {
if (listener.datapoint_id == datapoint.id)
listener.on_datapoint(datapoint);
len -= data_size + 4;
buffer = data + data_size;
// drop update if datapoint is in ignore_mcu_datapoint_update list
bool skip = false;
for (auto i : this->ignore_mcu_update_on_datapoints_) {
if (datapoint.id == i) {
ESP_LOGV(TAG, "Datapoint %u found in ignore_mcu_update_on_datapoints list, dropping MCU update", datapoint.id);
skip = true;
break;
}
}
if (skip)
continue;
// Update internal datapoints
bool found = false;
for (auto &other : this->datapoints_) {
if (other.id == datapoint.id) {
other = datapoint;
found = true;
}
}
if (!found) {
this->datapoints_.push_back(datapoint);
}
// Run through listeners
for (auto &listener : this->listeners_) {
if (listener.datapoint_id == datapoint.id)
listener.on_datapoint(datapoint);
}
}
}

View File

@@ -101,7 +101,7 @@ class Tuya : public Component, public uart::UARTDevice {
protected:
void handle_char_(uint8_t c);
void handle_datapoint_(const uint8_t *buffer, size_t len);
void handle_datapoints_(const uint8_t *buffer, size_t len);
optional<TuyaDatapoint> get_datapoint_(uint8_t datapoint_id);
bool validate_message_();

View File

@@ -2,9 +2,6 @@ import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import text_sensor
from esphome.const import (
CONF_ENTITY_CATEGORY,
CONF_ID,
CONF_ICON,
ENTITY_CATEGORY_DIAGNOSTIC,
ICON_NEW_BOX,
CONF_HIDE_TIMESTAMP,
@@ -15,20 +12,22 @@ VersionTextSensor = version_ns.class_(
"VersionTextSensor", text_sensor.TextSensor, cg.Component
)
CONFIG_SCHEMA = text_sensor.TEXT_SENSOR_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(VersionTextSensor),
cv.Optional(CONF_ICON, default=ICON_NEW_BOX): text_sensor.icon,
cv.Optional(CONF_HIDE_TIMESTAMP, default=False): cv.boolean,
cv.Optional(
CONF_ENTITY_CATEGORY, default=ENTITY_CATEGORY_DIAGNOSTIC
): cv.entity_category,
}
).extend(cv.COMPONENT_SCHEMA)
CONFIG_SCHEMA = (
text_sensor.text_sensor_schema(
icon=ICON_NEW_BOX,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
)
.extend(
{
cv.GenerateID(): cv.declare_id(VersionTextSensor),
cv.Optional(CONF_HIDE_TIMESTAMP, default=False): cv.boolean,
}
)
.extend(cv.COMPONENT_SCHEMA)
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await text_sensor.register_text_sensor(var, config)
var = await text_sensor.new_text_sensor(config)
await cg.register_component(var, config)
cg.add(var.set_hide_timestamp(config[CONF_HIDE_TIMESTAMP]))

View File

@@ -152,6 +152,13 @@ void WebServer::setup() {
client->send(this->select_json(obj, obj->state).c_str(), "state");
}
#endif
#ifdef USE_LOCK
for (auto *obj : App.get_locks()) {
if (this->include_internal_ || !obj->is_internal())
client->send(this->lock_json(obj, obj->state).c_str(), "state");
}
#endif
});
#ifdef USE_LOGGER
@@ -287,6 +294,20 @@ void WebServer::handle_index_request(AsyncWebServerRequest *request) {
}
#endif
#ifdef USE_LOCK
for (auto *obj : App.get_locks()) {
if (this->include_internal_ || !obj->is_internal()) {
write_row(stream, obj, "lock", "", [](AsyncResponseStream &stream, EntityBase *obj) {
lock::Lock *lock = (lock::Lock *) obj;
stream.print("<button>Lock</button><button>Unlock</button>");
if (lock->traits.get_supports_open()) {
stream.print("<button>Open</button>");
}
});
}
}
#endif
stream->print(F("</tbody></table><p>See <a href=\"https://esphome.io/web-api/index.html\">ESPHome Web API</a> for "
"REST API documentation.</p>"));
if (this->allow_ota_) {
@@ -763,6 +784,43 @@ std::string WebServer::select_json(select::Select *obj, const std::string &value
}
#endif
#ifdef USE_LOCK
void WebServer::on_lock_update(lock::Lock *obj) {
this->events_.send(this->lock_json(obj, obj->state).c_str(), "state");
}
std::string WebServer::lock_json(lock::Lock *obj, lock::LockState value) {
return json::build_json([obj, value](JsonObject root) {
root["id"] = "lock-" + obj->get_object_id();
root["state"] = lock::lock_state_to_string(value);
root["value"] = value;
});
}
void WebServer::handle_lock_request(AsyncWebServerRequest *request, const UrlMatch &match) {
for (lock::Lock *obj : App.get_locks()) {
if (obj->get_object_id() != match.id)
continue;
if (request->method() == HTTP_GET) {
std::string data = this->lock_json(obj, obj->state);
request->send(200, "text/json", data.c_str());
} else if (match.method == "lock") {
this->defer([obj]() { obj->lock(); });
request->send(200);
} else if (match.method == "unlock") {
this->defer([obj]() { obj->unlock(); });
request->send(200);
} else if (match.method == "open") {
this->defer([obj]() { obj->open(); });
request->send(200);
} else {
request->send(404);
}
return;
}
request->send(404);
}
#endif
bool WebServer::canHandle(AsyncWebServerRequest *request) {
if (request->url() == "/")
return true;
@@ -830,6 +888,11 @@ bool WebServer::canHandle(AsyncWebServerRequest *request) {
return true;
#endif
#ifdef USE_LOCK
if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain == "lock")
return true;
#endif
return false;
}
void WebServer::handleRequest(AsyncWebServerRequest *request) {
@@ -922,6 +985,13 @@ void WebServer::handleRequest(AsyncWebServerRequest *request) {
return;
}
#endif
#ifdef USE_LOCK
if (match.domain == "lock") {
this->handle_lock_request(request, match);
return;
}
#endif
}
bool WebServer::isRequestHandlerTrivial() { return false; }

View File

@@ -185,6 +185,16 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
std::string select_json(select::Select *obj, const std::string &value);
#endif
#ifdef USE_LOCK
void on_lock_update(lock::Lock *obj) override;
/// Handle a lock request under '/lock/<id>/</lock/unlock/open>'.
void handle_lock_request(AsyncWebServerRequest *request, const UrlMatch &match);
/// Dump the lock state with its value as a JSON string.
std::string lock_json(lock::Lock *obj, lock::LockState value);
#endif
/// Override the web handler's canHandle method.
bool canHandle(AsyncWebServerRequest *request) override;
/// Override the web handler's handleRequest method.

View File

@@ -3,8 +3,6 @@ import esphome.config_validation as cv
from esphome.components import text_sensor
from esphome.const import (
CONF_BSSID,
CONF_ENTITY_CATEGORY,
CONF_ID,
CONF_IP_ADDRESS,
CONF_SCAN_RESULTS,
CONF_SSID,
@@ -16,14 +14,16 @@ DEPENDENCIES = ["wifi"]
wifi_info_ns = cg.esphome_ns.namespace("wifi_info")
IPAddressWiFiInfo = wifi_info_ns.class_(
"IPAddressWiFiInfo", text_sensor.TextSensor, cg.Component
"IPAddressWiFiInfo", text_sensor.TextSensor, cg.PollingComponent
)
ScanResultsWiFiInfo = wifi_info_ns.class_(
"ScanResultsWiFiInfo", text_sensor.TextSensor, cg.PollingComponent
)
SSIDWiFiInfo = wifi_info_ns.class_("SSIDWiFiInfo", text_sensor.TextSensor, cg.Component)
SSIDWiFiInfo = wifi_info_ns.class_(
"SSIDWiFiInfo", text_sensor.TextSensor, cg.PollingComponent
)
BSSIDWiFiInfo = wifi_info_ns.class_(
"BSSIDWiFiInfo", text_sensor.TextSensor, cg.Component
"BSSIDWiFiInfo", text_sensor.TextSensor, cg.PollingComponent
)
MacAddressWifiInfo = wifi_info_ns.class_(
"MacAddressWifiInfo", text_sensor.TextSensor, cg.Component
@@ -31,45 +31,20 @@ MacAddressWifiInfo = wifi_info_ns.class_(
CONFIG_SCHEMA = cv.Schema(
{
cv.Optional(CONF_IP_ADDRESS): text_sensor.TEXT_SENSOR_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(IPAddressWiFiInfo),
cv.Optional(
CONF_ENTITY_CATEGORY, default=ENTITY_CATEGORY_DIAGNOSTIC
): cv.entity_category,
}
),
cv.Optional(CONF_SCAN_RESULTS): text_sensor.TEXT_SENSOR_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(ScanResultsWiFiInfo),
cv.Optional(
CONF_ENTITY_CATEGORY, default=ENTITY_CATEGORY_DIAGNOSTIC
): cv.entity_category,
}
cv.Optional(CONF_IP_ADDRESS): text_sensor.text_sensor_schema(
klass=IPAddressWiFiInfo, entity_category=ENTITY_CATEGORY_DIAGNOSTIC
).extend(cv.polling_component_schema("1s")),
cv.Optional(CONF_SCAN_RESULTS): text_sensor.text_sensor_schema(
klass=ScanResultsWiFiInfo, entity_category=ENTITY_CATEGORY_DIAGNOSTIC
).extend(cv.polling_component_schema("60s")),
cv.Optional(CONF_SSID): text_sensor.TEXT_SENSOR_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(SSIDWiFiInfo),
cv.Optional(
CONF_ENTITY_CATEGORY, default=ENTITY_CATEGORY_DIAGNOSTIC
): cv.entity_category,
}
),
cv.Optional(CONF_BSSID): text_sensor.TEXT_SENSOR_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(BSSIDWiFiInfo),
cv.Optional(
CONF_ENTITY_CATEGORY, default=ENTITY_CATEGORY_DIAGNOSTIC
): cv.entity_category,
}
),
cv.Optional(CONF_MAC_ADDRESS): text_sensor.TEXT_SENSOR_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(MacAddressWifiInfo),
cv.Optional(
CONF_ENTITY_CATEGORY, default=ENTITY_CATEGORY_DIAGNOSTIC
): cv.entity_category,
}
cv.Optional(CONF_SSID): text_sensor.text_sensor_schema(
klass=SSIDWiFiInfo, entity_category=ENTITY_CATEGORY_DIAGNOSTIC
).extend(cv.polling_component_schema("1s")),
cv.Optional(CONF_BSSID): text_sensor.text_sensor_schema(
klass=BSSIDWiFiInfo, entity_category=ENTITY_CATEGORY_DIAGNOSTIC
).extend(cv.polling_component_schema("1s")),
cv.Optional(CONF_MAC_ADDRESS): text_sensor.text_sensor_schema(
klass=MacAddressWifiInfo, entity_category=ENTITY_CATEGORY_DIAGNOSTIC
),
}
)
@@ -78,9 +53,8 @@ CONFIG_SCHEMA = cv.Schema(
async def setup_conf(config, key):
if key in config:
conf = config[key]
var = cg.new_Pvariable(conf[CONF_ID])
var = await text_sensor.new_text_sensor(conf)
await cg.register_component(var, conf)
await text_sensor.register_text_sensor(var, conf)
async def to_code(config):

View File

@@ -7,9 +7,9 @@
namespace esphome {
namespace wifi_info {
class IPAddressWiFiInfo : public Component, public text_sensor::TextSensor {
class IPAddressWiFiInfo : public PollingComponent, public text_sensor::TextSensor {
public:
void loop() override {
void update() override {
auto ip = wifi::global_wifi_component->wifi_sta_ip();
if (ip != this->last_ip_) {
this->last_ip_ = ip;
@@ -53,9 +53,9 @@ class ScanResultsWiFiInfo : public PollingComponent, public text_sensor::TextSen
std::string last_scan_results_;
};
class SSIDWiFiInfo : public Component, public text_sensor::TextSensor {
class SSIDWiFiInfo : public PollingComponent, public text_sensor::TextSensor {
public:
void loop() override {
void update() override {
std::string ssid = wifi::global_wifi_component->wifi_ssid();
if (this->last_ssid_ != ssid) {
this->last_ssid_ = ssid;
@@ -70,9 +70,9 @@ class SSIDWiFiInfo : public Component, public text_sensor::TextSensor {
std::string last_ssid_;
};
class BSSIDWiFiInfo : public Component, public text_sensor::TextSensor {
class BSSIDWiFiInfo : public PollingComponent, public text_sensor::TextSensor {
public:
void loop() override {
void update() override {
wifi::bssid_t bssid = wifi::global_wifi_component->wifi_bssid();
if (memcmp(bssid.data(), last_bssid_.data(), 6) != 0) {
std::copy(bssid.begin(), bssid.end(), last_bssid_.begin());

View File

@@ -1713,30 +1713,49 @@ def require_framework_version(
esp_idf=None,
esp32_arduino=None,
esp8266_arduino=None,
max_version=False,
extra_message=None,
):
def validator(value):
core_data = CORE.data[KEY_CORE]
framework = core_data[KEY_TARGET_FRAMEWORK]
if framework == "esp-idf":
if esp_idf is None:
raise Invalid("This feature is incompatible with esp-idf")
msg = "This feature is incompatible with esp-idf"
if extra_message:
msg += f". {extra_message}"
raise Invalid(msg)
required = esp_idf
elif CORE.is_esp32 and framework == "arduino":
if esp32_arduino is None:
raise Invalid(
"This feature is incompatible with ESP32 using arduino framework"
)
msg = "This feature is incompatible with ESP32 using arduino framework"
if extra_message:
msg += f". {extra_message}"
raise Invalid(msg)
required = esp32_arduino
elif CORE.is_esp8266 and framework == "arduino":
if esp8266_arduino is None:
raise Invalid("This feature is incompatible with ESP8266")
msg = "This feature is incompatible with ESP8266"
if extra_message:
msg += f". {extra_message}"
raise Invalid(msg)
required = esp8266_arduino
else:
raise NotImplementedError
if max_version:
if core_data[KEY_FRAMEWORK_VERSION] > required:
msg = f"This feature requires framework version {required} or lower"
if extra_message:
msg += f". {extra_message}"
raise Invalid(msg)
return value
if core_data[KEY_FRAMEWORK_VERSION] < required:
raise Invalid(
f"This feature requires at least framework version {required}"
)
msg = f"This feature requires at least framework version {required}"
if extra_message:
msg += f". {extra_message}"
raise Invalid(msg)
return value
return validator

View File

@@ -1,6 +1,6 @@
"""Constants used by esphome."""
__version__ = "2022.2.0-dev"
__version__ = "2022.2.0b2"
ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
@@ -168,6 +168,7 @@ CONF_DIRECTION = "direction"
CONF_DIRECTION_OUTPUT = "direction_output"
CONF_DISABLED_BY_DEFAULT = "disabled_by_default"
CONF_DISCOVERY = "discovery"
CONF_DISCOVERY_OBJECT_ID_GENERATOR = "discovery_object_id_generator"
CONF_DISCOVERY_PREFIX = "discovery_prefix"
CONF_DISCOVERY_RETAIN = "discovery_retain"
CONF_DISCOVERY_UNIQUE_ID_GENERATOR = "discovery_unique_id_generator"
@@ -334,6 +335,7 @@ CONF_LINE_THICKNESS = "line_thickness"
CONF_LINE_TYPE = "line_type"
CONF_LOADED_INTEGRATIONS = "loaded_integrations"
CONF_LOCAL = "local"
CONF_LOCK_ACTION = "lock_action"
CONF_LOG_TOPIC = "log_topic"
CONF_LOGGER = "logger"
CONF_LOGS = "logs"
@@ -426,9 +428,11 @@ CONF_ON_ENROLLMENT_SCAN = "on_enrollment_scan"
CONF_ON_FINGER_SCAN_MATCHED = "on_finger_scan_matched"
CONF_ON_FINGER_SCAN_UNMATCHED = "on_finger_scan_unmatched"
CONF_ON_JSON_MESSAGE = "on_json_message"
CONF_ON_LOCK = "on_lock"
CONF_ON_LOOP = "on_loop"
CONF_ON_MESSAGE = "on_message"
CONF_ON_MULTI_CLICK = "on_multi_click"
CONF_ON_OPEN = "on_open"
CONF_ON_PRESS = "on_press"
CONF_ON_RAW_VALUE = "on_raw_value"
CONF_ON_RELEASE = "on_release"
@@ -442,6 +446,7 @@ CONF_ON_TIME_SYNC = "on_time_sync"
CONF_ON_TOUCH = "on_touch"
CONF_ON_TURN_OFF = "on_turn_off"
CONF_ON_TURN_ON = "on_turn_on"
CONF_ON_UNLOCK = "on_unlock"
CONF_ON_VALUE = "on_value"
CONF_ON_VALUE_RANGE = "on_value_range"
CONF_ONE = "one"
@@ -709,6 +714,7 @@ CONF_UART_ID = "uart_id"
CONF_UID = "uid"
CONF_UNIQUE = "unique"
CONF_UNIT_OF_MEASUREMENT = "unit_of_measurement"
CONF_UNLOCK_ACTION = "unlock_action"
CONF_UPDATE_INTERVAL = "update_interval"
CONF_UPDATE_ON_BOOT = "update_on_boot"
CONF_URL = "url"
@@ -915,6 +921,9 @@ DEVICE_CLASS_VOLTAGE = "voltage"
DEVICE_CLASS_UPDATE = "update"
# device classes of button component
DEVICE_CLASS_RESTART = "restart"
# device classes of switch component
DEVICE_CLASS_OUTLET = "outlet"
DEVICE_CLASS_SWITCH = "switch"
# state classes

View File

@@ -20,7 +20,7 @@ from esphome.coroutine import FakeEventLoop as _FakeEventLoop
# pylint: disable=unused-import
from esphome.coroutine import coroutine, coroutine_with_priority # noqa
from esphome.helpers import ensure_unique_string, is_hassio
from esphome.helpers import ensure_unique_string, is_ha_addon
from esphome.util import OrderedDict
if TYPE_CHECKING:
@@ -568,12 +568,12 @@ class EsphomeCore:
return self.relative_build_path("src", *path)
def relative_pioenvs_path(self, *path):
if is_hassio():
if is_ha_addon():
return os.path.join("/data", self.name, ".pioenvs", *path)
return self.relative_build_path(".pioenvs", *path)
def relative_piolibdeps_path(self, *path):
if is_hassio():
if is_ha_addon():
return os.path.join("/data", self.name, ".piolibdeps", *path)
return self.relative_build_path(".piolibdeps", *path)

View File

@@ -42,6 +42,9 @@
#ifdef USE_SELECT
#include "esphome/components/select/select.h"
#endif
#ifdef USE_LOCK
#include "esphome/components/lock/lock.h"
#endif
namespace esphome {
@@ -104,6 +107,10 @@ class Application {
void register_select(select::Select *select) { this->selects_.push_back(select); }
#endif
#ifdef USE_LOCK
void register_lock(lock::Lock *a_lock) { this->locks_.push_back(a_lock); }
#endif
/// Register the component in this Application instance.
template<class C> C *register_component(C *c) {
static_assert(std::is_base_of<Component, C>::value, "Only Component subclasses can be registered");
@@ -257,6 +264,15 @@ class Application {
return nullptr;
}
#endif
#ifdef USE_LOCK
const std::vector<lock::Lock *> &get_locks() { return this->locks_; }
lock::Lock *get_lock_by_key(uint32_t key, bool include_internal = false) {
for (auto *obj : this->locks_)
if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal()))
return obj;
return nullptr;
}
#endif
Scheduler scheduler;
@@ -305,6 +321,9 @@ class Application {
#ifdef USE_SELECT
std::vector<select::Select *> selects_{};
#endif
#ifdef USE_LOCK
std::vector<lock::Lock *> locks_{};
#endif
std::string name_;
std::string compilation_time_;

View File

@@ -65,6 +65,12 @@ void Controller::setup_controller(bool include_internal) {
obj->add_on_state_callback([this, obj](const std::string &state) { this->on_select_update(obj, state); });
}
#endif
#ifdef USE_LOCK
for (auto *obj : App.get_locks()) {
if (include_internal || !obj->is_internal())
obj->add_on_state_callback([this, obj]() { this->on_lock_update(obj); });
}
#endif
}
} // namespace esphome

View File

@@ -34,6 +34,9 @@
#ifdef USE_SELECT
#include "esphome/components/select/select.h"
#endif
#ifdef USE_LOCK
#include "esphome/components/lock/lock.h"
#endif
namespace esphome {
@@ -70,6 +73,9 @@ class Controller {
#ifdef USE_SELECT
virtual void on_select_update(select::Select *obj, const std::string &state){};
#endif
#ifdef USE_LOCK
virtual void on_lock_update(lock::Lock *obj){};
#endif
};
} // namespace esphome

View File

@@ -26,6 +26,7 @@
#define USE_GRAPH
#define USE_HOMEASSISTANT_TIME
#define USE_LIGHT
#define USE_LOCK
#define USE_LOGGER
#define USE_MDNS
#define USE_NUMBER

View File

@@ -773,9 +773,11 @@ class MockObj(Expression):
return MockObj(f"{self.base} &", "")
if name == "ptr":
return MockObj(f"{self.base} *", "")
if name == "const_ptr":
return MockObj(f"{self.base} *const", "")
if name == "const":
return MockObj(f"const {self.base}", "")
raise ValueError("Expected one of ref, ptr, const.")
raise ValueError("Expected one of ref, ptr, const_ptr, const.")
@property
def using(self) -> "MockObj":

View File

@@ -15,6 +15,7 @@ uint16 = global_ns.namespace("uint16_t")
uint32 = global_ns.namespace("uint32_t")
uint64 = global_ns.namespace("uint64_t")
int32 = global_ns.namespace("int32_t")
int64 = global_ns.namespace("int64_t")
const_char_ptr = global_ns.namespace("const char *")
NAN = global_ns.namespace("NAN")
esphome_ns = global_ns # using namespace esphome;
@@ -34,3 +35,4 @@ InternalGPIOPin = esphome_ns.class_("InternalGPIOPin", GPIOPin)
gpio_ns = esphome_ns.namespace("gpio")
gpio_Flags = gpio_ns.enum("Flags", is_class=True)
EntityCategory = esphome_ns.enum("EntityCategory")
Parented = esphome_ns.class_("Parented")

View File

@@ -55,13 +55,13 @@ class DashboardSettings:
self.password_hash = ""
self.username = ""
self.using_password = False
self.on_hassio = False
self.on_ha_addon = False
self.cookie_secret = None
def parse_args(self, args):
self.on_hassio = args.hassio
self.on_ha_addon = args.ha_addon
password = args.password or os.getenv("PASSWORD", "")
if not self.on_hassio:
if not self.on_ha_addon:
self.username = args.username or os.getenv("USERNAME", "")
self.using_password = bool(password)
if self.using_password:
@@ -77,14 +77,14 @@ class DashboardSettings:
return get_bool_env("ESPHOME_DASHBOARD_USE_PING")
@property
def using_hassio_auth(self):
if not self.on_hassio:
def using_ha_addon_auth(self):
if not self.on_ha_addon:
return False
return not get_bool_env("DISABLE_HA_AUTHENTICATION")
@property
def using_auth(self):
return self.using_password or self.using_hassio_auth
return self.using_password or self.using_ha_addon_auth
def check_password(self, username, password):
if not self.using_auth:
@@ -138,10 +138,10 @@ def authenticated(func):
def is_authenticated(request_handler):
if settings.on_hassio:
if settings.on_ha_addon:
# Handle ingress - disable auth on ingress port
# X-Hassio-Ingress is automatically stripped on the non-ingress server in nginx
header = request_handler.request.headers.get("X-Hassio-Ingress", "NO")
# X-HA-Ingress is automatically stripped on the non-ingress server in nginx
header = request_handler.request.headers.get("X-HA-Ingress", "NO")
if str(header) == "YES":
return True
if settings.using_auth:
@@ -792,23 +792,23 @@ class LoginHandler(BaseHandler):
self.render(
"login.template.html",
error=error,
hassio=settings.using_hassio_auth,
ha_addon=settings.using_ha_addon_auth,
has_username=bool(settings.username),
**template_args(),
)
def post_hassio_login(self):
def post_ha_addon_login(self):
import requests
headers = {
"X-HASSIO-KEY": os.getenv("HASSIO_TOKEN"),
"Authentication": f"Bearer {os.getenv('SUPERVISOR_TOKEN')}",
}
data = {
"username": self.get_argument("username", ""),
"password": self.get_argument("password", ""),
}
try:
req = requests.post("http://hassio/auth", headers=headers, data=data)
req = requests.post("http://supervisor/auth", headers=headers, data=data)
if req.status_code == 200:
self.set_secure_cookie("authenticated", cookie_authenticated_yes)
self.redirect("/")
@@ -835,8 +835,8 @@ class LoginHandler(BaseHandler):
self.render_login_page(error=error_str)
def post(self):
if settings.using_hassio_auth:
self.post_hassio_login()
if settings.using_ha_addon_auth:
self.post_ha_addon_login()
else:
self.post_native_login()

View File

@@ -1,4 +1,5 @@
import codecs
from contextlib import suppress
import logging
import os
@@ -143,8 +144,8 @@ def get_bool_env(var, default=False):
return bool(os.getenv(var, default))
def is_hassio():
return get_bool_env("ESPHOME_IS_HASSIO")
def is_ha_addon():
return get_bool_env("ESPHOME_IS_HA_ADDON")
def walk_files(path):
@@ -233,8 +234,20 @@ def copy_file_if_changed(src: os.PathLike, dst: os.PathLike) -> None:
return
mkdir_p(os.path.dirname(dst))
try:
shutil.copy(src, dst)
shutil.copyfile(src, dst)
except OSError as err:
if isinstance(err, PermissionError):
# Older esphome versions copied over the src file permissions too.
# So when the dst file had 444 permissions, the dst file would have those
# too and subsequent writes would fail
# -> delete file (it would be overwritten anyway), and try again
# if that fails, use normal error handler
with suppress(OSError):
os.unlink(dst)
shutil.copyfile(src, dst)
return
from esphome.core import EsphomeError
raise EsphomeError(f"Error copying file {src} to {dst}: {err}") from err

View File

@@ -35,9 +35,10 @@ build_flags =
lib_deps =
esphome/noise-c@0.1.4 ; api
makuna/NeoPixelBus@2.6.9 ; neopixelbus
esphome/Improv@1.2.0 ; improv_serial / esp32_improv
esphome/Improv@1.2.1 ; improv_serial / esp32_improv
bblanchon/ArduinoJson@6.18.5 ; json
wjtje/qr-code-generator-library@1.7.0 ; qr_code
functionpointer/arduino-MLX90393@1.0.0 ; mlx90393
build_flags =
-DESPHOME_LOG_LEVEL=ESPHOME_LOG_LEVEL_VERY_VERBOSE
src_filter =
@@ -93,6 +94,7 @@ lib_deps =
DNSServer ; captive_portal (Arduino built-in)
build_flags =
${common:arduino.build_flags}
-Wno-nonnull-compare
-DUSE_ESP8266
-DUSE_ESP8266_FRAMEWORK_ARDUINO
extra_scripts = post:esphome/components/esp8266/post_build.py.script

View File

@@ -9,8 +9,8 @@ pyserial==3.5
platformio==5.2.4 # When updating platformio, also update Dockerfile
esptool==3.2
click==8.0.3
esphome-dashboard==20220116.0
aioesphomeapi==10.8.1
esphome-dashboard==20220209.0
aioesphomeapi==10.8.2
zeroconf==0.37.0
# esp-idf requires this, but doesn't bundle it by default

View File

@@ -4,9 +4,9 @@ black==22.1.0
pre-commit
# Unit tests
pytest==6.2.5
pytest==7.0.0
pytest-cov==3.0.0
pytest-mock==3.7.0
pytest-asyncio==0.17.2
pytest-asyncio==0.18.0
asyncmock==0.4.2
hypothesis==5.49.0

View File

@@ -222,7 +222,13 @@ def lint_ext_check(fname):
@lint_file_check(
exclude=["**.sh", "docker/hassio-rootfs/**", "docker/*.py", "script/*", "setup.py"]
exclude=[
"**.sh",
"docker/ha-addon-rootfs/**",
"docker/*.py",
"script/*",
"setup.py",
]
)
def lint_executable_bit(fname):
ex = EXECUTABLE_BIT[fname]
@@ -598,6 +604,7 @@ def lint_inclusive_language(fname, match):
"esphome/components/display/display_buffer.h",
"esphome/components/fan/fan.h",
"esphome/components/i2c/i2c.h",
"esphome/components/lock/lock.h",
"esphome/components/mqtt/mqtt_component.h",
"esphome/components/number/number.h",
"esphome/components/output/binary_output.h",

View File

@@ -1077,7 +1077,21 @@ sensor:
cs_pin:
mcp23xxx: mcp23017_hub
number: 14
- platform: max9611
i2c_id: i2c_bus
shunt_resistance: 0.2 ohm
gain: '1X'
voltage:
name: Max9611 Voltage
current:
name: Max9611 Current
power:
name: Max9611 Watts
temperature:
name: Max9611 Temp
update_interval: 1s
esp32_touch:
setup_mode: False
iir_filter: 10ms
@@ -2037,6 +2051,10 @@ switch:
- platform: template
id: ble1_status
optimistic: true
- platform: template
id: outlet_switch
optimistic: true
device_class: outlet
fan:
- platform: binary
@@ -2582,3 +2600,28 @@ select:
qr_code:
- id: homepage_qr
value: https://esphome.io/index.html
lock:
- platform: template
id: test_lock1
name: "Template Switch"
lambda: |-
if (id(binary_sensor1).state) {
return LOCK_STATE_LOCKED;
}else{
return LOCK_STATE_UNLOCKED;
}
optimistic: true
assumed_state: no
on_unlock:
- lock.template.publish:
id: test_lock1
state: !lambda "return LOCK_STATE_UNLOCKED;"
on_lock:
- lock.template.publish:
id: test_lock1
state: !lambda "return LOCK_STATE_LOCKED;"
- platform: output
name: "Generic Output Lock"
id: test_lock2
output: pca_6

View File

@@ -324,6 +324,13 @@ sensor:
bus_voltage:
name: "INA260 Voltage"
update_interval: 60s
- platform: radon_eye_rd200
ble_client_id: radon_eye_ble_id
update_interval: 10min
radon:
name: "RD200 Radon"
radon_long_term:
name: "RD200 Radon Long Term"
time:
- platform: homeassistant
@@ -423,10 +430,14 @@ ble_client:
id: airthings01
- mac_address: 01:02:03:04:05:06
id: airthingsmini01
- mac_address: 01:02:03:04:05:06
id: radon_eye_ble_id
airthings_ble:
radon_eye_ble:
ruuvi_ble:
xiaomi_ble:

View File

@@ -692,6 +692,20 @@ sensor:
name: 'testwave'
component_id: 2
wave_channel_id: 1
- platform: mlx90393
oversampling: 1
filter: 0
gain: "3X"
x_axis:
name: "mlxxaxis"
y_axis:
name: "mlxyaxis"
z_axis:
name: "mlxzaxis"
resolution: 17BIT
temperature:
name: "mlxtemp"
oversampling: 2
time:
- platform: homeassistant

View File

@@ -328,6 +328,7 @@ binary_sensor:
number: 3
- platform: touchscreen
touchscreen_id: lilygo_touchscreen
id: touch_key1
x_min: 0
x_max: 100
@@ -561,3 +562,12 @@ touchscreen:
- logger.log:
format: Touch at (%d, %d)
args: ["touch.x", "touch.y"]
- platform: lilygo_t5_47
id: lilygo_touchscreen
interrupt_pin: GPIO36
display: inkplate_display
on_touch:
- logger.log:
format: Touch at (%d, %d)
args: ["touch.x", "touch.y"]

View File

@@ -124,13 +124,13 @@ def test_get_bool_env(monkeypatch, var, value, default, expected):
@pytest.mark.parametrize("value, expected", ((None, False), ("Yes", True)))
def test_is_hassio(monkeypatch, value, expected):
def test_is_ha_addon(monkeypatch, value, expected):
if value is None:
monkeypatch.delenv("ESPHOME_IS_HASSIO", raising=False)
monkeypatch.delenv("ESPHOME_IS_HA_ADDON", raising=False)
else:
monkeypatch.setenv("ESPHOME_IS_HASSIO", value)
monkeypatch.setenv("ESPHOME_IS_HA_ADDON", value)
actual = helpers.is_hassio()
actual = helpers.is_ha_addon()
assert actual == expected