Compare commits

...

297 Commits

Author SHA1 Message Date
Jesse Hills
8558d3bbb4 Add to tests 2021-12-20 08:18:14 +13:00
Jesse Hills
f135ff285a Add codeowner 2021-12-20 08:14:52 +13:00
Jesse Hills
0ab8f2be2e Create safe mode button platform 2021-12-20 08:04:40 +13:00
Martin
1fb0a7109d Modbus: use multiply for publishing number (#2916) 2021-12-15 22:38:23 +13:00
sveip
192eb49589 ESP32 CAM add Automatic Exposure Control option (#2892)
Co-authored-by: Peter <psv@tsat.net>
Co-authored-by: Carlos Garcia Saura <CarlosGS@users.noreply.github.com>
2021-12-15 07:46:43 +13:00
Petr Vraník
5d70ff702b quantile filter support (#2900)
Co-authored-by: Oxan van Leeuwen <oxan@oxanvanleeuwen.nl>
Co-authored-by: pvranik <petr.vranik@mgm-tp.com>
2021-12-15 07:43:42 +13:00
jddonovan
a7b05db2a1 Adding Pascal unit to constants (#2914) 2021-12-15 07:39:50 +13:00
wilberforce
45e346cf1b Allow button POST on press from web server (#2913) 2021-12-14 15:08:01 +13:00
dependabot[bot]
80e2bfada3 Bump black from 21.11b1 to 21.12b0 (#2879)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Oxan van Leeuwen <oxan@oxanvanleeuwen.nl>
2021-12-13 19:15:49 +01:00
Martin
16e7bd0388 fix multi-line comment warning/error (#2891) 2021-12-13 19:15:22 +01:00
Oxan van Leeuwen
b3fb35783e Set text sensor state property to filter output (#2893) 2021-12-13 15:21:09 +13:00
myhomeiot
a79c6aa9e0 Added access to ble_scan_result_evt_param as get_scan_result (#2854) 2021-12-13 13:08:18 +13:00
Martin
4bb58b2de9 Add gpio 12 to strapping pin list (#2902) 2021-12-13 11:03:08 +13:00
Jesse Hills
4e10881331 Log the actual value in modbus number (#2901) 2021-12-13 10:28:19 +13:00
Ben Owen
cec4a81e14 Add reset_duration option for waveshare epaper HAT rev 2.1 (#1481)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2021-12-13 10:27:11 +13:00
Carlos Garcia Saura
da45923d05 Turn verbose a debug statement in bme280 (#2906) 2021-12-13 09:32:37 +13:00
Carlos Garcia Saura
31a61b598b Reduce timing noise in duty_cycle (#2881) 2021-12-13 09:30:47 +13:00
tony
9c0506592b Add light.on_state trigger (#2868) 2021-12-13 09:19:57 +13:00
Oxan van Leeuwen
beeb0c7c5a Introduce hex parsing & formatting helper functions (#2882) 2021-12-13 09:15:23 +13:00
Martin
b2f05faee0 Move i2c scan to setup (#2869)
Co-authored-by: Oxan van Leeuwen <oxan@oxanvanleeuwen.nl>
2021-12-13 09:12:50 +13:00
Jesse Hills
8375e1d64d Bump esphome-dashboard to 20211211.0 (#2904) 2021-12-11 21:03:41 +13:00
Keith Burzinski
cf5193d3e5 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 20:03:22 +13:00
Guillermo Ruffino
c490388e80 Modbus number/output use write single (#2896)
Co-authored-by: Martin <25747549+martgras@users.noreply.github.com>
2021-12-10 09:44:43 +13:00
Jesse Hills
24ec5a6e9d Fix published state for modbus number (#2894) 2021-12-10 09:32:34 +13:00
Oxan van Leeuwen
6df1d5222d Drop unused xSemaphoreWait define (#2888) 2021-12-08 12:46:36 +13:00
Jesse Hills
58fb7a02f6 Bump esphome-dashboard to 20211208.0 (#2887) 2021-12-08 12:42:50 +13:00
Jesse Hills
3d51ac8df0 Use new platform component config blocks for wizard (#2885) 2021-12-08 09:22:03 +13:00
Oxan van Leeuwen
6fe4ff7f85 Drop len parameter from parse_number() (#2883) 2021-12-08 08:46:25 +13:00
Yuval Brik
2253d4bc16 Support different run duration for non-timer wakeup (#2861) 2021-12-06 23:30:27 +01:00
Carlos Garcia Saura
e5cc19de43 Feed watchdog while setting up OTA (#2876) 2021-12-06 23:26:06 +01:00
Jesse Hills
5404617d43 Bump esphome-dashboard to 20211207.0 (#2877) 2021-12-07 07:41:40 +13:00
Oxan van Leeuwen
12467a18e6 Feed watchdog when no component loops (#2857) 2021-12-07 07:24:20 +13:00
Jesse Hills
1db7043a4d Allow wizard to specify secrets (#2875) 2021-12-06 20:58:51 +13:00
Jesse Hills
49932747b3 Adopt using wifi secrets that should exist at this point (#2874) 2021-12-06 20:57:56 +13:00
Jesse Hills
55db190875 Add endpoint to fetch secrets keys (#2873) 2021-12-06 20:15:34 +13:00
Massimiliano Ravelli
71fe2f7ed3 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-06 20:01:50 +13:00
Oxan van Leeuwen
ffc112c9d0 Don't disable idle task WDT when it's not enabled (#2856) 2021-12-06 20:01:14 +13:00
Oxan van Leeuwen
d3e48e296f Fix MCP23x17 not disabling pullup after config change (#2855) 2021-12-06 19:59:50 +13:00
Martin
14f6ae75ea SPS30 : fix i2c read size (#2866) 2021-12-06 19:58:26 +13:00
Carlos Garcia Saura
c84efe64d3 ADC: Turn verbose the debugging "got voltage" (#2863) 2021-12-06 19:56:53 +13:00
Martin
10e89a7dbb tlc59208f : fix compilation error (#2867) 2021-12-06 19:54:46 +13:00
Jesse Hills
ef44acbf10 Bump esphome-dashboard to 20211206.0 (#2870) 2021-12-06 19:42:49 +13:00
dependabot[bot]
06da540ab0 Bump pylint from 2.12.1 to 2.12.2 (#2858)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-12-05 13:29:08 +01:00
Oxan van Leeuwen
40c017fd54 Update ota_component.cpp (#2852) 2021-12-03 07:52:56 +13:00
Jesse Hills
f0bcf81a98 Add a simple helper to remap values (#2850) 2021-12-02 09:23:11 +01:00
Jesse Hills
6a0b343289 Bump version to 2022.1.0-dev 2021-12-02 19:38:49 +13:00
Martin
9ca4e8f32a modbus_controller: bugfix: enable overriding calculated register size (#2845)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2021-12-02 15:45:11 +13:00
Alexandre-Jacques St-Jacques
1b88b7a166 Fix wifi not working with manual_ip using esp-idf (#2849) 2021-12-02 15:33:48 +13:00
Paul Nicholls
caf352ff06 Tuya Cover improvements (#2637) 2021-12-02 15:26:56 +13:00
Oxan van Leeuwen
54106179a1 Set ESP32 watchdog to loop task (#2846) 2021-12-02 09:05:42 +13:00
Oxan van Leeuwen
607601b3a4 Enable a bunch of clang-tidy checks (#2149) 2021-12-02 09:03:51 +13:00
Oxan van Leeuwen
f58828cb82 Support setting manual_ip under networks option (#2839) 2021-12-02 08:55:27 +13:00
Leon Loopik
11330af05f Expand uart invert feature to ESP8266 (#1727) 2021-12-01 20:31:04 +01:00
Oxan van Leeuwen
fbe1bca1b9 Fix compilation using subprocesses (#2834) 2021-12-01 17:37:24 +01:00
Mark Dietzer
24a5325db3 Declare arch_get_cpu_cycle_count for esp8266 as IRAM (#2843) 2021-12-01 10:01:15 +01:00
Yuval Brik
1ec3140759 ESP32 Deep Sleep: correct level value (#2812)
Upon registering for ESP32 deep sleep, DeepSleepComponent::begin_sleep
calculates the level value to wake up on.
As part of PR #2303, the level was changed to be based on `inverted`
instead of `!inverted`:
Before:
1e8e471dec/esphome/components/deep_sleep/deep_sleep_component.cpp (L76)
After:
2b04152482/esphome/components/deep_sleep/deep_sleep_component.cpp (L80)

The level argument to `esp_sleep_enable_ext0_wakeup(pin, level)` [0]
should be 0 when the inverted property is true (low triggers wakeup),
and 1 when inverted property is false (high triggers wakeup).

Also revert the changes of #2644.

[0]
https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/system/sleep_modes.html#_CPPv428esp_sleep_enable_ext0_wakeup10gpio_num_ti
2021-12-01 09:38:58 +01:00
Oxan van Leeuwen
ca8db7696e Don't enable namespace comment clang-tidy check twice (#2830) 2021-12-01 17:21:19 +13:00
Oxan van Leeuwen
c9190574a9 Fix CI check for Windows line endings (#2831) 2021-12-01 17:14:25 +13:00
Oxan van Leeuwen
bfeb0b3639 Add problem matcher for Python formatting errors (#2833) 2021-12-01 17:12:14 +13:00
Oxan van Leeuwen
cbc1334b8d Fix compile warning in Tuya automations (#2837) 2021-12-01 17:11:21 +13:00
mechanarchy
08cbb97ec9 Allow Git credentials to be loaded from secrets (#2825) 2021-12-01 17:10:25 +13:00
Jesse Hills
5719cc1a24 Bump esphome-dashboard to 20211201.0 (#2842) 2021-12-01 16:54:30 +13:00
Jesse Hills
d9513e5ff2 Number mode (#2838) 2021-12-01 08:11:38 +13:00
puuu
b5a0e8b2c0 Implement unit_of_measurement for number component (#2804)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2021-11-30 16:20:59 +01:00
Jesse Hills
b32b918936 Button device class (#2835) 2021-11-30 16:18:21 +01:00
dependabot[bot]
0f47ffd908 Bump aioesphomeapi from 10.2.0 to 10.6.0 (#2840) 2021-11-30 16:17:48 +01:00
Carlos Garcia Saura
cd018ad3a5 Burst read for BME280, to reduce spurious spikes (#2809) 2021-11-30 16:12:52 +01:00
Adrián Panella
24dfecb6f0 cse7766: add energy sensor (#2822) 2021-11-30 16:08:00 +01:00
Oxan van Leeuwen
ab027a6ae2 Fix too-broad matcher for custom CI script (#2829) 2021-11-30 09:35:52 +01:00
Keith Burzinski
556d071e7f Fix 8266 SPI Clock Polarity Setting (#2836) 2021-11-30 19:30:45 +13:00
dentra
939fb313df Tuya text_sensor and raw data usage (#1812) 2021-11-30 08:08:52 +13:00
Jesse Hills
b5639a6472 Add support for button entities (#2824) 2021-11-30 08:00:51 +13:00
definitio
f50e40e0b8 Fix custom mode_state_topic (#2827) 2021-11-29 18:09:09 +01:00
mechanarchy
6f07421911 Optionally show internal components on the web server (#2627)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
Co-authored-by: Oxan van Leeuwen <oxan@oxanvanleeuwen.nl>
2021-11-29 16:52:20 +01:00
Maurice Makaay
adf48246a9 Improve DSMR read timeout handling (#2699) 2021-11-29 16:40:53 +01:00
anatoly-savchenkov
cae283dc86 Fixed data type inside fast_random_8() routine (#2818) 2021-11-29 08:31:15 +13:00
Conclusio
7afcb0fb04 Add delay to improve stability (#2793) 2021-11-29 08:13:42 +13:00
Dave T
10f830c3ef Correct bitmask for third color (blue) scaling. (#2817) 2021-11-29 08:12:40 +13:00
Carlos Garcia Saura
7a5c3aa7ed Fix compilation error for WPA enterprise in ESP-IDF (#2815) 2021-11-29 08:06:53 +13:00
Oxan van Leeuwen
2b50406856 Fix parsing of multiple values in EZO sensor (#2814)
Co-authored-by: Lydia Sevelt <LydiaSevelt@gmail.com>
2021-11-29 08:02:10 +13:00
Oxan van Leeuwen
10a2a7e0fc Fix parsing numbers in Anova (#2816) 2021-11-29 08:00:29 +13:00
Oxan van Leeuwen
7a564b222d Make clang-tidy suggest stdint.h int types (#2820) 2021-11-29 07:59:30 +13:00
Maurice Makaay
671d68bc2c Add missing nvs_flash_init() to ESP32 preferences code (#2805)
Co-authored-by: Maurice Makaay <mmakaay1@xs4all.net>
2021-11-26 21:25:58 +01:00
Oxan van Leeuwen
5946c37925 Fix usage of deprecated climate method in anova (#2801) 2021-11-26 09:16:39 +01:00
Martin
17a37b1de9 Modbus_controller: Add custom command. (#2680) 2021-11-26 12:48:52 +13:00
Adrián Panella
e7827a6997 total_daily_energy: allow to disable restore mode (#2795) 2021-11-25 22:35:36 +01:00
Jesse Hills
2347e043a9 Cancel previous workflows for PRs and branches (#2800) 2021-11-25 22:02:39 +01:00
Oxan van Leeuwen
00965fe19e Consistently format errors in CI scripts (#2762) 2021-11-26 09:54:11 +13:00
Oxan van Leeuwen
9681dfb458 Correct constant for dynamic I2S bus in NeoPixelBus (#2797) 2021-11-26 09:37:27 +13:00
Oxan van Leeuwen
5e631bc6ba Only match GCC warnings from ESPHome source files in CI (#2756) 2021-11-26 09:36:42 +13:00
Oxan van Leeuwen
b5f660398c Add map filter for text sensors (#2761) 2021-11-26 09:35:33 +13:00
Oxan van Leeuwen
d50bdf619f Cache virtualenv instead of pip cache between CI runs (#2759) 2021-11-26 09:29:10 +13:00
Oxan van Leeuwen
4e448b21ff Drop obsolete comment from CI workflow file (#2758) 2021-11-26 09:27:53 +13:00
Oxan van Leeuwen
2a78c2970d Fix CI cache key for test3.yaml compile (#2757) 2021-11-26 09:27:34 +13:00
Oxan van Leeuwen
3637be251e Fix parsing numbers from null-terminated buffers (#2755) 2021-11-26 09:00:49 +13:00
dependabot[bot]
2aea27d272 Bump pylint from 2.11.1 to 2.12.1 (#2798) 2021-11-25 20:34:11 +01:00
Maurice Makaay
ceb9b1d1ff Allow empty UART debug: option, logging in hex format by default (#2771)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
Co-authored-by: Maurice Makaay <account-github@makaay.nl>
2021-11-25 11:51:56 +13:00
Martin
ccfa1e23f0 Add support for sdp8xx (#2779) 2021-11-25 11:28:19 +13:00
rsumner
290da8df2d Fix LEDC resolution calculation on ESP32-C3/S2/S3 (#2794)
Co-authored-by: Oxan van Leeuwen <oxan@oxanvanleeuwen.nl>
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2021-11-25 11:22:51 +13:00
Martin
4b1d73791d remove LEDC_HIGH_SPEED_MODE for C3, S2, S3 (#2791) 2021-11-25 08:06:08 +13:00
Jesse Hills
7e8012c1a0 Allow specifying the dashboard bind address (#2787) 2021-11-25 07:59:32 +13:00
Maurice Makaay
15cd602e8b Add support for P1 Data Request pin control (#2676) 2021-11-23 09:34:10 +01:00
krunkel
598f5b241f Remove unnecessary write in AHT10 update (#2675) 2021-11-23 09:26:16 +01:00
dependabot[bot]
335e69e6cd Bump black from 21.10b0 to 21.11b1 (#2760) 2021-11-23 09:24:28 +01:00
Paul Monigatti
05fe5db030 Relax the icon validator to allow non-mdi icons (#2764) 2021-11-23 09:21:14 +01:00
Andreas Hergert
710096b1c6 Fixed wrong setup of tc9548a (#2766) 2021-11-23 09:20:55 +01:00
Dave T
07b882c801 Fix distorted gif frames when resizing (#2774) 2021-11-23 09:20:36 +01:00
cvwillegen
3e5331a263 Prettier date time display after time sync (#2778) 2021-11-23 09:20:20 +01:00
Oxan van Leeuwen
897277992b Introduce str_snprintf helper function (#2780) 2021-11-23 20:30:49 +13:00
Samuel Sieb
1424091ee5 Remove floating point ops from the ISR (#2751)
Co-authored-by: Samuel Sieb <samuel@sieb.net>
2021-11-22 12:11:36 +13:00
Kamil Trzciński
61ec16cdfc esp32_camera_web_server: Improve support for MotionEye (#2777) 2021-11-22 12:09:11 +13:00
Dave T
e5cb5756aa Fix frame scaling for animated gifs (#2750) 2021-11-18 23:20:32 +01:00
Maurice Makaay
9e1c3e8f01 Allow UART debug configuration with no after: definition (#2753) 2021-11-18 22:41:26 +01:00
Martin
448e1690aa Add retry handler (#2721)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
Co-authored-by: Oxan van Leeuwen <oxan@oxanvanleeuwen.nl>
2021-11-18 11:59:40 +13:00
Martin
8267f01ccd Remove arduino dependency from hm3301 (#2745) 2021-11-18 08:03:46 +13:00
Sergey V. DUDANOV
6f9439e1bc Fix byte order in NEC protocol implementation (#2534) 2021-11-17 18:35:50 +01:00
spattinson
06994c0dfc Change LUT for ttgo t5 2.13inch to improve partial refresh (#2475) 2021-11-17 18:28:36 +01:00
Maurice Makaay
dee5d639e2 Add max_telegram_length option to dsmr (#2674)
Co-authored-by: Maurice Makaay <mmakaay1@xs4all.net>
Co-authored-by: Oxan van Leeuwen <oxan@oxanvanleeuwen.nl>
2021-11-17 18:24:02 +01:00
Jesse Hills
df6730be55 Move to use improv lib from platformio (#2741) 2021-11-17 18:23:17 +01:00
Franck Nijhof
6c1ef398bb Re-instate device class update for binary sensors (#2743) 2021-11-17 23:28:31 +13:00
Evgeny
0469e19f54 Fix HM3301 AQI index calculator (#2739) 2021-11-17 09:52:40 +01:00
Jesse Hills
dbcfa7b599 Remove duplicated const data in esp8266 boards (#2740) 2021-11-17 16:22:38 +13:00
rotarykite
df68403b6d 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 07:57:03 +13:00
Ryan Hoffman
57bdc2b885 Add ble_client binary_output (#2200)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2021-11-17 07:30:42 +13:00
Ryan Hoffman
f565ff5def 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-16 18:53:36 +01:00
H. Árkosi Róbert
8ece639987 Change log level from DEBUG to INFO for sniffing services (#2736)
Sniffing for codes only happens if the user deliberately asked for it with the related service through HA - to find out the codes present in the air. The resulted data shouldn't be printed out only in debug mode, as this is information required to be known on demand for later use, not actually a debug info. Changing log level from DEBUG to INFO for sniffing services has two benefits:
- no need to run firmware with DEBUG enabled for occasional sniffing with devices in production (no need to flash back and forth with different log levels set just for this reason)
- if the user still wants DEBUG enabled, sniffed data appears in different color, it's easier to find between the lines.
2021-11-16 23:28:12 +13:00
Jan Harkes
b35f509784 Allow for subsecond sampling of hmc5883l (#2735) 2021-11-16 09:16:43 +01:00
Jesse Hills
f1954df573 Fix zeroconf time comparisons (#2733)
Co-authored-by: J. Nick Koston <nick@koston.org>
2021-11-16 12:47:06 +13:00
Jesse Hills
9e4fa5dcf1 Improv serial/checksum changes (#2731)
Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
2021-11-16 11:02:45 +13:00
Jesse Hills
0809673ba9 Add zeroconf as a direct dependency and lock the version (#2729) 2021-11-16 09:53:52 +13:00
cvwillegen
b386284180 Ignore secrets.yaml on command line (#2715) 2021-11-15 20:06:55 +01:00
Krzysztof Białek
515519bc87 Provide an option to select MQTT unique_id generator (#2701)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
Co-authored-by: Oxan van Leeuwen <oxan@oxanvanleeuwen.nl>
2021-11-15 15:49:18 +01:00
Oxan van Leeuwen
5404163be0 Clean-up MAC address helpers (#2713) 2021-11-15 15:48:16 +01:00
Alexandre-Jacques St-Jacques
0b193eee43 Remove unnecessary duplicate touch_pad_filter_start (#2724) 2021-11-15 11:58:22 +13:00
Jesse Hills
7333123ba4 Fix indentation of write_lambda for modbus_controller number (#2722) 2021-11-15 10:59:48 +13:00
Sergey V. DUDANOV
d99c5ed890 RemoteTransmitter fix. Bug from version 2021.10. Some changes. (#2706) 2021-11-15 10:40:35 +13:00
Oxan van Leeuwen
04740fbcbb Install test requirements in lint Docker image (#2719) 2021-11-15 10:04:43 +13:00
Oxan van Leeuwen
6a7440f7d3 Feed WDT between doing ESP32 touchpad measurements (#2720) 2021-11-15 09:45:25 +13:00
Oxan van Leeuwen
14299bb2cc Drop unused constants from const.py (#2718) 2021-11-15 08:07:58 +13:00
Oxan van Leeuwen
66cebfc992 Restore InterruptLock on wifi-less ESP8266 (#2712) 2021-11-15 08:05:11 +13:00
Maurice Makaay
108b8e6705 Fix rom/rtc.h deprecation compile warning for debug component (#2520) 2021-11-14 16:17:13 +01:00
Clifford Roche
4eaa6afa4d Add greeyac protocol to IR Climate / HeatpumpIR (#2694) 2021-11-14 16:11:21 +01:00
Krzysztof Białek
f643a46bbf Allow setting custom command_topic for Select and Number components (#2714) 2021-11-14 14:59:34 +01:00
Sergey V. DUDANOV
aae63a7ff3 Add climate on_state trigger (#2707) 2021-11-13 15:42:15 +01:00
NeoAcheron
582567696e pmsx003: add support for PMS5003S device (#2710) 2021-11-13 15:14:23 +01:00
Jesse Hills
2e0c89409d Bump ESPAsyncWebServer to 2.1.0 (#2686) 2021-11-13 21:22:32 +13:00
lcavalli
7bb7456a8b Update device classes for binary sensors (#2703) 2021-11-12 13:17:10 +13:00
Jesse Hills
0372e12b81 Defines tidy (#2696)
* Move webserver defines inside arduino block

* Move esp8266 flash define

* Move prometheus define
2021-11-11 10:56:54 +01:00
Jesse Hills
a6873c1520 Only allow prometheus when using arduino (#2697) 2021-11-11 10:56:35 +01:00
Jesse Hills
f11220da3a Remove my.ha links from improv (#2695) 2021-11-11 15:15:37 +13:00
Oxan van Leeuwen
bb9793d5b7 Enable addressable light power supply based on raw values (#2690) 2021-11-11 11:53:25 +13:00
Maurice Makaay
e99af991ec 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 11:34:17 +13:00
Carlos Garcia Saura
abf3708cc2 [remote_transmitter] accurate pulse timing for ESP8266 (#2476) 2021-11-11 11:28:45 +13:00
Jesse Hills
4395d6156d Fix template number initial value being NaN (#2692) 2021-11-10 23:24:48 +01:00
Jesse Hills
04ba53c870 Bump version to 2021.12.0-dev 2021-11-11 10:10:05 +13:00
anatoly-savchenkov
f310cacd41 [ms5611] Re-implement conversion from ADC readings to sensor values (#2665) 2021-11-10 22:01:47 +01:00
Jesse Hills
5ff7c8418c Implement Improv via Serial component (#2423)
Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
2021-11-11 08:55:45 +13:00
Laszlo Gazdag
0bdb48bcac Make OTA function switchable in web_server component (#2685)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2021-11-11 08:31:22 +13:00
Oxan van Leeuwen
99c775d8cb Introduce encode_value/decode_value() template functions (#2662) 2021-11-10 19:44:01 +01:00
Oxan van Leeuwen
4d43396835 Clean-up string sanitation helpers (#2660) 2021-11-10 19:42:41 +01:00
TVDLoewe
92321e219a Max7219digit multiline (#1622)
Co-authored-by: Otto winter <otto@otto-winter.com>
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2021-11-11 07:41:04 +13:00
Oxan van Leeuwen
c422b2fb0b Introduce byteswap helpers (#2661)
* Backport std::byteswap() in helpers.h

* Introduce convert_big_endian() function

* Use convert_big_endian() in i2c byte swap functions
2021-11-10 19:40:18 +01:00
Otto Winter
8aa72f4c1e Neopixelbus redo method definitions (#2616) 2021-11-11 07:35:31 +13:00
Oxan van Leeuwen
d8e33c5a69 Add repeat action for automations (#2538)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2021-11-11 07:30:07 +13:00
Oxan van Leeuwen
15f9677d33 Introduce parse_number() helper function (#2659) 2021-11-11 07:15:06 +13:00
Carlos Garcia Saura
219b225ac0 [ESP32 ADC] Add option for raw uncalibrated output (#2663) 2021-11-10 19:12:57 +01:00
Oxan van Leeuwen
2ac232e634 Add missing hal.h include in esp32_camera_web_server (#2689) 2021-11-10 19:09:10 +01:00
Sam Hughes
710866ff4e CAP1188 Capacitive Touch Sensor Support (#2653) 2021-11-10 18:52:49 +01:00
Martin
662773b075 modbus_controller: remove hard coded register size (#2654) 2021-11-10 16:24:44 +13:00
Carlos Garcia Saura
875b803483 Remove "delay_microseconds_accurate()" and improve systemwide delayMicroseconds() (#2497) 2021-11-10 16:22:00 +13:00
Guillermo Ruffino
6e5cfac927 fix rc switch protocol 6 (#2672) 2021-11-10 16:15:15 +13:00
Duncan Findlay
97eaf3d4a1 Set up output_switch at priority DATA instead of HARDWARE. (#2648) 2021-11-10 16:12:20 +13:00
cvwillegen
366552a969 Remote base add pronto protocol (#2619) 2021-11-10 16:11:35 +13:00
Guillermo Ruffino
57b07441a1 fix esp32 rmt receiver item array length (#2671) 2021-11-10 13:15:02 +13:00
Kamil Trzciński
fb57ab0add Add esp32_camera_web_server: to expose mjpg/jpg images (#2237)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2021-11-10 13:10:07 +13:00
Jesse Hills
d6717c0032 Fix dashboard imports for adoption (#2684) 2021-11-10 08:38:20 +13:00
ychieux
f72389147d SSD1306_base: Add support for 64x32 size and fix flip functions (#2682)
* Add support for SSD1306 OLED display 0.42inch 64x32 and fix a typo in __init__.py preventing flip functions to operate as intended

* convert tab to spaces

* fix typo on filename for __init__.py
2021-11-09 18:47:19 +01:00
Jesse Hills
a509f6ccd2 Fix when package url has no branch/ref (#2683) 2021-11-09 17:14:08 +13:00
Martin
add484a2ea Fix gpio validation for esp32 variants (#2609)
Co-authored-by: Otto Winter <otto@otto-winter.com>
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2021-11-09 07:29:28 +13:00
Paul Monigatti
a17a6d5346 Add HA Entity Category support to MQTT (#2678) 2021-11-08 10:03:30 +01:00
Maurice Makaay
be9439f10d Fix for encrypted DSMR regression (#2679)
Co-authored-by: Maurice Makaay <account-github@makaay.nl>
2021-11-07 20:39:16 -03:00
Maurice Makaay
96a50f5c6b Add SPI lib for ESP8266 and only add lib for ESP32 when using Arduino (#2677)
* Add SPI lib for ESP8266 and only add lib for ESP32 when using Arduino

* Make inclusion of the SPI library unconditional

As suggested by @Oxan. Because the component requires Arduino anyway, there is no need to make the inclusion conditional.

Co-authored-by: Oxan van Leeuwen <oxan@oxanvanleeuwen.nl>

* Fix Python lint issue

Co-authored-by: Maurice Makaay <account-github@makaay.nl>
Co-authored-by: Oxan van Leeuwen <oxan@oxanvanleeuwen.nl>
2021-11-07 19:31:41 +01:00
Jesse Hills
3c0414c420 Add Entity categories for Home Assistant (#2636) 2021-11-08 07:24:52 +13:00
Maurice Makaay
b450d4c734 Fix CRC error during DSMR chunked message reading (#2622)
* DSMR chunk size from 50 to 500

* Still a few CRC errors with 500, upping to 1024.

* Adding timers to measure how long processing DSMR takes

* Handle chunked output from smart meter.

* Cleaning up and commenting the new chunk handling code

* Remove debug code.

* Fixing clang-tidy issues.

* Implementing chunked reading support for encrypted telegrams.

* Remove redundant extra delay for encrypted reader

* Beware not to flush crypted telegram headers

* Use insane data timeout for testing

* Improve logging

* Make clang-tidy happy

Co-authored-by: Maurice Makaay <mmakaay1@xs4all.net>
Co-authored-by: Maurice Makaay <account-github@makaay.nl>
2021-11-06 18:52:04 -03:00
Guillermo Ruffino
d536509a63 Allow esp8266 to compile with no wifi (#2664) 2021-11-05 10:52:38 +13:00
Tim Niemueller
11f1e28139 Make per-loop display clearing optional (#2626)
Currently, in each loop during DisplayBuffer::update_() the display is
cleared by calling DisplayBuffer::clear().

This prevents more efficient display usages that do not render the
screen in each loop, but only if necessary. This can be helpful, for
example, if images are rendered. This would cause the loop time to be
exceeded frequently.

This change adds a new optional flag "auto_clear" that can be used to
control the clearing behavior. If unset, the DisplayBuffer defaults to
enabled auto clearing, the current behavior and thus backward compatible.

This flag applies to displays that use DisplayBuffer.

Example excerpt:
globals:
  - id: state
    type: bool
    restore_value: no
    initial_value: "false"
  - id: state_processed
    type: bool
    restore_value: no
    initial_value: "false"

switch:
  - platform: template
    name: "State"
    id: state_switch
    lambda: |-
      return id(state);
    turn_on_action:
      - globals.set:
          id: state
          value: "true"
      - globals.set:
          id: state_processed
          value: "false"
    turn_off_action:
      - globals.set:
          id: state
          value: "false"
      - globals.set:
          id: state_processed
          value: "false"

display:
  - platform: ili9341
    # ...
    auto_clear_enabled: false
    lambda: |-
      if (!id(state_processed)) {
        it.fill(COLOR_WHITE);
        if (id(state)) {
          it.image(80, 20, id(image1));
        } else {
          it.image(80, 20, id(image2));
        }
        id(state_processed) = true;
      }

Co-authored-by: Tim Niemueller <timdn@google.com>
2021-11-03 17:56:09 +01:00
niklasweber
379c3e98f5 Add restore_mode to rotary_encoder (#2643) 2021-11-03 07:32:24 +13:00
dependabot[bot]
5ea77894b7 Bump black from 21.9b0 to 21.10b0 (#2650)
Bumps [black](https://github.com/psf/black) from 21.9b0 to 21.10b0.
- [Release notes](https://github.com/psf/black/releases)
- [Changelog](https://github.com/psf/black/blob/main/CHANGES.md)
- [Commits](https://github.com/psf/black/commits)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-11-01 21:03:23 +01:00
Carlos Garcia Saura
d54b4e7c44 Fix for noise in pulse_counter and duty_cycle components (#2646) 2021-11-02 08:27:57 +13:00
Jesse Hills
d8b3af3815 Expose webserver_port to the native API (#2640) 2021-11-01 09:33:04 +13:00
Otto Winter
2b04152482 Fix deep sleep invert_wakeup mode (#2644) 2021-10-31 16:07:06 +01:00
Paul Monigatti
331a3ac387 Add option to use MQTT abbreviations (#2641) 2021-10-31 15:34:08 +13:00
Geoffrey Van Landeghem
7eee3cdc7f convert SCD30 into Component, polls dataready register (#2308)
Co-authored-by: Oxan van Leeuwen <oxan@oxanvanleeuwen.nl>
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2021-10-31 15:29:22 +13:00
dependabot[bot]
b12c7432e0 Bump tzlocal from 4.0.2 to 4.1 (#2645) 2021-10-30 20:12:57 +02:00
Ed
696643d037 BH1750: Fix a too high default H-res2 mode value (#2536) 2021-10-29 11:51:57 +13:00
dependabot[bot]
7e54f97003 Bump aioesphomeapi from 10.1.0 to 10.2.0 (#2642)
Bumps [aioesphomeapi](https://github.com/esphome/aioesphomeapi) from 10.1.0 to 10.2.0.
- [Release notes](https://github.com/esphome/aioesphomeapi/releases)
- [Commits](https://github.com/esphome/aioesphomeapi/compare/v10.1.0...v10.2.0)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-10-28 21:24:48 +02:00
Arturo Casal
77dbf84e55 Add support for CSE7761 sensor (#2546)
* Add CSE7761 sensor support

* CSE7761: Added test at test3.yaml

* CSE7761: changed string style

* CSE7761: fixed cpp lint

* CSE7761: Added codeowners

* Lots of code cleanup

* Revert incorrect setup_priority suggestion

* Added error log in read with retries.

Co-authored-by: Oxan van Leeuwen <oxan@oxanvanleeuwen.nl>

* Improved log messages

Co-authored-by: Oxan van Leeuwen <oxan@oxanvanleeuwen.nl>
2021-10-28 20:58:48 +02:00
Martin
2350c5054c use update_interval for sntp synchronization (#2563)
* use update_interval for sntp synchronization

* revert  override of default interval
2021-10-28 20:57:39 +02:00
Jesse Hills
73accf747f Allow cloning/fetching Github PR branches in external_components (#2639) 2021-10-29 07:12:05 +13:00
Alex Iribarren
0d3e6b2c4c Expose web_server port via the API (#2467) 2021-10-28 11:46:55 +13:00
dependabot[bot]
b3d7cc637b Bump aioesphomeapi from 10.0.3 to 10.1.0 (#2638)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-10-28 09:17:30 +13:00
Sean Brogan
2147bcbc29 Remove autoload of xiaomi_ble and ruuvi_ble (#2617) 2021-10-28 09:16:12 +13:00
niklasweber
980c2d4cae Add publish_initial_value option to rotary encoder (#2503) 2021-10-28 08:09:37 +13:00
dependabot[bot]
68316cbcf9 Bump esptool from 3.1 to 3.2 (#2632) 2021-10-26 22:14:44 +02:00
dependabot[bot]
2f4b9263c3 Bump tzlocal from 4.0.1 to 4.0.2 (#2631) 2021-10-26 22:12:37 +02:00
Jesse Hills
c2623a08e3 Fix select.set using lambda (#2633) 2021-10-27 08:27:51 +13:00
Jan Čermák
9f625ee7d1 Fix pin number validation for sn74hc595 (#2621) 2021-10-26 18:10:45 +02:00
Martin
2f85c27a05 fix modbus output (#2630) 2021-10-26 11:30:25 +02:00
Otto Winter
c612a3bf60 Constrain GH Actions workflows permissions (#2625) 2021-10-26 10:55:27 +02:00
Jesse Hills
a01f5f5cf1 Remove power and energy from sensors that are not true power (#2628) 2021-10-26 10:55:20 +02:00
Oxan van Leeuwen
87328686a0 Allow setting URL as platform_version (#2598) 2021-10-26 10:55:09 +02:00
Martin
81c11ba1f7 relax max entities checking (#2629) 2021-10-26 08:53:47 +13:00
Otto Winter
49b17c5a2d Switch issue-close-app to GH Actions and workflow cleanup (#2624) 2021-10-23 21:58:04 +02:00
Otto Winter
de06a781ff ESP8266 disable PIO LDF (#2608) 2021-10-23 19:44:55 +02:00
Otto Winter
8e77e3c685 Fix glue code missing micros() (#2623) 2021-10-23 19:25:53 +02:00
0hax
a687b083ae 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-23 19:01:23 +02:00
Stefan Agner
b9e5c7eb35 Autodetect flash size (#2615) 2021-10-23 13:25:46 +02:00
Otto Winter
1a6a063e04 Move default build path to .esphome directory (#2586) 2021-10-23 12:38:57 +02:00
Otto Winter
d85b7a6bd0 Bump platform-espressif8266 from 2.6.2 to 2.6.3 (#2620) 2021-10-23 12:37:50 +02:00
Andreas Hergert
1c4700f447 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-22 18:52:47 +02:00
Andreas Hergert
83400d0417 Bugfix tca9548a and idf refactor anh (#2612)
Co-authored-by: Andreas Hergert <andreas.hergert@otrs.com>
2021-10-22 18:20:57 +02:00
Otto Winter
77a6461c9d Fix ESP8266 OTA compression only starting framework v2.7.0 (#2610) 2021-10-22 17:23:31 +02:00
Otto Winter
6db9d1122f Fix compiler warnings and update platformio line filter (#2607) 2021-10-22 16:52:43 +02:00
Otto Winter
83bef85415 Add owner to all libraries used (#2604) 2021-10-22 14:14:14 +02:00
Otto Winter
b5b3914bbf Re-raise keyboardinterrupt (#2603) 2021-10-22 14:14:07 +02:00
Otto Winter
0d90ef94ae Add OTA upload compression for ESP8266 (#2601) 2021-10-22 13:02:55 +02:00
Otto Winter
c08b21b7cd Bump noise-c from 0.1.3 to 0.1.4 (#2602) 2021-10-22 12:12:07 +02:00
Paul Monigatti
be3cb9ef00 Add EntityBase properties to ESP32 Camera (#2600) 2021-10-22 12:10:29 +02:00
Oxan van Leeuwen
f7b3f52731 Limit hostnames to 31 characters (#2531) 2021-10-22 12:09:47 +02:00
Otto Winter
9220d9fc52 Fix socket connection closed not detected (#2587) 2021-10-22 10:46:44 +02:00
Otto Winter
68c8547067 Add IDF support to dallas (#2578) 2021-10-21 22:48:28 +02:00
dependabot[bot]
1468acfced Bump tzlocal from 3.0 to 4.0.1 (#2553)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Oxan van Leeuwen <oxan@oxanvanleeuwen.nl>
2021-10-21 22:46:05 +02:00
dependabot[bot]
b141aea4c0 Bump aioesphomeapi from 10.0.0 to 10.0.3 (#2595)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-10-21 20:54:12 +02:00
Jesse Hills
a88c022406 Logging a proper url allows terminals to make it clickable (#2554) 2021-10-21 20:53:06 +02:00
dependabot[bot]
5389382798 Bump paho-mqtt from 1.6.0 to 1.6.1 (#2596)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-10-21 20:09:29 +02:00
Otto Winter
4765173778 Update docker base images (#2583) 2021-10-21 20:07:44 +02:00
Oxan van Leeuwen
07a9cb910f Fix validation of addressable light IDs (#2588) 2021-10-21 20:07:37 +02:00
dependabot[bot]
f408f074c4 Bump platformio from 5.2.1 to 5.2.2 (#2569)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Oxan van Leeuwen <oxan@oxanvanleeuwen.nl>
2021-10-21 20:01:27 +02:00
dependabot[bot]
f1f2640d0e Bump esphome-dashboard from 20211021.0 to 20211021.1 (#2594)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-10-21 20:01:03 +02:00
Oxan van Leeuwen
27d7d7ca69 Fix old-style arduino_version on ESP8266 and with magic values (#2591) 2021-10-21 19:56:47 +02:00
Oxan van Leeuwen
c0fc5b48ae Fix pin/component switchup in SX1509 pin configuration (#2593) 2021-10-21 19:55:19 +02:00
Oxan van Leeuwen
8735d3b83e Fix PlatformIO version for latest Arduino framework (#2590) 2021-10-21 18:59:49 +02:00
Otto Winter
ca59dd1302 Fix HeatpumpIR pin (#2585) 2021-10-21 18:57:03 +02:00
Otto Winter
eccdef8211 Fix mDNS ESP8266 log not included (#2589) 2021-10-21 18:53:08 +02:00
Maurice Makaay
f2ebfe7aef Add mDNS config dump (#2576) 2021-10-21 16:02:28 +02:00
Otto Winter
cac5b356db ESP32 ADC use factory calibration data (#2574) 2021-10-21 16:01:33 +02:00
Otto Winter
156104d5f5 Fix platformio_install_deps no longer installing all lib_deps (#2584) 2021-10-21 15:29:32 +02:00
Otto Winter
c248ba4043 Fix platformio version in Dockerfile doesn't match requirements (#2582) 2021-10-21 14:53:08 +02:00
Otto Winter
c615dc573a Fix ESP8266 dallas GPIO16 INPUT_PULLUP (#2581) 2021-10-21 14:39:36 +02:00
Otto Winter
1caabb6419 Fix ESP8266 OTA adds unnecessary Update library (#2579) 2021-10-21 14:20:57 +02:00
Otto Winter
f41f7994a3 Arduino global delay/millis/... symbols workaround (#2575) 2021-10-21 14:20:23 +02:00
Otto Winter
e39f314e7a Fix wifi ble coexistence check (#2573) 2021-10-21 12:24:01 +02:00
Otto Winter
7f34561e53 Fix ESP8266 GPIO0 Pullup Validation (#2572) 2021-10-21 12:23:37 +02:00
Maurice Makaay
34606b0f1f 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 12:23:21 +02:00
dependabot[bot]
c51b509501 Bump paho-mqtt from 1.5.1 to 1.6.0 (#2568)
Bumps [paho-mqtt](https://github.com/eclipse/paho.mqtt.python) from 1.5.1 to 1.6.0.
- [Release notes](https://github.com/eclipse/paho.mqtt.python/releases)
- [Changelog](https://github.com/eclipse/paho.mqtt.python/blob/master/ChangeLog.txt)
- [Commits](https://github.com/eclipse/paho.mqtt.python/commits)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-10-20 22:59:46 +02:00
Otto Winter
15b5968418 Revert nextion clang-tidy changes (#2566) 2021-10-21 07:31:13 +13:00
Otto Winter
64a45dc6a6 Move running process log line to debug level (#2565) 2021-10-20 20:15:09 +02:00
Jesse Hills
7cfede5b83 Bump esphome-dashboard to 20211021.0 (#2564) 2021-10-21 07:08:34 +13:00
Jesse Hills
e4d17e0b15 A few esp32_ble_server/improv fixes (#2562) 2021-10-21 06:45:10 +13:00
Carlos Garcia Saura
e79f7ce290 [ESP32] ADC auto-range setting (#2541) 2021-10-20 19:44:51 +02:00
Jesse Hills
bcc77c73e1 Bump esphome-dashboard to 20211020.1 (#2559) 2021-10-20 16:17:00 +13:00
Jesse Hills
8b11e5aeb1 Fix HA addon so it does not have logout button (#2558) 2021-10-20 13:25:00 +13:00
Jesse Hills
cb48394e8a Bump dashboard to 20211020.0 (#2556) 2021-10-20 10:53:49 +13:00
Martin
f5441a87e3 ignore exception when not waiting for a response (#2552) 2021-10-20 10:10:24 +13:00
dependabot[bot]
e2a812fa4b Bump pytest-asyncio from 0.15.1 to 0.16.0 (#2547)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-10-19 20:28:48 +02:00
Oxan van Leeuwen
5b5ead872b Fix ADC pin validation on ESP32-C3 (#2551) 2021-10-19 12:56:49 +02:00
Jesse Hills
03cfd78c59 Bump dashboard to 20211019.0 (#2549) 2021-10-19 15:16:39 +13:00
Oxan van Leeuwen
ced11bc707 Autodetect ESP32 variant (#2530)
Co-authored-by: Otto winter <otto@otto-winter.com>
2021-10-18 13:36:18 +13:00
Jesse Hills
6b9c084162 Fix Bluetooth setup_priorities (#2458)
Co-authored-by: Otto Winter <otto@otto-winter.com>
2021-10-18 09:56:31 +13:00
Jesse Hills
644ce2a26c Fix const used for IDF recommended version (#2542) 2021-10-18 09:55:35 +13:00
Otto Winter
5425e45851 Only show timestamp for dashboard access logs (#2540) 2021-10-18 08:01:51 +13:00
Paulus Schoutsen
12fce7a08d Bump dashboard to 20211015.0 (#2525)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2021-10-17 20:26:59 +13:00
Jesse Hills
0991ab3543 Allow downloading all bin files from backend in dashboard (#2514)
Co-authored-by: Otto Winter <otto@otto-winter.com>
2021-10-17 19:54:09 +13:00
Oxan van Leeuwen
65d2b37496 Fix bitshift on read in ADE7953 (#2537) 2021-10-17 19:53:49 +13:00
Oxan van Leeuwen
94d518a418 Replace framework version_hint with source option (#2529) 2021-10-15 22:07:05 +02:00
Carlos Garcia Saura
7cca673902 [esp-idf fix] increase FreeRTOS ticker loop from 100Hz to 1kHz (#2527)
Co-authored-by: Otto Winter <otto@otto-winter.com>
2021-10-15 22:06:49 +02:00
Maurice Makaay
384f8d97d8 OTA firmware MD5 check + password support for esp-idf (#2507)
Co-authored-by: Maurice Makaay <account-github@makaay.nl>
2021-10-15 22:06:32 +02:00
Oxan van Leeuwen
c82d5d63e3 Move TemplatableValue helper class to automation.h (#2511) 2021-10-15 22:05:11 +02:00
dependabot[bot]
653a3d5d11 Bump aioesphomeapi from 9.1.5 to 10.0.0 (#2508)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Oxan van Leeuwen <oxan@oxanvanleeuwen.nl>
2021-10-15 22:05:04 +02:00
Tom Matheussen
884b7201de Continue ethernet setup if hostname fails (#2430) 2021-10-15 20:46:58 +02:00
Carlos Garcia Saura
85d2f24447 Clarify statement at the cmd wizard tool, for new users (#2519)
* Clarify next steps for the install wizard

* Update wizard.py

* Link to relevant section of guide

* Formatting

Co-authored-by: Oxan van Leeuwen <oxan@oxanvanleeuwen.nl>
2021-10-15 19:51:42 +02:00
dependabot[bot]
935992bcb3 Bump pyyaml from 5.4.1 to 6.0 (#2521)
Bumps [pyyaml](https://github.com/yaml/pyyaml) from 5.4.1 to 6.0.
- [Release notes](https://github.com/yaml/pyyaml/releases)
- [Changelog](https://github.com/yaml/pyyaml/blob/master/CHANGES)
- [Commits](https://github.com/yaml/pyyaml/compare/5.4.1...6.0)

---
updated-dependencies:
- dependency-name: pyyaml
  dependency-type: direct:production
  update-type: version-update:semver-major
...

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-10-15 19:38:09 +02:00
Dmitriy Lopatko
dc15d1c8ec use no hold master mode for si7021/htu21d (#2528) 2021-10-15 16:52:03 +02:00
Martin
6beb9e568a Fix bug in register name definition (#2526) 2021-10-15 20:27:56 +13:00
Keith Burzinski
7178f10bda Fix Nextion HTTPClient error for ESP32 (#2524) 2021-10-15 20:26:26 +13:00
Paul Monigatti
1308236429 Revert "Added test for bme680_bsec" (#2518)
This reverts commit 7f6a50d291b14935b17802b4dce52135fad1e49e due to BSEC library license restrictions.
2021-10-14 12:31:52 +02:00
Oxan van Leeuwen
63d6b610b8 Don't define UART_SELECTION_UART2 when UART2 is unavailable (#2512) 2021-10-14 11:25:10 +02:00
Martin
8823024509 Add pressure compensation during runtime (#2493)
Co-authored-by: Oxan van Leeuwen <oxan@oxanvanleeuwen.nl>
2021-10-14 11:24:57 +02:00
Paul Monigatti
4896f870f0 Fix: Color modes not being correctly used in light partitions (#2513) 2021-10-14 21:04:50 +13:00
Dmitriy Lopatko
7e482901d9 add missing include in sgp30 (#2517) 2021-10-14 21:00:53 +13:00
Paul Monigatti
07b309e65d Fix BME680_BSEC compilation issue with ESP32 (#2516) 2021-10-14 20:58:35 +13:00
Oxan van Leeuwen
6bbb5e9b56 Disallow using UART2 for logger on ESP-32 variants that lack it (#2510) 2021-10-14 09:21:43 +13:00
Paul Monigatti
867fecd157 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-13 21:59:52 +02:00
Oxan van Leeuwen
05388d2dfc Fix light state remaining on after turn off with transition (#2509) 2021-10-14 08:53:00 +13:00
Maurice Makaay
e06b6d7140 Add ESP32 IDF as a test env for PRs (#2494)
Co-authored-by: Maurice Makaay <account-github@makaay.nl>
2021-10-14 08:22:57 +13:00
Carlos Garcia Saura
859e508392 change millis() to micros() in feed_wdt for 3ms check (#2492) 2021-10-13 18:50:27 +02:00
razorback16
534ce11d54 TCS34725 BugFix and GA factor (#2445)
- Fixed endianness bug on tcs34725 data read
- Fixed lux adjustments based on gain, integration time and GA factor
- Added glass attenuation factor to allow using this sensor behind
  semi transparent glass

Co-authored-by: Razorback16 <razorback16@users.noreply.github.com>
2021-10-13 18:45:41 +02:00
508 changed files with 11096 additions and 3425 deletions

View File

@@ -5,11 +5,8 @@ Checks: >-
-altera-*,
-android-*,
-boost-*,
-bugprone-branch-clone,
-bugprone-easily-swappable-parameters,
-bugprone-narrowing-conversions,
-bugprone-signed-char-misuse,
-bugprone-too-small-loop-variable,
-cert-dcl50-cpp,
-cert-err58-cpp,
-cert-oop57-cpp,
@@ -19,12 +16,10 @@ Checks: >-
-clang-diagnostic-delete-abstract-non-virtual-dtor,
-clang-diagnostic-delete-non-abstract-non-virtual-dtor,
-clang-diagnostic-shadow-field,
-clang-diagnostic-sign-compare,
-clang-diagnostic-unused-variable,
-clang-diagnostic-unused-const-variable,
-clang-diagnostic-unused-parameter,
-concurrency-*,
-cppcoreguidelines-avoid-c-arrays,
-cppcoreguidelines-avoid-goto,
-cppcoreguidelines-avoid-magic-numbers,
-cppcoreguidelines-init-variables,
-cppcoreguidelines-macro-usage,
@@ -41,7 +36,6 @@ Checks: >-
-cppcoreguidelines-pro-type-union-access,
-cppcoreguidelines-pro-type-vararg,
-cppcoreguidelines-special-member-functions,
-fuchsia-default-arguments,
-fuchsia-multiple-inheritance,
-fuchsia-overloaded-operator,
-fuchsia-statically-constructed-objects,
@@ -51,6 +45,7 @@ Checks: >-
-google-explicit-constructor,
-google-readability-braces-around-statements,
-google-readability-casting,
-google-readability-namespace-comments,
-google-readability-todo,
-google-runtime-references,
-hicpp-*,
@@ -97,9 +92,11 @@ CheckOptions:
value: '1'
- key: google-readability-function-size.StatementThreshold
value: '800'
- key: google-readability-namespace-comments.ShortNamespaceLines
- key: google-runtime-int.TypeSuffix
value: '_t'
- key: llvm-namespace-comment.ShortNamespaceLines
value: '10'
- key: google-readability-namespace-comments.SpacesBeforeComments
- key: llvm-namespace-comment.SpacesBeforeComments
value: '2'
- key: modernize-loop-convert.MaxCopySize
value: '16'

View File

@@ -16,6 +16,7 @@ Quick description and explanation of changes
## Test Environment
- [ ] ESP32
- [ ] ESP32 IDF
- [ ] ESP8266
## Example entry for `config.yaml`:

View File

@@ -1,7 +0,0 @@
comment: >-
https://github.com/esphome/esphome/issues/430
issueConfigs:
- content:
- "OTHERWISE THE ISSUE WILL BE CLOSED AUTOMATICALLY"
caseInsensitive: false

36
.github/lock.yml vendored
View File

@@ -1,36 +0,0 @@
# Configuration for Lock Threads - https://github.com/dessant/lock-threads
# Number of days of inactivity before a closed issue or pull request is locked
daysUntilLock: 7
# Skip issues and pull requests created before a given timestamp. Timestamp must
# follow ISO 8601 (`YYYY-MM-DD`). Set to `false` to disable
skipCreatedBefore: false
# Issues and pull requests with these labels will be ignored. Set to `[]` to disable
exemptLabels:
- keep-open
# Label to add before locking, such as `outdated`. Set to `false` to disable
lockLabel: false
# Comment to post before locking. Set to `false` to disable
lockComment: false
# Assign `resolved` as the reason for locking. Set to `false` to disable
setLockReason: false
# Limit to only `issues` or `pulls`
# only: issues
# Optionally, specify configuration settings just for `issues` or `pulls`
# issues:
# exemptLabels:
# - help-wanted
# lockLabel: outdated
# pulls:
# daysUntilLock: 30
# Repository to extend settings from
# _extends: repo

View File

@@ -17,6 +17,10 @@ on:
- 'requirements*.txt'
- 'platformio.ini'
permissions:
contents: read
packages: read
jobs:
check-docker:
name: Build docker containers

View File

@@ -1,5 +1,3 @@
# THESE JOBS ARE COPIED IN release.yml and release-dev.yml
# PLEASE ALSO UPDATE THOSE FILES WHEN CHANGING LINES HERE
name: CI
on:
@@ -8,6 +6,13 @@ on:
pull_request:
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
ci:
name: ${{ matrix.name }}
@@ -31,7 +36,7 @@ jobs:
- id: test
file: tests/test3.yaml
name: Test tests/test3.yaml
pio_cache_key: test1
pio_cache_key: test3
- id: test
file: tests/test4.yaml
name: Test tests/test4.yaml
@@ -77,18 +82,23 @@ jobs:
with:
python-version: '3.7'
- name: Cache pip modules
- name: Cache virtualenv
uses: actions/cache@v2
with:
path: ~/.cache/pip
key: pip-${{ steps.python.outputs.python-version }}-${{ hashFiles('requirements*.txt') }}
path: .venv
key: venv-${{ steps.python.outputs.python-version }}-${{ hashFiles('requirements*.txt') }}
restore-keys: |
pip-${{ steps.python.outputs.python-version }}-
venv-${{ steps.python.outputs.python-version }}-
- name: Set up python environment
- name: Set up virtualenv
run: |
pip3 install -r requirements.txt -r requirements_optional.txt -r requirements_test.txt
pip3 install -e .
python -m venv .venv
source .venv/bin/activate
pip install -U pip
pip install -r requirements.txt -r requirements_optional.txt -r requirements_test.txt
pip install -e .
echo "$GITHUB_WORKSPACE/.venv/bin" >> $GITHUB_PATH
echo "VIRTUAL_ENV=$GITHUB_WORKSPACE/.venv" >> $GITHUB_ENV
# Use per check platformio cache because checks use different parts
- name: Cache platformio

View File

@@ -9,13 +9,19 @@ permissions:
issues: write
pull-requests: write
concurrency:
group: lock
jobs:
lock:
runs-on: ubuntu-latest
steps:
- uses: dessant/lock-threads@v2
- uses: dessant/lock-threads@v3
with:
github-token: ${{ github.token }}
pr-lock-inactive-days: "1"
pr-inactive-days: "1"
pr-lock-reason: ""
process-only: prs
exclude-any-pr-labels: keep-open
issue-inactive-days: "7"
issue-lock-reason: ""
exclude-any-issue-labels: keep-open

View File

@@ -4,7 +4,7 @@
"owner": "ci-custom",
"pattern": [
{
"regexp": "^ERROR (.*):(\\d+):(\\d+) - (.*)$",
"regexp": "^(.*):(\\d+):(\\d+):\\s+lint:\\s+(.*)$",
"file": 1,
"line": 2,
"column": 3,

View File

@@ -5,7 +5,7 @@
"severity": "error",
"pattern": [
{
"regexp": "^(.*):(\\d+):(\\d+):\\s+(?:fatal\\s+)?(warning|error):\\s+(.*)$",
"regexp": "^src/(.*):(\\d+):(\\d+):\\s+(?:fatal\\s+)?(warning|error):\\s+(.*)$",
"file": 1,
"line": 2,
"column": 3,

View File

@@ -1,11 +1,22 @@
{
"problemMatcher": [
{
"owner": "black",
"severity": "error",
"pattern": [
{
"regexp": "^(.*): (Please format this file with the black formatter)",
"file": 1,
"message": 2
}
]
},
{
"owner": "flake8",
"severity": "error",
"pattern": [
{
"regexp": "^(.*):(\\d+) - ([EFCDNW]\\d{3}.*)$",
"regexp": "^(.*):(\\d+): ([EFCDNW]\\d{3}.*)$",
"file": 1,
"line": 2,
"message": 3
@@ -17,7 +28,7 @@
"severity": "error",
"pattern": [
{
"regexp": "^(.*):(\\d+) - (\\[[EFCRW]\\d{4}\\(.*\\),.*\\].*)$",
"regexp": "^(.*):(\\d+): (\\[[EFCRW]\\d{4}\\(.*\\),.*\\].*)$",
"file": 1,
"line": 2,
"message": 3

View File

@@ -7,6 +7,9 @@ on:
schedule:
- cron: "0 2 * * *"
permissions:
contents: read
jobs:
init:
name: Initialize build
@@ -52,6 +55,9 @@ jobs:
deploy-docker:
name: Build and publish docker containers
if: github.repository == 'esphome/esphome'
permissions:
contents: read
packages: write
runs-on: ubuntu-latest
needs: [init]
strategy:
@@ -93,6 +99,9 @@ jobs:
deploy-docker-manifest:
if: github.repository == 'esphome/esphome'
permissions:
contents: read
packages: write
runs-on: ubuntu-latest
needs: [init, deploy-docker]
strategy:

View File

@@ -9,13 +9,15 @@ permissions:
issues: write
pull-requests: write
concurrency:
group: lock
jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v4
with:
repo-token: ${{ github.token }}
days-before-pr-stale: 90
days-before-pr-close: 7
days-before-issue-stale: -1
@@ -28,3 +30,19 @@ jobs:
pull request has been automatically marked as stale because of that
and will be closed if no further activity occurs within 7 days.
Thank you for your contributions.
# Use stale to automatically close issues with a reference to the issue tracker
close-issues:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v4
with:
days-before-pr-stale: -1
days-before-pr-close: -1
days-before-issue-stale: 1
days-before-issue-close: 1
remove-stale-when-updated: true
stale-issue-label: "stale"
exempt-issue-labels: "not-stale"
stale-issue-message: >
https://github.com/esphome/esphome/issues/430

View File

@@ -30,7 +30,9 @@ esphome/components/bang_bang/* @OttoWinter
esphome/components/binary_sensor/* @esphome/core
esphome/components/ble_client/* @buxtronix
esphome/components/bme680_bsec/* @trvrnrth
esphome/components/button/* @esphome/core
esphome/components/canbus/* @danielschramm @mvturnho
esphome/components/cap1188/* @MrEditor97
esphome/components/captive_portal/* @OttoWinter
esphome/components/ccs811/* @habbie
esphome/components/climate/* @esphome/core
@@ -39,6 +41,7 @@ esphome/components/color_temperature/* @jesserockz
esphome/components/coolix/* @glmnet
esphome/components/cover/* @esphome/core
esphome/components/cs5460a/* @balrog-kun
esphome/components/cse7761/* @berfenger
esphome/components/ct_clamp/* @jesserockz
esphome/components/current_based/* @djwmarcx
esphome/components/daly_bms/* @s1lvi0
@@ -51,6 +54,7 @@ esphome/components/dsmr/* @glmnet @zuidwijk
esphome/components/esp32/* @esphome/core
esphome/components/esp32_ble/* @jesserockz
esphome/components/esp32_ble_server/* @jesserockz
esphome/components/esp32_camera_web_server/* @ayufan
esphome/components/esp32_improv/* @jesserockz
esphome/components/esp8266/* @esphome/core
esphome/components/exposure_notifications/* @OttoWinter
@@ -69,7 +73,7 @@ esphome/components/hitachi_ac424/* @sourabhjaiswal
esphome/components/homeassistant/* @OttoWinter
esphome/components/hrxl_maxsonar_wr/* @netmikey
esphome/components/i2c/* @esphome/core
esphome/components/improv/* @jesserockz
esphome/components/improv_serial/* @esphome/core
esphome/components/inkbird_ibsth1_mini/* @fkirill
esphome/components/inkplate6/* @jesserockz
esphome/components/integration/* @OttoWinter
@@ -89,6 +93,7 @@ esphome/components/mcp23x17_base/* @jesserockz
esphome/components/mcp23xxx_base/* @jesserockz
esphome/components/mcp2515/* @danielschramm @mvturnho
esphome/components/mcp9808/* @k7hpn
esphome/components/md5/* @esphome/core
esphome/components/mdns/* @esphome/core
esphome/components/midea/* @dudanov
esphome/components/mitsubishi/* @RubyBailey
@@ -128,6 +133,7 @@ esphome/components/rf_bridge/* @jesserockz
esphome/components/rgbct/* @jesserockz
esphome/components/rtttl/* @glmnet
esphome/components/safe_mode/* @paulmonigatti
esphome/components/safe_mode/button/* @jesserockz
esphome/components/scd4x/* @sjtrny
esphome/components/script/* @esphome/core
esphome/components/sdm_meter/* @jesserockz @polyfaces
@@ -176,6 +182,7 @@ esphome/components/tuya/binary_sensor/* @jesserockz
esphome/components/tuya/climate/* @jesserockz
esphome/components/tuya/sensor/* @jesserockz
esphome/components/tuya/switch/* @jesserockz
esphome/components/tuya/text_sensor/* @dentra
esphome/components/uart/* @esphome/core
esphome/components/ultrasonic/* @OttoWinter
esphome/components/version/* @esphome/core

View File

@@ -5,12 +5,12 @@
# One of "docker", "hassio"
ARG BASEIMGTYPE=docker
FROM ghcr.io/hassio-addons/debian-base/amd64:5.1.0 AS base-hassio-amd64
FROM ghcr.io/hassio-addons/debian-base/aarch64:5.1.0 AS base-hassio-arm64
FROM ghcr.io/hassio-addons/debian-base/armv7:5.1.0 AS base-hassio-armv7
FROM debian:bullseye-20210902-slim AS base-docker-amd64
FROM debian:bullseye-20210902-slim AS base-docker-arm64
FROM debian:bullseye-20210902-slim AS base-docker-armv7
FROM ghcr.io/hassio-addons/debian-base/amd64:5.1.1 AS base-hassio-amd64
FROM ghcr.io/hassio-addons/debian-base/aarch64:5.1.1 AS base-hassio-arm64
FROM ghcr.io/hassio-addons/debian-base/armv7:5.1.1 AS base-hassio-armv7
FROM debian:bullseye-20211011-slim AS base-docker-amd64
FROM debian:bullseye-20211011-slim AS base-docker-arm64
FROM debian:bullseye-20211011-slim AS base-docker-armv7
# Use TARGETARCH/TARGETVARIANT defined by docker
# https://docs.docker.com/engine/reference/builder/#automatic-platform-args-in-the-global-scope
@@ -43,7 +43,7 @@ RUN \
# Ubuntu python3-pip is missing wheel
pip3 install --no-cache-dir \
wheel==0.36.2 \
platformio==5.2.0 \
platformio==5.2.2 \
# Change some platformio settings
&& platformio settings set enable_telemetry No \
&& platformio settings set check_libraries_interval 1000000 \
@@ -147,9 +147,9 @@ RUN \
/var/{cache,log}/* \
/var/lib/apt/lists/*
COPY requirements.txt requirements_optional.txt docker/platformio_install_deps.py platformio.ini /
COPY requirements.txt requirements_optional.txt requirements_test.txt docker/platformio_install_deps.py platformio.ini /
RUN \
pip3 install --no-cache-dir -r /requirements.txt -r /requirements_optional.txt \
pip3 install --no-cache-dir -r /requirements.txt -r /requirements_optional.txt -r /requirements_test.txt \
&& /platformio_install_deps.py /platformio.ini
VOLUME ["/esphome"]

View File

@@ -8,6 +8,23 @@ import sys
config = configparser.ConfigParser(inline_comment_prefixes=(';', ))
config.read(sys.argv[1])
libs = [x for x in config['common']['lib_deps'].splitlines() if len(x) != 0]
libs = []
# Extract from every lib_deps key in all sections
for section in config.sections():
conf = config[section]
if "lib_deps" not in conf:
continue
for lib_dep in conf["lib_deps"].splitlines():
if not lib_dep:
# Empty line or comment
continue
if lib_dep.startswith("${"):
# Extending from another section
continue
if "@" not in lib_dep:
# No version pinned, this is an internal lib
continue
libs.append(lib_dep)
subprocess.check_call(['platformio', 'lib', '-g', 'install', *libs])

View File

@@ -18,6 +18,7 @@ from esphome.const import (
CONF_PORT,
CONF_ESPHOME,
CONF_PLATFORMIO_OPTIONS,
SECRETS_FILES,
)
from esphome.core import CORE, EsphomeError, coroutine
from esphome.helpers import indent
@@ -144,6 +145,8 @@ def wrap_to_code(name, comp):
if comp.config_schema is not None:
conf_str = yaml_util.dump(conf)
conf_str = conf_str.replace("//", "")
# remove tailing \ to avoid multi-line comment warning
conf_str = conf_str.replace("\\\n", "\n")
cg.add(cg.LineComment(indent(conf_str)))
await coro(conf)
@@ -180,7 +183,11 @@ def compile_program(args, config):
from esphome import platformio_api
_LOGGER.info("Compiling app...")
return platformio_api.run_compile(config, CORE.verbose)
rc = platformio_api.run_compile(config, CORE.verbose)
if rc != 0:
return rc
idedata = platformio_api.get_idedata(config)
return 0 if idedata is not None else 1
def upload_using_esptool(config, port):
@@ -196,8 +203,7 @@ def upload_using_esptool(config, port):
firmware_offset = "0x10000" if CORE.is_esp32 else "0x0"
flash_images = [
platformio_api.FlashImage(
path=idedata.firmware_bin_path,
offset=firmware_offset,
path=idedata.firmware_bin_path, offset=firmware_offset
),
*idedata.extra_flash_images,
]
@@ -222,6 +228,8 @@ def upload_using_esptool(config, port):
mcu,
"write_flash",
"-z",
"--flash_size",
"detect",
]
for img in flash_images:
cmd += [img.offset, img.path]
@@ -458,6 +466,21 @@ def command_update_all(args):
return failed
def command_idedata(args, config):
from esphome import platformio_api
import json
logging.disable(logging.INFO)
logging.disable(logging.WARNING)
idedata = platformio_api.get_idedata(config)
if idedata is None:
return 1
print(json.dumps(idedata.raw, indent=2) + "\n")
return 0
PRE_CONFIG_ACTIONS = {
"wizard": command_wizard,
"version": command_version,
@@ -475,6 +498,7 @@ POST_CONFIG_ACTIONS = {
"clean-mqtt": command_clean_mqtt,
"mqtt-fingerprint": command_mqtt_fingerprint,
"clean": command_clean,
"idedata": command_idedata,
}
@@ -585,10 +609,7 @@ def parse_args(argv):
"wizard",
help="A helpful setup wizard that will guide you through setting up ESPHome.",
)
parser_wizard.add_argument(
"configuration",
help="Your YAML configuration file.",
)
parser_wizard.add_argument("configuration", help="Your YAML configuration file.")
parser_fingerprint = subparsers.add_parser(
"mqtt-fingerprint", help="Get the SSL fingerprint from a MQTT broker."
@@ -610,8 +631,7 @@ def parse_args(argv):
"dashboard", help="Create a simple web server for a dashboard."
)
parser_dashboard.add_argument(
"configuration",
help="Your YAML configuration file directory.",
"configuration", help="Your YAML configuration file directory."
)
parser_dashboard.add_argument(
"--port",
@@ -619,6 +639,12 @@ def parse_args(argv):
type=int,
default=6052,
)
parser_dashboard.add_argument(
"--address",
help="The address to bind to.",
type=str,
default="0.0.0.0",
)
parser_dashboard.add_argument(
"--username",
help="The optional username to require for authentication.",
@@ -650,6 +676,11 @@ def parse_args(argv):
"configuration", help="Your YAML configuration file directories.", nargs="+"
)
parser_idedata = subparsers.add_parser("idedata")
parser_idedata.add_argument(
"configuration", help="Your YAML configuration file(s).", nargs=1
)
# Keep backward compatibility with the old command line format of
# esphome <config> <command>.
#
@@ -733,7 +764,12 @@ def run_esphome(argv):
args = parse_args(argv)
CORE.dashboard = args.dashboard
setup_log(args.verbose, args.quiet)
setup_log(
args.verbose,
args.quiet,
# Show timestamp for dashboard access logs
args.command == "dashboard",
)
if args.deprecated_argv_suggestion is not None and args.command != "vscode":
_LOGGER.warning(
"Calling ESPHome with the configuration before the command is deprecated "
@@ -757,12 +793,16 @@ def run_esphome(argv):
return 1
for conf_path in args.configuration:
if any(os.path.basename(conf_path) == x for x in SECRETS_FILES):
_LOGGER.warning("Skipping secrets file %s", conf_path)
continue
CORE.config_path = conf_path
CORE.dashboard = args.dashboard
config = read_config(dict(args.substitution) if args.substitution else {})
if config is None:
return 1
return 2
CORE.config = config
if args.command not in POST_CONFIG_ACTIONS:

View File

@@ -3,6 +3,7 @@ import esphome.config_validation as cv
from esphome.const import (
CONF_AUTOMATION_ID,
CONF_CONDITION,
CONF_COUNT,
CONF_ELSE,
CONF_ID,
CONF_THEN,
@@ -66,6 +67,7 @@ DelayAction = cg.esphome_ns.class_("DelayAction", Action, cg.Component)
LambdaAction = cg.esphome_ns.class_("LambdaAction", Action)
IfAction = cg.esphome_ns.class_("IfAction", Action)
WhileAction = cg.esphome_ns.class_("WhileAction", Action)
RepeatAction = cg.esphome_ns.class_("RepeatAction", Action)
WaitUntilAction = cg.esphome_ns.class_("WaitUntilAction", Action, cg.Component)
UpdateComponentAction = cg.esphome_ns.class_("UpdateComponentAction", Action)
Automation = cg.esphome_ns.class_("Automation")
@@ -241,6 +243,25 @@ async def while_action_to_code(config, action_id, template_arg, args):
return var
@register_action(
"repeat",
RepeatAction,
cv.Schema(
{
cv.Required(CONF_COUNT): cv.templatable(cv.positive_not_null_int),
cv.Required(CONF_THEN): validate_action_list,
}
),
)
async def repeat_action_to_code(config, action_id, template_arg, args):
var = cg.new_Pvariable(action_id, template_arg)
count_template = await cg.templatable(config[CONF_COUNT], args, cg.uint32)
cg.add(var.set_count(count_template))
actions = await build_action_list(config[CONF_THEN], template_arg, args)
cg.add(var.add_then(actions))
return var
def validate_wait_until(value):
schema = cv.Schema(
{

View File

@@ -81,4 +81,5 @@ from esphome.cpp_types import ( # noqa
GPIOPin,
InternalGPIOPin,
gpio_Flags,
EntityCategory,
)

View File

@@ -25,7 +25,7 @@ void AdalightLightEffect::stop() {
AddressableLightEffect::stop();
}
int AdalightLightEffect::get_frame_size_(int led_count) const {
unsigned int AdalightLightEffect::get_frame_size_(int led_count) const {
// 3 bytes: Ada
// 2 bytes: LED count
// 1 byte: checksum

View File

@@ -25,7 +25,7 @@ class AdalightLightEffect : public light::AddressableLightEffect, public uart::U
CONSUMED,
};
int get_frame_size_(int led_count) const;
unsigned int get_frame_size_(int led_count) const;
void reset_frame_(light::AddressableLight &it);
void blank_all_leds_(light::AddressableLight &it);
Frame parse_frame_(light::AddressableLight &it);

View File

@@ -1,5 +1,6 @@
#include "adc_sensor.h"
#include "esphome/core/log.h"
#include "esphome/core/helpers.h"
#ifdef USE_ESP8266
#ifdef USE_ADC_SENSOR_VCC
@@ -15,50 +16,6 @@ namespace adc {
static const char *const TAG = "adc";
#ifdef USE_ESP32
void ADCSensor::set_attenuation(adc_atten_t attenuation) { this->attenuation_ = attenuation; }
inline adc1_channel_t gpio_to_adc1(uint8_t pin) {
#if CONFIG_IDF_TARGET_ESP32
switch (pin) {
case 36:
return ADC1_CHANNEL_0;
case 37:
return ADC1_CHANNEL_1;
case 38:
return ADC1_CHANNEL_2;
case 39:
return ADC1_CHANNEL_3;
case 32:
return ADC1_CHANNEL_4;
case 33:
return ADC1_CHANNEL_5;
case 34:
return ADC1_CHANNEL_6;
case 35:
return ADC1_CHANNEL_7;
default:
return ADC1_CHANNEL_MAX;
}
#elif CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32H2
switch (pin) {
case 0:
return ADC1_CHANNEL_0;
case 1:
return ADC1_CHANNEL_1;
case 2:
return ADC1_CHANNEL_2;
case 3:
return ADC1_CHANNEL_3;
case 4:
return ADC1_CHANNEL_4;
default:
return ADC1_CHANNEL_MAX;
}
#endif
}
#endif
void ADCSensor::setup() {
ESP_LOGCONFIG(TAG, "Setting up ADC '%s'...", this->get_name().c_str());
#ifndef USE_ADC_SENSOR_VCC
@@ -66,13 +23,36 @@ void ADCSensor::setup() {
#endif
#ifdef USE_ESP32
adc1_config_channel_atten(gpio_to_adc1(pin_->get_pin()), attenuation_);
adc1_config_width(ADC_WIDTH_BIT_12);
#if !CONFIG_IDF_TARGET_ESP32C3 && !CONFIG_IDF_TARGET_ESP32H2
adc_gpio_init(ADC_UNIT_1, (adc_channel_t) gpio_to_adc1(pin_->get_pin()));
#endif
if (!autorange_) {
adc1_config_channel_atten(channel_, attenuation_);
}
// load characteristics for each attenuation
for (int i = 0; i < (int) ADC_ATTEN_MAX; i++) {
auto cal_value = esp_adc_cal_characterize(ADC_UNIT_1, (adc_atten_t) i, ADC_WIDTH_BIT_12,
1100, // default vref
&cal_characteristics_[i]);
switch (cal_value) {
case ESP_ADC_CAL_VAL_EFUSE_VREF:
ESP_LOGV(TAG, "Using eFuse Vref for calibration");
break;
case ESP_ADC_CAL_VAL_EFUSE_TP:
ESP_LOGV(TAG, "Using two-point eFuse Vref for calibration");
break;
case ESP_ADC_CAL_VAL_DEFAULT_VREF:
default:
break;
}
}
// adc_gpio_init doesn't exist on ESP32-C3 or ESP32-H2
#if !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32H2)
adc_gpio_init(ADC_UNIT_1, (adc_channel_t) channel_);
#endif
#endif // USE_ESP32
}
void ADCSensor::dump_config() {
LOG_SENSOR("", "ADC Sensor", this);
#ifdef USE_ESP8266
@@ -81,84 +61,107 @@ void ADCSensor::dump_config() {
#else
LOG_PIN(" Pin: ", pin_);
#endif
#endif
#endif // USE_ESP8266
#ifdef USE_ESP32
LOG_PIN(" Pin: ", pin_);
switch (this->attenuation_) {
case ADC_ATTEN_DB_0:
ESP_LOGCONFIG(TAG, " Attenuation: 0db (max 1.1V)");
break;
case ADC_ATTEN_DB_2_5:
ESP_LOGCONFIG(TAG, " Attenuation: 2.5db (max 1.5V)");
break;
case ADC_ATTEN_DB_6:
ESP_LOGCONFIG(TAG, " Attenuation: 6db (max 2.2V)");
break;
case ADC_ATTEN_DB_11:
ESP_LOGCONFIG(TAG, " Attenuation: 11db (max 3.9V)");
break;
default: // This is to satisfy the unused ADC_ATTEN_MAX
break;
}
#endif
if (autorange_)
ESP_LOGCONFIG(TAG, " Attenuation: auto");
else
switch (this->attenuation_) {
case ADC_ATTEN_DB_0:
ESP_LOGCONFIG(TAG, " Attenuation: 0db (max 1.1V)");
break;
case ADC_ATTEN_DB_2_5:
ESP_LOGCONFIG(TAG, " Attenuation: 2.5db (max 1.5V)");
break;
case ADC_ATTEN_DB_6:
ESP_LOGCONFIG(TAG, " Attenuation: 6db (max 2.2V)");
break;
case ADC_ATTEN_DB_11:
ESP_LOGCONFIG(TAG, " Attenuation: 11db (max 3.9V)");
break;
default: // This is to satisfy the unused ADC_ATTEN_MAX
break;
}
#endif // USE_ESP32
LOG_UPDATE_INTERVAL(this);
}
float ADCSensor::get_setup_priority() const { return setup_priority::DATA; }
void ADCSensor::update() {
float value_v = this->sample();
ESP_LOGD(TAG, "'%s': Got voltage=%.2fV", this->get_name().c_str(), value_v);
ESP_LOGV(TAG, "'%s': Got voltage=%.4fV", this->get_name().c_str(), value_v);
this->publish_state(value_v);
}
float ADCSensor::sample() {
#ifdef USE_ESP32
int raw = adc1_get_raw(gpio_to_adc1(pin_->get_pin()));
float value_v = raw / 4095.0f;
#if CONFIG_IDF_TARGET_ESP32
switch (this->attenuation_) {
case ADC_ATTEN_DB_0:
value_v *= 1.1;
break;
case ADC_ATTEN_DB_2_5:
value_v *= 1.5;
break;
case ADC_ATTEN_DB_6:
value_v *= 2.2;
break;
case ADC_ATTEN_DB_11:
value_v *= 3.9;
break;
default: // This is to satisfy the unused ADC_ATTEN_MAX
break;
}
#elif CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32H2
switch (this->attenuation_) {
case ADC_ATTEN_DB_0:
value_v *= 0.84;
break;
case ADC_ATTEN_DB_2_5:
value_v *= 1.13;
break;
case ADC_ATTEN_DB_6:
value_v *= 1.56;
break;
case ADC_ATTEN_DB_11:
value_v *= 3.0;
break;
default: // This is to satisfy the unused ADC_ATTEN_MAX
break;
}
#endif
return value_v;
#endif
#ifdef USE_ESP8266
float ADCSensor::sample() {
#ifdef USE_ADC_SENSOR_VCC
return ESP.getVcc() / 1024.0f; // NOLINT(readability-static-accessed-through-instance)
int raw = ESP.getVcc(); // NOLINT(readability-static-accessed-through-instance)
#else
return analogRead(this->pin_->get_pin()) / 1024.0f; // NOLINT
#endif
int raw = analogRead(this->pin_->get_pin()); // NOLINT
#endif
if (output_raw_) {
return raw;
}
return raw / 1024.0f;
}
#endif
#ifdef USE_ESP32
float ADCSensor::sample() {
if (!autorange_) {
int raw = adc1_get_raw(channel_);
if (raw == -1) {
return NAN;
}
if (output_raw_) {
return raw;
}
uint32_t mv = esp_adc_cal_raw_to_voltage(raw, &cal_characteristics_[(int) attenuation_]);
return mv / 1000.0f;
}
int raw11, raw6 = 4095, raw2 = 4095, raw0 = 4095;
adc1_config_channel_atten(channel_, ADC_ATTEN_DB_11);
raw11 = adc1_get_raw(channel_);
if (raw11 < 4095) {
adc1_config_channel_atten(channel_, ADC_ATTEN_DB_6);
raw6 = adc1_get_raw(channel_);
if (raw6 < 4095) {
adc1_config_channel_atten(channel_, ADC_ATTEN_DB_2_5);
raw2 = adc1_get_raw(channel_);
if (raw2 < 4095) {
adc1_config_channel_atten(channel_, ADC_ATTEN_DB_0);
raw0 = adc1_get_raw(channel_);
}
}
}
if (raw0 == -1 || raw2 == -1 || raw6 == -1 || raw11 == -1) {
return NAN;
}
uint32_t mv11 = esp_adc_cal_raw_to_voltage(raw11, &cal_characteristics_[(int) ADC_ATTEN_DB_11]);
uint32_t mv6 = esp_adc_cal_raw_to_voltage(raw6, &cal_characteristics_[(int) ADC_ATTEN_DB_6]);
uint32_t mv2 = esp_adc_cal_raw_to_voltage(raw2, &cal_characteristics_[(int) ADC_ATTEN_DB_2_5]);
uint32_t mv0 = esp_adc_cal_raw_to_voltage(raw0, &cal_characteristics_[(int) ADC_ATTEN_DB_0]);
// Contribution of each value, in range 0-2048
uint32_t c11 = std::min(raw11, 2048);
uint32_t c6 = 2048 - std::abs(raw6 - 2048);
uint32_t c2 = 2048 - std::abs(raw2 - 2048);
uint32_t c0 = std::min(4095 - raw0, 2048);
// max theoretical csum value is 2048*4 = 8192
uint32_t csum = c11 + c6 + c2 + c0;
// each mv is max 3900; so max value is 3900*2048*4, fits in unsigned
uint32_t mv_scaled = (mv11 * c11) + (mv6 * c6) + (mv2 * c2) + (mv0 * c0);
return mv_scaled / (float) (csum * 1000U);
}
#endif // USE_ESP32
#ifdef USE_ESP8266
std::string ADCSensor::unique_id() { return get_mac_address() + "-adc"; }
#endif

View File

@@ -8,6 +8,7 @@
#ifdef USE_ESP32
#include "driver/adc.h"
#include <esp_adc_cal.h>
#endif
namespace esphome {
@@ -17,7 +18,9 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage
public:
#ifdef USE_ESP32
/// Set the attenuation for this pin. Only available on the ESP32.
void set_attenuation(adc_atten_t attenuation);
void set_attenuation(adc_atten_t attenuation) { attenuation_ = attenuation; }
void set_channel(adc1_channel_t channel) { channel_ = channel; }
void set_autorange(bool autorange) { autorange_ = autorange; }
#endif
/// Update adc values.
@@ -28,6 +31,7 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage
/// `HARDWARE_LATE` setup priority.
float get_setup_priority() const override;
void set_pin(InternalGPIOPin *pin) { this->pin_ = pin; }
void set_output_raw(bool output_raw) { output_raw_ = output_raw; }
float sample() override;
#ifdef USE_ESP8266
@@ -36,9 +40,13 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage
protected:
InternalGPIOPin *pin_;
bool output_raw_{false};
#ifdef USE_ESP32
adc_atten_t attenuation_{ADC_ATTEN_DB_0};
adc1_channel_t channel_{};
bool autorange_{false};
esp_adc_cal_characteristics_t cal_characteristics_[(int) ADC_ATTEN_MAX] = {};
#endif
};

View File

@@ -4,14 +4,24 @@ from esphome import pins
from esphome.components import sensor, voltage_sampler
from esphome.const import (
CONF_ATTENUATION,
CONF_RAW,
CONF_ID,
CONF_INPUT,
CONF_NUMBER,
CONF_PIN,
DEVICE_CLASS_VOLTAGE,
STATE_CLASS_MEASUREMENT,
UNIT_VOLT,
)
from esphome.core import CORE
from esphome.components.esp32 import get_esp32_variant
from esphome.components.esp32.const import (
VARIANT_ESP32,
VARIANT_ESP32C3,
VARIANT_ESP32H2,
VARIANT_ESP32S2,
VARIANT_ESP32S3,
)
AUTO_LOAD = ["voltage_sampler"]
@@ -21,6 +31,62 @@ ATTENUATION_MODES = {
"2.5db": cg.global_ns.ADC_ATTEN_DB_2_5,
"6db": cg.global_ns.ADC_ATTEN_DB_6,
"11db": cg.global_ns.ADC_ATTEN_DB_11,
"auto": "auto",
}
adc1_channel_t = cg.global_ns.enum("adc1_channel_t")
# From https://github.com/espressif/esp-idf/blob/master/components/driver/include/driver/adc_common.h
# pin to adc1 channel mapping
ESP32_VARIANT_ADC1_PIN_TO_CHANNEL = {
VARIANT_ESP32: {
36: adc1_channel_t.ADC1_CHANNEL_0,
37: adc1_channel_t.ADC1_CHANNEL_1,
38: adc1_channel_t.ADC1_CHANNEL_2,
39: adc1_channel_t.ADC1_CHANNEL_3,
32: adc1_channel_t.ADC1_CHANNEL_4,
33: adc1_channel_t.ADC1_CHANNEL_5,
34: adc1_channel_t.ADC1_CHANNEL_6,
35: adc1_channel_t.ADC1_CHANNEL_7,
},
VARIANT_ESP32S2: {
1: adc1_channel_t.ADC1_CHANNEL_0,
2: adc1_channel_t.ADC1_CHANNEL_1,
3: adc1_channel_t.ADC1_CHANNEL_2,
4: adc1_channel_t.ADC1_CHANNEL_3,
5: adc1_channel_t.ADC1_CHANNEL_4,
6: adc1_channel_t.ADC1_CHANNEL_5,
7: adc1_channel_t.ADC1_CHANNEL_6,
8: adc1_channel_t.ADC1_CHANNEL_7,
9: adc1_channel_t.ADC1_CHANNEL_8,
10: adc1_channel_t.ADC1_CHANNEL_9,
},
VARIANT_ESP32S3: {
1: adc1_channel_t.ADC1_CHANNEL_0,
2: adc1_channel_t.ADC1_CHANNEL_1,
3: adc1_channel_t.ADC1_CHANNEL_2,
4: adc1_channel_t.ADC1_CHANNEL_3,
5: adc1_channel_t.ADC1_CHANNEL_4,
6: adc1_channel_t.ADC1_CHANNEL_5,
7: adc1_channel_t.ADC1_CHANNEL_6,
8: adc1_channel_t.ADC1_CHANNEL_7,
9: adc1_channel_t.ADC1_CHANNEL_8,
10: adc1_channel_t.ADC1_CHANNEL_9,
},
VARIANT_ESP32C3: {
0: adc1_channel_t.ADC1_CHANNEL_0,
1: adc1_channel_t.ADC1_CHANNEL_1,
2: adc1_channel_t.ADC1_CHANNEL_2,
3: adc1_channel_t.ADC1_CHANNEL_3,
4: adc1_channel_t.ADC1_CHANNEL_4,
},
VARIANT_ESP32H2: {
0: adc1_channel_t.ADC1_CHANNEL_0,
1: adc1_channel_t.ADC1_CHANNEL_1,
2: adc1_channel_t.ADC1_CHANNEL_2,
3: adc1_channel_t.ADC1_CHANNEL_3,
4: adc1_channel_t.ADC1_CHANNEL_4,
},
}
@@ -29,15 +95,16 @@ def validate_adc_pin(value):
return cv.only_on_esp8266("VCC")
if CORE.is_esp32:
from esphome.components.esp32 import is_esp32c3
value = pins.internal_gpio_input_pin_number(value)
if is_esp32c3():
if not (0 <= value <= 4): # ADC1
raise cv.Invalid("ESP32-C3: Only pins 0 though 4 support ADC.")
if not (32 <= value <= 39): # ADC1
raise cv.Invalid("ESP32: Only pins 32 though 39 support ADC.")
elif CORE.is_esp8266:
variant = get_esp32_variant()
if variant not in ESP32_VARIANT_ADC1_PIN_TO_CHANNEL:
raise cv.Invalid(f"This ESP32 variant ({variant}) is not supported")
if value not in ESP32_VARIANT_ADC1_PIN_TO_CHANNEL[variant]:
raise cv.Invalid(f"{variant} doesn't support ADC on this pin")
return pins.internal_gpio_input_pin_schema(value)
if CORE.is_esp8266:
from esphome.components.esp8266.gpio import CONF_ANALOG
value = pins.internal_gpio_pin_number({CONF_ANALOG: True, CONF_INPUT: True})(
@@ -49,10 +116,14 @@ def validate_adc_pin(value):
return pins.gpio_pin_schema(
{CONF_ANALOG: True, CONF_INPUT: True}, internal=True
)(value)
else:
raise NotImplementedError
return pins.internal_gpio_input_pin_schema(value)
raise NotImplementedError
def validate_config(config):
if config[CONF_RAW] and config.get(CONF_ATTENUATION, None) == "auto":
raise cv.Invalid("Automatic attenuation cannot be used when raw output is set.")
return config
adc_ns = cg.esphome_ns.namespace("adc")
@@ -60,7 +131,7 @@ ADCSensor = adc_ns.class_(
"ADCSensor", sensor.Sensor, cg.PollingComponent, voltage_sampler.VoltageSampler
)
CONFIG_SCHEMA = (
CONFIG_SCHEMA = cv.All(
sensor.sensor_schema(
unit_of_measurement=UNIT_VOLT,
accuracy_decimals=2,
@@ -71,12 +142,14 @@ CONFIG_SCHEMA = (
{
cv.GenerateID(): cv.declare_id(ADCSensor),
cv.Required(CONF_PIN): validate_adc_pin,
cv.Optional(CONF_RAW, default=False): cv.boolean,
cv.SplitDefault(CONF_ATTENUATION, esp32="0db"): cv.All(
cv.only_on_esp32, cv.enum(ATTENUATION_MODES, lower=True)
),
}
)
.extend(cv.polling_component_schema("60s"))
.extend(cv.polling_component_schema("60s")),
validate_config,
)
@@ -91,5 +164,17 @@ async def to_code(config):
pin = await cg.gpio_pin_expression(config[CONF_PIN])
cg.add(var.set_pin(pin))
if CONF_RAW in config:
cg.add(var.set_output_raw(config[CONF_RAW]))
if CONF_ATTENUATION in config:
cg.add(var.set_attenuation(config[CONF_ATTENUATION]))
if config[CONF_ATTENUATION] == "auto":
cg.add(var.set_autorange(cg.global_ns.true))
else:
cg.add(var.set_attenuation(config[CONF_ATTENUATION]))
if CORE.is_esp32:
variant = get_esp32_variant()
pin_num = config[CONF_PIN][CONF_NUMBER]
chan = ESP32_VARIANT_ADC1_PIN_TO_CHANNEL[variant][pin_num]
cg.add(var.set_channel(chan))

View File

@@ -76,9 +76,9 @@ class ADE7953 : public i2c::I2CDevice, public PollingComponent {
return err;
*value = 0;
*value |= ((uint32_t) recv[0]) << 24;
*value |= ((uint32_t) recv[1]) << 24;
*value |= ((uint32_t) recv[2]) << 24;
*value |= ((uint32_t) recv[3]) << 24;
*value |= ((uint32_t) recv[1]) << 16;
*value |= ((uint32_t) recv[2]) << 8;
*value |= ((uint32_t) recv[3]);
return i2c::ERROR_OK;
}

View File

@@ -73,13 +73,6 @@ void AHT10Component::update() {
bool success = false;
for (int i = 0; i < AHT10_ATTEMPTS; ++i) {
ESP_LOGVV(TAG, "Attempt %d at %6u", i, millis());
delay_microseconds_accurate(4);
uint8_t reg = 0;
if (this->write(&reg, 1) != i2c::ERROR_OK) {
ESP_LOGD(TAG, "Communication with AHT10 failed, waiting...");
continue;
}
delay(delay_ms);
if (this->read(data, 6) != i2c::ERROR_OK) {
ESP_LOGD(TAG, "Communication with AHT10 failed, waiting...");
@@ -117,12 +110,12 @@ void AHT10Component::update() {
uint32_t raw_temperature = ((data[3] & 0x0F) << 16) | (data[4] << 8) | data[5];
uint32_t raw_humidity = ((data[1] << 16) | (data[2] << 8) | data[3]) >> 4;
float temperature = ((200.0 * (float) raw_temperature) / 1048576.0) - 50.0;
float temperature = ((200.0f * (float) raw_temperature) / 1048576.0f) - 50.0f;
float humidity;
if (raw_humidity == 0) { // unrealistic value
humidity = NAN;
} else {
humidity = (float) raw_humidity * 100.0 / 1048576.0;
humidity = (float) raw_humidity * 100.0f / 1048576.0f;
}
if (this->temperature_sensor_ != nullptr) {

View File

@@ -38,9 +38,9 @@ void AM2320Component::update() {
return;
}
float temperature = (((data[4] & 0x7F) << 8) + data[5]) / 10.0;
float temperature = (((data[4] & 0x7F) << 8) + data[5]) / 10.0f;
temperature = (data[4] & 0x80) ? -temperature : temperature;
float humidity = ((data[2] << 8) + data[3]) / 10.0;
float humidity = ((data[2] << 8) + data[3]) / 10.0f;
ESP_LOGD(TAG, "Got temperature=%.1f°C humidity=%.1f%%", temperature, humidity);
if (this->temperature_sensor_ != nullptr)

View File

@@ -4,7 +4,8 @@ from esphome.components import sensor, ble_client
from esphome.const import (
CONF_ID,
CONF_BATTERY_LEVEL,
ICON_BATTERY,
DEVICE_CLASS_BATTERY,
ENTITY_CATEGORY_DIAGNOSTIC,
CONF_ILLUMINANCE,
ICON_BRIGHTNESS_5,
UNIT_PERCENT,
@@ -20,10 +21,15 @@ CONFIG_SCHEMA = (
{
cv.GenerateID(): cv.declare_id(Am43),
cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema(
UNIT_PERCENT, ICON_BATTERY, 0
unit_of_measurement=UNIT_PERCENT,
device_class=DEVICE_CLASS_BATTERY,
accuracy_decimals=0,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
cv.Optional(CONF_ILLUMINANCE): sensor.sensor_schema(
UNIT_PERCENT, ICON_BRIGHTNESS_5, 0
unit_of_measurement=UNIT_PERCENT,
icon=ICON_BRIGHTNESS_5,
accuracy_decimals=0,
),
}
)

View File

@@ -44,8 +44,9 @@ async def to_code(config):
width, height = image.size
frames = image.n_frames
if CONF_RESIZE in config:
image.thumbnail(config[CONF_RESIZE])
width, height = image.size
new_width_max, new_height_max = config[CONF_RESIZE]
ratio = min(new_width_max / width, new_height_max / height)
width, height = int(width * ratio), int(height * ratio)
else:
if width > 500 or height > 500:
_LOGGER.warning(
@@ -59,7 +60,13 @@ async def to_code(config):
for frameIndex in range(frames):
image.seek(frameIndex)
frame = image.convert("L", dither=Image.NONE)
if CONF_RESIZE in config:
frame = frame.resize([width, height])
pixels = list(frame.getdata())
if len(pixels) != height * width:
raise core.EsphomeError(
f"Unexpected number of pixels in {path} frame {frameIndex}: ({len(pixels)} != {height*width})"
)
for pix in pixels:
data[pos] = pix
pos += 1
@@ -70,7 +77,13 @@ async def to_code(config):
for frameIndex in range(frames):
image.seek(frameIndex)
frame = image.convert("RGB")
if CONF_RESIZE in config:
frame = frame.resize([width, height])
pixels = list(frame.getdata())
if len(pixels) != height * width:
raise core.EsphomeError(
f"Unexpected number of pixels in {path} frame {frameIndex}: ({len(pixels)} != {height*width})"
)
for pix in pixels:
data[pos] = pix[0]
pos += 1
@@ -85,6 +98,8 @@ async def to_code(config):
for frameIndex in range(frames):
image.seek(frameIndex)
frame = image.convert("1", dither=Image.NONE)
if CONF_RESIZE in config:
frame = frame.resize([width, height])
for y in range(height):
for x in range(width):
if frame.getpixel((x, y)):

View File

@@ -30,7 +30,7 @@ class Anova : public climate::Climate, public esphome::ble_client::BLEClientNode
climate::ClimateTraits traits() override {
auto traits = climate::ClimateTraits();
traits.set_supports_current_temperature(true);
traits.set_supports_heat_mode(true);
traits.set_supported_modes({climate::CLIMATE_MODE_OFF, climate::ClimateMode::CLIMATE_MODE_HEAT});
traits.set_visual_min_temperature(25.0);
traits.set_visual_max_temperature(100.0);
traits.set_visual_temperature_step(0.1);

View File

@@ -73,51 +73,46 @@ AnovaPacket *AnovaCodec::get_stop_request() {
}
void AnovaCodec::decode(const uint8_t *data, uint16_t length) {
memset(this->buf_, 0, 32);
strncpy(this->buf_, (char *) data, length);
char buf[32];
memset(buf, 0, sizeof(buf));
strncpy(buf, (char *) data, std::min<uint16_t>(length, sizeof(buf) - 1));
this->has_target_temp_ = this->has_current_temp_ = this->has_unit_ = this->has_running_ = false;
switch (this->current_query_) {
case READ_DEVICE_STATUS: {
if (!strncmp(this->buf_, "stopped", 7)) {
if (!strncmp(buf, "stopped", 7)) {
this->has_running_ = true;
this->running_ = false;
}
if (!strncmp(this->buf_, "running", 7)) {
if (!strncmp(buf, "running", 7)) {
this->has_running_ = true;
this->running_ = true;
}
break;
}
case START: {
if (!strncmp(this->buf_, "start", 5)) {
if (!strncmp(buf, "start", 5)) {
this->has_running_ = true;
this->running_ = true;
}
break;
}
case STOP: {
if (!strncmp(this->buf_, "stop", 4)) {
if (!strncmp(buf, "stop", 4)) {
this->has_running_ = true;
this->running_ = false;
}
break;
}
case READ_TARGET_TEMPERATURE: {
this->target_temp_ = strtof(this->buf_, nullptr);
if (this->fahrenheit_)
this->target_temp_ = ftoc(this->target_temp_);
this->has_target_temp_ = true;
break;
}
case READ_TARGET_TEMPERATURE:
case SET_TARGET_TEMPERATURE: {
this->target_temp_ = strtof(this->buf_, nullptr);
this->target_temp_ = parse_number<float>(str_until(buf, '\r')).value_or(0.0f);
if (this->fahrenheit_)
this->target_temp_ = ftoc(this->target_temp_);
this->has_target_temp_ = true;
break;
}
case READ_CURRENT_TEMPERATURE: {
this->current_temp_ = strtof(this->buf_, nullptr);
this->current_temp_ = parse_number<float>(str_until(buf, '\r')).value_or(0.0f);
if (this->fahrenheit_)
this->current_temp_ = ftoc(this->current_temp_);
this->has_current_temp_ = true;
@@ -125,8 +120,8 @@ void AnovaCodec::decode(const uint8_t *data, uint16_t length) {
}
case SET_UNIT:
case READ_UNIT: {
this->unit_ = this->buf_[0];
this->fahrenheit_ = this->buf_[0] == 'f';
this->unit_ = buf[0];
this->fahrenheit_ = buf[0] == 'f';
this->has_unit_ = true;
break;
}

View File

@@ -70,7 +70,6 @@ class AnovaCodec {
bool has_current_temp_;
bool has_unit_;
bool has_running_;
char buf_[32];
bool fahrenheit_;
CurrentQuery current_query_;

View File

@@ -121,7 +121,7 @@ async def to_code(config):
decoded = base64.b64decode(conf[CONF_KEY])
cg.add(var.set_noise_psk(list(decoded)))
cg.add_define("USE_API_NOISE")
cg.add_library("esphome/noise-c", "0.1.3")
cg.add_library("esphome/noise-c", "0.1.4")
else:
cg.add_define("USE_API_PLAINTEXT")

View File

@@ -40,6 +40,7 @@ service APIConnection {
rpc climate_command (ClimateCommandRequest) returns (void) {}
rpc number_command (NumberCommandRequest) returns (void) {}
rpc select_command (SelectCommandRequest) returns (void) {}
rpc button_command (ButtonCommandRequest) returns (void) {}
}
@@ -182,6 +183,8 @@ message DeviceInfoResponse {
// The esphome project details if set
string project_name = 8;
string project_version = 9;
uint32 webserver_port = 10;
}
message ListEntitiesRequest {
@@ -201,6 +204,14 @@ message SubscribeStatesRequest {
// Empty
}
// ==================== COMMON =====================
enum EntityCategory {
ENTITY_CATEGORY_NONE = 0;
ENTITY_CATEGORY_CONFIG = 1;
ENTITY_CATEGORY_DIAGNOSTIC = 2;
}
// ==================== BINARY SENSOR ====================
message ListEntitiesBinarySensorResponse {
option (id) = 12;
@@ -216,6 +227,7 @@ message ListEntitiesBinarySensorResponse {
bool is_status_binary_sensor = 6;
bool disabled_by_default = 7;
string icon = 8;
EntityCategory entity_category = 9;
}
message BinarySensorStateResponse {
option (id) = 21;
@@ -247,6 +259,7 @@ message ListEntitiesCoverResponse {
string device_class = 8;
bool disabled_by_default = 9;
string icon = 10;
EntityCategory entity_category = 11;
}
enum LegacyCoverState {
@@ -316,6 +329,7 @@ message ListEntitiesFanResponse {
int32 supported_speed_count = 8;
bool disabled_by_default = 9;
string icon = 10;
EntityCategory entity_category = 11;
}
enum FanSpeed {
FAN_SPEED_LOW = 0;
@@ -392,6 +406,7 @@ message ListEntitiesLightResponse {
repeated string effects = 11;
bool disabled_by_default = 13;
string icon = 14;
EntityCategory entity_category = 15;
}
message LightStateResponse {
option (id) = 24;
@@ -480,6 +495,7 @@ message ListEntitiesSensorResponse {
// Last reset type removed in 2021.9.0
SensorLastResetType legacy_last_reset_type = 11;
bool disabled_by_default = 12;
EntityCategory entity_category = 13;
}
message SensorStateResponse {
option (id) = 25;
@@ -508,6 +524,7 @@ message ListEntitiesSwitchResponse {
string icon = 5;
bool assumed_state = 6;
bool disabled_by_default = 7;
EntityCategory entity_category = 8;
}
message SwitchStateResponse {
option (id) = 26;
@@ -541,6 +558,7 @@ message ListEntitiesTextSensorResponse {
string icon = 5;
bool disabled_by_default = 6;
EntityCategory entity_category = 7;
}
message TextSensorStateResponse {
option (id) = 27;
@@ -701,6 +719,8 @@ message ListEntitiesCameraResponse {
string name = 3;
string unique_id = 4;
bool disabled_by_default = 5;
string icon = 6;
EntityCategory entity_category = 7;
}
message CameraImageResponse {
@@ -795,6 +815,7 @@ message ListEntitiesClimateResponse {
repeated string supported_custom_presets = 17;
bool disabled_by_default = 18;
string icon = 19;
EntityCategory entity_category = 20;
}
message ClimateStateResponse {
option (id) = 47;
@@ -848,6 +869,11 @@ message ClimateCommandRequest {
}
// ==================== NUMBER ====================
enum NumberMode {
NUMBER_MODE_AUTO = 0;
NUMBER_MODE_BOX = 1;
NUMBER_MODE_SLIDER = 2;
}
message ListEntitiesNumberResponse {
option (id) = 49;
option (source) = SOURCE_SERVER;
@@ -863,6 +889,9 @@ message ListEntitiesNumberResponse {
float max_value = 7;
float step = 8;
bool disabled_by_default = 9;
EntityCategory entity_category = 10;
string unit_of_measurement = 11;
NumberMode mode = 12;
}
message NumberStateResponse {
option (id) = 50;
@@ -900,6 +929,7 @@ message ListEntitiesSelectResponse {
string icon = 5;
repeated string options = 6;
bool disabled_by_default = 7;
EntityCategory entity_category = 8;
}
message SelectStateResponse {
option (id) = 53;
@@ -922,3 +952,28 @@ message SelectCommandRequest {
fixed32 key = 1;
string state = 2;
}
// ==================== BUTTON ====================
message ListEntitiesButtonResponse {
option (id) = 61;
option (source) = SOURCE_SERVER;
option (ifdef) = "USE_BUTTON";
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;
string device_class = 8;
}
message ButtonCommandRequest {
option (id) = 62;
option (source) = SOURCE_CLIENT;
option (ifdef) = "USE_BUTTON";
option (no_delay) = true;
fixed32 key = 1;
}

View File

@@ -78,6 +78,8 @@ void APIConnection::loop() {
on_fatal_error();
if (err == APIError::SOCKET_READ_FAILED && errno == ECONNRESET) {
ESP_LOGW(TAG, "%s: Connection reset", client_info_.c_str());
} else if (err == APIError::CONNECTION_CLOSED) {
ESP_LOGW(TAG, "%s: Connection closed", client_info_.c_str());
} else {
ESP_LOGW(TAG, "%s: Reading failed: %s errno=%d", client_info_.c_str(), api_error_to_str(err), errno);
}
@@ -130,7 +132,7 @@ void APIConnection::loop() {
if (state_subs_at_ != -1) {
const auto &subs = this->parent_->get_state_subs();
if (state_subs_at_ >= subs.size()) {
if (state_subs_at_ >= (int) subs.size()) {
state_subs_at_ = -1;
} else {
auto &it = subs[state_subs_at_];
@@ -182,6 +184,7 @@ bool APIConnection::send_binary_sensor_info(binary_sensor::BinarySensor *binary_
msg.is_status_binary_sensor = binary_sensor->is_status_binary_sensor();
msg.disabled_by_default = binary_sensor->is_disabled_by_default();
msg.icon = binary_sensor->get_icon();
msg.entity_category = static_cast<enums::EntityCategory>(binary_sensor->get_entity_category());
return this->send_list_entities_binary_sensor_response(msg);
}
#endif
@@ -215,6 +218,7 @@ bool APIConnection::send_cover_info(cover::Cover *cover) {
msg.device_class = cover->get_device_class();
msg.disabled_by_default = cover->is_disabled_by_default();
msg.icon = cover->get_icon();
msg.entity_category = static_cast<enums::EntityCategory>(cover->get_entity_category());
return this->send_list_entities_cover_response(msg);
}
void APIConnection::cover_command(const CoverCommandRequest &msg) {
@@ -281,6 +285,7 @@ bool APIConnection::send_fan_info(fan::FanState *fan) {
msg.supported_speed_count = traits.supported_speed_count();
msg.disabled_by_default = fan->is_disabled_by_default();
msg.icon = fan->get_icon();
msg.entity_category = static_cast<enums::EntityCategory>(fan->get_entity_category());
return this->send_list_entities_fan_response(msg);
}
void APIConnection::fan_command(const FanCommandRequest &msg) {
@@ -344,6 +349,7 @@ bool APIConnection::send_light_info(light::LightState *light) {
msg.disabled_by_default = light->is_disabled_by_default();
msg.icon = light->get_icon();
msg.entity_category = static_cast<enums::EntityCategory>(light->get_entity_category());
for (auto mode : traits.get_supported_color_modes())
msg.supported_color_modes.push_back(static_cast<enums::ColorMode>(mode));
@@ -430,7 +436,7 @@ bool APIConnection::send_sensor_info(sensor::Sensor *sensor) {
msg.device_class = sensor->get_device_class();
msg.state_class = static_cast<enums::SensorStateClass>(sensor->get_state_class());
msg.disabled_by_default = sensor->is_disabled_by_default();
msg.entity_category = static_cast<enums::EntityCategory>(sensor->get_entity_category());
return this->send_list_entities_sensor_response(msg);
}
#endif
@@ -454,6 +460,7 @@ bool APIConnection::send_switch_info(switch_::Switch *a_switch) {
msg.icon = a_switch->get_icon();
msg.assumed_state = a_switch->assumed_state();
msg.disabled_by_default = a_switch->is_disabled_by_default();
msg.entity_category = static_cast<enums::EntityCategory>(a_switch->get_entity_category());
return this->send_list_entities_switch_response(msg);
}
void APIConnection::switch_command(const SwitchCommandRequest &msg) {
@@ -489,6 +496,7 @@ bool APIConnection::send_text_sensor_info(text_sensor::TextSensor *text_sensor)
msg.unique_id = get_default_unique_id("text_sensor", text_sensor);
msg.icon = text_sensor->get_icon();
msg.disabled_by_default = text_sensor->is_disabled_by_default();
msg.entity_category = static_cast<enums::EntityCategory>(text_sensor->get_entity_category());
return this->send_list_entities_text_sensor_response(msg);
}
#endif
@@ -535,6 +543,7 @@ bool APIConnection::send_climate_info(climate::Climate *climate) {
msg.disabled_by_default = climate->is_disabled_by_default();
msg.icon = climate->get_icon();
msg.entity_category = static_cast<enums::EntityCategory>(climate->get_entity_category());
msg.supports_current_temperature = traits.get_supports_current_temperature();
msg.supports_two_point_target_temperature = traits.get_supports_two_point_target_temperature();
@@ -609,6 +618,9 @@ bool APIConnection::send_number_info(number::Number *number) {
msg.unique_id = get_default_unique_id("number", number);
msg.icon = number->get_icon();
msg.disabled_by_default = number->is_disabled_by_default();
msg.entity_category = static_cast<enums::EntityCategory>(number->get_entity_category());
msg.unit_of_measurement = number->traits.get_unit_of_measurement();
msg.mode = static_cast<enums::NumberMode>(number->traits.get_mode());
msg.min_value = number->traits.get_min_value();
msg.max_value = number->traits.get_max_value();
@@ -646,6 +658,7 @@ bool APIConnection::send_select_info(select::Select *select) {
msg.unique_id = get_default_unique_id("select", select);
msg.icon = select->get_icon();
msg.disabled_by_default = select->is_disabled_by_default();
msg.entity_category = static_cast<enums::EntityCategory>(select->get_entity_category());
for (const auto &option : select->traits.get_options())
msg.options.push_back(option);
@@ -663,6 +676,28 @@ void APIConnection::select_command(const SelectCommandRequest &msg) {
}
#endif
#ifdef USE_BUTTON
bool APIConnection::send_button_info(button::Button *button) {
ListEntitiesButtonResponse msg;
msg.key = button->get_object_id_hash();
msg.object_id = button->get_object_id();
msg.name = button->get_name();
msg.unique_id = get_default_unique_id("button", button);
msg.icon = button->get_icon();
msg.disabled_by_default = button->is_disabled_by_default();
msg.entity_category = static_cast<enums::EntityCategory>(button->get_entity_category());
msg.device_class = button->get_device_class();
return this->send_list_entities_button_response(msg);
}
void APIConnection::button_command(const ButtonCommandRequest &msg) {
button::Button *button = App.get_button_by_key(msg.key);
if (button == nullptr)
return;
button->press();
}
#endif
#ifdef USE_ESP32_CAMERA
void APIConnection::send_camera_state(std::shared_ptr<esp32_camera::CameraImage> image) {
if (!this->state_subscription_)
@@ -678,6 +713,8 @@ bool APIConnection::send_camera_info(esp32_camera::ESP32Camera *camera) {
msg.name = camera->get_name();
msg.unique_id = get_default_unique_id("camera", camera);
msg.disabled_by_default = camera->is_disabled_by_default();
msg.icon = camera->get_icon();
msg.entity_category = static_cast<enums::EntityCategory>(camera->get_entity_category());
return this->send_list_entities_camera_response(msg);
}
void APIConnection::camera_image(const CameraImageRequest &msg) {
@@ -756,6 +793,9 @@ DeviceInfoResponse APIConnection::device_info(const DeviceInfoRequest &msg) {
#ifdef ESPHOME_PROJECT_NAME
resp.project_name = ESPHOME_PROJECT_NAME;
resp.project_version = ESPHOME_PROJECT_VERSION;
#endif
#ifdef USE_WEBSERVER
resp.webserver_port = WEBSERVER_PORT;
#endif
return resp;
}

View File

@@ -73,6 +73,10 @@ class APIConnection : public APIServerConnection {
bool send_select_state(select::Select *select, std::string state);
bool send_select_info(select::Select *select);
void select_command(const SelectCommandRequest &msg) override;
#endif
#ifdef USE_BUTTON
bool send_button_info(button::Button *button);
void button_command(const ButtonCommandRequest &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

@@ -10,7 +10,7 @@ namespace api {
static const char *const TAG = "api.socket";
/// Is the given return value (from read/write syscalls) a wouldblock error?
/// Is the given return value (from write syscalls) a wouldblock error?
bool is_would_block(ssize_t ret) {
if (ret == -1) {
return errno == EWOULDBLOCK || errno == EAGAIN;
@@ -64,6 +64,8 @@ const char *api_error_to_str(APIError err) {
return "HANDSHAKESTATE_SPLIT_FAILED";
} else if (err == APIError::BAD_HANDSHAKE_ERROR_BYTE) {
return "BAD_HANDSHAKE_ERROR_BYTE";
} else if (err == APIError::CONNECTION_CLOSED) {
return "CONNECTION_CLOSED";
}
return "UNKNOWN";
}
@@ -172,9 +174,6 @@ APIError APINoiseFrameHelper::loop() {
* errno API_ERROR_HANDSHAKE_PACKET_LEN: Packet too big for this phase.
*/
APIError APINoiseFrameHelper::try_read_frame_(ParsedFrame *frame) {
int err;
APIError aerr;
if (frame == nullptr) {
HELPER_LOG("Bad argument for try_read_frame_");
return APIError::BAD_ARG;
@@ -185,15 +184,20 @@ APIError APINoiseFrameHelper::try_read_frame_(ParsedFrame *frame) {
// no header information yet
size_t to_read = 3 - rx_header_buf_len_;
ssize_t received = socket_->read(&rx_header_buf_[rx_header_buf_len_], to_read);
if (is_would_block(received)) {
return APIError::WOULD_BLOCK;
} else if (received == -1) {
if (received == -1) {
if (errno == EWOULDBLOCK || errno == EAGAIN) {
return APIError::WOULD_BLOCK;
}
state_ = State::FAILED;
HELPER_LOG("Socket read failed with errno %d", errno);
return APIError::SOCKET_READ_FAILED;
} else if (received == 0) {
state_ = State::FAILED;
HELPER_LOG("Connection closed");
return APIError::CONNECTION_CLOSED;
}
rx_header_buf_len_ += received;
if (received != to_read) {
if ((size_t) received != to_read) {
// not a full read
return APIError::WOULD_BLOCK;
}
@@ -227,15 +231,20 @@ APIError APINoiseFrameHelper::try_read_frame_(ParsedFrame *frame) {
// more data to read
size_t to_read = msg_size - rx_buf_len_;
ssize_t received = socket_->read(&rx_buf_[rx_buf_len_], to_read);
if (is_would_block(received)) {
return APIError::WOULD_BLOCK;
} else if (received == -1) {
if (received == -1) {
if (errno == EWOULDBLOCK || errno == EAGAIN) {
return APIError::WOULD_BLOCK;
}
state_ = State::FAILED;
HELPER_LOG("Socket read failed with errno %d", errno);
return APIError::SOCKET_READ_FAILED;
} else if (received == 0) {
state_ = State::FAILED;
HELPER_LOG("Connection closed");
return APIError::CONNECTION_CLOSED;
}
rx_buf_len_ += received;
if (received != to_read) {
if ((size_t) received != to_read) {
// not all read
return APIError::WOULD_BLOCK;
}
@@ -243,7 +252,7 @@ APIError APINoiseFrameHelper::try_read_frame_(ParsedFrame *frame) {
// uncomment for even more debugging
#ifdef HELPER_LOG_PACKETS
ESP_LOGVV(TAG, "Received frame: %s", hexencode(rx_buf_).c_str());
ESP_LOGVV(TAG, "Received frame: %s", format_hex_pretty(rx_buf_).c_str());
#endif
frame->msg = std::move(rx_buf_);
// consume msg
@@ -532,13 +541,13 @@ APIError APINoiseFrameHelper::try_send_tx_buf_() {
APIError APINoiseFrameHelper::write_raw_(const struct iovec *iov, int iovcnt) {
if (iovcnt == 0)
return APIError::OK;
int err;
APIError aerr;
size_t total_write_len = 0;
for (int i = 0; i < iovcnt; i++) {
#ifdef HELPER_LOG_PACKETS
ESP_LOGVV(TAG, "Sending raw: %s", hexencode(reinterpret_cast<uint8_t *>(iov[i].iov_base), iov[i].iov_len).c_str());
ESP_LOGVV(TAG, "Sending raw: %s",
format_hex_pretty(reinterpret_cast<uint8_t *>(iov[i].iov_base), iov[i].iov_len).c_str());
#endif
total_write_len += iov[i].iov_len;
}
@@ -572,7 +581,7 @@ APIError APINoiseFrameHelper::write_raw_(const struct iovec *iov, int iovcnt) {
state_ = State::FAILED;
HELPER_LOG("Socket write failed with errno %d", errno);
return APIError::SOCKET_WRITE_FAILED;
} else if (sent != total_write_len) {
} else if ((size_t) sent != total_write_len) {
// partially sent, add end to tx_buf
size_t to_consume = sent;
for (int i = 0; i < iovcnt; i++) {
@@ -766,9 +775,6 @@ APIError APIPlaintextFrameHelper::loop() {
* error API_ERROR_BAD_INDICATOR: Bad indicator byte at start of frame.
*/
APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) {
int err;
APIError aerr;
if (frame == nullptr) {
HELPER_LOG("Bad argument for try_read_frame_");
return APIError::BAD_ARG;
@@ -778,12 +784,17 @@ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) {
while (!rx_header_parsed_) {
uint8_t data;
ssize_t received = socket_->read(&data, 1);
if (is_would_block(received)) {
return APIError::WOULD_BLOCK;
} else if (received == -1) {
if (received == -1) {
if (errno == EWOULDBLOCK || errno == EAGAIN) {
return APIError::WOULD_BLOCK;
}
state_ = State::FAILED;
HELPER_LOG("Socket read failed with errno %d", errno);
return APIError::SOCKET_READ_FAILED;
} else if (received == 0) {
state_ = State::FAILED;
HELPER_LOG("Connection closed");
return APIError::CONNECTION_CLOSED;
}
rx_header_buf_.push_back(data);
@@ -824,15 +835,20 @@ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) {
// more data to read
size_t to_read = rx_header_parsed_len_ - rx_buf_len_;
ssize_t received = socket_->read(&rx_buf_[rx_buf_len_], to_read);
if (is_would_block(received)) {
return APIError::WOULD_BLOCK;
} else if (received == -1) {
if (received == -1) {
if (errno == EWOULDBLOCK || errno == EAGAIN) {
return APIError::WOULD_BLOCK;
}
state_ = State::FAILED;
HELPER_LOG("Socket read failed with errno %d", errno);
return APIError::SOCKET_READ_FAILED;
} else if (received == 0) {
state_ = State::FAILED;
HELPER_LOG("Connection closed");
return APIError::CONNECTION_CLOSED;
}
rx_buf_len_ += received;
if (received != to_read) {
if ((size_t) received != to_read) {
// not all read
return APIError::WOULD_BLOCK;
}
@@ -840,7 +856,7 @@ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) {
// uncomment for even more debugging
#ifdef HELPER_LOG_PACKETS
ESP_LOGVV(TAG, "Received frame: %s", hexencode(rx_buf_).c_str());
ESP_LOGVV(TAG, "Received frame: %s", format_hex_pretty(rx_buf_).c_str());
#endif
frame->msg = std::move(rx_buf_);
// consume msg
@@ -852,7 +868,6 @@ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) {
}
APIError APIPlaintextFrameHelper::read_packet(ReadPacketBuffer *buffer) {
int err;
APIError aerr;
if (state_ != State::DATA) {
@@ -872,9 +887,6 @@ APIError APIPlaintextFrameHelper::read_packet(ReadPacketBuffer *buffer) {
}
bool APIPlaintextFrameHelper::can_write_without_blocking() { return state_ == State::DATA && tx_buf_.empty(); }
APIError APIPlaintextFrameHelper::write_packet(uint16_t type, const uint8_t *payload, size_t payload_len) {
int err;
APIError aerr;
if (state_ != State::DATA) {
return APIError::BAD_STATE;
}
@@ -918,13 +930,13 @@ APIError APIPlaintextFrameHelper::try_send_tx_buf_() {
APIError APIPlaintextFrameHelper::write_raw_(const struct iovec *iov, int iovcnt) {
if (iovcnt == 0)
return APIError::OK;
int err;
APIError aerr;
size_t total_write_len = 0;
for (int i = 0; i < iovcnt; i++) {
#ifdef HELPER_LOG_PACKETS
ESP_LOGVV(TAG, "Sending raw: %s", hexencode(reinterpret_cast<uint8_t *>(iov[i].iov_base), iov[i].iov_len).c_str());
ESP_LOGVV(TAG, "Sending raw: %s",
format_hex_pretty(reinterpret_cast<uint8_t *>(iov[i].iov_base), iov[i].iov_len).c_str());
#endif
total_write_len += iov[i].iov_len;
}
@@ -958,7 +970,7 @@ APIError APIPlaintextFrameHelper::write_raw_(const struct iovec *iov, int iovcnt
state_ = State::FAILED;
HELPER_LOG("Socket write failed with errno %d", errno);
return APIError::SOCKET_WRITE_FAILED;
} else if (sent != total_write_len) {
} else if ((size_t) sent != total_write_len) {
// partially sent, add end to tx_buf
size_t to_consume = sent;
for (int i = 0; i < iovcnt; i++) {

View File

@@ -53,6 +53,7 @@ enum class APIError : int {
HANDSHAKESTATE_SETUP_FAILED = 1019,
HANDSHAKESTATE_SPLIT_FAILED = 1020,
BAD_HANDSHAKE_ERROR_BYTE = 1021,
CONNECTION_CLOSED = 1022,
};
const char *api_error_to_str(APIError err);

View File

@@ -6,6 +6,18 @@
namespace esphome {
namespace api {
template<> const char *proto_enum_to_string<enums::EntityCategory>(enums::EntityCategory value) {
switch (value) {
case enums::ENTITY_CATEGORY_NONE:
return "ENTITY_CATEGORY_NONE";
case enums::ENTITY_CATEGORY_CONFIG:
return "ENTITY_CATEGORY_CONFIG";
case enums::ENTITY_CATEGORY_DIAGNOSTIC:
return "ENTITY_CATEGORY_DIAGNOSTIC";
default:
return "UNKNOWN";
}
}
template<> const char *proto_enum_to_string<enums::LegacyCoverState>(enums::LegacyCoverState value) {
switch (value) {
case enums::LEGACY_COVER_STATE_OPEN:
@@ -254,6 +266,18 @@ template<> const char *proto_enum_to_string<enums::ClimatePreset>(enums::Climate
return "UNKNOWN";
}
}
template<> const char *proto_enum_to_string<enums::NumberMode>(enums::NumberMode value) {
switch (value) {
case enums::NUMBER_MODE_AUTO:
return "NUMBER_MODE_AUTO";
case enums::NUMBER_MODE_BOX:
return "NUMBER_MODE_BOX";
case enums::NUMBER_MODE_SLIDER:
return "NUMBER_MODE_SLIDER";
default:
return "UNKNOWN";
}
}
bool HelloRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 1: {
@@ -267,7 +291,7 @@ bool HelloRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value)
void HelloRequest::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(1, this->client_info); }
#ifdef HAS_PROTO_MESSAGE_DUMP
void HelloRequest::dump_to(std::string &out) const {
char buffer[64];
__attribute__((unused)) char buffer[64];
out.append("HelloRequest {\n");
out.append(" client_info: ");
out.append("'").append(this->client_info).append("'");
@@ -306,7 +330,7 @@ void HelloResponse::encode(ProtoWriteBuffer buffer) const {
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void HelloResponse::dump_to(std::string &out) const {
char buffer[64];
__attribute__((unused)) char buffer[64];
out.append("HelloResponse {\n");
out.append(" api_version_major: ");
sprintf(buffer, "%u", this->api_version_major);
@@ -337,7 +361,7 @@ bool ConnectRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value
void ConnectRequest::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(1, this->password); }
#ifdef HAS_PROTO_MESSAGE_DUMP
void ConnectRequest::dump_to(std::string &out) const {
char buffer[64];
__attribute__((unused)) char buffer[64];
out.append("ConnectRequest {\n");
out.append(" password: ");
out.append("'").append(this->password).append("'");
@@ -358,7 +382,7 @@ bool ConnectResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
void ConnectResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(1, this->invalid_password); }
#ifdef HAS_PROTO_MESSAGE_DUMP
void ConnectResponse::dump_to(std::string &out) const {
char buffer[64];
__attribute__((unused)) char buffer[64];
out.append("ConnectResponse {\n");
out.append(" invalid_password: ");
out.append(YESNO(this->invalid_password));
@@ -396,6 +420,10 @@ bool DeviceInfoResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
this->has_deep_sleep = value.as_bool();
return true;
}
case 10: {
this->webserver_port = value.as_uint32();
return true;
}
default:
return false;
}
@@ -444,10 +472,11 @@ void DeviceInfoResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_bool(7, this->has_deep_sleep);
buffer.encode_string(8, this->project_name);
buffer.encode_string(9, this->project_version);
buffer.encode_uint32(10, this->webserver_port);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void DeviceInfoResponse::dump_to(std::string &out) const {
char buffer[64];
__attribute__((unused)) char buffer[64];
out.append("DeviceInfoResponse {\n");
out.append(" uses_password: ");
out.append(YESNO(this->uses_password));
@@ -484,6 +513,11 @@ void DeviceInfoResponse::dump_to(std::string &out) const {
out.append(" project_version: ");
out.append("'").append(this->project_version).append("'");
out.append("\n");
out.append(" webserver_port: ");
sprintf(buffer, "%u", this->webserver_port);
out.append(buffer);
out.append("\n");
out.append("}");
}
#endif
@@ -509,6 +543,10 @@ bool ListEntitiesBinarySensorResponse::decode_varint(uint32_t field_id, ProtoVar
this->disabled_by_default = value.as_bool();
return true;
}
case 9: {
this->entity_category = value.as_enum<enums::EntityCategory>();
return true;
}
default:
return false;
}
@@ -558,10 +596,11 @@ void ListEntitiesBinarySensorResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_bool(6, this->is_status_binary_sensor);
buffer.encode_bool(7, this->disabled_by_default);
buffer.encode_string(8, this->icon);
buffer.encode_enum<enums::EntityCategory>(9, this->entity_category);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesBinarySensorResponse::dump_to(std::string &out) const {
char buffer[64];
__attribute__((unused)) char buffer[64];
out.append("ListEntitiesBinarySensorResponse {\n");
out.append(" object_id: ");
out.append("'").append(this->object_id).append("'");
@@ -595,6 +634,10 @@ void ListEntitiesBinarySensorResponse::dump_to(std::string &out) const {
out.append(" icon: ");
out.append("'").append(this->icon).append("'");
out.append("\n");
out.append(" entity_category: ");
out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
out.append("\n");
out.append("}");
}
#endif
@@ -629,7 +672,7 @@ void BinarySensorStateResponse::encode(ProtoWriteBuffer buffer) const {
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void BinarySensorStateResponse::dump_to(std::string &out) const {
char buffer[64];
__attribute__((unused)) char buffer[64];
out.append("BinarySensorStateResponse {\n");
out.append(" key: ");
sprintf(buffer, "%u", this->key);
@@ -664,6 +707,10 @@ bool ListEntitiesCoverResponse::decode_varint(uint32_t field_id, ProtoVarInt val
this->disabled_by_default = value.as_bool();
return true;
}
case 11: {
this->entity_category = value.as_enum<enums::EntityCategory>();
return true;
}
default:
return false;
}
@@ -715,10 +762,11 @@ void ListEntitiesCoverResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(8, this->device_class);
buffer.encode_bool(9, this->disabled_by_default);
buffer.encode_string(10, this->icon);
buffer.encode_enum<enums::EntityCategory>(11, this->entity_category);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesCoverResponse::dump_to(std::string &out) const {
char buffer[64];
__attribute__((unused)) char buffer[64];
out.append("ListEntitiesCoverResponse {\n");
out.append(" object_id: ");
out.append("'").append(this->object_id).append("'");
@@ -760,6 +808,10 @@ void ListEntitiesCoverResponse::dump_to(std::string &out) const {
out.append(" icon: ");
out.append("'").append(this->icon).append("'");
out.append("\n");
out.append(" entity_category: ");
out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
out.append("\n");
out.append("}");
}
#endif
@@ -804,7 +856,7 @@ void CoverStateResponse::encode(ProtoWriteBuffer buffer) const {
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void CoverStateResponse::dump_to(std::string &out) const {
char buffer[64];
__attribute__((unused)) char buffer[64];
out.append("CoverStateResponse {\n");
out.append(" key: ");
sprintf(buffer, "%u", this->key);
@@ -887,7 +939,7 @@ void CoverCommandRequest::encode(ProtoWriteBuffer buffer) const {
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void CoverCommandRequest::dump_to(std::string &out) const {
char buffer[64];
__attribute__((unused)) char buffer[64];
out.append("CoverCommandRequest {\n");
out.append(" key: ");
sprintf(buffer, "%u", this->key);
@@ -948,6 +1000,10 @@ bool ListEntitiesFanResponse::decode_varint(uint32_t field_id, ProtoVarInt value
this->disabled_by_default = value.as_bool();
return true;
}
case 11: {
this->entity_category = value.as_enum<enums::EntityCategory>();
return true;
}
default:
return false;
}
@@ -995,10 +1051,11 @@ void ListEntitiesFanResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_int32(8, this->supported_speed_count);
buffer.encode_bool(9, this->disabled_by_default);
buffer.encode_string(10, this->icon);
buffer.encode_enum<enums::EntityCategory>(11, this->entity_category);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesFanResponse::dump_to(std::string &out) const {
char buffer[64];
__attribute__((unused)) char buffer[64];
out.append("ListEntitiesFanResponse {\n");
out.append(" object_id: ");
out.append("'").append(this->object_id).append("'");
@@ -1041,6 +1098,10 @@ void ListEntitiesFanResponse::dump_to(std::string &out) const {
out.append(" icon: ");
out.append("'").append(this->icon).append("'");
out.append("\n");
out.append(" entity_category: ");
out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
out.append("\n");
out.append("}");
}
#endif
@@ -1090,7 +1151,7 @@ void FanStateResponse::encode(ProtoWriteBuffer buffer) const {
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void FanStateResponse::dump_to(std::string &out) const {
char buffer[64];
__attribute__((unused)) char buffer[64];
out.append("FanStateResponse {\n");
out.append(" key: ");
sprintf(buffer, "%u", this->key);
@@ -1191,7 +1252,7 @@ void FanCommandRequest::encode(ProtoWriteBuffer buffer) const {
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void FanCommandRequest::dump_to(std::string &out) const {
char buffer[64];
__attribute__((unused)) char buffer[64];
out.append("FanCommandRequest {\n");
out.append(" key: ");
sprintf(buffer, "%u", this->key);
@@ -1267,6 +1328,10 @@ bool ListEntitiesLightResponse::decode_varint(uint32_t field_id, ProtoVarInt val
this->disabled_by_default = value.as_bool();
return true;
}
case 15: {
this->entity_category = value.as_enum<enums::EntityCategory>();
return true;
}
default:
return false;
}
@@ -1334,10 +1399,11 @@ void ListEntitiesLightResponse::encode(ProtoWriteBuffer buffer) const {
}
buffer.encode_bool(13, this->disabled_by_default);
buffer.encode_string(14, this->icon);
buffer.encode_enum<enums::EntityCategory>(15, this->entity_category);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesLightResponse::dump_to(std::string &out) const {
char buffer[64];
__attribute__((unused)) char buffer[64];
out.append("ListEntitiesLightResponse {\n");
out.append(" object_id: ");
out.append("'").append(this->object_id).append("'");
@@ -1401,6 +1467,10 @@ void ListEntitiesLightResponse::dump_to(std::string &out) const {
out.append(" icon: ");
out.append("'").append(this->icon).append("'");
out.append("\n");
out.append(" entity_category: ");
out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
out.append("\n");
out.append("}");
}
#endif
@@ -1491,7 +1561,7 @@ void LightStateResponse::encode(ProtoWriteBuffer buffer) const {
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void LightStateResponse::dump_to(std::string &out) const {
char buffer[64];
__attribute__((unused)) char buffer[64];
out.append("LightStateResponse {\n");
out.append(" key: ");
sprintf(buffer, "%u", this->key);
@@ -1714,7 +1784,7 @@ void LightCommandRequest::encode(ProtoWriteBuffer buffer) const {
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void LightCommandRequest::dump_to(std::string &out) const {
char buffer[64];
__attribute__((unused)) char buffer[64];
out.append("LightCommandRequest {\n");
out.append(" key: ");
sprintf(buffer, "%u", this->key);
@@ -1860,6 +1930,10 @@ bool ListEntitiesSensorResponse::decode_varint(uint32_t field_id, ProtoVarInt va
this->disabled_by_default = value.as_bool();
return true;
}
case 13: {
this->entity_category = value.as_enum<enums::EntityCategory>();
return true;
}
default:
return false;
}
@@ -1917,10 +1991,11 @@ void ListEntitiesSensorResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_enum<enums::SensorStateClass>(10, this->state_class);
buffer.encode_enum<enums::SensorLastResetType>(11, this->legacy_last_reset_type);
buffer.encode_bool(12, this->disabled_by_default);
buffer.encode_enum<enums::EntityCategory>(13, this->entity_category);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesSensorResponse::dump_to(std::string &out) const {
char buffer[64];
__attribute__((unused)) char buffer[64];
out.append("ListEntitiesSensorResponse {\n");
out.append(" object_id: ");
out.append("'").append(this->object_id).append("'");
@@ -1971,6 +2046,10 @@ void ListEntitiesSensorResponse::dump_to(std::string &out) const {
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("}");
}
#endif
@@ -2005,7 +2084,7 @@ void SensorStateResponse::encode(ProtoWriteBuffer buffer) const {
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void SensorStateResponse::dump_to(std::string &out) const {
char buffer[64];
__attribute__((unused)) char buffer[64];
out.append("SensorStateResponse {\n");
out.append(" key: ");
sprintf(buffer, "%u", this->key);
@@ -2033,6 +2112,10 @@ bool ListEntitiesSwitchResponse::decode_varint(uint32_t field_id, ProtoVarInt va
this->disabled_by_default = value.as_bool();
return true;
}
case 8: {
this->entity_category = value.as_enum<enums::EntityCategory>();
return true;
}
default:
return false;
}
@@ -2077,10 +2160,11 @@ void ListEntitiesSwitchResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(5, this->icon);
buffer.encode_bool(6, this->assumed_state);
buffer.encode_bool(7, this->disabled_by_default);
buffer.encode_enum<enums::EntityCategory>(8, this->entity_category);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesSwitchResponse::dump_to(std::string &out) const {
char buffer[64];
__attribute__((unused)) char buffer[64];
out.append("ListEntitiesSwitchResponse {\n");
out.append(" object_id: ");
out.append("'").append(this->object_id).append("'");
@@ -2110,6 +2194,10 @@ void ListEntitiesSwitchResponse::dump_to(std::string &out) const {
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("}");
}
#endif
@@ -2139,7 +2227,7 @@ void SwitchStateResponse::encode(ProtoWriteBuffer buffer) const {
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void SwitchStateResponse::dump_to(std::string &out) const {
char buffer[64];
__attribute__((unused)) char buffer[64];
out.append("SwitchStateResponse {\n");
out.append(" key: ");
sprintf(buffer, "%u", this->key);
@@ -2178,7 +2266,7 @@ void SwitchCommandRequest::encode(ProtoWriteBuffer buffer) const {
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void SwitchCommandRequest::dump_to(std::string &out) const {
char buffer[64];
__attribute__((unused)) char buffer[64];
out.append("SwitchCommandRequest {\n");
out.append(" key: ");
sprintf(buffer, "%u", this->key);
@@ -2197,6 +2285,10 @@ bool ListEntitiesTextSensorResponse::decode_varint(uint32_t field_id, ProtoVarIn
this->disabled_by_default = value.as_bool();
return true;
}
case 7: {
this->entity_category = value.as_enum<enums::EntityCategory>();
return true;
}
default:
return false;
}
@@ -2240,10 +2332,11 @@ void ListEntitiesTextSensorResponse::encode(ProtoWriteBuffer buffer) const {
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);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesTextSensorResponse::dump_to(std::string &out) const {
char buffer[64];
__attribute__((unused)) char buffer[64];
out.append("ListEntitiesTextSensorResponse {\n");
out.append(" object_id: ");
out.append("'").append(this->object_id).append("'");
@@ -2269,6 +2362,10 @@ void ListEntitiesTextSensorResponse::dump_to(std::string &out) const {
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("}");
}
#endif
@@ -2309,7 +2406,7 @@ void TextSensorStateResponse::encode(ProtoWriteBuffer buffer) const {
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void TextSensorStateResponse::dump_to(std::string &out) const {
char buffer[64];
__attribute__((unused)) char buffer[64];
out.append("TextSensorStateResponse {\n");
out.append(" key: ");
sprintf(buffer, "%u", this->key);
@@ -2346,7 +2443,7 @@ void SubscribeLogsRequest::encode(ProtoWriteBuffer buffer) const {
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void SubscribeLogsRequest::dump_to(std::string &out) const {
char buffer[64];
__attribute__((unused)) char buffer[64];
out.append("SubscribeLogsRequest {\n");
out.append(" level: ");
out.append(proto_enum_to_string<enums::LogLevel>(this->level));
@@ -2389,7 +2486,7 @@ void SubscribeLogsResponse::encode(ProtoWriteBuffer buffer) const {
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void SubscribeLogsResponse::dump_to(std::string &out) const {
char buffer[64];
__attribute__((unused)) char buffer[64];
out.append("SubscribeLogsResponse {\n");
out.append(" level: ");
out.append(proto_enum_to_string<enums::LogLevel>(this->level));
@@ -2431,7 +2528,7 @@ void HomeassistantServiceMap::encode(ProtoWriteBuffer buffer) const {
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void HomeassistantServiceMap::dump_to(std::string &out) const {
char buffer[64];
__attribute__((unused)) char buffer[64];
out.append("HomeassistantServiceMap {\n");
out.append(" key: ");
out.append("'").append(this->key).append("'");
@@ -2490,7 +2587,7 @@ void HomeassistantServiceResponse::encode(ProtoWriteBuffer buffer) const {
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void HomeassistantServiceResponse::dump_to(std::string &out) const {
char buffer[64];
__attribute__((unused)) char buffer[64];
out.append("HomeassistantServiceResponse {\n");
out.append(" service: ");
out.append("'").append(this->service).append("'");
@@ -2546,7 +2643,7 @@ void SubscribeHomeAssistantStateResponse::encode(ProtoWriteBuffer buffer) const
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void SubscribeHomeAssistantStateResponse::dump_to(std::string &out) const {
char buffer[64];
__attribute__((unused)) char buffer[64];
out.append("SubscribeHomeAssistantStateResponse {\n");
out.append(" entity_id: ");
out.append("'").append(this->entity_id).append("'");
@@ -2583,7 +2680,7 @@ void HomeAssistantStateResponse::encode(ProtoWriteBuffer buffer) const {
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void HomeAssistantStateResponse::dump_to(std::string &out) const {
char buffer[64];
__attribute__((unused)) char buffer[64];
out.append("HomeAssistantStateResponse {\n");
out.append(" entity_id: ");
out.append("'").append(this->entity_id).append("'");
@@ -2616,7 +2713,7 @@ bool GetTimeResponse::decode_32bit(uint32_t field_id, Proto32Bit value) {
void GetTimeResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_fixed32(1, this->epoch_seconds); }
#ifdef HAS_PROTO_MESSAGE_DUMP
void GetTimeResponse::dump_to(std::string &out) const {
char buffer[64];
__attribute__((unused)) char buffer[64];
out.append("GetTimeResponse {\n");
out.append(" epoch_seconds: ");
sprintf(buffer, "%u", this->epoch_seconds);
@@ -2651,7 +2748,7 @@ void ListEntitiesServicesArgument::encode(ProtoWriteBuffer buffer) const {
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesServicesArgument::dump_to(std::string &out) const {
char buffer[64];
__attribute__((unused)) char buffer[64];
out.append("ListEntitiesServicesArgument {\n");
out.append(" name: ");
out.append("'").append(this->name).append("'");
@@ -2696,7 +2793,7 @@ void ListEntitiesServicesResponse::encode(ProtoWriteBuffer buffer) const {
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesServicesResponse::dump_to(std::string &out) const {
char buffer[64];
__attribute__((unused)) char buffer[64];
out.append("ListEntitiesServicesResponse {\n");
out.append(" name: ");
out.append("'").append(this->name).append("'");
@@ -2790,7 +2887,7 @@ void ExecuteServiceArgument::encode(ProtoWriteBuffer buffer) const {
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void ExecuteServiceArgument::dump_to(std::string &out) const {
char buffer[64];
__attribute__((unused)) char buffer[64];
out.append("ExecuteServiceArgument {\n");
out.append(" bool_: ");
out.append(YESNO(this->bool_));
@@ -2871,7 +2968,7 @@ void ExecuteServiceRequest::encode(ProtoWriteBuffer buffer) const {
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void ExecuteServiceRequest::dump_to(std::string &out) const {
char buffer[64];
__attribute__((unused)) char buffer[64];
out.append("ExecuteServiceRequest {\n");
out.append(" key: ");
sprintf(buffer, "%u", this->key);
@@ -2892,6 +2989,10 @@ bool ListEntitiesCameraResponse::decode_varint(uint32_t field_id, ProtoVarInt va
this->disabled_by_default = value.as_bool();
return true;
}
case 7: {
this->entity_category = value.as_enum<enums::EntityCategory>();
return true;
}
default:
return false;
}
@@ -2910,6 +3011,10 @@ bool ListEntitiesCameraResponse::decode_length(uint32_t field_id, ProtoLengthDel
this->unique_id = value.as_string();
return true;
}
case 6: {
this->icon = value.as_string();
return true;
}
default:
return false;
}
@@ -2930,10 +3035,12 @@ void ListEntitiesCameraResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(3, this->name);
buffer.encode_string(4, this->unique_id);
buffer.encode_bool(5, this->disabled_by_default);
buffer.encode_string(6, this->icon);
buffer.encode_enum<enums::EntityCategory>(7, this->entity_category);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesCameraResponse::dump_to(std::string &out) const {
char buffer[64];
__attribute__((unused)) char buffer[64];
out.append("ListEntitiesCameraResponse {\n");
out.append(" object_id: ");
out.append("'").append(this->object_id).append("'");
@@ -2955,6 +3062,14 @@ void ListEntitiesCameraResponse::dump_to(std::string &out) const {
out.append(" disabled_by_default: ");
out.append(YESNO(this->disabled_by_default));
out.append("\n");
out.append(" icon: ");
out.append("'").append(this->icon).append("'");
out.append("\n");
out.append(" entity_category: ");
out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
out.append("\n");
out.append("}");
}
#endif
@@ -2995,7 +3110,7 @@ void CameraImageResponse::encode(ProtoWriteBuffer buffer) const {
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void CameraImageResponse::dump_to(std::string &out) const {
char buffer[64];
__attribute__((unused)) char buffer[64];
out.append("CameraImageResponse {\n");
out.append(" key: ");
sprintf(buffer, "%u", this->key);
@@ -3032,7 +3147,7 @@ void CameraImageRequest::encode(ProtoWriteBuffer buffer) const {
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void CameraImageRequest::dump_to(std::string &out) const {
char buffer[64];
__attribute__((unused)) char buffer[64];
out.append("CameraImageRequest {\n");
out.append(" single: ");
out.append(YESNO(this->single));
@@ -3082,6 +3197,10 @@ bool ListEntitiesClimateResponse::decode_varint(uint32_t field_id, ProtoVarInt v
this->disabled_by_default = value.as_bool();
return true;
}
case 20: {
this->entity_category = value.as_enum<enums::EntityCategory>();
return true;
}
default:
return false;
}
@@ -3170,10 +3289,11 @@ void ListEntitiesClimateResponse::encode(ProtoWriteBuffer buffer) const {
}
buffer.encode_bool(18, this->disabled_by_default);
buffer.encode_string(19, this->icon);
buffer.encode_enum<enums::EntityCategory>(20, this->entity_category);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesClimateResponse::dump_to(std::string &out) const {
char buffer[64];
__attribute__((unused)) char buffer[64];
out.append("ListEntitiesClimateResponse {\n");
out.append(" object_id: ");
out.append("'").append(this->object_id).append("'");
@@ -3266,6 +3386,10 @@ void ListEntitiesClimateResponse::dump_to(std::string &out) const {
out.append(" icon: ");
out.append("'").append(this->icon).append("'");
out.append("\n");
out.append(" entity_category: ");
out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
out.append("\n");
out.append("}");
}
#endif
@@ -3356,7 +3480,7 @@ void ClimateStateResponse::encode(ProtoWriteBuffer buffer) const {
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void ClimateStateResponse::dump_to(std::string &out) const {
char buffer[64];
__attribute__((unused)) char buffer[64];
out.append("ClimateStateResponse {\n");
out.append(" key: ");
sprintf(buffer, "%u", this->key);
@@ -3544,7 +3668,7 @@ void ClimateCommandRequest::encode(ProtoWriteBuffer buffer) const {
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void ClimateCommandRequest::dump_to(std::string &out) const {
char buffer[64];
__attribute__((unused)) char buffer[64];
out.append("ClimateCommandRequest {\n");
out.append(" key: ");
sprintf(buffer, "%u", this->key);
@@ -3642,6 +3766,14 @@ bool ListEntitiesNumberResponse::decode_varint(uint32_t field_id, ProtoVarInt va
this->disabled_by_default = value.as_bool();
return true;
}
case 10: {
this->entity_category = value.as_enum<enums::EntityCategory>();
return true;
}
case 12: {
this->mode = value.as_enum<enums::NumberMode>();
return true;
}
default:
return false;
}
@@ -3664,6 +3796,10 @@ bool ListEntitiesNumberResponse::decode_length(uint32_t field_id, ProtoLengthDel
this->icon = value.as_string();
return true;
}
case 11: {
this->unit_of_measurement = value.as_string();
return true;
}
default:
return false;
}
@@ -3700,10 +3836,13 @@ void ListEntitiesNumberResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_float(7, this->max_value);
buffer.encode_float(8, this->step);
buffer.encode_bool(9, this->disabled_by_default);
buffer.encode_enum<enums::EntityCategory>(10, this->entity_category);
buffer.encode_string(11, this->unit_of_measurement);
buffer.encode_enum<enums::NumberMode>(12, this->mode);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesNumberResponse::dump_to(std::string &out) const {
char buffer[64];
__attribute__((unused)) char buffer[64];
out.append("ListEntitiesNumberResponse {\n");
out.append(" object_id: ");
out.append("'").append(this->object_id).append("'");
@@ -3744,6 +3883,18 @@ void ListEntitiesNumberResponse::dump_to(std::string &out) const {
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(" unit_of_measurement: ");
out.append("'").append(this->unit_of_measurement).append("'");
out.append("\n");
out.append(" mode: ");
out.append(proto_enum_to_string<enums::NumberMode>(this->mode));
out.append("\n");
out.append("}");
}
#endif
@@ -3778,7 +3929,7 @@ void NumberStateResponse::encode(ProtoWriteBuffer buffer) const {
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void NumberStateResponse::dump_to(std::string &out) const {
char buffer[64];
__attribute__((unused)) char buffer[64];
out.append("NumberStateResponse {\n");
out.append(" key: ");
sprintf(buffer, "%u", this->key);
@@ -3816,7 +3967,7 @@ void NumberCommandRequest::encode(ProtoWriteBuffer buffer) const {
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void NumberCommandRequest::dump_to(std::string &out) const {
char buffer[64];
__attribute__((unused)) char buffer[64];
out.append("NumberCommandRequest {\n");
out.append(" key: ");
sprintf(buffer, "%u", this->key);
@@ -3836,6 +3987,10 @@ bool ListEntitiesSelectResponse::decode_varint(uint32_t field_id, ProtoVarInt va
this->disabled_by_default = value.as_bool();
return true;
}
case 8: {
this->entity_category = value.as_enum<enums::EntityCategory>();
return true;
}
default:
return false;
}
@@ -3886,10 +4041,11 @@ void ListEntitiesSelectResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(6, it, true);
}
buffer.encode_bool(7, this->disabled_by_default);
buffer.encode_enum<enums::EntityCategory>(8, this->entity_category);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesSelectResponse::dump_to(std::string &out) const {
char buffer[64];
__attribute__((unused)) char buffer[64];
out.append("ListEntitiesSelectResponse {\n");
out.append(" object_id: ");
out.append("'").append(this->object_id).append("'");
@@ -3921,6 +4077,10 @@ void ListEntitiesSelectResponse::dump_to(std::string &out) const {
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("}");
}
#endif
@@ -3961,7 +4121,7 @@ void SelectStateResponse::encode(ProtoWriteBuffer buffer) const {
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void SelectStateResponse::dump_to(std::string &out) const {
char buffer[64];
__attribute__((unused)) char buffer[64];
out.append("SelectStateResponse {\n");
out.append(" key: ");
sprintf(buffer, "%u", this->key);
@@ -4004,7 +4164,7 @@ void SelectCommandRequest::encode(ProtoWriteBuffer buffer) const {
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void SelectCommandRequest::dump_to(std::string &out) const {
char buffer[64];
__attribute__((unused)) char buffer[64];
out.append("SelectCommandRequest {\n");
out.append(" key: ");
sprintf(buffer, "%u", this->key);
@@ -4017,6 +4177,127 @@ void SelectCommandRequest::dump_to(std::string &out) const {
out.append("}");
}
#endif
bool ListEntitiesButtonResponse::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;
}
default:
return false;
}
}
bool ListEntitiesButtonResponse::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 8: {
this->device_class = value.as_string();
return true;
}
default:
return false;
}
}
bool ListEntitiesButtonResponse::decode_32bit(uint32_t field_id, Proto32Bit value) {
switch (field_id) {
case 2: {
this->key = value.as_fixed32();
return true;
}
default:
return false;
}
}
void ListEntitiesButtonResponse::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_string(8, this->device_class);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesButtonResponse::dump_to(std::string &out) const {
char buffer[64];
out.append("ListEntitiesButtonResponse {\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(" device_class: ");
out.append("'").append(this->device_class).append("'");
out.append("\n");
out.append("}");
}
#endif
bool ButtonCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
switch (field_id) {
case 1: {
this->key = value.as_fixed32();
return true;
}
default:
return false;
}
}
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];
out.append("ButtonCommandRequest {\n");
out.append(" key: ");
sprintf(buffer, "%u", this->key);
out.append(buffer);
out.append("\n");
out.append("}");
}
#endif
} // namespace api
} // namespace esphome

View File

@@ -9,6 +9,11 @@ namespace api {
namespace enums {
enum EntityCategory : uint32_t {
ENTITY_CATEGORY_NONE = 0,
ENTITY_CATEGORY_CONFIG = 1,
ENTITY_CATEGORY_DIAGNOSTIC = 2,
};
enum LegacyCoverState : uint32_t {
LEGACY_COVER_STATE_OPEN = 0,
LEGACY_COVER_STATE_CLOSED = 1,
@@ -118,6 +123,11 @@ enum ClimatePreset : uint32_t {
CLIMATE_PRESET_SLEEP = 6,
CLIMATE_PRESET_ACTIVITY = 7,
};
enum NumberMode : uint32_t {
NUMBER_MODE_AUTO = 0,
NUMBER_MODE_BOX = 1,
NUMBER_MODE_SLIDER = 2,
};
} // namespace enums
@@ -224,6 +234,7 @@ class DeviceInfoResponse : public ProtoMessage {
bool has_deep_sleep{false};
std::string project_name{};
std::string project_version{};
uint32_t webserver_port{0};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
@@ -270,6 +281,7 @@ class ListEntitiesBinarySensorResponse : public ProtoMessage {
bool is_status_binary_sensor{false};
bool disabled_by_default{false};
std::string icon{};
enums::EntityCategory entity_category{};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
@@ -306,6 +318,7 @@ class ListEntitiesCoverResponse : public ProtoMessage {
std::string device_class{};
bool disabled_by_default{false};
std::string icon{};
enums::EntityCategory entity_category{};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
@@ -363,6 +376,7 @@ class ListEntitiesFanResponse : public ProtoMessage {
int32_t supported_speed_count{0};
bool disabled_by_default{false};
std::string icon{};
enums::EntityCategory entity_category{};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
@@ -428,6 +442,7 @@ class ListEntitiesLightResponse : public ProtoMessage {
std::vector<std::string> effects{};
bool disabled_by_default{false};
std::string icon{};
enums::EntityCategory entity_category{};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
@@ -516,6 +531,7 @@ class ListEntitiesSensorResponse : public ProtoMessage {
enums::SensorStateClass state_class{};
enums::SensorLastResetType legacy_last_reset_type{};
bool disabled_by_default{false};
enums::EntityCategory entity_category{};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
@@ -549,6 +565,7 @@ class ListEntitiesSwitchResponse : public ProtoMessage {
std::string icon{};
bool assumed_state{false};
bool disabled_by_default{false};
enums::EntityCategory entity_category{};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
@@ -593,6 +610,7 @@ class ListEntitiesTextSensorResponse : public ProtoMessage {
std::string unique_id{};
std::string icon{};
bool disabled_by_default{false};
enums::EntityCategory entity_category{};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
@@ -803,6 +821,8 @@ class ListEntitiesCameraResponse : public ProtoMessage {
std::string name{};
std::string unique_id{};
bool disabled_by_default{false};
std::string icon{};
enums::EntityCategory entity_category{};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
@@ -861,6 +881,7 @@ class ListEntitiesClimateResponse : public ProtoMessage {
std::vector<std::string> supported_custom_presets{};
bool disabled_by_default{false};
std::string icon{};
enums::EntityCategory entity_category{};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
@@ -940,6 +961,9 @@ class ListEntitiesNumberResponse : public ProtoMessage {
float max_value{0.0f};
float step{0.0f};
bool disabled_by_default{false};
enums::EntityCategory entity_category{};
std::string unit_of_measurement{};
enums::NumberMode mode{};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
@@ -985,6 +1009,7 @@ class ListEntitiesSelectResponse : public ProtoMessage {
std::string icon{};
std::vector<std::string> options{};
bool disabled_by_default{false};
enums::EntityCategory entity_category{};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
@@ -1023,6 +1048,37 @@ 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 ListEntitiesButtonResponse : 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{};
std::string device_class{};
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 ButtonCommandRequest : public ProtoMessage {
public:
uint32_t key{0};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
};
} // namespace api
} // namespace esphome

View File

@@ -282,6 +282,16 @@ bool APIServerConnectionBase::send_select_state_response(const SelectStateRespon
#endif
#ifdef USE_SELECT
#endif
#ifdef USE_BUTTON
bool APIServerConnectionBase::send_list_entities_button_response(const ListEntitiesButtonResponse &msg) {
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "send_list_entities_button_response: %s", msg.dump().c_str());
#endif
return this->send_message_<ListEntitiesButtonResponse>(msg, 61);
}
#endif
#ifdef USE_BUTTON
#endif
bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) {
switch (msg_type) {
case 1: {
@@ -513,6 +523,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 62: {
#ifdef USE_BUTTON
ButtonCommandRequest msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_button_command_request: %s", msg.dump().c_str());
#endif
this->on_button_command_request(msg);
#endif
break;
}
@@ -737,6 +758,19 @@ void APIServerConnection::on_select_command_request(const SelectCommandRequest &
this->select_command(msg);
}
#endif
#ifdef USE_BUTTON
void APIServerConnection::on_button_command_request(const ButtonCommandRequest &msg) {
if (!this->is_connection_setup()) {
this->on_no_setup_connection();
return;
}
if (!this->is_authenticated()) {
this->on_unauthenticated_access();
return;
}
this->button_command(msg);
}
#endif
} // namespace api
} // namespace esphome

View File

@@ -129,6 +129,12 @@ class APIServerConnectionBase : public ProtoService {
#endif
#ifdef USE_SELECT
virtual void on_select_command_request(const SelectCommandRequest &value){};
#endif
#ifdef USE_BUTTON
bool send_list_entities_button_response(const ListEntitiesButtonResponse &msg);
#endif
#ifdef USE_BUTTON
virtual void on_button_command_request(const ButtonCommandRequest &value){};
#endif
protected:
bool read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override;
@@ -171,6 +177,9 @@ class APIServerConnection : public APIServerConnectionBase {
#endif
#ifdef USE_SELECT
virtual void select_command(const SelectCommandRequest &msg) = 0;
#endif
#ifdef USE_BUTTON
virtual void button_command(const ButtonCommandRequest &msg) = 0;
#endif
protected:
void on_hello_request(const HelloRequest &msg) override;
@@ -209,6 +218,9 @@ class APIServerConnection : public APIServerConnectionBase {
#ifdef USE_SELECT
void on_select_command_request(const SelectCommandRequest &msg) override;
#endif
#ifdef USE_BUTTON
void on_button_command_request(const ButtonCommandRequest &msg) override;
#endif
};
} // namespace api

View File

@@ -77,7 +77,7 @@ void APIServer::setup() {
this->last_connected_ = millis();
#ifdef USE_ESP32_CAMERA
if (esp32_camera::global_esp32_camera != nullptr) {
if (esp32_camera::global_esp32_camera != nullptr && !esp32_camera::global_esp32_camera->is_internal()) {
esp32_camera::global_esp32_camera->add_image_callback(
[this](const std::shared_ptr<esp32_camera::CameraImage> &image) {
for (auto &c : this->clients_)

View File

@@ -23,7 +23,6 @@ async def async_run_logs(config, address):
_LOGGER.info("Starting log output from %s using esphome API", address)
zc = zeroconf.Zeroconf()
cli = APIClient(
asyncio.get_event_loop(),
address,
port,
password,

View File

@@ -8,6 +8,18 @@
namespace esphome {
namespace api {
template<typename... X> class TemplatableStringValue : public TemplatableValue<std::string, X...> {
public:
TemplatableStringValue() : TemplatableValue<std::string, X...>() {}
template<typename F, enable_if_t<!is_callable<F, X...>::value, int> = 0>
TemplatableStringValue(F value) : TemplatableValue<std::string, X...>(value) {}
template<typename F, enable_if_t<is_callable<F, X...>::value, int> = 0>
TemplatableStringValue(F f)
: TemplatableValue<std::string, X...>([f](X... x) -> std::string { return to_string(f(x...)); }) {}
};
template<typename... Ts> class TemplatableKeyValuePair {
public:
template<typename T> TemplatableKeyValuePair(std::string key, T value) : key(std::move(key)), value(value) {}
@@ -19,7 +31,8 @@ template<typename... Ts> class HomeAssistantServiceCallAction : public Action<Ts
public:
explicit HomeAssistantServiceCallAction(APIServer *parent, bool is_event) : parent_(parent), is_event_(is_event) {}
TEMPLATABLE_STRING_VALUE(service);
template<typename T> void set_service(T service) { this->service_ = service; }
template<typename T> void add_data(std::string key, T value) {
this->data_.push_back(TemplatableKeyValuePair<Ts...>(key, value));
}
@@ -58,6 +71,7 @@ template<typename... Ts> class HomeAssistantServiceCallAction : public Action<Ts
protected:
APIServer *parent_;
bool is_event_;
TemplatableStringValue<Ts...> service_{};
std::vector<TemplatableKeyValuePair<Ts...>> data_;
std::vector<TemplatableKeyValuePair<Ts...>> data_template_;
std::vector<TemplatableKeyValuePair<Ts...>> variables_;

View File

@@ -27,6 +27,9 @@ bool ListEntitiesIterator::on_sensor(sensor::Sensor *sensor) { return this->clie
#ifdef USE_SWITCH
bool ListEntitiesIterator::on_switch(switch_::Switch *a_switch) { return this->client_->send_switch_info(a_switch); }
#endif
#ifdef USE_BUTTON
bool ListEntitiesIterator::on_button(button::Button *button) { return this->client_->send_button_info(button); }
#endif
#ifdef USE_TEXT_SENSOR
bool ListEntitiesIterator::on_text_sensor(text_sensor::TextSensor *text_sensor) {
return this->client_->send_text_sensor_info(text_sensor);

View File

@@ -30,6 +30,9 @@ class ListEntitiesIterator : public ComponentIterator {
#ifdef USE_SWITCH
bool on_switch(switch_::Switch *a_switch) override;
#endif
#ifdef USE_BUTTON
bool on_button(button::Button *button) override;
#endif
#ifdef USE_TEXT_SENSOR
bool on_text_sensor(text_sensor::TextSensor *text_sensor) override;
#endif

View File

@@ -31,6 +31,9 @@ class InitialStateIterator : public ComponentIterator {
#ifdef USE_SWITCH
bool on_switch(switch_::Switch *a_switch) override;
#endif
#ifdef USE_BUTTON
bool on_button(button::Button *button) override { return true; };
#endif
#ifdef USE_TEXT_SENSOR
bool on_text_sensor(text_sensor::TextSensor *text_sensor) override;
#endif

View File

@@ -116,6 +116,21 @@ void ComponentIterator::advance() {
}
break;
#endif
#ifdef USE_BUTTON
case IteratorState::BUTTON:
if (this->at_ >= App.get_buttons().size()) {
advance_platform = true;
} else {
auto *button = App.get_buttons()[this->at_];
if (button->is_internal()) {
success = true;
break;
} else {
success = this->on_button(button);
}
}
break;
#endif
#ifdef USE_TEXT_SENSOR
case IteratorState::TEXT_SENSOR:
if (this->at_ >= App.get_text_sensors().size()) {

View File

@@ -38,6 +38,9 @@ class ComponentIterator {
#ifdef USE_SWITCH
virtual bool on_switch(switch_::Switch *a_switch) = 0;
#endif
#ifdef USE_BUTTON
virtual bool on_button(button::Button *button) = 0;
#endif
#ifdef USE_TEXT_SENSOR
virtual bool on_text_sensor(text_sensor::TextSensor *text_sensor) = 0;
#endif
@@ -78,6 +81,9 @@ class ComponentIterator {
#ifdef USE_SWITCH
SWITCH,
#endif
#ifdef USE_BUTTON
BUTTON,
#endif
#ifdef USE_TEXT_SENSOR
TEXT_SENSOR,
#endif

View File

@@ -12,6 +12,7 @@ from esphome.const import (
DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_TEMPERATURE,
DEVICE_CLASS_VOLTAGE,
ENTITY_CATEGORY_DIAGNOSTIC,
STATE_CLASS_MEASUREMENT,
UNIT_CELSIUS,
UNIT_PERCENT,
@@ -49,12 +50,14 @@ CONFIG_SCHEMA = (
accuracy_decimals=0,
device_class=DEVICE_CLASS_BATTERY,
state_class=STATE_CLASS_MEASUREMENT,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
cv.Optional(CONF_BATTERY_VOLTAGE): sensor.sensor_schema(
unit_of_measurement=UNIT_VOLT,
accuracy_decimals=3,
device_class=DEVICE_CLASS_VOLTAGE,
state_class=STATE_CLASS_MEASUREMENT,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
}
)

View File

@@ -17,6 +17,7 @@ from esphome.const import (
DEVICE_CLASS_POWER_FACTOR,
DEVICE_CLASS_TEMPERATURE,
DEVICE_CLASS_VOLTAGE,
ENTITY_CATEGORY_DIAGNOSTIC,
ICON_LIGHTBULB,
ICON_CURRENT_AC,
STATE_CLASS_MEASUREMENT,
@@ -125,6 +126,7 @@ CONFIG_SCHEMA = (
accuracy_decimals=1,
device_class=DEVICE_CLASS_TEMPERATURE,
state_class=STATE_CLASS_MEASUREMENT,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
cv.Required(CONF_LINE_FREQUENCY): cv.enum(LINE_FREQS, upper=True),
cv.Optional(CONF_CURRENT_PHASES, default="3"): cv.enum(

View File

@@ -13,6 +13,7 @@ from esphome.const import (
DEVICE_CLASS_ILLUMINANCE,
DEVICE_CLASS_TEMPERATURE,
DEVICE_CLASS_VOLTAGE,
ENTITY_CATEGORY_DIAGNOSTIC,
STATE_CLASS_MEASUREMENT,
UNIT_CELSIUS,
UNIT_LUX,
@@ -51,6 +52,7 @@ CONFIG_SCHEMA = (
accuracy_decimals=3,
device_class=DEVICE_CLASS_VOLTAGE,
state_class=STATE_CLASS_MEASUREMENT,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
cv.Optional(CONF_MOISTURE): sensor.sensor_schema(
unit_of_measurement=UNIT_PERCENT,

View File

@@ -79,6 +79,9 @@ void BH1750Sensor::read_data_() {
float lx = float(raw_value) / 1.2f;
lx *= 69.0f / this->measurement_duration_;
if (this->resolution_ == BH1750_RESOLUTION_0P5_LX) {
lx /= 2.0f;
}
ESP_LOGD(TAG, "'%s': Got illuminance=%.1flx", this->get_name().c_str(), lx);
this->publish_state(lx);
this->status_clear_warning();

View File

@@ -44,9 +44,11 @@ from esphome.const import (
DEVICE_CLASS_POWER,
DEVICE_CLASS_PRESENCE,
DEVICE_CLASS_PROBLEM,
DEVICE_CLASS_RUNNING,
DEVICE_CLASS_SAFETY,
DEVICE_CLASS_SMOKE,
DEVICE_CLASS_SOUND,
DEVICE_CLASS_TAMPER,
DEVICE_CLASS_UPDATE,
DEVICE_CLASS_VIBRATION,
DEVICE_CLASS_WINDOW,
@@ -76,9 +78,11 @@ DEVICE_CLASSES = [
DEVICE_CLASS_POWER,
DEVICE_CLASS_PRESENCE,
DEVICE_CLASS_PROBLEM,
DEVICE_CLASS_RUNNING,
DEVICE_CLASS_SAFETY,
DEVICE_CLASS_SMOKE,
DEVICE_CLASS_SOUND,
DEVICE_CLASS_TAMPER,
DEVICE_CLASS_UPDATE,
DEVICE_CLASS_VIBRATION,
DEVICE_CLASS_WINDOW,
@@ -313,6 +317,7 @@ def validate_multi_click_timing(value):
device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space="_")
BINARY_SENSOR_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend(
{
cv.GenerateID(): cv.declare_id(BinarySensor),

View File

@@ -11,6 +11,8 @@ namespace ble_client {
static const char *const TAG = "ble_client";
float BLEClient::get_setup_priority() const { return setup_priority::AFTER_BLUETOOTH; }
void BLEClient::setup() {
auto ret = esp_ble_gattc_app_register(this->app_id);
if (ret) {
@@ -386,6 +388,15 @@ 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) {
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);
if (status) {
ESP_LOGW(TAG, "Error sending write value to BLE gattc server, status=%d", status);
}
}
} // namespace ble_client
} // namespace esphome

View File

@@ -59,7 +59,7 @@ class BLECharacteristic {
void parse_descriptors();
BLEDescriptor *get_descriptor(espbt::ESPBTUUID uuid);
BLEDescriptor *get_descriptor(uint16_t uuid);
void write_value(uint8_t *new_val, int16_t new_val_size);
BLEService *service;
};
@@ -81,6 +81,7 @@ class BLEClient : public espbt::ESPBTClient, public Component {
void setup() override;
void dump_config() override;
void loop() override;
float get_setup_priority() const 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;

View File

@@ -0,0 +1,67 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import output, ble_client, esp32_ble_tracker
from esphome.const import CONF_ID, CONF_SERVICE_UUID
from .. import ble_client_ns
DEPENDENCIES = ["ble_client"]
CONF_CHARACTERISTIC_UUID = "characteristic_uuid"
BLEBinaryOutput = ble_client_ns.class_(
"BLEBinaryOutput", output.BinaryOutput, ble_client.BLEClientNode, cg.Component
)
CONFIG_SCHEMA = cv.All(
output.BINARY_OUTPUT_SCHEMA.extend(
{
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,
}
)
.extend(cv.COMPONENT_SCHEMA)
.extend(ble_client.BLE_CLIENT_SCHEMA)
)
def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
if len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid16_format):
cg.add(
var.set_service_uuid16(esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID]))
)
elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid32_format):
cg.add(
var.set_service_uuid32(esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID]))
)
elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid128_format):
uuid128 = esp32_ble_tracker.as_reversed_hex_array(config[CONF_SERVICE_UUID])
cg.add(var.set_service_uuid128(uuid128))
if len(config[CONF_CHARACTERISTIC_UUID]) == len(esp32_ble_tracker.bt_uuid16_format):
cg.add(
var.set_char_uuid16(
esp32_ble_tracker.as_hex(config[CONF_CHARACTERISTIC_UUID])
)
)
elif len(config[CONF_CHARACTERISTIC_UUID]) == len(
esp32_ble_tracker.bt_uuid32_format
):
cg.add(
var.set_char_uuid32(
esp32_ble_tracker.as_hex(config[CONF_CHARACTERISTIC_UUID])
)
)
elif len(config[CONF_CHARACTERISTIC_UUID]) == len(
esp32_ble_tracker.bt_uuid128_format
):
uuid128 = esp32_ble_tracker.as_reversed_hex_array(
config[CONF_CHARACTERISTIC_UUID]
)
cg.add(var.set_char_uuid128(uuid128))
yield output.register_output(var, config)
yield ble_client.register_ble_node(var, config)
yield cg.register_component(var, config)

View File

@@ -0,0 +1,71 @@
#include "ble_binary_output.h"
#include "esphome/core/log.h"
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
#ifdef USE_ESP32
namespace esphome {
namespace ble_client {
static const char *const TAG = "ble_binary_output";
void BLEBinaryOutput::dump_config() {
ESP_LOGCONFIG(TAG, "BLE Binary Output:");
ESP_LOGCONFIG(TAG, " MAC address : %s", this->parent_->address_str().c_str());
ESP_LOGCONFIG(TAG, " Service UUID : %s", this->service_uuid_.to_string().c_str());
ESP_LOGCONFIG(TAG, " Characteristic UUID: %s", this->char_uuid_.to_string().c_str());
LOG_BINARY_OUTPUT(this);
}
void BLEBinaryOutput::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
esp_ble_gattc_cb_param_t *param) {
switch (event) {
case ESP_GATTC_OPEN_EVT:
this->client_state_ = espbt::ClientState::ESTABLISHED;
ESP_LOGW(TAG, "[%s] Connected successfully!", this->char_uuid_.to_string().c_str());
break;
case ESP_GATTC_DISCONNECT_EVT:
ESP_LOGW(TAG, "[%s] Disconnected", this->char_uuid_.to_string().c_str());
this->client_state_ = espbt::ClientState::IDLE;
break;
case ESP_GATTC_WRITE_CHAR_EVT: {
if (param->write.status == 0) {
break;
}
auto chr = this->parent()->get_characteristic(this->service_uuid_, this->char_uuid_);
if (chr == nullptr) {
ESP_LOGW(TAG, "[%s] Characteristic not found.", this->char_uuid_.to_string().c_str());
break;
}
if (param->write.handle == chr->handle) {
ESP_LOGW(TAG, "[%s] Write error, status=%d", this->char_uuid_.to_string().c_str(), param->write.status);
}
break;
}
default:
break;
}
}
void BLEBinaryOutput::write_state(bool state) {
if (this->client_state_ != espbt::ClientState::ESTABLISHED) {
ESP_LOGW(TAG, "[%s] Not connected to BLE client. State update can not be written.",
this->char_uuid_.to_string().c_str());
return;
}
auto chr = this->parent()->get_characteristic(this->service_uuid_, this->char_uuid_);
if (chr == nullptr) {
ESP_LOGW(TAG, "[%s] Characteristic not found. State update can not be written.",
this->char_uuid_.to_string().c_str());
return;
}
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));
}
} // namespace ble_client
} // namespace esphome
#endif

View File

@@ -0,0 +1,39 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/ble_client/ble_client.h"
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
#include "esphome/components/output/binary_output.h"
#ifdef USE_ESP32
#include <esp_gattc_api.h>
namespace esphome {
namespace ble_client {
namespace espbt = esphome::esp32_ble_tracker;
class BLEBinaryOutput : public output::BinaryOutput, public BLEClientNode, public Component {
public:
void dump_config() override;
void loop() override {}
float get_setup_priority() const override { return setup_priority::DATA; }
void set_service_uuid16(uint16_t uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_uint16(uuid); }
void set_service_uuid32(uint32_t uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_uint32(uuid); }
void set_service_uuid128(uint8_t *uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_raw(uuid); }
void set_char_uuid16(uint16_t uuid) { this->char_uuid_ = espbt::ESPBTUUID::from_uint16(uuid); }
void set_char_uuid32(uint32_t uuid) { this->char_uuid_ = espbt::ESPBTUUID::from_uint32(uuid); }
void set_char_uuid128(uint8_t *uuid) { this->char_uuid_ = espbt::ESPBTUUID::from_raw(uuid); }
void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
esp_ble_gattc_cb_param_t *param) override;
protected:
void write_state(bool state) override;
espbt::ESPBTUUID service_uuid_;
espbt::ESPBTUUID char_uuid_;
espbt::ClientState client_state_;
};
} // namespace ble_client
} // namespace esphome
#endif

View File

@@ -67,7 +67,7 @@ async def to_code(config):
var.set_service_uuid32(esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID]))
)
elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid128_format):
uuid128 = esp32_ble_tracker.as_hex_array(config[CONF_SERVICE_UUID])
uuid128 = esp32_ble_tracker.as_reversed_hex_array(config[CONF_SERVICE_UUID])
cg.add(var.set_service_uuid128(uuid128))
if len(config[CONF_CHARACTERISTIC_UUID]) == len(esp32_ble_tracker.bt_uuid16_format):
@@ -87,7 +87,9 @@ async def to_code(config):
elif len(config[CONF_CHARACTERISTIC_UUID]) == len(
esp32_ble_tracker.bt_uuid128_format
):
uuid128 = esp32_ble_tracker.as_hex_array(config[CONF_CHARACTERISTIC_UUID])
uuid128 = esp32_ble_tracker.as_reversed_hex_array(
config[CONF_CHARACTERISTIC_UUID]
)
cg.add(var.set_char_uuid128(uuid128))
if CONF_DESCRIPTOR_UUID in config:
@@ -108,7 +110,9 @@ async def to_code(config):
elif len(config[CONF_DESCRIPTOR_UUID]) == len(
esp32_ble_tracker.bt_uuid128_format
):
uuid128 = esp32_ble_tracker.as_hex_array(config[CONF_DESCRIPTOR_UUID])
uuid128 = esp32_ble_tracker.as_reversed_hex_array(
config[CONF_DESCRIPTOR_UUID]
)
cg.add(var.set_descr_uuid128(uuid128))
if CONF_LAMBDA in config:

View File

@@ -33,6 +33,7 @@ static const uint8_t BME280_REGISTER_CONTROLHUMID = 0xF2;
static const uint8_t BME280_REGISTER_STATUS = 0xF3;
static const uint8_t BME280_REGISTER_CONTROL = 0xF4;
static const uint8_t BME280_REGISTER_CONFIG = 0xF5;
static const uint8_t BME280_REGISTER_MEASUREMENTS = 0xF7;
static const uint8_t BME280_REGISTER_PRESSUREDATA = 0xF7;
static const uint8_t BME280_REGISTER_TEMPDATA = 0xFA;
static const uint8_t BME280_REGISTER_HUMIDDATA = 0xFD;
@@ -178,23 +179,29 @@ void BME280Component::update() {
return;
}
float meas_time = 1.5;
float meas_time = 1.5f;
meas_time += 2.3f * oversampling_to_time(this->temperature_oversampling_);
meas_time += 2.3f * oversampling_to_time(this->pressure_oversampling_) + 0.575f;
meas_time += 2.3f * oversampling_to_time(this->humidity_oversampling_) + 0.575f;
this->set_timeout("data", uint32_t(ceilf(meas_time)), [this]() {
uint8_t data[8];
if (!this->read_bytes(BME280_REGISTER_MEASUREMENTS, data, 8)) {
ESP_LOGW(TAG, "Error reading registers.");
this->status_set_warning();
return;
}
int32_t t_fine = 0;
float temperature = this->read_temperature_(&t_fine);
float temperature = this->read_temperature_(data, &t_fine);
if (std::isnan(temperature)) {
ESP_LOGW(TAG, "Invalid temperature, cannot read pressure & humidity values.");
this->status_set_warning();
return;
}
float pressure = this->read_pressure_(t_fine);
float humidity = this->read_humidity_(t_fine);
float pressure = this->read_pressure_(data, t_fine);
float humidity = this->read_humidity_(data, t_fine);
ESP_LOGD(TAG, "Got temperature=%.1f°C pressure=%.1fhPa humidity=%.1f%%", temperature, pressure, humidity);
ESP_LOGV(TAG, "Got temperature=%.1f°C pressure=%.1fhPa humidity=%.1f%%", temperature, pressure, humidity);
if (this->temperature_sensor_ != nullptr)
this->temperature_sensor_->publish_state(temperature);
if (this->pressure_sensor_ != nullptr)
@@ -204,11 +211,8 @@ void BME280Component::update() {
this->status_clear_warning();
});
}
float BME280Component::read_temperature_(int32_t *t_fine) {
uint8_t data[3];
if (!this->read_bytes(BME280_REGISTER_TEMPDATA, data, 3))
return NAN;
int32_t adc = ((data[0] & 0xFF) << 16) | ((data[1] & 0xFF) << 8) | (data[2] & 0xFF);
float BME280Component::read_temperature_(const uint8_t *data, int32_t *t_fine) {
int32_t adc = ((data[3] & 0xFF) << 16) | ((data[4] & 0xFF) << 8) | (data[5] & 0xFF);
adc >>= 4;
if (adc == 0x80000)
// temperature was disabled
@@ -226,10 +230,7 @@ float BME280Component::read_temperature_(int32_t *t_fine) {
return temperature / 100.0f;
}
float BME280Component::read_pressure_(int32_t t_fine) {
uint8_t data[3];
if (!this->read_bytes(BME280_REGISTER_PRESSUREDATA, data, 3))
return NAN;
float BME280Component::read_pressure_(const uint8_t *data, int32_t t_fine) {
int32_t adc = ((data[0] & 0xFF) << 16) | ((data[1] & 0xFF) << 8) | (data[2] & 0xFF);
adc >>= 4;
if (adc == 0x80000)
@@ -265,9 +266,9 @@ float BME280Component::read_pressure_(int32_t t_fine) {
return (p / 256.0f) / 100.0f;
}
float BME280Component::read_humidity_(int32_t t_fine) {
uint16_t raw_adc;
if (!this->read_byte_16(BME280_REGISTER_HUMIDDATA, &raw_adc) || raw_adc == 0x8000)
float BME280Component::read_humidity_(const uint8_t *data, int32_t t_fine) {
uint16_t raw_adc = ((data[6] & 0xFF) << 8) | (data[7] & 0xFF);
if (raw_adc == 0x8000)
return NAN;
int32_t adc = raw_adc;

View File

@@ -82,11 +82,11 @@ class BME280Component : public PollingComponent, public i2c::I2CDevice {
protected:
/// Read the temperature value and store the calculated ambient temperature in t_fine.
float read_temperature_(int32_t *t_fine);
float read_temperature_(const uint8_t *data, int32_t *t_fine);
/// Read the pressure value in hPa using the provided t_fine value.
float read_pressure_(int32_t t_fine);
float read_pressure_(const uint8_t *data, int32_t t_fine);
/// Read the humidity value in % using the provided t_fine value.
float read_humidity_(int32_t t_fine);
float read_humidity_(const uint8_t *data, int32_t t_fine);
uint8_t read_u8_(uint8_t a_register);
uint16_t read_u16_le_(uint8_t a_register);
int16_t read_s16_le_(uint8_t a_register);

View File

@@ -44,7 +44,8 @@ CONFIG_SCHEMA = cv.Schema(
cv.Optional(
CONF_STATE_SAVE_INTERVAL, default="6hours"
): cv.positive_time_period_minutes,
}
},
cv.only_with_arduino,
).extend(i2c.i2c_device_schema(0x76))
@@ -60,5 +61,8 @@ async def to_code(config):
var.set_state_save_interval(config[CONF_STATE_SAVE_INTERVAL].total_milliseconds)
)
# Although this component does not use SPI, the BSEC library requires the SPI library
cg.add_library("SPI", None)
cg.add_define("USE_BSEC")
cg.add_library("BSEC Software Library", "1.6.1480")
cg.add_library("boschsensortec/BSEC Software Library", "1.6.1480")

View File

@@ -0,0 +1,127 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import automation
from esphome.automation import maybe_simple_id
from esphome.components import mqtt
from esphome.const import (
CONF_DEVICE_CLASS,
CONF_ENTITY_CATEGORY,
CONF_ICON,
CONF_ID,
CONF_ON_PRESS,
CONF_TRIGGER_ID,
CONF_MQTT_ID,
DEVICE_CLASS_RESTART,
DEVICE_CLASS_UPDATE,
)
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_RESTART,
DEVICE_CLASS_UPDATE,
]
button_ns = cg.esphome_ns.namespace("button")
Button = button_ns.class_("Button", cg.EntityBase)
ButtonPtr = Button.operator("ptr")
PressAction = button_ns.class_("PressAction", automation.Action)
ButtonPressTrigger = button_ns.class_(
"ButtonPressTrigger", automation.Trigger.template()
)
validate_device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space="_")
BUTTON_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend(
{
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTButtonComponent),
cv.Optional(CONF_DEVICE_CLASS): validate_device_class,
cv.Optional(CONF_ON_PRESS): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ButtonPressTrigger),
}
),
}
)
_UNDEF = object()
def button_schema(
icon: str = _UNDEF,
entity_category: str = _UNDEF,
device_class: str = _UNDEF,
) -> cv.Schema:
schema = BUTTON_SCHEMA
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
}
)
if device_class is not _UNDEF:
schema = schema.extend(
{
cv.Optional(
CONF_DEVICE_CLASS, default=device_class
): validate_device_class
}
)
return schema
async def setup_button_core_(var, config):
await setup_entity(var, config)
for conf in config.get(CONF_ON_PRESS, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)
if CONF_DEVICE_CLASS in config:
cg.add(var.set_device_class(config[CONF_DEVICE_CLASS]))
if CONF_MQTT_ID in config:
mqtt_ = cg.new_Pvariable(config[CONF_MQTT_ID], var)
await mqtt.register_mqtt_component(mqtt_, config)
async def register_button(var, config):
if not CORE.has_id(config[CONF_ID]):
var = cg.Pvariable(config[CONF_ID], var)
cg.add(cg.App.register_button(var))
await setup_button_core_(var, config)
async def new_button(config):
var = cg.new_Pvariable(config[CONF_ID])
await register_button(var, config)
return var
BUTTON_PRESS_SCHEMA = maybe_simple_id(
{
cv.Required(CONF_ID): cv.use_id(Button),
}
)
@automation.register_action("button.press", PressAction, BUTTON_PRESS_SCHEMA)
async def button_press_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)
@coroutine_with_priority(100.0)
async def to_code(config):
cg.add_global(button_ns.using)
cg.add_define("USE_BUTTON")

View File

@@ -0,0 +1,28 @@
#pragma once
#include "esphome/components/button/button.h"
#include "esphome/core/automation.h"
#include "esphome/core/component.h"
namespace esphome {
namespace button {
template<typename... Ts> class PressAction : public Action<Ts...> {
public:
explicit PressAction(Button *button) : button_(button) {}
void play(Ts... x) override { this->button_->press(); }
protected:
Button *button_;
};
class ButtonPressTrigger : public Trigger<> {
public:
ButtonPressTrigger(Button *button) {
button->add_on_press_callback([this]() { this->trigger(); });
}
};
} // namespace button
} // namespace esphome

View File

@@ -0,0 +1,28 @@
#include "button.h"
#include "esphome/core/log.h"
namespace esphome {
namespace button {
static const char *const TAG = "button";
Button::Button(const std::string &name) : EntityBase(name) {}
Button::Button() : Button("") {}
void Button::press() {
ESP_LOGD(TAG, "'%s' Pressed.", this->get_name().c_str());
this->press_action();
this->press_callback_.call();
}
void Button::add_on_press_callback(std::function<void()> &&callback) { this->press_callback_.add(std::move(callback)); }
uint32_t Button::hash_base() { return 1495763804UL; }
void Button::set_device_class(const std::string &device_class) { this->device_class_ = device_class; }
std::string Button::get_device_class() {
if (this->device_class_.has_value())
return *this->device_class_;
return "";
}
} // namespace button
} // namespace esphome

View File

@@ -0,0 +1,57 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/entity_base.h"
#include "esphome/core/helpers.h"
namespace esphome {
namespace button {
#define LOG_BUTTON(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()); \
} \
}
/** Base class for all buttons.
*
* A button is just a momentary switch that does not have a state, only a trigger.
*/
class Button : public EntityBase {
public:
explicit Button();
explicit Button(const std::string &name);
/** Press this button. This is called by the front-end.
*
* For implementing buttons, please override press_action.
*/
void press();
/** Set callback for state changes.
*
* @param callback The void() callback.
*/
void add_on_press_callback(std::function<void()> &&callback);
/// Set the Home Assistant device class (see button::device_class).
void set_device_class(const std::string &device_class);
/// Get the device class for this button.
std::string get_device_class();
protected:
/** You should implement this virtual method if you want to create your own button.
*/
virtual void press_action(){};
uint32_t hash_base() override;
CallbackManager<void()> press_callback_{};
optional<std::string> device_class_{};
};
} // namespace button
} // namespace esphome

View File

@@ -0,0 +1,45 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import i2c
from esphome.const import CONF_ID, CONF_RESET_PIN
from esphome import pins
CONF_TOUCH_THRESHOLD = "touch_threshold"
CONF_ALLOW_MULTIPLE_TOUCHES = "allow_multiple_touches"
DEPENDENCIES = ["i2c"]
AUTO_LOAD = ["binary_sensor", "output"]
CODEOWNERS = ["@MrEditor97"]
cap1188_ns = cg.esphome_ns.namespace("cap1188")
CONF_CAP1188_ID = "cap1188_id"
CAP1188Component = cap1188_ns.class_("CAP1188Component", cg.Component, i2c.I2CDevice)
MULTI_CONF = True
CONFIG_SCHEMA = (
cv.Schema(
{
cv.GenerateID(): cv.declare_id(CAP1188Component),
cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema,
cv.Optional(CONF_TOUCH_THRESHOLD, default=0x20): cv.int_range(
min=0x01, max=0x80
),
cv.Optional(CONF_ALLOW_MULTIPLE_TOUCHES, default=False): cv.boolean,
}
)
.extend(cv.COMPONENT_SCHEMA)
.extend(i2c.i2c_device_schema(0x29))
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
cg.add(var.set_touch_threshold(config[CONF_TOUCH_THRESHOLD]))
cg.add(var.set_allow_multiple_touches(config[CONF_ALLOW_MULTIPLE_TOUCHES]))
if CONF_RESET_PIN in config:
pin = await cg.gpio_pin_expression(config[CONF_RESET_PIN])
cg.add(var.set_reset_pin(pin))
await cg.register_component(var, config)
await i2c.register_i2c_device(var, config)

View File

@@ -0,0 +1,25 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import binary_sensor
from esphome.const import CONF_CHANNEL, CONF_ID
from . import cap1188_ns, CAP1188Component, CONF_CAP1188_ID
DEPENDENCIES = ["cap1188"]
CAP1188Channel = cap1188_ns.class_("CAP1188Channel", binary_sensor.BinarySensor)
CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(CAP1188Channel),
cv.GenerateID(CONF_CAP1188_ID): cv.use_id(CAP1188Component),
cv.Required(CONF_CHANNEL): cv.int_range(min=0, max=7),
}
)
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_CAP1188_ID])
cg.add(var.set_channel(config[CONF_CHANNEL]))
cg.add(hub.register_channel(var))

View File

@@ -0,0 +1,88 @@
#include "cap1188.h"
#include "esphome/core/log.h"
#include "esphome/core/hal.h"
namespace esphome {
namespace cap1188 {
static const char *const TAG = "cap1188";
void CAP1188Component::setup() {
ESP_LOGCONFIG(TAG, "Setting up CAP1188...");
// Reset device using the reset pin
if (this->reset_pin_ != nullptr) {
this->reset_pin_->setup();
this->reset_pin_->digital_write(false);
delay(100); // NOLINT
this->reset_pin_->digital_write(true);
delay(100); // NOLINT
this->reset_pin_->digital_write(false);
delay(100); // NOLINT
}
// Check if CAP1188 is actually connected
this->read_byte(CAP1188_PRODUCT_ID, &this->cap1188_product_id_);
this->read_byte(CAP1188_MANUFACTURE_ID, &this->cap1188_manufacture_id_);
this->read_byte(CAP1188_REVISION, &this->cap1188_revision_);
if ((this->cap1188_product_id_ != 0x50) || (this->cap1188_manufacture_id_ != 0x5D)) {
this->error_code_ = COMMUNICATION_FAILED;
this->mark_failed();
return;
}
// Set sensitivity
uint8_t sensitivity = 0;
this->read_byte(CAP1188_SENSITVITY, &sensitivity);
sensitivity = sensitivity & 0x0f;
this->write_byte(CAP1188_SENSITVITY, sensitivity | this->touch_threshold_);
// Allow multiple touches
this->write_byte(CAP1188_MULTI_TOUCH, this->allow_multiple_touches_);
// Have LEDs follow touches
this->write_byte(CAP1188_LED_LINK, 0xFF);
// Speed up a bit
this->write_byte(CAP1188_STAND_BY_CONFIGURATION, 0x30);
}
void CAP1188Component::dump_config() {
ESP_LOGCONFIG(TAG, "CAP1188:");
LOG_I2C_DEVICE(this);
LOG_PIN(" Reset Pin: ", this->reset_pin_);
ESP_LOGCONFIG(TAG, " Product ID: 0x%x", this->cap1188_product_id_);
ESP_LOGCONFIG(TAG, " Manufacture ID: 0x%x", this->cap1188_manufacture_id_);
ESP_LOGCONFIG(TAG, " Revision ID: 0x%x", this->cap1188_revision_);
switch (this->error_code_) {
case COMMUNICATION_FAILED:
ESP_LOGE(TAG, "Product ID or Manufacture ID of the connected device does not match a known CAP1188.");
break;
case NONE:
default:
break;
}
}
void CAP1188Component::loop() {
uint8_t touched = 0;
this->read_register(CAP1188_SENSOR_INPUT_STATUS, &touched, 1);
if (touched) {
uint8_t data = 0;
this->read_register(CAP1188_MAIN, &data, 1);
data = data & ~CAP1188_MAIN_INT;
this->write_register(CAP1188_MAIN, &data, 2);
}
for (auto *channel : this->channels_) {
channel->process(touched);
}
}
} // namespace cap1188
} // namespace esphome

View File

@@ -0,0 +1,68 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/hal.h"
#include "esphome/components/i2c/i2c.h"
#include "esphome/components/output/binary_output.h"
#include "esphome/components/binary_sensor/binary_sensor.h"
namespace esphome {
namespace cap1188 {
enum {
CAP1188_I2CADDR = 0x29,
CAP1188_SENSOR_INPUT_STATUS = 0x3,
CAP1188_MULTI_TOUCH = 0x2A,
CAP1188_LED_LINK = 0x72,
CAP1188_PRODUCT_ID = 0xFD,
CAP1188_MANUFACTURE_ID = 0xFE,
CAP1188_STAND_BY_CONFIGURATION = 0x41,
CAP1188_REVISION = 0xFF,
CAP1188_MAIN = 0x00,
CAP1188_MAIN_INT = 0x01,
CAP1188_LEDPOL = 0x73,
CAP1188_INTERUPT_REPEAT = 0x28,
CAP1188_SENSITVITY = 0x1f,
};
class CAP1188Channel : public binary_sensor::BinarySensor {
public:
void set_channel(uint8_t channel) { channel_ = channel; }
void process(uint8_t data) { this->publish_state(static_cast<bool>(data & (1 << this->channel_))); }
protected:
uint8_t channel_{0};
};
class CAP1188Component : public Component, public i2c::I2CDevice {
public:
void register_channel(CAP1188Channel *channel) { this->channels_.push_back(channel); }
void set_touch_threshold(uint8_t touch_threshold) { this->touch_threshold_ = touch_threshold; };
void set_allow_multiple_touches(bool allow_multiple_touches) {
this->allow_multiple_touches_ = allow_multiple_touches ? 0x41 : 0x80;
};
void set_reset_pin(GPIOPin *reset_pin) { this->reset_pin_ = reset_pin; }
void setup() override;
void dump_config() override;
float get_setup_priority() const override { return setup_priority::DATA; }
void loop() override;
protected:
std::vector<CAP1188Channel *> channels_{};
uint8_t touch_threshold_{0x20};
uint8_t allow_multiple_touches_{0x80};
GPIOPin *reset_pin_{nullptr};
uint8_t cap1188_product_id_{0};
uint8_t cap1188_manufacture_id_{0};
uint8_t cap1188_revision_{0};
enum ErrorCode {
NONE = 0,
COMMUNICATION_FAILED,
} error_code_{NONE};
};
} // namespace cap1188
} // namespace esphome

View File

@@ -36,3 +36,5 @@ async def to_code(config):
if CORE.is_esp32:
cg.add_library("DNSServer", None)
cg.add_library("WiFi", None)
if CORE.is_esp8266:
cg.add_library("DNSServer", None)

View File

@@ -67,6 +67,7 @@ void CaptivePortal::handle_wifisave(AsyncWebServerRequest *request) {
ESP_LOGI(TAG, " SSID='%s'", ssid.c_str());
ESP_LOGI(TAG, " Password=" LOG_SECRET("'%s'"), psk.c_str());
wifi::global_wifi_component->save_wifi_sta(ssid, psk);
wifi::global_wifi_component->start_scanning();
request->redirect("/?save=true");
}
@@ -84,14 +85,7 @@ void CaptivePortal::start() {
this->dns_server_->start(53, "*", (uint32_t) ip);
this->base_->get_server()->onNotFound([this](AsyncWebServerRequest *req) {
bool not_found = false;
if (!this->active_) {
not_found = true;
} else if (req->host().c_str() == wifi::global_wifi_component->wifi_soft_ap_ip().str()) {
not_found = true;
}
if (not_found) {
if (!this->active_ || req->host().c_str() == wifi::global_wifi_component->wifi_soft_ap_ip().str()) {
req->send(404, "text/html", "File not found");
return;
}

View File

@@ -52,7 +52,7 @@ void CCS811Component::setup() {
if (this->baseline_.has_value()) {
// baseline available, write to sensor
this->write_bytes(0x11, decode_uint16(*this->baseline_));
this->write_bytes(0x11, decode_value(*this->baseline_));
}
auto hardware_version_data = this->read_bytes<1>(0x21);

View File

@@ -20,6 +20,7 @@ from esphome.const import (
CONF_MODE,
CONF_MODE_COMMAND_TOPIC,
CONF_MODE_STATE_TOPIC,
CONF_ON_STATE,
CONF_PRESET,
CONF_SWING_MODE,
CONF_SWING_MODE_COMMAND_TOPIC,
@@ -34,6 +35,7 @@ from esphome.const import (
CONF_TARGET_TEMPERATURE_LOW_COMMAND_TOPIC,
CONF_TARGET_TEMPERATURE_LOW_STATE_TOPIC,
CONF_TEMPERATURE_STEP,
CONF_TRIGGER_ID,
CONF_VISUAL,
CONF_MQTT_ID,
)
@@ -101,6 +103,7 @@ validate_climate_swing_mode = cv.enum(CLIMATE_SWING_MODES, upper=True)
# Actions
ControlAction = climate_ns.class_("ControlAction", automation.Action)
StateTrigger = climate_ns.class_("StateTrigger", automation.Trigger.template())
CLIMATE_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend(
{
@@ -161,6 +164,11 @@ CLIMATE_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).
cv.Optional(CONF_TARGET_TEMPERATURE_LOW_STATE_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
cv.Optional(CONF_ON_STATE): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(StateTrigger),
}
),
}
)
@@ -205,7 +213,7 @@ async def setup_climate_core_(var, config):
if CONF_MODE_COMMAND_TOPIC in config:
cg.add(mqtt_.set_custom_mode_command_topic(config[CONF_MODE_COMMAND_TOPIC]))
if CONF_MODE_STATE_TOPIC in config:
cg.add(mqtt_.set_custom_state_topic(config[CONF_MODE_STATE_TOPIC]))
cg.add(mqtt_.set_custom_mode_state_topic(config[CONF_MODE_STATE_TOPIC]))
if CONF_SWING_MODE_COMMAND_TOPIC in config:
cg.add(
@@ -256,6 +264,10 @@ async def setup_climate_core_(var, config):
)
)
for conf in config.get(CONF_ON_STATE, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)
async def register_climate(var, config):
if not CORE.has_id(config[CONF_ID]):

View File

@@ -42,5 +42,12 @@ template<typename... Ts> class ControlAction : public Action<Ts...> {
Climate *climate_;
};
class StateTrigger : public Trigger<> {
public:
StateTrigger(Climate *climate) {
climate->add_on_state_callback([this]() { this->trigger(); });
}
};
} // namespace climate
} // namespace esphome

View File

@@ -440,7 +440,11 @@ void Climate::set_visual_max_temperature_override(float visual_max_temperature_o
void Climate::set_visual_temperature_step_override(float visual_temperature_step_override) {
this->visual_temperature_step_override_ = visual_temperature_step_override;
}
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
Climate::Climate(const std::string &name) : EntityBase(name) {}
#pragma GCC diagnostic pop
Climate::Climate() : Climate("") {}
ClimateCall Climate::make_call() { return ClimateCall(this); }

View File

@@ -102,8 +102,6 @@ void CS5460AComponent::hw_init_() {
/* Doesn't reset the register values etc., just restarts the "computation cycle" */
void CS5460AComponent::restart_() {
int cnt;
this->enable();
/* Stop running conversion, wake up if needed */
this->write_byte(CMD_POWER_UP);

View File

View File

@@ -0,0 +1,244 @@
#include "cse7761.h"
#include "esphome/core/log.h"
namespace esphome {
namespace cse7761 {
static const char *const TAG = "cse7761";
/*********************************************************************************************\
* CSE7761 - Energy (Sonoff Dual R3 Pow v1.x)
*
* Based on Tasmota source code
* See https://github.com/arendst/Tasmota/discussions/10793
* https://github.com/arendst/Tasmota/blob/development/tasmota/xnrg_19_cse7761.ino
\*********************************************************************************************/
static const int CSE7761_UREF = 42563; // RmsUc
static const int CSE7761_IREF = 52241; // RmsIAC
static const int CSE7761_PREF = 44513; // PowerPAC
static const uint8_t CSE7761_REG_SYSCON = 0x00; // (2) System Control Register (0x0A04)
static const uint8_t CSE7761_REG_EMUCON = 0x01; // (2) Metering control register (0x0000)
static const uint8_t CSE7761_REG_EMUCON2 = 0x13; // (2) Metering control register 2 (0x0001)
static const uint8_t CSE7761_REG_PULSE1SEL = 0x1D; // (2) Pin function output select register (0x3210)
static const uint8_t CSE7761_REG_RMSIA = 0x24; // (3) The effective value of channel A current (0x000000)
static const uint8_t CSE7761_REG_RMSIB = 0x25; // (3) The effective value of channel B current (0x000000)
static const uint8_t CSE7761_REG_RMSU = 0x26; // (3) Voltage RMS (0x000000)
static const uint8_t CSE7761_REG_POWERPA = 0x2C; // (4) Channel A active power, update rate 27.2Hz (0x00000000)
static const uint8_t CSE7761_REG_POWERPB = 0x2D; // (4) Channel B active power, update rate 27.2Hz (0x00000000)
static const uint8_t CSE7761_REG_SYSSTATUS = 0x43; // (1) System status register
static const uint8_t CSE7761_REG_COEFFCHKSUM = 0x6F; // (2) Coefficient checksum
static const uint8_t CSE7761_REG_RMSIAC = 0x70; // (2) Channel A effective current conversion coefficient
static const uint8_t CSE7761_SPECIAL_COMMAND = 0xEA; // Start special command
static const uint8_t CSE7761_CMD_RESET = 0x96; // Reset command, after receiving the command, the chip resets
static const uint8_t CSE7761_CMD_CLOSE_WRITE = 0xDC; // Close write operation
static const uint8_t CSE7761_CMD_ENABLE_WRITE = 0xE5; // Enable write operation
enum CSE7761 { RMS_IAC, RMS_IBC, RMS_UC, POWER_PAC, POWER_PBC, POWER_SC, ENERGY_AC, ENERGY_BC };
void CSE7761Component::setup() {
ESP_LOGCONFIG(TAG, "Setting up CSE7761...");
this->write_(CSE7761_SPECIAL_COMMAND, CSE7761_CMD_RESET);
uint16_t syscon = this->read_(0x00, 2); // Default 0x0A04
if ((0x0A04 == syscon) && this->chip_init_()) {
this->write_(CSE7761_SPECIAL_COMMAND, CSE7761_CMD_CLOSE_WRITE);
ESP_LOGD(TAG, "CSE7761 found");
this->data_.ready = true;
} else {
this->mark_failed();
}
}
void CSE7761Component::dump_config() {
ESP_LOGCONFIG(TAG, "CSE7761:");
if (this->is_failed()) {
ESP_LOGE(TAG, "Communication with CSE7761 failed!");
}
LOG_UPDATE_INTERVAL(this);
this->check_uart_settings(38400, 1, uart::UART_CONFIG_PARITY_EVEN, 8);
}
float CSE7761Component::get_setup_priority() const { return setup_priority::DATA; }
void CSE7761Component::update() {
if (this->data_.ready) {
this->get_data_();
}
}
void CSE7761Component::write_(uint8_t reg, uint16_t data) {
uint8_t buffer[5];
buffer[0] = 0xA5;
buffer[1] = reg;
uint32_t len = 2;
if (data) {
if (data < 0xFF) {
buffer[2] = data & 0xFF;
len = 3;
} else {
buffer[2] = (data >> 8) & 0xFF;
buffer[3] = data & 0xFF;
len = 4;
}
uint8_t crc = 0;
for (uint32_t i = 0; i < len; i++) {
crc += buffer[i];
}
buffer[len] = ~crc;
len++;
}
this->write_array(buffer, len);
}
bool CSE7761Component::read_once_(uint8_t reg, uint8_t size, uint32_t *value) {
while (this->available()) {
this->read();
}
this->write_(reg, 0);
uint8_t buffer[8] = {0};
uint32_t rcvd = 0;
for (uint32_t i = 0; i <= size; i++) {
int value = this->read();
if (value > -1 && rcvd < sizeof(buffer) - 1) {
buffer[rcvd++] = value;
}
}
if (!rcvd) {
ESP_LOGD(TAG, "Received 0 bytes for register %hhu", reg);
return false;
}
rcvd--;
uint32_t result = 0;
// CRC check
uint8_t crc = 0xA5 + reg;
for (uint32_t i = 0; i < rcvd; i++) {
result = (result << 8) | buffer[i];
crc += buffer[i];
}
crc = ~crc;
if (crc != buffer[rcvd]) {
return false;
}
*value = result;
return true;
}
uint32_t CSE7761Component::read_(uint8_t reg, uint8_t size) {
bool result = false; // Start loop
uint8_t retry = 3; // Retry up to three times
uint32_t value = 0; // Default no value
while (!result && retry > 0) {
retry--;
if (this->read_once_(reg, size, &value))
return value;
}
ESP_LOGE(TAG, "Reading register %hhu failed!", reg);
return value;
}
uint32_t CSE7761Component::coefficient_by_unit_(uint32_t unit) {
switch (unit) {
case RMS_UC:
return 0x400000 * 100 / this->data_.coefficient[RMS_UC];
case RMS_IAC:
return (0x800000 * 100 / this->data_.coefficient[RMS_IAC]) * 10; // Stay within 32 bits
case POWER_PAC:
return 0x80000000 / this->data_.coefficient[POWER_PAC];
}
return 0;
}
bool CSE7761Component::chip_init_() {
uint16_t calc_chksum = 0xFFFF;
for (uint32_t i = 0; i < 8; i++) {
this->data_.coefficient[i] = this->read_(CSE7761_REG_RMSIAC + i, 2);
calc_chksum += this->data_.coefficient[i];
}
calc_chksum = ~calc_chksum;
uint16_t coeff_chksum = this->read_(CSE7761_REG_COEFFCHKSUM, 2);
if ((calc_chksum != coeff_chksum) || (!calc_chksum)) {
ESP_LOGD(TAG, "Default calibration");
this->data_.coefficient[RMS_IAC] = CSE7761_IREF;
this->data_.coefficient[RMS_UC] = CSE7761_UREF;
this->data_.coefficient[POWER_PAC] = CSE7761_PREF;
}
this->write_(CSE7761_SPECIAL_COMMAND, CSE7761_CMD_ENABLE_WRITE);
uint8_t sys_status = this->read_(CSE7761_REG_SYSSTATUS, 1);
if (sys_status & 0x10) { // Write enable to protected registers (WREN)
this->write_(CSE7761_REG_SYSCON | 0x80, 0xFF04);
this->write_(CSE7761_REG_EMUCON | 0x80, 0x1183);
this->write_(CSE7761_REG_EMUCON2 | 0x80, 0x0FC1);
this->write_(CSE7761_REG_PULSE1SEL | 0x80, 0x3290);
} else {
ESP_LOGD(TAG, "Write failed at chip_init");
return false;
}
return true;
}
void CSE7761Component::get_data_() {
// The effective value of current and voltage Rms is a 24-bit signed number,
// the highest bit is 0 for valid data,
// and when the highest bit is 1, the reading will be processed as zero
// The active power parameter PowerA/B is in twos complement format, 32-bit
// data, the highest bit is Sign bit.
uint32_t value = this->read_(CSE7761_REG_RMSU, 3);
this->data_.voltage_rms = (value >= 0x800000) ? 0 : value;
value = this->read_(CSE7761_REG_RMSIA, 3);
this->data_.current_rms[0] = ((value >= 0x800000) || (value < 1600)) ? 0 : value; // No load threshold of 10mA
value = this->read_(CSE7761_REG_POWERPA, 4);
this->data_.active_power[0] = (0 == this->data_.current_rms[0]) ? 0 : ((uint32_t) abs((int) value));
value = this->read_(CSE7761_REG_RMSIB, 3);
this->data_.current_rms[1] = ((value >= 0x800000) || (value < 1600)) ? 0 : value; // No load threshold of 10mA
value = this->read_(CSE7761_REG_POWERPB, 4);
this->data_.active_power[1] = (0 == this->data_.current_rms[1]) ? 0 : ((uint32_t) abs((int) value));
// convert values and publish to sensors
float voltage = (float) this->data_.voltage_rms / this->coefficient_by_unit_(RMS_UC);
if (this->voltage_sensor_ != nullptr) {
this->voltage_sensor_->publish_state(voltage);
}
for (uint32_t channel = 0; channel < 2; channel++) {
// Active power = PowerPA * PowerPAC * 1000 / 0x80000000
float active_power = (float) this->data_.active_power[channel] / this->coefficient_by_unit_(POWER_PAC); // W
float amps = (float) this->data_.current_rms[channel] / this->coefficient_by_unit_(RMS_IAC); // A
ESP_LOGD(TAG, "Channel %d power %f W, current %f A", channel + 1, active_power, amps);
if (channel == 0) {
if (this->power_sensor_1_ != nullptr) {
this->power_sensor_1_->publish_state(active_power);
}
if (this->current_sensor_1_ != nullptr) {
this->current_sensor_1_->publish_state(amps);
}
} else if (channel == 1) {
if (this->power_sensor_2_ != nullptr) {
this->power_sensor_2_->publish_state(active_power);
}
if (this->current_sensor_2_ != nullptr) {
this->current_sensor_2_->publish_state(amps);
}
}
}
}
} // namespace cse7761
} // namespace esphome

View File

@@ -0,0 +1,52 @@
#pragma once
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/uart/uart.h"
#include "esphome/core/component.h"
namespace esphome {
namespace cse7761 {
struct CSE7761DataStruct {
uint32_t frequency = 0;
uint32_t voltage_rms = 0;
uint32_t current_rms[2] = {0};
uint32_t energy[2] = {0};
uint32_t active_power[2] = {0};
uint16_t coefficient[8] = {0};
uint8_t energy_update = 0;
bool ready = false;
};
/// This class implements support for the CSE7761 UART power sensor.
class CSE7761Component : public PollingComponent, public uart::UARTDevice {
public:
void set_voltage_sensor(sensor::Sensor *voltage_sensor) { voltage_sensor_ = voltage_sensor; }
void set_active_power_1_sensor(sensor::Sensor *power_sensor_1) { power_sensor_1_ = power_sensor_1; }
void set_current_1_sensor(sensor::Sensor *current_sensor_1) { current_sensor_1_ = current_sensor_1; }
void set_active_power_2_sensor(sensor::Sensor *power_sensor_2) { power_sensor_2_ = power_sensor_2; }
void set_current_2_sensor(sensor::Sensor *current_sensor_2) { current_sensor_2_ = current_sensor_2; }
void setup() override;
void dump_config() override;
float get_setup_priority() const override;
void update() override;
protected:
// Sensors
sensor::Sensor *voltage_sensor_{nullptr};
sensor::Sensor *power_sensor_1_{nullptr};
sensor::Sensor *current_sensor_1_{nullptr};
sensor::Sensor *power_sensor_2_{nullptr};
sensor::Sensor *current_sensor_2_{nullptr};
CSE7761DataStruct data_;
void write_(uint8_t reg, uint16_t data);
bool read_once_(uint8_t reg, uint8_t size, uint32_t *value);
uint32_t read_(uint8_t reg, uint8_t size);
uint32_t coefficient_by_unit_(uint32_t unit);
bool chip_init_();
void get_data_();
};
} // namespace cse7761
} // namespace esphome

View File

@@ -0,0 +1,90 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import sensor, uart
from esphome.const import (
CONF_ID,
CONF_VOLTAGE,
DEVICE_CLASS_CURRENT,
DEVICE_CLASS_POWER,
DEVICE_CLASS_VOLTAGE,
STATE_CLASS_MEASUREMENT,
UNIT_VOLT,
UNIT_AMPERE,
UNIT_WATT,
)
CODEOWNERS = ["@berfenger"]
DEPENDENCIES = ["uart"]
cse7761_ns = cg.esphome_ns.namespace("cse7761")
CSE7761Component = cse7761_ns.class_(
"CSE7761Component", cg.PollingComponent, uart.UARTDevice
)
CONF_CURRENT_1 = "current_1"
CONF_CURRENT_2 = "current_2"
CONF_ACTIVE_POWER_1 = "active_power_1"
CONF_ACTIVE_POWER_2 = "active_power_2"
CONFIG_SCHEMA = (
cv.Schema(
{
cv.GenerateID(): cv.declare_id(CSE7761Component),
cv.Optional(CONF_VOLTAGE): sensor.sensor_schema(
unit_of_measurement=UNIT_VOLT,
accuracy_decimals=1,
device_class=DEVICE_CLASS_VOLTAGE,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_CURRENT_1): sensor.sensor_schema(
unit_of_measurement=UNIT_AMPERE,
accuracy_decimals=2,
device_class=DEVICE_CLASS_CURRENT,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_CURRENT_2): sensor.sensor_schema(
unit_of_measurement=UNIT_AMPERE,
accuracy_decimals=2,
device_class=DEVICE_CLASS_CURRENT,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_ACTIVE_POWER_1): sensor.sensor_schema(
unit_of_measurement=UNIT_WATT,
accuracy_decimals=1,
device_class=DEVICE_CLASS_POWER,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_ACTIVE_POWER_2): sensor.sensor_schema(
unit_of_measurement=UNIT_WATT,
accuracy_decimals=1,
device_class=DEVICE_CLASS_POWER,
state_class=STATE_CLASS_MEASUREMENT,
),
}
)
.extend(cv.polling_component_schema("60s"))
.extend(uart.UART_DEVICE_SCHEMA)
)
FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema(
"cse7761", baud_rate=38400, require_rx=True, require_tx=True
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await uart.register_uart_device(var, config)
for key in [
CONF_VOLTAGE,
CONF_CURRENT_1,
CONF_CURRENT_2,
CONF_ACTIVE_POWER_1,
CONF_ACTIVE_POWER_2,
]:
if key not in config:
continue
conf = config[key]
sens = await sensor.new_sensor(conf)
cg.add(getattr(var, f"set_{key}_sensor")(sens))

View File

@@ -90,6 +90,7 @@ void CSE7766Component::parse_data_() {
uint32_t power_cycle = this->get_24_bit_uint_(17);
uint8_t adj = this->raw_data_[20];
uint32_t cf_pulses = (this->raw_data_[21] << 8) + this->raw_data_[22];
bool power_ok = true;
bool voltage_ok = true;
@@ -127,6 +128,18 @@ void CSE7766Component::parse_data_() {
power = power_calib / float(power_cycle);
this->power_acc_ += power;
this->power_counts_ += 1;
uint32_t difference;
if (this->cf_pulses_last_ == 0)
this->cf_pulses_last_ = cf_pulses;
if (cf_pulses < this->cf_pulses_last_) {
difference = cf_pulses + (0x10000 - this->cf_pulses_last_);
} else {
difference = cf_pulses - this->cf_pulses_last_;
}
this->cf_pulses_last_ = cf_pulses;
this->energy_total_ += difference * float(power_calib) / 1000000.0 / 3600.0;
}
if ((adj & 0x20) == 0x20 && current_ok && voltage_ok && power != 0.0) {
@@ -136,9 +149,9 @@ void CSE7766Component::parse_data_() {
}
}
void CSE7766Component::update() {
float voltage = this->voltage_counts_ > 0 ? this->voltage_acc_ / this->voltage_counts_ : 0.0;
float current = this->current_counts_ > 0 ? this->current_acc_ / this->current_counts_ : 0.0;
float power = this->power_counts_ > 0 ? this->power_acc_ / this->power_counts_ : 0.0;
float voltage = this->voltage_counts_ > 0 ? this->voltage_acc_ / this->voltage_counts_ : 0.0f;
float current = this->current_counts_ > 0 ? this->current_acc_ / this->current_counts_ : 0.0f;
float power = this->power_counts_ > 0 ? this->power_acc_ / this->power_counts_ : 0.0f;
ESP_LOGV(TAG, "Got voltage_acc=%.2f current_acc=%.2f power_acc=%.2f", this->voltage_acc_, this->current_acc_,
this->power_acc_);
@@ -152,6 +165,8 @@ void CSE7766Component::update() {
this->current_sensor_->publish_state(current);
if (this->power_sensor_ != nullptr)
this->power_sensor_->publish_state(power);
if (this->energy_sensor_ != nullptr)
this->energy_sensor_->publish_state(this->energy_total_);
this->voltage_acc_ = 0.0f;
this->current_acc_ = 0.0f;
@@ -172,6 +187,7 @@ void CSE7766Component::dump_config() {
LOG_SENSOR(" ", "Voltage", this->voltage_sensor_);
LOG_SENSOR(" ", "Current", this->current_sensor_);
LOG_SENSOR(" ", "Power", this->power_sensor_);
LOG_SENSOR(" ", "Energy", this->energy_sensor_);
this->check_uart_settings(4800);
}

View File

@@ -12,6 +12,7 @@ class CSE7766Component : public PollingComponent, public uart::UARTDevice {
void set_voltage_sensor(sensor::Sensor *voltage_sensor) { voltage_sensor_ = voltage_sensor; }
void set_current_sensor(sensor::Sensor *current_sensor) { current_sensor_ = current_sensor; }
void set_power_sensor(sensor::Sensor *power_sensor) { power_sensor_ = power_sensor; }
void set_energy_sensor(sensor::Sensor *energy_sensor) { energy_sensor_ = energy_sensor; }
void loop() override;
float get_setup_priority() const override;
@@ -29,9 +30,12 @@ class CSE7766Component : public PollingComponent, public uart::UARTDevice {
sensor::Sensor *voltage_sensor_{nullptr};
sensor::Sensor *current_sensor_{nullptr};
sensor::Sensor *power_sensor_{nullptr};
sensor::Sensor *energy_sensor_{nullptr};
float voltage_acc_{0.0f};
float current_acc_{0.0f};
float power_acc_{0.0f};
float energy_total_{0.0f};
uint32_t cf_pulses_last_{0};
uint32_t voltage_counts_{0};
uint32_t current_counts_{0};
uint32_t power_counts_{0};

View File

@@ -3,16 +3,20 @@ import esphome.config_validation as cv
from esphome.components import sensor, uart
from esphome.const import (
CONF_CURRENT,
CONF_ENERGY,
CONF_ID,
CONF_POWER,
CONF_VOLTAGE,
DEVICE_CLASS_CURRENT,
DEVICE_CLASS_ENERGY,
DEVICE_CLASS_POWER,
DEVICE_CLASS_VOLTAGE,
STATE_CLASS_MEASUREMENT,
STATE_CLASS_TOTAL_INCREASING,
UNIT_VOLT,
UNIT_AMPERE,
UNIT_WATT,
UNIT_WATT_HOURS,
)
DEPENDENCIES = ["uart"]
@@ -44,6 +48,12 @@ CONFIG_SCHEMA = (
device_class=DEVICE_CLASS_POWER,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_ENERGY): sensor.sensor_schema(
unit_of_measurement=UNIT_WATT_HOURS,
accuracy_decimals=3,
device_class=DEVICE_CLASS_ENERGY,
state_class=STATE_CLASS_TOTAL_INCREASING,
),
}
)
.extend(cv.polling_component_schema("60s"))
@@ -71,3 +81,7 @@ async def to_code(config):
conf = config[CONF_POWER]
sens = await sensor.new_sensor(conf)
cg.add(var.set_power_sensor(sens))
if CONF_ENERGY in config:
conf = config[CONF_ENERGY]
sens = await sensor.new_sensor(conf)
cg.add(var.set_energy_sensor(sens))

View File

@@ -231,7 +231,7 @@ bool DaikinClimate::on_receive(remote_base::RemoteReceiveData data) {
// frame header
if (byte != 0x27)
return false;
} else if (pos == 3) {
} else if (pos == 3) { // NOLINT(bugprone-branch-clone)
// frame header
if (byte != 0x00)
return false;

View File

@@ -6,26 +6,20 @@ from esphome.const import CONF_ID, CONF_PIN
MULTI_CONF = True
AUTO_LOAD = ["sensor"]
CONF_ONE_WIRE_ID = "one_wire_id"
dallas_ns = cg.esphome_ns.namespace("dallas")
DallasComponent = dallas_ns.class_("DallasComponent", cg.PollingComponent)
ESPOneWire = dallas_ns.class_("ESPOneWire")
CONFIG_SCHEMA = cv.All(
cv.Schema(
{
cv.GenerateID(): cv.declare_id(DallasComponent),
cv.GenerateID(CONF_ONE_WIRE_ID): cv.declare_id(ESPOneWire),
cv.Required(CONF_PIN): pins.internal_gpio_output_pin_schema,
}
).extend(cv.polling_component_schema("60s")),
# pin_mode call logs in esp-idf, but InterruptLock is active -> crash
cv.only_with_arduino,
)
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(): cv.declare_id(DallasComponent),
cv.Required(CONF_PIN): pins.internal_gpio_output_pin_schema,
}
).extend(cv.polling_component_schema("60s"))
async def to_code(config):
pin = await cg.gpio_pin_expression(config[CONF_PIN])
one_wire = cg.new_Pvariable(config[CONF_ONE_WIRE_ID], pin)
var = cg.new_Pvariable(config[CONF_ID], one_wire)
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
pin = await cg.gpio_pin_expression(config[CONF_PIN])
cg.add(var.set_pin(pin))

View File

@@ -31,12 +31,11 @@ uint16_t DallasTemperatureSensor::millis_to_wait_for_conversion() const {
void DallasComponent::setup() {
ESP_LOGCONFIG(TAG, "Setting up DallasComponent...");
yield();
pin_->setup();
one_wire_ = new ESPOneWire(pin_); // NOLINT(cppcoreguidelines-owning-memory)
std::vector<uint64_t> raw_sensors;
{
InterruptLock lock;
raw_sensors = this->one_wire_->search_vec();
}
raw_sensors = this->one_wire_->search_vec();
for (auto &address : raw_sensors) {
std::string s = uint64_to_string(address);
@@ -70,7 +69,7 @@ void DallasComponent::setup() {
}
void DallasComponent::dump_config() {
ESP_LOGCONFIG(TAG, "DallasComponent:");
LOG_PIN(" Pin: ", this->one_wire_->get_pin());
LOG_PIN(" Pin: ", this->pin_);
LOG_UPDATE_INTERVAL(this);
if (this->found_sensors_.empty()) {
@@ -102,15 +101,12 @@ void DallasComponent::update() {
this->status_clear_warning();
bool result;
{
InterruptLock lock;
if (!this->one_wire_->reset()) {
result = false;
} else {
result = true;
this->one_wire_->skip();
this->one_wire_->write8(DALLAS_COMMAND_START_CONVERSION);
}
if (!this->one_wire_->reset()) {
result = false;
} else {
result = true;
this->one_wire_->skip();
this->one_wire_->write8(DALLAS_COMMAND_START_CONVERSION);
}
if (!result) {
@@ -121,11 +117,7 @@ void DallasComponent::update() {
for (auto *sensor : this->sensors_) {
this->set_timeout(sensor->get_address_name(), sensor->millis_to_wait_for_conversion(), [this, sensor] {
bool res;
{
InterruptLock lock;
res = sensor->read_scratch_pad();
}
bool res = sensor->read_scratch_pad();
if (!res) {
ESP_LOGW(TAG, "'%s' - Resetting bus for read failed!", sensor->get_name().c_str());
@@ -146,7 +138,6 @@ void DallasComponent::update() {
});
}
}
DallasComponent::DallasComponent(ESPOneWire *one_wire) : one_wire_(one_wire) {}
void DallasTemperatureSensor::set_address(uint64_t address) { this->address_ = address; }
uint8_t DallasTemperatureSensor::get_resolution() const { return this->resolution_; }
@@ -162,7 +153,7 @@ const std::string &DallasTemperatureSensor::get_address_name() {
return this->address_name_;
}
bool IRAM_ATTR DallasTemperatureSensor::read_scratch_pad() {
ESPOneWire *wire = this->parent_->one_wire_;
auto *wire = this->parent_->one_wire_;
if (!wire->reset()) {
return false;
}
@@ -176,11 +167,7 @@ bool IRAM_ATTR DallasTemperatureSensor::read_scratch_pad() {
return true;
}
bool DallasTemperatureSensor::setup_sensor() {
bool r;
{
InterruptLock lock;
r = this->read_scratch_pad();
}
bool r = this->read_scratch_pad();
if (!r) {
ESP_LOGE(TAG, "Reading scratchpad failed: reset");
@@ -214,21 +201,18 @@ bool DallasTemperatureSensor::setup_sensor() {
break;
}
ESPOneWire *wire = this->parent_->one_wire_;
{
InterruptLock lock;
if (wire->reset()) {
wire->select(this->address_);
wire->write8(DALLAS_COMMAND_WRITE_SCRATCH_PAD);
wire->write8(this->scratch_pad_[2]); // high alarm temp
wire->write8(this->scratch_pad_[3]); // low alarm temp
wire->write8(this->scratch_pad_[4]); // resolution
wire->reset();
auto *wire = this->parent_->one_wire_;
if (wire->reset()) {
wire->select(this->address_);
wire->write8(DALLAS_COMMAND_WRITE_SCRATCH_PAD);
wire->write8(this->scratch_pad_[2]); // high alarm temp
wire->write8(this->scratch_pad_[3]); // low alarm temp
wire->write8(this->scratch_pad_[4]); // resolution
wire->reset();
// write value to EEPROM
wire->select(this->address_);
wire->write8(0x48);
}
// write value to EEPROM
wire->select(this->address_);
wire->write8(0x48);
}
delay(20); // allow it to finish operation

View File

@@ -11,8 +11,7 @@ class DallasTemperatureSensor;
class DallasComponent : public PollingComponent {
public:
explicit DallasComponent(ESPOneWire *one_wire);
void set_pin(InternalGPIOPin *pin) { pin_ = pin; }
void register_sensor(DallasTemperatureSensor *sensor);
void setup() override;
@@ -24,6 +23,7 @@ class DallasComponent : public PollingComponent {
protected:
friend DallasTemperatureSensor;
InternalGPIOPin *pin_;
ESPOneWire *one_wire_;
std::vector<DallasTemperatureSensor *> sensors_;
std::vector<uint64_t> found_sensors_;

View File

@@ -10,115 +10,123 @@ static const char *const TAG = "dallas.one_wire";
const uint8_t ONE_WIRE_ROM_SELECT = 0x55;
const int ONE_WIRE_ROM_SEARCH = 0xF0;
ESPOneWire::ESPOneWire(GPIOPin *pin) : pin_(pin) {}
ESPOneWire::ESPOneWire(InternalGPIOPin *pin) { pin_ = pin->to_isr(); }
bool HOT IRAM_ATTR ESPOneWire::reset() {
uint8_t retries = 125;
// See reset here:
// https://www.maximintegrated.com/en/design/technical-documents/app-notes/1/126.html
InterruptLock lock;
// Wait for communication to clear
this->pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
// Wait for communication to clear (delay G)
pin_.pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
uint8_t retries = 125;
do {
if (--retries == 0)
return false;
delayMicroseconds(2);
} while (!this->pin_->digital_read());
} while (!pin_.digital_read());
// Send 480µs LOW TX reset pulse
this->pin_->pin_mode(gpio::FLAG_OUTPUT);
this->pin_->digital_write(false);
// Send 480µs LOW TX reset pulse (drive bus low, delay H)
pin_.pin_mode(gpio::FLAG_OUTPUT);
pin_.digital_write(false);
delayMicroseconds(480);
// Switch into RX mode, letting the pin float
this->pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
// after 15µs-60µs wait time, responder pulls low for 60µs-240µs
// let's have 70µs just in case
// Release the bus, delay I
pin_.pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
delayMicroseconds(70);
bool r = !this->pin_->digital_read();
// sample bus, 0=device(s) present, 1=no device present
bool r = !pin_.digital_read();
// delay J
delayMicroseconds(410);
return r;
}
void HOT IRAM_ATTR ESPOneWire::write_bit(bool bit) {
// Initiate write/read by pulling low.
this->pin_->pin_mode(gpio::FLAG_OUTPUT);
this->pin_->digital_write(false);
// See write 1/0 bit here:
// https://www.maximintegrated.com/en/design/technical-documents/app-notes/1/126.html
InterruptLock lock;
// bus sampled within 15µs and 60µs after pulling LOW.
if (bit) {
// pull high/release within 15µs
delayMicroseconds(10);
this->pin_->digital_write(true);
// in total minimum of 60µs long
delayMicroseconds(55);
} else {
// continue pulling LOW for at least 60µs
delayMicroseconds(65);
this->pin_->digital_write(true);
// grace period, 1µs recovery time
delayMicroseconds(5);
}
// drive bus low
pin_.pin_mode(gpio::FLAG_OUTPUT);
pin_.digital_write(false);
uint32_t delay0 = bit ? 10 : 65;
uint32_t delay1 = bit ? 55 : 5;
// delay A/C
delayMicroseconds(delay0);
// release bus
pin_.digital_write(true);
// delay B/D
delayMicroseconds(delay1);
}
bool HOT IRAM_ATTR ESPOneWire::read_bit() {
// Initiate read slot by pulling LOW for at least 1µs
this->pin_->pin_mode(gpio::FLAG_OUTPUT);
this->pin_->digital_write(false);
// See read bit here:
// https://www.maximintegrated.com/en/design/technical-documents/app-notes/1/126.html
InterruptLock lock;
// drive bus low, delay A
pin_.pin_mode(gpio::FLAG_OUTPUT);
pin_.digital_write(false);
delayMicroseconds(3);
// release bus, we have to sample within 15µs of pulling low
this->pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
// release bus, delay E
pin_.pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
delayMicroseconds(10);
bool r = this->pin_->digital_read();
// read time slot at least 60µs long + 1µs recovery time between slots
// sample bus to read bit from peer
bool r = pin_.digital_read();
// delay F
delayMicroseconds(53);
return r;
}
void IRAM_ATTR ESPOneWire::write8(uint8_t val) {
void ESPOneWire::write8(uint8_t val) {
for (uint8_t i = 0; i < 8; i++) {
this->write_bit(bool((1u << i) & val));
}
}
void IRAM_ATTR ESPOneWire::write64(uint64_t val) {
void ESPOneWire::write64(uint64_t val) {
for (uint8_t i = 0; i < 64; i++) {
this->write_bit(bool((1ULL << i) & val));
}
}
uint8_t IRAM_ATTR ESPOneWire::read8() {
uint8_t ESPOneWire::read8() {
uint8_t ret = 0;
for (uint8_t i = 0; i < 8; i++) {
ret |= (uint8_t(this->read_bit()) << i);
}
return ret;
}
uint64_t IRAM_ATTR ESPOneWire::read64() {
uint64_t ESPOneWire::read64() {
uint64_t ret = 0;
for (uint8_t i = 0; i < 8; i++) {
ret |= (uint64_t(this->read_bit()) << i);
}
return ret;
}
void IRAM_ATTR ESPOneWire::select(uint64_t address) {
void ESPOneWire::select(uint64_t address) {
this->write8(ONE_WIRE_ROM_SELECT);
this->write64(address);
}
void IRAM_ATTR ESPOneWire::reset_search() {
void ESPOneWire::reset_search() {
this->last_discrepancy_ = 0;
this->last_device_flag_ = false;
this->last_family_discrepancy_ = 0;
this->rom_number_ = 0;
}
uint64_t HOT IRAM_ATTR ESPOneWire::search() {
uint64_t ESPOneWire::search() {
if (this->last_device_flag_) {
return 0u;
}
if (!this->reset()) {
// Reset failed
// Reset failed or no devices present
this->reset_search();
return 0u;
}
@@ -196,7 +204,7 @@ uint64_t HOT IRAM_ATTR ESPOneWire::search() {
return this->rom_number_;
}
std::vector<uint64_t> IRAM_ATTR ESPOneWire::search_vec() {
std::vector<uint64_t> ESPOneWire::search_vec() {
std::vector<uint64_t> res;
this->reset_search();
@@ -206,10 +214,9 @@ std::vector<uint64_t> IRAM_ATTR ESPOneWire::search_vec() {
return res;
}
void IRAM_ATTR ESPOneWire::skip() {
void ESPOneWire::skip() {
this->write8(0xCC); // skip ROM
}
GPIOPin *ESPOneWire::get_pin() { return this->pin_; }
uint8_t IRAM_ATTR *ESPOneWire::rom_number8_() { return reinterpret_cast<uint8_t *>(&this->rom_number_); }

View File

@@ -1,6 +1,5 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/hal.h"
#include <vector>
@@ -12,7 +11,7 @@ extern const int ONE_WIRE_ROM_SEARCH;
class ESPOneWire {
public:
explicit ESPOneWire(GPIOPin *pin);
explicit ESPOneWire(InternalGPIOPin *pin);
/** Reset the bus, should be done before all write operations.
*
@@ -55,13 +54,11 @@ class ESPOneWire {
/// Helper that wraps search in a std::vector.
std::vector<uint64_t> search_vec();
GPIOPin *get_pin();
protected:
/// Helper to get the internal 64-bit unsigned rom number as a 8-bit integer pointer.
inline uint8_t *rom_number8_();
GPIOPin *pin_;
ISRInternalGPIOPin pin_;
uint8_t last_discrepancy_{0};
uint8_t last_family_discrepancy_{0};
bool last_device_flag_{false};

View File

@@ -29,6 +29,13 @@ CONFIG_SCHEMA = cv.Schema(
}
)
WIFI_CONFIG = """
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
"""
async def to_code(config):
cg.add_define("USE_DASHBOARD_IMPORT")
@@ -41,5 +48,12 @@ def import_config(path: str, name: str, project_name: str, import_url: str) -> N
if p.exists():
raise FileExistsError
config = {"substitutions": {"name": name}, "packages": {project_name: import_url}}
p.write_text(dump(config), encoding="utf8")
config = {
"substitutions": {"name": name},
"packages": {project_name: import_url},
"esphome": {"name_add_mac_suffix": False},
}
p.write_text(
dump(config) + WIFI_CONFIG,
encoding="utf8",
)

View File

@@ -4,20 +4,23 @@
#include "esphome/core/defines.h"
#include "esphome/core/version.h"
#ifdef USE_ESP_IDF
#include <esp_heap_caps.h>
#include <esp_system.h>
#endif
#ifdef USE_ESP32
#if ESP_IDF_VERSION_MAJOR >= 4
#include <esp32/rom/rtc.h>
#else
#include <rom/rtc.h>
#include <esp_idf_version.h>
#endif
#endif
#ifdef USE_ARDUINO
#include <Esp.h>
#endif
#ifdef USE_ESP_IDF
#include <esp_heap_caps.h>
#include <esp_system.h>
#endif
namespace esphome {
namespace debug {

View File

@@ -41,15 +41,30 @@ EXT1_WAKEUP_MODES = {
"ALL_LOW": esp_sleep_ext1_wakeup_mode_t.ESP_EXT1_WAKEUP_ALL_LOW,
"ANY_HIGH": esp_sleep_ext1_wakeup_mode_t.ESP_EXT1_WAKEUP_ANY_HIGH,
}
WakeupCauseToRunDuration = deep_sleep_ns.struct("WakeupCauseToRunDuration")
CONF_WAKEUP_PIN_MODE = "wakeup_pin_mode"
CONF_ESP32_EXT1_WAKEUP = "esp32_ext1_wakeup"
CONF_TOUCH_WAKEUP = "touch_wakeup"
CONF_DEFAULT = "default"
CONF_GPIO_WAKEUP_REASON = "gpio_wakeup_reason"
CONF_TOUCH_WAKEUP_REASON = "touch_wakeup_reason"
WAKEUP_CAUSES_SCHEMA = cv.Schema(
{
cv.Required(CONF_DEFAULT): cv.positive_time_period_milliseconds,
cv.Optional(CONF_TOUCH_WAKEUP_REASON): cv.positive_time_period_milliseconds,
cv.Optional(CONF_GPIO_WAKEUP_REASON): cv.positive_time_period_milliseconds,
}
)
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(): cv.declare_id(DeepSleepComponent),
cv.Optional(CONF_RUN_DURATION): cv.positive_time_period_milliseconds,
cv.Optional(CONF_RUN_DURATION): cv.Any(
cv.All(cv.only_on_esp32, WAKEUP_CAUSES_SCHEMA),
cv.positive_time_period_milliseconds,
),
cv.Optional(CONF_SLEEP_DURATION): cv.positive_time_period_milliseconds,
cv.Optional(CONF_WAKEUP_PIN): cv.All(
cv.only_on_esp32, pins.internal_gpio_input_pin_schema, validate_pin_number
@@ -85,7 +100,28 @@ async def to_code(config):
if CONF_WAKEUP_PIN_MODE in config:
cg.add(var.set_wakeup_pin_mode(config[CONF_WAKEUP_PIN_MODE]))
if CONF_RUN_DURATION in config:
cg.add(var.set_run_duration(config[CONF_RUN_DURATION]))
run_duration_config = config[CONF_RUN_DURATION]
if not isinstance(run_duration_config, dict):
cg.add(var.set_run_duration(config[CONF_RUN_DURATION]))
else:
default_run_duration = run_duration_config[CONF_DEFAULT]
wakeup_cause_to_run_duration = cg.StructInitializer(
WakeupCauseToRunDuration,
("default_cause", default_run_duration),
(
"touch_cause",
run_duration_config.get(
CONF_TOUCH_WAKEUP_REASON, default_run_duration
),
),
(
"gpio_cause",
run_duration_config.get(
CONF_GPIO_WAKEUP_REASON, default_run_duration
),
),
)
cg.add(var.set_run_duration(wakeup_cause_to_run_duration))
if CONF_ESP32_EXT1_WAKEUP in config:
conf = config[CONF_ESP32_EXT1_WAKEUP]

View File

@@ -13,12 +13,35 @@ static const char *const TAG = "deep_sleep";
bool global_has_deep_sleep = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
optional<uint32_t> DeepSleepComponent::get_run_duration_() const {
#ifdef USE_ESP32
if (this->wakeup_cause_to_run_duration_.has_value()) {
esp_sleep_wakeup_cause_t wakeup_cause = esp_sleep_get_wakeup_cause();
switch (wakeup_cause) {
case ESP_SLEEP_WAKEUP_EXT0:
case ESP_SLEEP_WAKEUP_EXT1:
return this->wakeup_cause_to_run_duration_->gpio_cause;
case ESP_SLEEP_WAKEUP_TOUCHPAD:
return this->wakeup_cause_to_run_duration_->touch_cause;
default:
return this->wakeup_cause_to_run_duration_->default_cause;
}
}
#endif
return this->run_duration_;
}
void DeepSleepComponent::setup() {
ESP_LOGCONFIG(TAG, "Setting up Deep Sleep...");
global_has_deep_sleep = true;
if (this->run_duration_.has_value())
this->set_timeout(*this->run_duration_, [this]() { this->begin_sleep(); });
const optional<uint32_t> run_duration = get_run_duration_();
if (run_duration.has_value()) {
ESP_LOGI(TAG, "Scheduling Deep Sleep to start in %u ms", *run_duration);
this->set_timeout(*run_duration, [this]() { this->begin_sleep(); });
} else {
ESP_LOGD(TAG, "Not scheduling Deep Sleep, as no run duration is configured.");
}
}
void DeepSleepComponent::dump_config() {
ESP_LOGCONFIG(TAG, "Setting up Deep Sleep...");
@@ -33,6 +56,11 @@ void DeepSleepComponent::dump_config() {
if (wakeup_pin_ != nullptr) {
LOG_PIN(" Wakeup Pin: ", this->wakeup_pin_);
}
if (this->wakeup_cause_to_run_duration_.has_value()) {
ESP_LOGCONFIG(TAG, " Default Wakeup Run Duration: %u ms", this->wakeup_cause_to_run_duration_->default_cause);
ESP_LOGCONFIG(TAG, " Touch Wakeup Run Duration: %u ms", this->wakeup_cause_to_run_duration_->touch_cause);
ESP_LOGCONFIG(TAG, " GPIO Wakeup Run Duration: %u ms", this->wakeup_cause_to_run_duration_->gpio_cause);
}
#endif
}
void DeepSleepComponent::loop() {
@@ -49,6 +77,9 @@ void DeepSleepComponent::set_wakeup_pin_mode(WakeupPinMode wakeup_pin_mode) {
}
void DeepSleepComponent::set_ext1_wakeup(Ext1Wakeup ext1_wakeup) { this->ext1_wakeup_ = ext1_wakeup; }
void DeepSleepComponent::set_touch_wakeup(bool touch_wakeup) { this->touch_wakeup_ = touch_wakeup; }
void DeepSleepComponent::set_run_duration(WakeupCauseToRunDuration wakeup_cause_to_run_duration) {
wakeup_cause_to_run_duration_ = wakeup_cause_to_run_duration;
}
#endif
void DeepSleepComponent::set_run_duration(uint32_t time_ms) { this->run_duration_ = time_ms; }
void DeepSleepComponent::begin_sleep(bool manual) {
@@ -77,9 +108,10 @@ void DeepSleepComponent::begin_sleep(bool manual) {
if (this->sleep_duration_.has_value())
esp_sleep_enable_timer_wakeup(*this->sleep_duration_);
if (this->wakeup_pin_ != nullptr) {
bool level = this->wakeup_pin_->is_inverted();
if (this->wakeup_pin_mode_ == WAKEUP_PIN_MODE_INVERT_WAKEUP && this->wakeup_pin_->digital_read())
bool level = !this->wakeup_pin_->is_inverted();
if (this->wakeup_pin_mode_ == WAKEUP_PIN_MODE_INVERT_WAKEUP && this->wakeup_pin_->digital_read()) {
level = !level;
}
esp_sleep_enable_ext0_wakeup(gpio_num_t(this->wakeup_pin_->get_pin()), level);
}
if (this->ext1_wakeup_.has_value()) {

View File

@@ -32,6 +32,15 @@ struct Ext1Wakeup {
esp_sleep_ext1_wakeup_mode_t wakeup_mode;
};
struct WakeupCauseToRunDuration {
// Run duration if woken up by timer or any other reason besides those below.
uint32_t default_cause;
// Run duration if woken up by touch pads.
uint32_t touch_cause;
// Run duration if woken up by GPIO pins.
uint32_t gpio_cause;
};
#endif
template<typename... Ts> class EnterDeepSleepAction;
@@ -59,6 +68,11 @@ class DeepSleepComponent : public Component {
void set_ext1_wakeup(Ext1Wakeup ext1_wakeup);
void set_touch_wakeup(bool touch_wakeup);
// Set the duration in ms for how long the code should run before entering
// deep sleep mode, according to the cause the ESP32 has woken.
void set_run_duration(WakeupCauseToRunDuration wakeup_cause_to_run_duration);
#endif
/// Set a duration in ms for how long the code should run before entering deep sleep mode.
void set_run_duration(uint32_t time_ms);
@@ -75,12 +89,17 @@ class DeepSleepComponent : public Component {
void prevent_deep_sleep();
protected:
// Returns nullopt if no run duration is set. Otherwise, returns the run
// duration before entering deep sleep.
optional<uint32_t> get_run_duration_() const;
optional<uint64_t> sleep_duration_;
#ifdef USE_ESP32
InternalGPIOPin *wakeup_pin_;
WakeupPinMode wakeup_pin_mode_{WAKEUP_PIN_MODE_IGNORE};
optional<Ext1Wakeup> ext1_wakeup_;
optional<bool> touch_wakeup_;
optional<WakeupCauseToRunDuration> wakeup_cause_to_run_duration_;
#endif
optional<uint32_t> run_duration_;
bool next_enter_deep_sleep_{false};

View File

@@ -3,6 +3,7 @@ import esphome.config_validation as cv
from esphome import core, automation
from esphome.automation import maybe_simple_id
from esphome.const import (
CONF_AUTO_CLEAR_ENABLED,
CONF_ID,
CONF_LAMBDA,
CONF_PAGES,
@@ -79,6 +80,7 @@ FULL_DISPLAY_SCHEMA = BASIC_DISPLAY_SCHEMA.extend(
cv.Optional(CONF_TO): cv.use_id(DisplayPage),
}
),
cv.Optional(CONF_AUTO_CLEAR_ENABLED, default=True): cv.boolean,
}
)
@@ -86,6 +88,10 @@ FULL_DISPLAY_SCHEMA = BASIC_DISPLAY_SCHEMA.extend(
async def setup_display_core_(var, config):
if CONF_ROTATION in config:
cg.add(var.set_rotation(DISPLAY_ROTATIONS[config[CONF_ROTATION]]))
if CONF_AUTO_CLEAR_ENABLED in config:
cg.add(var.set_auto_clear(config[CONF_AUTO_CLEAR_ENABLED]))
if CONF_PAGES in config:
pages = []
for conf in config[CONF_PAGES]:

View File

@@ -336,7 +336,9 @@ void DisplayBuffer::show_page(DisplayPage *page) {
void DisplayBuffer::show_next_page() { this->page_->show_next(); }
void DisplayBuffer::show_prev_page() { this->page_->show_prev(); }
void DisplayBuffer::do_update_() {
this->clear();
if (this->auto_clear_enabled_) {
this->clear();
}
if (this->page_ != nullptr) {
this->page_->get_writer()(*this);
} else if (this->writer_.has_value()) {
@@ -494,7 +496,7 @@ bool Animation::get_pixel(int x, int y) const {
return false;
const uint32_t width_8 = ((this->width_ + 7u) / 8u) * 8u;
const uint32_t frame_index = this->height_ * width_8 * this->current_frame_;
if (frame_index >= this->width_ * this->height_ * this->animation_frame_count_)
if (frame_index >= (uint32_t)(this->width_ * this->height_ * this->animation_frame_count_))
return false;
const uint32_t pos = x + y * width_8 + frame_index;
return progmem_read_byte(this->data_start_ + (pos / 8u)) & (0x80 >> (pos % 8u));
@@ -503,7 +505,7 @@ Color Animation::get_color_pixel(int x, int y) const {
if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_)
return Color::BLACK;
const uint32_t frame_index = this->width_ * this->height_ * this->current_frame_;
if (frame_index >= this->width_ * this->height_ * this->animation_frame_count_)
if (frame_index >= (uint32_t)(this->width_ * this->height_ * this->animation_frame_count_))
return Color::BLACK;
const uint32_t pos = (x + y * this->width_ + frame_index) * 3;
const uint32_t color32 = (progmem_read_byte(this->data_start_ + pos + 2) << 0) |
@@ -515,7 +517,7 @@ Color Animation::get_grayscale_pixel(int x, int y) const {
if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_)
return Color::BLACK;
const uint32_t frame_index = this->width_ * this->height_ * this->current_frame_;
if (frame_index >= this->width_ * this->height_ * this->animation_frame_count_)
if (frame_index >= (uint32_t)(this->width_ * this->height_ * this->animation_frame_count_))
return Color::BLACK;
const uint32_t pos = (x + y * this->width_ + frame_index);
const uint8_t gray = progmem_read_byte(this->data_start_ + pos);

View File

@@ -333,6 +333,9 @@ class DisplayBuffer {
/// Internal method to set the display rotation with.
void set_rotation(DisplayRotation rotation);
// Internal method to set display auto clearing.
void set_auto_clear(bool auto_clear_enabled) { this->auto_clear_enabled_ = auto_clear_enabled; }
protected:
void vprintf_(int x, int y, Font *font, Color color, TextAlign align, const char *format, va_list arg);
@@ -352,6 +355,7 @@ class DisplayBuffer {
DisplayPage *page_{nullptr};
DisplayPage *previous_page_{nullptr};
std::vector<DisplayOnPageChangeTrigger *> on_page_change_triggers_;
bool auto_clear_enabled_{true};
};
class DisplayPage {

View File

@@ -42,7 +42,7 @@ class ColorUtil {
? esp_scale(((colorcode >> third_bits) & ((1 << second_bits) - 1)), ((1 << second_bits) - 1))
: esp_scale(((colorcode >> 8) & 0xFF), ((1 << second_bits) - 1));
third_color = (right_bit_aligned ? esp_scale(((colorcode >> 0) & 0xFF), ((1 << third_bits) - 1))
third_color = (right_bit_aligned ? esp_scale(((colorcode >> 0) & ((1 << third_bits) - 1)), ((1 << third_bits) - 1))
: esp_scale(((colorcode >> 0) & 0xFF), (1 << third_bits) - 1));
Color color_return;

View File

@@ -1,9 +1,11 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import pins
from esphome.components import uart
from esphome.const import (
CONF_ID,
CONF_UART_ID,
CONF_RECEIVE_TIMEOUT,
)
CODEOWNERS = ["@glmnet", "@zuidwijk"]
@@ -11,10 +13,13 @@ CODEOWNERS = ["@glmnet", "@zuidwijk"]
DEPENDENCIES = ["uart"]
AUTO_LOAD = ["sensor", "text_sensor"]
CONF_DSMR_ID = "dsmr_id"
CONF_DECRYPTION_KEY = "decryption_key"
CONF_CRC_CHECK = "crc_check"
CONF_DECRYPTION_KEY = "decryption_key"
CONF_DSMR_ID = "dsmr_id"
CONF_GAS_MBUS_ID = "gas_mbus_id"
CONF_MAX_TELEGRAM_LENGTH = "max_telegram_length"
CONF_REQUEST_INTERVAL = "request_interval"
CONF_REQUEST_PIN = "request_pin"
# Hack to prevent compile error due to ambiguity with lib namespace
dsmr_ns = cg.esphome_ns.namespace("esphome::dsmr")
@@ -46,6 +51,14 @@ CONFIG_SCHEMA = cv.All(
cv.Optional(CONF_DECRYPTION_KEY): _validate_key,
cv.Optional(CONF_CRC_CHECK, default=True): cv.boolean,
cv.Optional(CONF_GAS_MBUS_ID, default=1): cv.int_,
cv.Optional(CONF_MAX_TELEGRAM_LENGTH, default=1500): cv.int_,
cv.Optional(CONF_REQUEST_PIN): pins.gpio_output_pin_schema,
cv.Optional(
CONF_REQUEST_INTERVAL, default="0ms"
): cv.positive_time_period_milliseconds,
cv.Optional(
CONF_RECEIVE_TIMEOUT, default="200ms"
): cv.positive_time_period_milliseconds,
}
).extend(uart.UART_DEVICE_SCHEMA),
cv.only_with_arduino,
@@ -55,10 +68,17 @@ CONFIG_SCHEMA = cv.All(
async def to_code(config):
uart_component = await cg.get_variable(config[CONF_UART_ID])
var = cg.new_Pvariable(config[CONF_ID], uart_component, config[CONF_CRC_CHECK])
cg.add(var.set_max_telegram_length(config[CONF_MAX_TELEGRAM_LENGTH]))
if CONF_DECRYPTION_KEY in config:
cg.add(var.set_decryption_key(config[CONF_DECRYPTION_KEY]))
await cg.register_component(var, config)
if CONF_REQUEST_PIN in config:
request_pin = await cg.gpio_pin_expression(config[CONF_REQUEST_PIN])
cg.add(var.set_request_pin(request_pin))
cg.add(var.set_request_interval(config[CONF_REQUEST_INTERVAL].total_milliseconds))
cg.add(var.set_receive_timeout(config[CONF_RECEIVE_TIMEOUT].total_milliseconds))
cg.add_define("DSMR_GAS_MBUS_ID", config[CONF_GAS_MBUS_ID])
# DSMR Parser

View File

@@ -12,156 +12,275 @@ namespace dsmr {
static const char *const TAG = "dsmr";
void Dsmr::setup() {
this->telegram_ = new char[this->max_telegram_len_]; // NOLINT
if (this->request_pin_ != nullptr) {
this->request_pin_->setup();
}
}
void Dsmr::loop() {
if (this->decryption_key_.empty())
this->receive_telegram_();
else
this->receive_encrypted_();
if (this->ready_to_request_data_()) {
if (this->decryption_key_.empty()) {
this->receive_telegram_();
} else {
this->receive_encrypted_telegram_();
}
}
}
bool Dsmr::ready_to_request_data_() {
// When using a request pin, then wait for the next request interval.
if (this->request_pin_ != nullptr) {
if (!this->requesting_data_ && this->request_interval_reached_()) {
this->start_requesting_data_();
}
}
// Otherwise, sink serial data until next request interval.
else {
if (this->request_interval_reached_()) {
this->start_requesting_data_();
}
if (!this->requesting_data_) {
while (this->available()) {
this->read();
}
}
}
return this->requesting_data_;
}
bool Dsmr::request_interval_reached_() {
if (this->last_request_time_ == 0) {
return true;
}
return millis() - this->last_request_time_ > this->request_interval_;
}
bool Dsmr::receive_timeout_reached_() { return millis() - this->last_read_time_ > this->receive_timeout_; }
bool Dsmr::available_within_timeout_() {
// Data are available for reading on the UART bus?
// Then we can start reading right away.
if (this->available()) {
this->last_read_time_ = millis();
return true;
}
// When we're not in the process of reading a telegram, then there is
// no need to actively wait for new data to come in.
if (!header_found_) {
return false;
}
// A telegram is being read. The smart meter might not deliver a telegram
// in one go, but instead send it in chunks with small pauses in between.
// When the UART RX buffer cannot hold a full telegram, then make sure
// that the UART read buffer does not overflow while other components
// perform their work in their loop. Do this by not returning control to
// the main loop, until the read timeout is reached.
if (this->parent_->get_rx_buffer_size() < this->max_telegram_len_) {
while (!this->receive_timeout_reached_()) {
delay(5);
if (this->available()) {
this->last_read_time_ = millis();
return true;
}
}
}
// No new data has come in during the read timeout? Then stop reading the
// telegram and start waiting for the next one to arrive.
if (this->receive_timeout_reached_()) {
ESP_LOGW(TAG, "Timeout while reading data for telegram");
this->reset_telegram_();
}
return false;
}
void Dsmr::start_requesting_data_() {
if (!this->requesting_data_) {
if (this->request_pin_ != nullptr) {
ESP_LOGV(TAG, "Start requesting data from P1 port");
this->request_pin_->digital_write(true);
} else {
ESP_LOGV(TAG, "Start reading data from P1 port");
}
this->requesting_data_ = true;
this->last_request_time_ = millis();
}
}
void Dsmr::stop_requesting_data_() {
if (this->requesting_data_) {
if (this->request_pin_ != nullptr) {
ESP_LOGV(TAG, "Stop requesting data from P1 port");
this->request_pin_->digital_write(false);
} else {
ESP_LOGV(TAG, "Stop reading data from P1 port");
}
while (this->available()) {
this->read();
}
this->requesting_data_ = false;
}
}
void Dsmr::reset_telegram_() {
this->header_found_ = false;
this->footer_found_ = false;
this->bytes_read_ = 0;
this->crypt_bytes_read_ = 0;
this->crypt_telegram_len_ = 0;
this->last_read_time_ = 0;
}
void Dsmr::receive_telegram_() {
int count = MAX_BYTES_PER_LOOP;
while (available() && count-- > 0) {
const char c = read();
while (this->available_within_timeout_()) {
const char c = this->read();
// Find a new telegram header, i.e. forward slash.
if (c == '/') {
ESP_LOGV(TAG, "Header found");
header_found_ = true;
footer_found_ = false;
telegram_len_ = 0;
ESP_LOGV(TAG, "Header of telegram found");
this->reset_telegram_();
this->header_found_ = true;
}
if (!header_found_)
if (!this->header_found_)
continue;
// Check for buffer overflow.
if (telegram_len_ >= MAX_TELEGRAM_LENGTH) {
header_found_ = false;
footer_found_ = false;
ESP_LOGE(TAG, "Error: Message larger than buffer");
if (this->bytes_read_ >= this->max_telegram_len_) {
this->reset_telegram_();
ESP_LOGE(TAG, "Error: telegram larger than buffer (%d bytes)", this->max_telegram_len_);
return;
}
// Some v2.2 or v3 meters will send a new value which starts with '('
// in a new line while the value belongs to the previous ObisId. For
// proper parsing remove these new line characters
while (c == '(' && (telegram_[telegram_len_ - 1] == '\n' || telegram_[telegram_len_ - 1] == '\r'))
telegram_len_--;
// in a new line, while the value belongs to the previous ObisId. For
// proper parsing, remove these new line characters.
if (c == '(') {
while (true) {
auto previous_char = this->telegram_[this->bytes_read_ - 1];
if (previous_char == '\n' || previous_char == '\r') {
this->bytes_read_--;
} else {
break;
}
}
}
// Store the byte in the buffer.
telegram_[telegram_len_] = c;
telegram_len_++;
this->telegram_[this->bytes_read_] = c;
this->bytes_read_++;
// Check for a footer, i.e. exlamation mark, followed by a hex checksum.
if (c == '!') {
ESP_LOGV(TAG, "Footer found");
footer_found_ = true;
ESP_LOGV(TAG, "Footer of telegram found");
this->footer_found_ = true;
continue;
}
// Check for the end of the hex checksum, i.e. a newline.
if (footer_found_ && c == '\n') {
header_found_ = false;
if (this->footer_found_ && c == '\n') {
// Parse the telegram and publish sensor values.
if (parse_telegram())
return;
this->parse_telegram();
this->reset_telegram_();
return;
}
}
}
void Dsmr::receive_encrypted_() {
// Encrypted buffer
uint8_t buffer[MAX_TELEGRAM_LENGTH];
size_t buffer_length = 0;
void Dsmr::receive_encrypted_telegram_() {
while (this->available_within_timeout_()) {
const char c = this->read();
size_t packet_size = 0;
while (available()) {
const char c = read();
if (!header_found_) {
if ((uint8_t) c == 0xdb) {
ESP_LOGV(TAG, "Start byte 0xDB found");
header_found_ = true;
// Find a new telegram start byte.
if (!this->header_found_) {
if ((uint8_t) c != 0xDB) {
continue;
}
ESP_LOGV(TAG, "Start byte 0xDB of encrypted telegram found");
this->reset_telegram_();
this->header_found_ = true;
}
// Sanity check
if (!header_found_ || buffer_length >= MAX_TELEGRAM_LENGTH) {
if (buffer_length == 0) {
ESP_LOGE(TAG, "First byte of encrypted telegram should be 0xDB, aborting.");
} else {
ESP_LOGW(TAG, "Unexpected data");
}
this->status_momentary_warning("unexpected_data");
this->flush();
while (available())
read();
// Check for buffer overflow.
if (this->crypt_bytes_read_ >= this->max_telegram_len_) {
this->reset_telegram_();
ESP_LOGE(TAG, "Error: encrypted telegram larger than buffer (%d bytes)", this->max_telegram_len_);
return;
}
buffer[buffer_length++] = c;
// Store the byte in the buffer.
this->crypt_telegram_[this->crypt_bytes_read_] = c;
this->crypt_bytes_read_++;
if (packet_size == 0 && buffer_length > 20) {
// Complete header + a few bytes of data
packet_size = buffer[11] << 8 | buffer[12];
}
if (buffer_length == packet_size + 13 && packet_size > 0) {
ESP_LOGV(TAG, "Encrypted data: %d bytes", buffer_length);
GCM<AES128> *gcmaes128{new GCM<AES128>()};
gcmaes128->setKey(this->decryption_key_.data(), gcmaes128->keySize());
// the iv is 8 bytes of the system title + 4 bytes frame counter
// system title is at byte 2 and frame counter at byte 15
for (int i = 10; i < 14; i++)
buffer[i] = buffer[i + 4];
constexpr uint16_t iv_size{12};
gcmaes128->setIV(&buffer[2], iv_size);
gcmaes128->decrypt(reinterpret_cast<uint8_t *>(this->telegram_),
// the ciphertext start at byte 18
&buffer[18],
// cipher size
buffer_length - 17);
delete gcmaes128; // NOLINT(cppcoreguidelines-owning-memory)
telegram_len_ = strnlen(this->telegram_, sizeof(this->telegram_));
ESP_LOGV(TAG, "Decrypted data length: %d", telegram_len_);
ESP_LOGVV(TAG, "Decrypted data %s", this->telegram_);
parse_telegram();
telegram_len_ = 0;
return;
// Read the length of the incoming encrypted telegram.
if (this->crypt_telegram_len_ == 0 && this->crypt_bytes_read_ > 20) {
// Complete header + data bytes
this->crypt_telegram_len_ = 13 + (this->crypt_telegram_[11] << 8 | this->crypt_telegram_[12]);
ESP_LOGV(TAG, "Encrypted telegram length: %d bytes", this->crypt_telegram_len_);
}
if (!available()) {
// baud rate is 115200 for encrypted data, this means a few byte should arrive every time
// program runs faster than buffer loading then available() might return false in the middle
delay(4); // Wait for data
// Check for the end of the encrypted telegram.
if (this->crypt_telegram_len_ == 0 || this->crypt_bytes_read_ != this->crypt_telegram_len_) {
continue;
}
}
if (buffer_length > 0) {
ESP_LOGW(TAG, "Timeout while waiting for encrypted data or invalid data received.");
ESP_LOGV(TAG, "End of encrypted telegram found");
// Decrypt the encrypted telegram.
GCM<AES128> *gcmaes128{new GCM<AES128>()};
gcmaes128->setKey(this->decryption_key_.data(), gcmaes128->keySize());
// the iv is 8 bytes of the system title + 4 bytes frame counter
// system title is at byte 2 and frame counter at byte 15
for (int i = 10; i < 14; i++)
this->crypt_telegram_[i] = this->crypt_telegram_[i + 4];
constexpr uint16_t iv_size{12};
gcmaes128->setIV(&this->crypt_telegram_[2], iv_size);
gcmaes128->decrypt(reinterpret_cast<uint8_t *>(this->telegram_),
// the ciphertext start at byte 18
&this->crypt_telegram_[18],
// cipher size
this->crypt_bytes_read_ - 17);
delete gcmaes128; // NOLINT(cppcoreguidelines-owning-memory)
this->bytes_read_ = strnlen(this->telegram_, this->max_telegram_len_);
ESP_LOGV(TAG, "Decrypted telegram size: %d bytes", this->bytes_read_);
ESP_LOGVV(TAG, "Decrypted telegram: %s", this->telegram_);
// Parse the decrypted telegram and publish sensor values.
this->parse_telegram();
this->reset_telegram_();
return;
}
}
bool Dsmr::parse_telegram() {
MyData data;
ESP_LOGV(TAG, "Trying to parse");
ESP_LOGV(TAG, "Trying to parse telegram");
this->stop_requesting_data_();
::dsmr::ParseResult<void> res =
::dsmr::P1Parser::parse(&data, telegram_, telegram_len_, false,
::dsmr::P1Parser::parse(&data, this->telegram_, this->bytes_read_, false,
this->crc_check_); // Parse telegram according to data definition. Ignore unknown values.
if (res.err) {
// Parsing error, show it
auto err_str = res.fullError(telegram_, telegram_ + telegram_len_);
auto err_str = res.fullError(this->telegram_, this->telegram_ + this->bytes_read_);
ESP_LOGE(TAG, "%s", err_str.c_str());
return false;
} else {
this->status_clear_warning();
publish_sensors(data);
this->publish_sensors(data);
return true;
}
}
void Dsmr::dump_config() {
ESP_LOGCONFIG(TAG, "dsmr:");
ESP_LOGCONFIG(TAG, "DSMR:");
ESP_LOGCONFIG(TAG, " Max telegram length: %d", this->max_telegram_len_);
ESP_LOGCONFIG(TAG, " Receive timeout: %.1fs", this->receive_timeout_ / 1e3f);
if (this->request_pin_ != nullptr) {
LOG_PIN(" Request Pin: ", this->request_pin_);
}
if (this->request_interval_ > 0) {
ESP_LOGCONFIG(TAG, " Request Interval: %.1fs", this->request_interval_ / 1e3f);
}
#define DSMR_LOG_SENSOR(s) LOG_SENSOR(" ", #s, this->s_##s##_);
DSMR_SENSOR_LIST(DSMR_LOG_SENSOR, )
@@ -174,23 +293,31 @@ void Dsmr::set_decryption_key(const std::string &decryption_key) {
if (decryption_key.length() == 0) {
ESP_LOGI(TAG, "Disabling decryption");
this->decryption_key_.clear();
if (this->crypt_telegram_ != nullptr) {
delete[] this->crypt_telegram_;
this->crypt_telegram_ = nullptr;
}
return;
}
if (decryption_key.length() != 32) {
ESP_LOGE(TAG, "Error, decryption key must be 32 character long.");
ESP_LOGE(TAG, "Error, decryption key must be 32 character long");
return;
}
this->decryption_key_.clear();
ESP_LOGI(TAG, "Decryption key is set.");
ESP_LOGI(TAG, "Decryption key is set");
// Verbose level prints decryption key
ESP_LOGV(TAG, "Using decryption key: %s", decryption_key.c_str());
char temp[3] = {0};
for (int i = 0; i < 16; i++) {
strncpy(temp, &(decryption_key.c_str()[i * 2]), 2);
decryption_key_.push_back(std::strtoul(temp, nullptr, 16));
this->decryption_key_.push_back(std::strtoul(temp, nullptr, 16));
}
if (this->crypt_telegram_ == nullptr) {
this->crypt_telegram_ = new uint8_t[this->max_telegram_len_]; // NOLINT
}
}

View File

@@ -16,10 +16,6 @@
namespace esphome {
namespace dsmr {
static constexpr uint32_t MAX_TELEGRAM_LENGTH = 1500;
static constexpr uint32_t MAX_BYTES_PER_LOOP = 50;
static constexpr uint32_t POLL_TIMEOUT = 1000;
using namespace ::dsmr::fields;
// DSMR_**_LIST generated by ESPHome and written in esphome/core/defines
@@ -53,6 +49,7 @@ class Dsmr : public Component, public uart::UARTDevice {
public:
Dsmr(uart::UARTComponent *uart, bool crc_check) : uart::UARTDevice(uart), crc_check_(crc_check) {}
void setup() override;
void loop() override;
bool parse_telegram();
@@ -72,6 +69,10 @@ class Dsmr : public Component, public uart::UARTDevice {
void dump_config() override;
void set_decryption_key(const std::string &decryption_key);
void set_max_telegram_length(size_t length) { this->max_telegram_len_ = length; }
void set_request_pin(GPIOPin *request_pin) { this->request_pin_ = request_pin; }
void set_request_interval(uint32_t interval) { this->request_interval_ = interval; }
void set_receive_timeout(uint32_t timeout) { this->receive_timeout_ = timeout; }
// Sensor setters
#define DSMR_SET_SENSOR(s) \
@@ -84,13 +85,40 @@ class Dsmr : public Component, public uart::UARTDevice {
protected:
void receive_telegram_();
void receive_encrypted_();
void receive_encrypted_telegram_();
void reset_telegram_();
// Telegram buffer
char telegram_[MAX_TELEGRAM_LENGTH];
int telegram_len_{0};
/// Wait for UART data to become available within the read timeout.
///
/// The smart meter might provide data in chunks, causing available() to
/// return 0. When we're already reading a telegram, then we don't return
/// right away (to handle further data in an upcoming loop) but wait a
/// little while using this method to see if more data are incoming.
/// By not returning, we prevent other components from taking so much
/// time that the UART RX buffer overflows and bytes of the telegram get
/// lost in the process.
bool available_within_timeout_();
// Serial parser
// Request telegram
uint32_t request_interval_;
bool request_interval_reached_();
GPIOPin *request_pin_{nullptr};
uint32_t last_request_time_{0};
bool requesting_data_{false};
bool ready_to_request_data_();
void start_requesting_data_();
void stop_requesting_data_();
// Read telegram
uint32_t receive_timeout_;
bool receive_timeout_reached_();
size_t max_telegram_len_;
char *telegram_{nullptr};
size_t bytes_read_{0};
uint8_t *crypt_telegram_{nullptr};
size_t crypt_telegram_len_{0};
size_t crypt_bytes_read_{0};
uint32_t last_read_time_{0};
bool header_found_{false};
bool footer_found_{false};

View File

@@ -75,14 +75,14 @@ CONFIG_SCHEMA = cv.Schema(
UNIT_KILOVOLT_AMPS_REACTIVE_HOURS,
ICON_EMPTY,
3,
DEVICE_CLASS_ENERGY,
DEVICE_CLASS_EMPTY,
STATE_CLASS_NONE,
),
cv.Optional("total_exported_energy"): sensor.sensor_schema(
UNIT_KILOVOLT_AMPS_REACTIVE_HOURS,
ICON_EMPTY,
3,
DEVICE_CLASS_ENERGY,
DEVICE_CLASS_EMPTY,
STATE_CLASS_NONE,
),
cv.Optional("power_delivered"): sensor.sensor_schema(
@@ -166,42 +166,42 @@ CONFIG_SCHEMA = cv.Schema(
UNIT_KILOVOLT_AMPS_REACTIVE,
ICON_EMPTY,
3,
DEVICE_CLASS_POWER,
DEVICE_CLASS_EMPTY,
STATE_CLASS_MEASUREMENT,
),
cv.Optional("reactive_power_delivered_l2"): sensor.sensor_schema(
UNIT_KILOVOLT_AMPS_REACTIVE,
ICON_EMPTY,
3,
DEVICE_CLASS_POWER,
DEVICE_CLASS_EMPTY,
STATE_CLASS_MEASUREMENT,
),
cv.Optional("reactive_power_delivered_l3"): sensor.sensor_schema(
UNIT_KILOVOLT_AMPS_REACTIVE,
ICON_EMPTY,
3,
DEVICE_CLASS_POWER,
DEVICE_CLASS_EMPTY,
STATE_CLASS_MEASUREMENT,
),
cv.Optional("reactive_power_returned_l1"): sensor.sensor_schema(
UNIT_KILOVOLT_AMPS_REACTIVE,
ICON_EMPTY,
3,
DEVICE_CLASS_POWER,
DEVICE_CLASS_EMPTY,
STATE_CLASS_MEASUREMENT,
),
cv.Optional("reactive_power_returned_l2"): sensor.sensor_schema(
UNIT_KILOVOLT_AMPS_REACTIVE,
ICON_EMPTY,
3,
DEVICE_CLASS_POWER,
DEVICE_CLASS_EMPTY,
STATE_CLASS_MEASUREMENT,
),
cv.Optional("reactive_power_returned_l3"): sensor.sensor_schema(
UNIT_KILOVOLT_AMPS_REACTIVE,
ICON_EMPTY,
3,
DEVICE_CLASS_POWER,
DEVICE_CLASS_EMPTY,
STATE_CLASS_MEASUREMENT,
),
cv.Optional("voltage_l1"): sensor.sensor_schema(

View File

@@ -12,7 +12,6 @@ void DutyCycleSensor::setup() {
this->pin_->setup();
this->store_.pin = this->pin_->to_isr();
this->store_.last_level = this->pin_->digital_read();
this->last_update_ = micros();
this->store_.last_interrupt = micros();
this->pin_->attach_interrupt(DutyCycleSensorStore::gpio_intr, &this->store_, gpio::INTERRUPT_ANY_EDGE);
@@ -24,21 +23,24 @@ void DutyCycleSensor::dump_config() {
}
void DutyCycleSensor::update() {
const uint32_t now = micros();
const bool level = this->store_.last_level;
const uint32_t last_interrupt = this->store_.last_interrupt;
const uint32_t last_interrupt = this->store_.last_interrupt; // Read the measurement taken by the interrupt
uint32_t on_time = this->store_.on_time;
if (level)
on_time += now - last_interrupt;
const float total_time = float(now - this->last_update_);
const float value = (on_time / total_time) * 100.0f;
ESP_LOGD(TAG, "'%s' Got duty cycle=%.1f%%", this->get_name().c_str(), value);
this->publish_state(value);
this->store_.on_time = 0;
this->store_.on_time = 0; // Start new measurement, exactly aligned with the micros() reading
this->store_.last_interrupt = now;
if (this->last_update_ != 0) {
const bool level = this->store_.last_level;
if (level)
on_time += now - last_interrupt;
const float total_time = float(now - this->last_update_);
const float value = (on_time * 100.0f) / total_time;
ESP_LOGD(TAG, "'%s' Got duty cycle=%.1f%%", this->get_name().c_str(), value);
this->publish_state(value);
}
this->last_update_ = now;
}

View File

@@ -30,7 +30,7 @@ class DutyCycleSensor : public sensor::Sensor, public PollingComponent {
InternalGPIOPin *pin_;
DutyCycleSensorStore store_{};
uint32_t last_update_;
uint32_t last_update_{0};
};
} // namespace duty_cycle

View File

@@ -7,6 +7,7 @@ from esphome.helpers import write_file_if_changed
from esphome.const import (
CONF_BOARD,
CONF_FRAMEWORK,
CONF_SOURCE,
CONF_TYPE,
CONF_VARIANT,
CONF_VERSION,
@@ -21,14 +22,16 @@ from esphome.core import CORE, HexInt
import esphome.config_validation as cv
import esphome.codegen as cg
from .const import (
from .const import ( # noqa
KEY_BOARD,
KEY_ESP32,
KEY_SDKCONFIG_OPTIONS,
KEY_VARIANT,
VARIANT_ESP32C3,
VARIANT_FRIENDLY,
VARIANTS,
)
from .boards import BOARD_TO_VARIANT
# force import gpio to register pin schema
from .gpio import esp32_pin_to_code # noqa
@@ -49,7 +52,7 @@ def set_core_data(config):
elif conf[CONF_TYPE] == FRAMEWORK_ARDUINO:
CORE.data[KEY_CORE][KEY_TARGET_FRAMEWORK] = "arduino"
CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] = cv.Version.parse(
config[CONF_FRAMEWORK][CONF_VERSION_HINT]
config[CONF_FRAMEWORK][CONF_VERSION]
)
CORE.data[KEY_ESP32][KEY_BOARD] = config[CONF_BOARD]
CORE.data[KEY_ESP32][KEY_VARIANT] = config[CONF_VARIANT]
@@ -90,6 +93,13 @@ def _format_framework_arduino_version(ver: cv.Version) -> str:
return f"~3.{ver.major}{ver.minor:02d}{ver.patch:02d}.0"
def _format_framework_espidf_version(ver: cv.Version) -> str:
# format the given arduino (https://github.com/espressif/esp-idf/releases) version to
# a PIO platformio/framework-espidf value
# List of package versions: https://api.registry.platformio.org/v3/packages/platformio/tool/framework-espidf
return f"~3.{ver.major}{ver.minor:02d}{ver.patch:02d}.0"
# NOTE: Keep this in mind when updating the recommended version:
# * New framework historically have had some regressions, especially for WiFi.
# The new version needs to be thoroughly validated before changing the
@@ -119,119 +129,123 @@ ESP_IDF_PLATFORM_VERSION = cv.Version(3, 3, 2)
def _arduino_check_versions(value):
value = value.copy()
lookups = {
"dev": ("https://github.com/espressif/arduino-esp32.git", cv.Version(2, 0, 0)),
"latest": ("", cv.Version(1, 0, 3)),
"recommended": (
_format_framework_arduino_version(RECOMMENDED_ARDUINO_FRAMEWORK_VERSION),
RECOMMENDED_ARDUINO_FRAMEWORK_VERSION,
),
"dev": (cv.Version(2, 0, 0), "https://github.com/espressif/arduino-esp32.git"),
"latest": (cv.Version(1, 0, 6), None),
"recommended": (RECOMMENDED_ARDUINO_FRAMEWORK_VERSION, None),
}
ver_value = value[CONF_VERSION]
default_ver_hint = None
if ver_value.lower() in lookups:
default_ver_hint = str(lookups[ver_value.lower()][1])
ver_value = lookups[ver_value.lower()][0]
if value[CONF_VERSION] in lookups:
if CONF_SOURCE in value:
raise cv.Invalid(
"Framework version needs to be explicitly specified when custom source is used."
)
version, source = lookups[value[CONF_VERSION]]
else:
with cv.suppress_invalid():
ver = cv.Version.parse(cv.version_number(value))
if ver <= cv.Version(1, 0, 3):
ver_value = f"~2.{ver.major}{ver.minor:02d}{ver.patch:02d}.0"
else:
ver_value = f"~3.{ver.major}{ver.minor:02d}{ver.patch:02d}.0"
default_ver_hint = str(ver)
value[CONF_VERSION] = ver_value
version = cv.Version.parse(cv.version_number(value[CONF_VERSION]))
source = value.get(CONF_SOURCE, None)
if CONF_VERSION_HINT not in value and default_ver_hint is None:
raise cv.Invalid("Needs a version hint to understand the framework version")
value[CONF_VERSION] = str(version)
value[CONF_SOURCE] = source or _format_framework_arduino_version(version)
ver_hint_s = value.get(CONF_VERSION_HINT, default_ver_hint)
value[CONF_VERSION_HINT] = ver_hint_s
plat_ver = value.get(CONF_PLATFORM_VERSION, ARDUINO_PLATFORM_VERSION)
value[CONF_PLATFORM_VERSION] = str(plat_ver)
value[CONF_PLATFORM_VERSION] = value.get(
CONF_PLATFORM_VERSION, _parse_platform_version(str(ARDUINO_PLATFORM_VERSION))
)
if cv.Version.parse(ver_hint_s) != RECOMMENDED_ARDUINO_FRAMEWORK_VERSION:
if version != RECOMMENDED_ARDUINO_FRAMEWORK_VERSION:
_LOGGER.warning(
"The selected arduino framework version is not the recommended one"
)
_LOGGER.warning(
"If there are connectivity or build issues please remove the manual version"
"The selected Arduino framework version is not the recommended one. "
"If there are connectivity or build issues please remove the manual version."
)
return value
def _format_framework_espidf_version(ver: cv.Version) -> str:
# format the given arduino (https://github.com/espressif/esp-idf/releases) version to
# a PIO platformio/framework-espidf value
# List of package versions: https://api.registry.platformio.org/v3/packages/platformio/tool/framework-espidf
return f"~3.{ver.major}{ver.minor:02d}{ver.patch:02d}.0"
def _esp_idf_check_versions(value):
value = value.copy()
lookups = {
"dev": ("https://github.com/espressif/esp-idf.git", cv.Version(4, 3, 1)),
"latest": ("", cv.Version(4, 3, 0)),
"recommended": (
_format_framework_espidf_version(RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION),
RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION,
),
"dev": (cv.Version(4, 3, 1), "https://github.com/espressif/esp-idf.git"),
"latest": (cv.Version(4, 3, 0), None),
"recommended": (RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION, None),
}
ver_value = value[CONF_VERSION]
default_ver_hint = None
if ver_value.lower() in lookups:
default_ver_hint = str(lookups[ver_value.lower()][1])
ver_value = lookups[ver_value.lower()][0]
if value[CONF_VERSION] in lookups:
if CONF_SOURCE in value:
raise cv.Invalid(
"Framework version needs to be explicitly specified when custom source is used."
)
version, source = lookups[value[CONF_VERSION]]
else:
with cv.suppress_invalid():
ver = cv.Version.parse(cv.version_number(value))
ver_value = f"~3.{ver.major}{ver.minor:02d}{ver.patch:02d}.0"
default_ver_hint = str(ver)
value[CONF_VERSION] = ver_value
version = cv.Version.parse(cv.version_number(value[CONF_VERSION]))
source = value.get(CONF_SOURCE, None)
if CONF_VERSION_HINT not in value and default_ver_hint is None:
raise cv.Invalid("Needs a version hint to understand the framework version")
if version < cv.Version(4, 0, 0):
raise cv.Invalid("Only ESP-IDF 4.0+ is supported.")
ver_hint_s = value.get(CONF_VERSION_HINT, default_ver_hint)
value[CONF_VERSION_HINT] = ver_hint_s
if cv.Version.parse(ver_hint_s) < cv.Version(4, 0, 0):
raise cv.Invalid("Only ESP-IDF 4.0+ is supported")
if cv.Version.parse(ver_hint_s) != RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION:
value[CONF_VERSION] = str(version)
value[CONF_SOURCE] = source or _format_framework_espidf_version(version)
value[CONF_PLATFORM_VERSION] = value.get(
CONF_PLATFORM_VERSION, _parse_platform_version(str(ESP_IDF_PLATFORM_VERSION))
)
if version != RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION:
_LOGGER.warning(
"The selected esp-idf framework version is not the recommended one"
"The selected ESP-IDF framework version is not the recommended one. "
"If there are connectivity or build issues please remove the manual version."
)
_LOGGER.warning(
"If there are connectivity or build issues please remove the manual version"
)
plat_ver = value.get(CONF_PLATFORM_VERSION, ESP_IDF_PLATFORM_VERSION)
value[CONF_PLATFORM_VERSION] = str(plat_ver)
return value
CONF_VERSION_HINT = "version_hint"
def _parse_platform_version(value):
try:
# if platform version is a valid version constraint, prefix the default package
cv.platformio_version_constraint(value)
return f"platformio/espressif32 @ {value}"
except cv.Invalid:
return value
def _detect_variant(value):
if CONF_VARIANT not in value:
board = value[CONF_BOARD]
if board not in BOARD_TO_VARIANT:
raise cv.Invalid(
"This board is unknown, please set the variant manually",
path=[CONF_BOARD],
)
value = value.copy()
value[CONF_VARIANT] = BOARD_TO_VARIANT[board]
return value
CONF_PLATFORM_VERSION = "platform_version"
ARDUINO_FRAMEWORK_SCHEMA = cv.All(
cv.Schema(
{
cv.Optional(CONF_VERSION, default="recommended"): cv.string_strict,
cv.Optional(CONF_VERSION_HINT): cv.version_number,
cv.Optional(CONF_PLATFORM_VERSION): cv.string_strict,
cv.Optional(CONF_SOURCE): cv.string_strict,
cv.Optional(CONF_PLATFORM_VERSION): _parse_platform_version,
}
),
_arduino_check_versions,
)
CONF_SDKCONFIG_OPTIONS = "sdkconfig_options"
ESP_IDF_FRAMEWORK_SCHEMA = cv.All(
cv.Schema(
{
cv.Optional(CONF_VERSION, default="recommended"): cv.string_strict,
cv.Optional(CONF_VERSION_HINT): cv.version_number,
cv.Optional(CONF_SOURCE): cv.string_strict,
cv.Optional(CONF_PLATFORM_VERSION): _parse_platform_version,
cv.Optional(CONF_SDKCONFIG_OPTIONS, default={}): {
cv.string_strict: cv.string_strict
},
cv.Optional(CONF_PLATFORM_VERSION): cv.string_strict,
cv.Optional(CONF_ADVANCED, default={}): cv.Schema(
{
cv.Optional(CONF_IGNORE_EFUSE_MAC_CRC, default=False): cv.boolean,
@@ -260,12 +274,11 @@ CONFIG_SCHEMA = cv.All(
cv.Schema(
{
cv.Required(CONF_BOARD): cv.string_strict,
cv.Optional(CONF_VARIANT, default="ESP32"): cv.one_of(
*VARIANTS, upper=True
),
cv.Optional(CONF_VARIANT): cv.one_of(*VARIANTS, upper=True),
cv.Optional(CONF_FRAMEWORK, default={}): FRAMEWORK_SCHEMA,
}
),
_detect_variant,
set_core_data,
)
@@ -275,21 +288,21 @@ async def to_code(config):
cg.add_build_flag("-DUSE_ESP32")
cg.add_define("ESPHOME_BOARD", config[CONF_BOARD])
cg.add_build_flag(f"-DUSE_ESP32_VARIANT_{config[CONF_VARIANT]}")
cg.add_define("ESPHOME_VARIANT", VARIANT_FRIENDLY[config[CONF_VARIANT]])
cg.add_platformio_option("lib_ldf_mode", "off")
conf = config[CONF_FRAMEWORK]
cg.add_platformio_option("platform", conf[CONF_PLATFORM_VERSION])
if conf[CONF_TYPE] == FRAMEWORK_ESP_IDF:
cg.add_platformio_option(
"platform", f"espressif32 @ {conf[CONF_PLATFORM_VERSION]}"
)
cg.add_platformio_option("framework", "espidf")
cg.add_build_flag("-DUSE_ESP_IDF")
cg.add_build_flag("-DUSE_ESP32_FRAMEWORK_ESP_IDF")
cg.add_build_flag("-Wno-nonnull-compare")
cg.add_platformio_option(
"platform_packages",
[f"platformio/framework-espidf @ {conf[CONF_VERSION]}"],
[f"platformio/framework-espidf @ {conf[CONF_SOURCE]}"],
)
add_idf_sdkconfig_option("CONFIG_PARTITION_TABLE_SINGLE_APP", False)
add_idf_sdkconfig_option("CONFIG_PARTITION_TABLE_CUSTOM", True)
@@ -299,6 +312,15 @@ async def to_code(config):
add_idf_sdkconfig_option("CONFIG_COMPILER_OPTIMIZATION_DEFAULT", False)
add_idf_sdkconfig_option("CONFIG_COMPILER_OPTIMIZATION_SIZE", True)
# Increase freertos tick speed from 100Hz to 1kHz so that delay() resolution is 1ms
add_idf_sdkconfig_option("CONFIG_FREERTOS_HZ", 1000)
# Setup watchdog
add_idf_sdkconfig_option("CONFIG_ESP_TASK_WDT", True)
add_idf_sdkconfig_option("CONFIG_ESP_TASK_WDT_PANIC", True)
add_idf_sdkconfig_option("CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0", False)
add_idf_sdkconfig_option("CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU1", False)
cg.add_platformio_option("board_build.partitions", "partitions.csv")
for name, value in conf[CONF_SDKCONFIG_OPTIONS].items():
@@ -311,15 +333,12 @@ async def to_code(config):
)
elif conf[CONF_TYPE] == FRAMEWORK_ARDUINO:
cg.add_platformio_option(
"platform", f"espressif32 @ {conf[CONF_PLATFORM_VERSION]}"
)
cg.add_platformio_option("framework", "arduino")
cg.add_build_flag("-DUSE_ARDUINO")
cg.add_build_flag("-DUSE_ESP32_FRAMEWORK_ARDUINO")
cg.add_platformio_option(
"platform_packages",
[f"platformio/framework-arduinoespressif32 @ {conf[CONF_VERSION]}"],
[f"platformio/framework-arduinoespressif32 @ {conf[CONF_SOURCE]}"],
)
cg.add_platformio_option("board_build.partitions", "partitions.csv")

View File

@@ -1,3 +1,5 @@
from .const import VARIANT_ESP32, VARIANT_ESP32S2, VARIANT_ESP32C3
ESP32_BASE_PINS = {
"TX": 1,
"RX": 3,
@@ -925,3 +927,125 @@ ESP32_BOARD_PINS = {
},
"xinabox_cw02": {"LED": 27},
}
"""
BOARD_TO_VARIANT generated with:
git clone https://github.com/platformio/platform-espressif32
for x in platform-espressif32/boards/*.json; do
mcu=$(jq -r .build.mcu <"$x");
fname=$(basename "$x")
board="${fname%.*}"
variant=$(echo "$mcu" | tr '[:lower:]' '[:upper:]')
echo " \"$board\": VARIANT_${variant},"
done | sort
"""
BOARD_TO_VARIANT = {
"alksesp32": VARIANT_ESP32,
"az-delivery-devkit-v4": VARIANT_ESP32,
"bpi-bit": VARIANT_ESP32,
"briki_abc_esp32": VARIANT_ESP32,
"briki_mbc-wb_esp32": VARIANT_ESP32,
"d-duino-32": VARIANT_ESP32,
"esp320": VARIANT_ESP32,
"esp32-c3-devkitm-1": VARIANT_ESP32C3,
"esp32cam": VARIANT_ESP32,
"esp32-devkitlipo": VARIANT_ESP32,
"esp32dev": VARIANT_ESP32,
"esp32doit-devkit-v1": VARIANT_ESP32,
"esp32doit-espduino": VARIANT_ESP32,
"esp32-evb": VARIANT_ESP32,
"esp32-gateway": VARIANT_ESP32,
"esp32-poe-iso": VARIANT_ESP32,
"esp32-poe": VARIANT_ESP32,
"esp32-pro": VARIANT_ESP32,
"esp32-s2-kaluga-1": VARIANT_ESP32S2,
"esp32-s2-saola-1": VARIANT_ESP32S2,
"esp32thing_plus": VARIANT_ESP32,
"esp32thing": VARIANT_ESP32,
"esp32vn-iot-uno": VARIANT_ESP32,
"espea32": VARIANT_ESP32,
"espectro32": VARIANT_ESP32,
"espino32": VARIANT_ESP32,
"esp-wrover-kit": VARIANT_ESP32,
"etboard": VARIANT_ESP32,
"featheresp32-s2": VARIANT_ESP32S2,
"featheresp32": VARIANT_ESP32,
"firebeetle32": VARIANT_ESP32,
"fm-devkit": VARIANT_ESP32,
"frogboard": VARIANT_ESP32,
"healthypi4": VARIANT_ESP32,
"heltec_wifi_kit_32_v2": VARIANT_ESP32,
"heltec_wifi_kit_32": VARIANT_ESP32,
"heltec_wifi_lora_32_V2": VARIANT_ESP32,
"heltec_wifi_lora_32": VARIANT_ESP32,
"heltec_wireless_stick_lite": VARIANT_ESP32,
"heltec_wireless_stick": VARIANT_ESP32,
"honeylemon": VARIANT_ESP32,
"hornbill32dev": VARIANT_ESP32,
"hornbill32minima": VARIANT_ESP32,
"imbrios-logsens-v1p1": VARIANT_ESP32,
"inex_openkb": VARIANT_ESP32,
"intorobot": VARIANT_ESP32,
"iotaap_magnolia": VARIANT_ESP32,
"iotbusio": VARIANT_ESP32,
"iotbusproteus": VARIANT_ESP32,
"kits-edu": VARIANT_ESP32,
"labplus_mpython": VARIANT_ESP32,
"lolin32_lite": VARIANT_ESP32,
"lolin32": VARIANT_ESP32,
"lolin_d32_pro": VARIANT_ESP32,
"lolin_d32": VARIANT_ESP32,
"lopy4": VARIANT_ESP32,
"lopy": VARIANT_ESP32,
"m5stack-atom": VARIANT_ESP32,
"m5stack-core2": VARIANT_ESP32,
"m5stack-core-esp32": VARIANT_ESP32,
"m5stack-coreink": VARIANT_ESP32,
"m5stack-fire": VARIANT_ESP32,
"m5stack-grey": VARIANT_ESP32,
"m5stack-timer-cam": VARIANT_ESP32,
"m5stick-c": VARIANT_ESP32,
"magicbit": VARIANT_ESP32,
"mgbot-iotik32a": VARIANT_ESP32,
"mgbot-iotik32b": VARIANT_ESP32,
"mhetesp32devkit": VARIANT_ESP32,
"mhetesp32minikit": VARIANT_ESP32,
"microduino-core-esp32": VARIANT_ESP32,
"nano32": VARIANT_ESP32,
"nina_w10": VARIANT_ESP32,
"node32s": VARIANT_ESP32,
"nodemcu-32s": VARIANT_ESP32,
"nscreen-32": VARIANT_ESP32,
"odroid_esp32": VARIANT_ESP32,
"onehorse32dev": VARIANT_ESP32,
"oroca_edubot": VARIANT_ESP32,
"pico32": VARIANT_ESP32,
"piranha_esp32": VARIANT_ESP32,
"pocket_32": VARIANT_ESP32,
"pycom_gpy": VARIANT_ESP32,
"qchip": VARIANT_ESP32,
"quantum": VARIANT_ESP32,
"sensesiot_weizen": VARIANT_ESP32,
"sg-o_airMon": VARIANT_ESP32,
"s_odi_ultra": VARIANT_ESP32,
"sparkfun_lora_gateway_1-channel": VARIANT_ESP32,
"tinypico": VARIANT_ESP32,
"ttgo-lora32-v1": VARIANT_ESP32,
"ttgo-lora32-v21": VARIANT_ESP32,
"ttgo-lora32-v2": VARIANT_ESP32,
"ttgo-t1": VARIANT_ESP32,
"ttgo-t7-v13-mini32": VARIANT_ESP32,
"ttgo-t7-v14-mini32": VARIANT_ESP32,
"ttgo-t-beam": VARIANT_ESP32,
"ttgo-t-watch": VARIANT_ESP32,
"turta_iot_node": VARIANT_ESP32,
"vintlabs-devkit-v1": VARIANT_ESP32,
"wemosbat": VARIANT_ESP32,
"wemos_d1_mini32": VARIANT_ESP32,
"wesp32": VARIANT_ESP32,
"widora-air": VARIANT_ESP32,
"wifiduino32": VARIANT_ESP32,
"xinabox_cw02": VARIANT_ESP32,
}

View File

@@ -18,4 +18,12 @@ VARIANTS = [
VARIANT_ESP32H2,
]
VARIANT_FRIENDLY = {
VARIANT_ESP32: "ESP32",
VARIANT_ESP32S2: "ESP32-S2",
VARIANT_ESP32S3: "ESP32-S3",
VARIANT_ESP32C3: "ESP32-C3",
VARIANT_ESP32H2: "ESP32-H2",
}
esp32_ns = cg.esphome_ns.namespace("esp32")

View File

@@ -6,12 +6,17 @@
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <esp_idf_version.h>
#include <esp_task_wdt.h>
#include <soc/rtc.h>
#if ESP_IDF_VERSION_MAJOR >= 4
#include <hal/cpu_hal.h>
#endif
#ifdef USE_ARDUINO
#include <esp32-hal.h>
#endif
void setup();
void loop();
@@ -21,11 +26,7 @@ void IRAM_ATTR HOT yield() { vPortYield(); }
uint32_t IRAM_ATTR HOT millis() { return (uint32_t)(esp_timer_get_time() / 1000ULL); }
void IRAM_ATTR HOT delay(uint32_t ms) { vTaskDelay(ms / portTICK_PERIOD_MS); }
uint32_t IRAM_ATTR HOT micros() { return (uint32_t) esp_timer_get_time(); }
void IRAM_ATTR HOT delayMicroseconds(uint32_t us) {
auto start = (uint64_t) esp_timer_get_time();
while (((uint64_t) esp_timer_get_time()) - start < us)
;
}
void IRAM_ATTR HOT delayMicroseconds(uint32_t us) { delay_microseconds_safe(us); }
void arch_restart() {
esp_restart();
// restart() doesn't always end execution
@@ -33,24 +34,24 @@ void arch_restart() {
yield();
}
}
void IRAM_ATTR HOT arch_feed_wdt() {
#ifdef USE_ARDUINO
#if CONFIG_ARDUINO_RUNNING_CORE == 0
#ifdef CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU0
// ESP32 uses "Task Watchdog" which is hooked to the FreeRTOS idle task.
// To cause the Watchdog to be triggered we need to put the current task
// to sleep to get the idle task scheduled.
delay(1);
#endif
#endif
#endif // USE_ARDUINO
#ifdef USE_ESP_IDF
#ifdef CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU0
delay(1);
void arch_init() {
// Enable the task watchdog only on the loop task (from which we're currently running)
#if defined(USE_ESP_IDF)
esp_task_wdt_add(nullptr);
// Idle task watchdog is disabled on ESP-IDF
#elif defined(USE_ARDUINO)
enableLoopWDT();
// Disable idle task watchdog on the core we're using (Arduino pins the task to a core)
#if defined(CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0) && CONFIG_ARDUINO_RUNNING_CORE == 0
disableCore0WDT();
#endif
#if defined(CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU1) && CONFIG_ARDUINO_RUNNING_CORE == 1
disableCore1WDT();
#endif
#endif
#endif // USE_ESP_IDF
}
void IRAM_ATTR HOT arch_feed_wdt() { esp_task_wdt_reset(); }
uint8_t progmem_read_byte(const uint8_t *addr) { return *addr; }
uint32_t arch_get_cpu_cycle_count() {

View File

@@ -1,4 +1,5 @@
import logging
from dataclasses import dataclass
from typing import Any
from esphome.const import (
CONF_ID,
@@ -17,10 +18,24 @@ import esphome.config_validation as cv
import esphome.codegen as cg
from . import boards
from .const import KEY_BOARD, KEY_ESP32, esp32_ns
from .const import (
KEY_BOARD,
KEY_ESP32,
KEY_VARIANT,
VARIANT_ESP32,
VARIANT_ESP32C3,
VARIANT_ESP32S2,
VARIANT_ESP32S3,
VARIANT_ESP32H2,
esp32_ns,
)
_LOGGER = logging.getLogger(__name__)
from .gpio_esp32 import esp32_validate_gpio_pin, esp32_validate_supports
from .gpio_esp32_s2 import esp32_s2_validate_gpio_pin, esp32_s2_validate_supports
from .gpio_esp32_c3 import esp32_c3_validate_gpio_pin, esp32_c3_validate_supports
from .gpio_esp32_s3 import esp32_s3_validate_gpio_pin, esp32_s3_validate_supports
from .gpio_esp32_h2 import esp32_h2_validate_gpio_pin, esp32_h2_validate_supports
IDFInternalGPIOPin = esp32_ns.class_("IDFInternalGPIOPin", cg.InternalGPIOPin)
@@ -59,65 +74,61 @@ def _translate_pin(value):
return _lookup_pin(value)
_ESP_SDIO_PINS = {
6: "Flash Clock",
7: "Flash Data 0",
8: "Flash Data 1",
11: "Flash Command",
@dataclass
class ESP32ValidationFunctions:
pin_validation: Any
usage_validation: Any
_esp32_validations = {
VARIANT_ESP32: ESP32ValidationFunctions(
pin_validation=esp32_validate_gpio_pin, usage_validation=esp32_validate_supports
),
VARIANT_ESP32S2: ESP32ValidationFunctions(
pin_validation=esp32_s2_validate_gpio_pin,
usage_validation=esp32_s2_validate_supports,
),
VARIANT_ESP32C3: ESP32ValidationFunctions(
pin_validation=esp32_c3_validate_gpio_pin,
usage_validation=esp32_c3_validate_supports,
),
VARIANT_ESP32S3: ESP32ValidationFunctions(
pin_validation=esp32_s3_validate_gpio_pin,
usage_validation=esp32_s3_validate_supports,
),
VARIANT_ESP32H2: ESP32ValidationFunctions(
pin_validation=esp32_h2_validate_gpio_pin,
usage_validation=esp32_h2_validate_supports,
),
}
def validate_gpio_pin(value):
value = _translate_pin(value)
if value < 0 or value > 39:
raise cv.Invalid(f"Invalid pin number: {value} (must be 0-39)")
if value in _ESP_SDIO_PINS:
raise cv.Invalid(
f"This pin cannot be used on ESP32s and is already used by the flash interface (function: {_ESP_SDIO_PINS[value]})"
)
if 9 <= value <= 10:
_LOGGER.warning(
"Pin %s (9-10) might already be used by the "
"flash interface in QUAD IO flash mode.",
value,
)
if value in (20, 24, 28, 29, 30, 31):
# These pins are not exposed in GPIO mux (reason unknown)
# but they're missing from IO_MUX list in datasheet
raise cv.Invalid(f"The pin GPIO{value} is not usable on ESP32s.")
return value
variant = CORE.data[KEY_ESP32][KEY_VARIANT]
if variant not in _esp32_validations:
raise cv.Invalid("Unsupported ESP32 variant {variant}")
return _esp32_validations[variant].pin_validation(value)
def validate_supports(value):
num = value[CONF_NUMBER]
mode = value[CONF_MODE]
is_input = mode[CONF_INPUT]
is_output = mode[CONF_OUTPUT]
is_open_drain = mode[CONF_OPEN_DRAIN]
is_pullup = mode[CONF_PULLUP]
is_pulldown = mode[CONF_PULLDOWN]
variant = CORE.data[KEY_ESP32][KEY_VARIANT]
if variant not in _esp32_validations:
raise cv.Invalid("Unsupported ESP32 variant {variant}")
if is_input:
# All ESP32 pins support input mode
pass
if is_output and 34 <= num <= 39:
raise cv.Invalid(
f"GPIO{num} (34-39) does not support output pin mode.",
[CONF_MODE, CONF_OUTPUT],
)
if is_open_drain and not is_output:
raise cv.Invalid(
"Open-drain only works with output mode", [CONF_MODE, CONF_OPEN_DRAIN]
)
if is_pullup and 34 <= num <= 39:
raise cv.Invalid(
f"GPIO{num} (34-39) does not support pullups.", [CONF_MODE, CONF_PULLUP]
)
if is_pulldown and 34 <= num <= 39:
raise cv.Invalid(
f"GPIO{num} (34-39) does not support pulldowns.", [CONF_MODE, CONF_PULLDOWN]
)
value = _esp32_validations[variant].usage_validation(value)
if CORE.using_arduino:
# (input, output, open_drain, pullup, pulldown)
supported_modes = {
@@ -138,7 +149,6 @@ def validate_supports(value):
"This pin mode is not supported on ESP32 for arduino frameworks",
[CONF_MODE],
)
return value

View File

@@ -9,6 +9,22 @@ namespace esp32 {
static const char *const TAG = "esp32";
static int IRAM_ATTR flags_to_mode(gpio::Flags flags) {
if (flags == gpio::FLAG_INPUT) {
return INPUT;
} else if (flags == gpio::FLAG_OUTPUT) {
return OUTPUT;
} else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLUP)) {
return INPUT_PULLUP;
} else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLDOWN)) {
return INPUT_PULLDOWN;
} else if (flags == (gpio::FLAG_OUTPUT | gpio::FLAG_OPEN_DRAIN)) {
return OUTPUT_OPEN_DRAIN;
} else {
return 0;
}
}
struct ISRPinArg {
uint8_t pin;
bool inverted;
@@ -43,22 +59,9 @@ void ArduinoInternalGPIOPin::attach_interrupt(void (*func)(void *), void *arg, g
attachInterruptArg(pin_, func, arg, arduino_mode);
}
void ArduinoInternalGPIOPin::pin_mode(gpio::Flags flags) {
uint8_t mode;
if (flags == gpio::FLAG_INPUT) {
mode = INPUT;
} else if (flags == gpio::FLAG_OUTPUT) {
mode = OUTPUT;
} else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLUP)) {
mode = INPUT_PULLUP;
} else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLDOWN)) {
mode = INPUT_PULLDOWN;
} else if (flags == (gpio::FLAG_OUTPUT | gpio::FLAG_OPEN_DRAIN)) {
mode = OUTPUT_OPEN_DRAIN;
} else {
return;
}
pinMode(pin_, mode); // NOLINT
pinMode(pin_, flags_to_mode(flags)); // NOLINT
}
std::string ArduinoInternalGPIOPin::dump_summary() const {
@@ -101,6 +104,10 @@ void IRAM_ATTR ISRInternalGPIOPin::clear_interrupt() {
}
#endif
}
void IRAM_ATTR ISRInternalGPIOPin::pin_mode(gpio::Flags flags) {
auto *arg = reinterpret_cast<ISRPinArg *>(arg_);
pinMode(arg->pin, flags_to_mode(flags)); // NOLINT
}
} // namespace esphome

View File

@@ -0,0 +1,77 @@
import logging
from esphome.const import (
CONF_INPUT,
CONF_MODE,
CONF_NUMBER,
CONF_OUTPUT,
CONF_PULLDOWN,
CONF_PULLUP,
)
import esphome.config_validation as cv
_ESP_SDIO_PINS = {
6: "Flash Clock",
7: "Flash Data 0",
8: "Flash Data 1",
11: "Flash Command",
}
_ESP32_STRAPPING_PINS = {0, 2, 4, 12, 15}
_LOGGER = logging.getLogger(__name__)
def esp32_validate_gpio_pin(value):
if value < 0 or value > 39:
raise cv.Invalid(f"Invalid pin number: {value} (must be 0-39)")
if value in _ESP_SDIO_PINS:
raise cv.Invalid(
f"This pin cannot be used on ESP32s and is already used by the flash interface (function: {_ESP_SDIO_PINS[value]})"
)
if 9 <= value <= 10:
_LOGGER.warning(
"Pin %s (9-10) might already be used by the "
"flash interface in QUAD IO flash mode.",
value,
)
if value in _ESP32_STRAPPING_PINS:
_LOGGER.warning(
"GPIO%d is a Strapping PIN and should be avoided.\n"
"Attaching external pullup/down resistors to strapping pins can cause unexpected failures.\n"
"See https://esphome.io/guides/faq.html#why-am-i-getting-a-warning-about-strapping-pins",
value,
)
if value in (20, 24, 28, 29, 30, 31):
# These pins are not exposed in GPIO mux (reason unknown)
# but they're missing from IO_MUX list in datasheet
raise cv.Invalid(f"The pin GPIO{value} is not usable on ESP32s.")
return value
def esp32_validate_supports(value):
num = value[CONF_NUMBER]
mode = value[CONF_MODE]
is_input = mode[CONF_INPUT]
is_output = mode[CONF_OUTPUT]
is_pullup = mode[CONF_PULLUP]
is_pulldown = mode[CONF_PULLDOWN]
if is_input:
# All ESP32 pins support input mode
pass
if is_output and 34 <= num <= 39:
raise cv.Invalid(
f"GPIO{num} (34-39) does not support output pin mode.",
[CONF_MODE, CONF_OUTPUT],
)
if is_pullup and 34 <= num <= 39:
raise cv.Invalid(
f"GPIO{num} (34-39) does not support pullups.", [CONF_MODE, CONF_PULLUP]
)
if is_pulldown and 34 <= num <= 39:
raise cv.Invalid(
f"GPIO{num} (34-39) does not support pulldowns.", [CONF_MODE, CONF_PULLDOWN]
)
return value

View File

@@ -0,0 +1,53 @@
import logging
from esphome.const import (
CONF_INPUT,
CONF_MODE,
CONF_NUMBER,
)
import esphome.config_validation as cv
_ESP32C3_SPI_PSRAM_PINS = {
12: "SPIHD",
13: "SPIWP",
14: "SPICS0",
15: "SPICLK",
16: "SPID",
17: "SPIQ",
}
_ESP32C3_STRAPPING_PINS = {2, 8, 9}
_LOGGER = logging.getLogger(__name__)
def esp32_c3_validate_gpio_pin(value):
if value < 0 or value > 21:
raise cv.Invalid(f"Invalid pin number: {value} (must be 0-21)")
if value in _ESP32C3_SPI_PSRAM_PINS:
raise cv.Invalid(
f"This pin cannot be used on ESP32-C3s and is already used by the SPI/PSRAM interface (function: {_ESP32C3_SPI_PSRAM_PINS[value]})"
)
if value in _ESP32C3_STRAPPING_PINS:
_LOGGER.warning(
"GPIO%d is a Strapping PIN and should be avoided.\n"
"Attaching external pullup/down resistors to strapping pins can cause unexpected failures.\n"
"See https://esphome.io/guides/faq.html#why-am-i-getting-a-warning-about-strapping-pins",
value,
)
return value
def esp32_c3_validate_supports(value):
num = value[CONF_NUMBER]
mode = value[CONF_MODE]
is_input = mode[CONF_INPUT]
if num < 0 or num > 21:
raise cv.Invalid(f"Invalid pin number: {value} (must be 0-21)")
if is_input:
# All ESP32 pins support input mode
pass
return value

View File

@@ -0,0 +1,11 @@
import esphome.config_validation as cv
def esp32_h2_validate_gpio_pin(value):
# ESP32-H2 not yet supported
raise cv.Invalid("ESP32-H2 isn't supported yet")
def esp32_h2_validate_supports(value):
# ESP32-H2 not yet supported
raise cv.Invalid("ESP32-H2 isn't supported yet")

View File

@@ -0,0 +1,80 @@
import logging
from esphome.const import (
CONF_INPUT,
CONF_MODE,
CONF_NUMBER,
CONF_OUTPUT,
CONF_PULLDOWN,
CONF_PULLUP,
)
import esphome.config_validation as cv
_ESP32S2_SPI_PSRAM_PINS = {
26: "SPICS1",
27: "SPIHD",
28: "SPIWP",
29: "SPICS0",
30: "SPICLK",
31: "SPIQ",
32: "SPID",
}
_ESP32S2_STRAPPING_PINS = {0, 45, 46}
_LOGGER = logging.getLogger(__name__)
def esp32_s2_validate_gpio_pin(value):
if value < 0 or value > 46:
raise cv.Invalid(f"Invalid pin number: {value} (must be 0-46)")
if value in _ESP32S2_SPI_PSRAM_PINS:
raise cv.Invalid(
f"This pin cannot be used on ESP32-S2s and is already used by the SPI/PSRAM interface (function: {_ESP32S2_SPI_PSRAM_PINS[value]})"
)
if value in _ESP32S2_STRAPPING_PINS:
_LOGGER.warning(
"GPIO%d is a Strapping PIN and should be avoided.\n"
"Attaching external pullup/down resistors to strapping pins can cause unexpected failures.\n"
"See https://esphome.io/guides/faq.html#why-am-i-getting-a-warning-about-strapping-pins",
value,
)
if value in (22, 23, 24, 25):
# These pins are not exposed in GPIO mux (reason unknown)
# but they're missing from IO_MUX list in datasheet
raise cv.Invalid(f"The pin GPIO{value} is not usable on ESP32-S2s.")
return value
def esp32_s2_validate_supports(value):
num = value[CONF_NUMBER]
mode = value[CONF_MODE]
is_input = mode[CONF_INPUT]
is_output = mode[CONF_OUTPUT]
is_pullup = mode[CONF_PULLUP]
is_pulldown = mode[CONF_PULLDOWN]
if num < 0 or num > 46:
raise cv.Invalid(f"Invalid pin number: {num} (must be 0-46)")
if is_input:
# All ESP32 pins support input mode
pass
if is_output and num == 46:
raise cv.Invalid(
f"GPIO{num} does not support output pin mode.",
[CONF_MODE, CONF_OUTPUT],
)
if is_pullup and num == 46:
raise cv.Invalid(
f"GPIO{num} does not support pullups.", [CONF_MODE, CONF_PULLUP]
)
if is_pulldown and num == 46:
raise cv.Invalid(
f"GPIO{num} does not support pulldowns.", [CONF_MODE, CONF_PULLDOWN]
)
return value

View File

@@ -0,0 +1,74 @@
import logging
from esphome.const import (
CONF_INPUT,
CONF_MODE,
CONF_NUMBER,
)
import esphome.config_validation as cv
_ESP_32S3_SPI_PSRAM_PINS = {
26: "SPICS1",
27: "SPIHD",
28: "SPIWP",
29: "SPICS0",
30: "SPICLK",
31: "SPIQ",
32: "SPID",
}
_ESP_32_ESP32_S3R8_PSRAM_PINS = {
33: "SPIIO4",
34: "SPIIO5",
35: "SPIIO6",
36: "SPIIO7",
37: "SPIDQS",
}
_ESP_32S3_STRAPPING_PINS = {0, 3, 45, 46}
_LOGGER = logging.getLogger(__name__)
def esp32_s3_validate_gpio_pin(value):
if value < 0 or value > 48:
raise cv.Invalid(f"Invalid pin number: {value} (must be 0-46)")
if value in _ESP_32S3_SPI_PSRAM_PINS:
raise cv.Invalid(
f"This pin cannot be used on ESP32-S3s and is already used by the SPI/PSRAM interface(function: {_ESP_32S3_SPI_PSRAM_PINS[value]})"
)
if value in _ESP_32_ESP32_S3R8_PSRAM_PINS:
_LOGGER.warning(
"GPIO%d is used by the PSRAM interface on ESP32-S3R8 / ESP32-S3R8V and should be avoided on these models",
value,
)
if value in _ESP_32S3_STRAPPING_PINS:
_LOGGER.warning(
"GPIO%d is a Strapping PIN and should be avoided.\n"
"Attaching external pullup/down resistors to strapping pins can cause unexpected failures.\n"
"See https://esphome.io/guides/faq.html#why-am-i-getting-a-warning-about-strapping-pins",
value,
)
if value in (22, 23, 24, 25):
# These pins are not exposed in GPIO mux (reason unknown)
# but they're missing from IO_MUX list in datasheet
raise cv.Invalid(f"The pin GPIO{value} is not usable on ESP32-S3s.")
return value
def esp32_s3_validate_supports(value):
num = value[CONF_NUMBER]
mode = value[CONF_MODE]
is_input = mode[CONF_INPUT]
if num < 0 or num > 48:
raise cv.Invalid(f"Invalid pin number: {num} (must be 0-46)")
if is_input:
# All ESP32 pins support input mode
pass
return value

View File

@@ -10,38 +10,7 @@ static const char *const TAG = "esp32";
bool IDFInternalGPIOPin::isr_service_installed = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
struct ISRPinArg {
gpio_num_t pin;
bool inverted;
};
ISRInternalGPIOPin IDFInternalGPIOPin::to_isr() const {
auto *arg = new ISRPinArg{}; // NOLINT(cppcoreguidelines-owning-memory)
arg->pin = pin_;
arg->inverted = inverted_;
return ISRInternalGPIOPin((void *) arg);
}
void IDFInternalGPIOPin::setup() {
pin_mode(flags_);
gpio_set_drive_capability(pin_, drive_strength_);
}
void IDFInternalGPIOPin::pin_mode(gpio::Flags flags) {
gpio_config_t conf{};
conf.pin_bit_mask = 1ULL << static_cast<uint32_t>(pin_);
conf.mode = flags_to_mode(flags);
conf.pull_up_en = flags & gpio::FLAG_PULLUP ? GPIO_PULLUP_ENABLE : GPIO_PULLUP_DISABLE;
conf.pull_down_en = flags & gpio::FLAG_PULLDOWN ? GPIO_PULLDOWN_ENABLE : GPIO_PULLDOWN_DISABLE;
conf.intr_type = GPIO_INTR_DISABLE;
gpio_config(&conf);
}
bool IDFInternalGPIOPin::digital_read() { return bool(gpio_get_level(pin_)) != inverted_; }
void IDFInternalGPIOPin::digital_write(bool value) { gpio_set_level(pin_, value != inverted_ ? 1 : 0); }
gpio_mode_t IDFInternalGPIOPin::flags_to_mode(gpio::Flags flags) {
static gpio_mode_t IRAM_ATTR flags_to_mode(gpio::Flags flags) {
flags = (gpio::Flags)(flags & ~(gpio::FLAG_PULLUP | gpio::FLAG_PULLDOWN));
if (flags == gpio::FLAG_NONE) {
return GPIO_MODE_DISABLE;
@@ -61,6 +30,18 @@ gpio_mode_t IDFInternalGPIOPin::flags_to_mode(gpio::Flags flags) {
}
}
struct ISRPinArg {
gpio_num_t pin;
bool inverted;
};
ISRInternalGPIOPin IDFInternalGPIOPin::to_isr() const {
auto *arg = new ISRPinArg{}; // NOLINT(cppcoreguidelines-owning-memory)
arg->pin = pin_;
arg->inverted = inverted_;
return ISRInternalGPIOPin((void *) arg);
}
void IDFInternalGPIOPin::attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const {
gpio_int_type_t idf_type = GPIO_INTR_ANYEDGE;
switch (type) {
@@ -99,6 +80,35 @@ std::string IDFInternalGPIOPin::dump_summary() const {
return buffer;
}
void IDFInternalGPIOPin::setup() {
gpio_config_t conf{};
conf.pin_bit_mask = 1ULL << static_cast<uint32_t>(pin_);
conf.mode = flags_to_mode(flags_);
conf.pull_up_en = flags_ & gpio::FLAG_PULLUP ? GPIO_PULLUP_ENABLE : GPIO_PULLUP_DISABLE;
conf.pull_down_en = flags_ & gpio::FLAG_PULLDOWN ? GPIO_PULLDOWN_ENABLE : GPIO_PULLDOWN_DISABLE;
conf.intr_type = GPIO_INTR_DISABLE;
gpio_config(&conf);
gpio_set_drive_capability(pin_, drive_strength_);
}
void IDFInternalGPIOPin::pin_mode(gpio::Flags flags) {
// can't call gpio_config here because that logs in esp-idf which may cause issues
gpio_set_direction(pin_, flags_to_mode(flags));
gpio_pull_mode_t pull_mode = GPIO_FLOATING;
if (flags & (gpio::FLAG_PULLUP | gpio::FLAG_PULLDOWN)) {
pull_mode = GPIO_PULLUP_PULLDOWN;
} else if (flags & gpio::FLAG_PULLUP) {
pull_mode = GPIO_PULLUP_ONLY;
} else if (flags & gpio::FLAG_PULLDOWN) {
pull_mode = GPIO_PULLDOWN_ONLY;
}
gpio_set_pull_mode(pin_, pull_mode);
}
bool IDFInternalGPIOPin::digital_read() { return bool(gpio_get_level(pin_)) != inverted_; }
void IDFInternalGPIOPin::digital_write(bool value) { gpio_set_level(pin_, value != inverted_ ? 1 : 0); }
void IDFInternalGPIOPin::detach_interrupt() const { gpio_intr_disable(pin_); }
} // namespace esp32
using namespace esp32;
@@ -114,6 +124,19 @@ void IRAM_ATTR ISRInternalGPIOPin::digital_write(bool value) {
void IRAM_ATTR ISRInternalGPIOPin::clear_interrupt() {
// not supported
}
void IRAM_ATTR ISRInternalGPIOPin::pin_mode(gpio::Flags flags) {
auto *arg = reinterpret_cast<ISRPinArg *>(arg_);
gpio_set_direction(arg->pin, flags_to_mode(flags));
gpio_pull_mode_t pull_mode = GPIO_FLOATING;
if (flags & (gpio::FLAG_PULLUP | gpio::FLAG_PULLDOWN)) {
pull_mode = GPIO_PULLUP_PULLDOWN;
} else if (flags & gpio::FLAG_PULLUP) {
pull_mode = GPIO_PULLUP_ONLY;
} else if (flags & gpio::FLAG_PULLDOWN) {
pull_mode = GPIO_PULLDOWN_ONLY;
}
gpio_set_pull_mode(arg->pin, pull_mode);
}
} // namespace esphome

View File

@@ -18,13 +18,12 @@ class IDFInternalGPIOPin : public InternalGPIOPin {
bool digital_read() override;
void digital_write(bool value) override;
std::string dump_summary() const override;
void detach_interrupt() const override { gpio_intr_disable(pin_); }
void detach_interrupt() const override;
ISRInternalGPIOPin to_isr() const override;
uint8_t get_pin() const override { return (uint8_t) pin_; }
bool is_inverted() const override { return inverted_; }
protected:
static gpio_mode_t flags_to_mode(gpio::Flags flags);
void attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const override;
gpio_num_t pin_;

View File

@@ -76,6 +76,7 @@ class ESP32Preferences : public ESPPreferences {
uint32_t current_offset = 0;
void open() {
nvs_flash_init();
esp_err_t err = nvs_open("esphome", NVS_READWRITE, &nvs_handle);
if (err == 0)
return;

View File

@@ -57,7 +57,7 @@ void ESP32BLEBeacon::setup() {
);
}
float ESP32BLEBeacon::get_setup_priority() const { return setup_priority::DATA; }
float ESP32BLEBeacon::get_setup_priority() const { return setup_priority::BLUETOOTH; }
void ESP32BLEBeacon::ble_core_task(void *params) {
ble_setup();

View File

@@ -98,19 +98,20 @@ bool BLEServer::create_device_characteristics_() {
return true;
}
BLEService *BLEServer::create_service(const uint8_t *uuid, bool advertise) {
std::shared_ptr<BLEService> BLEServer::create_service(const uint8_t *uuid, bool advertise) {
return this->create_service(ESPBTUUID::from_raw(uuid), advertise);
}
BLEService *BLEServer::create_service(uint16_t uuid, bool advertise) {
std::shared_ptr<BLEService> BLEServer::create_service(uint16_t uuid, bool advertise) {
return this->create_service(ESPBTUUID::from_uint16(uuid), advertise);
}
BLEService *BLEServer::create_service(const std::string &uuid, bool advertise) {
std::shared_ptr<BLEService> BLEServer::create_service(const std::string &uuid, bool advertise) {
return this->create_service(ESPBTUUID::from_raw(uuid), advertise);
}
BLEService *BLEServer::create_service(ESPBTUUID uuid, bool advertise, uint16_t num_handles, uint8_t inst_id) {
std::shared_ptr<BLEService> BLEServer::create_service(ESPBTUUID uuid, bool advertise, uint16_t num_handles,
uint8_t inst_id) {
ESP_LOGV(TAG, "Creating service - %s", uuid.to_string().c_str());
BLEService *service = new BLEService(uuid, num_handles, inst_id); // NOLINT(cppcoreguidelines-owning-memory)
this->services_.push_back(service);
std::shared_ptr<BLEService> service = std::make_shared<BLEService>(uuid, num_handles, inst_id);
this->services_.emplace_back(service);
if (advertise) {
esp32_ble::global_ble->get_advertising()->add_service_uuid(uuid);
}
@@ -149,12 +150,12 @@ void BLEServer::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t ga
break;
}
for (auto *service : this->services_) {
for (const auto &service : this->services_) {
service->gatts_event_handler(event, gatts_if, param);
}
}
float BLEServer::get_setup_priority() const { return setup_priority::BLUETOOTH - 10; }
float BLEServer::get_setup_priority() const { return setup_priority::AFTER_BLUETOOTH + 10; }
void BLEServer::dump_config() { ESP_LOGCONFIG(TAG, "ESP32 BLE Server:"); }

View File

@@ -11,6 +11,7 @@
#include "esphome/core/preferences.h"
#include <map>
#include <memory>
#ifdef USE_ESP32
@@ -43,10 +44,11 @@ class BLEServer : public Component {
void set_manufacturer(const std::string &manufacturer) { this->manufacturer_ = manufacturer; }
void set_model(const std::string &model) { this->model_ = model; }
BLEService *create_service(const uint8_t *uuid, bool advertise = false);
BLEService *create_service(uint16_t uuid, bool advertise = false);
BLEService *create_service(const std::string &uuid, bool advertise = false);
BLEService *create_service(ESPBTUUID uuid, bool advertise = false, uint16_t num_handles = 15, uint8_t inst_id = 0);
std::shared_ptr<BLEService> create_service(const uint8_t *uuid, bool advertise = false);
std::shared_ptr<BLEService> create_service(uint16_t uuid, bool advertise = false);
std::shared_ptr<BLEService> create_service(const std::string &uuid, bool advertise = false);
std::shared_ptr<BLEService> create_service(ESPBTUUID uuid, bool advertise = false, uint16_t num_handles = 15,
uint8_t inst_id = 0);
esp_gatt_if_t get_gatts_if() { return this->gatts_if_; }
uint32_t get_connected_client_count() { return this->connected_clients_; }
@@ -74,8 +76,8 @@ class BLEServer : public Component {
uint32_t connected_clients_{0};
std::map<uint16_t, void *> clients_;
std::vector<BLEService *> services_;
BLEService *device_information_service_;
std::vector<std::shared_ptr<BLEService>> services_;
std::shared_ptr<BLEService> device_information_service_;
std::vector<BLEServiceComponent *> service_components_;

View File

@@ -18,7 +18,6 @@ from esphome.core import CORE
from esphome.components.esp32 import add_idf_sdkconfig_option
DEPENDENCIES = ["esp32"]
AUTO_LOAD = ["xiaomi_ble", "ruuvi_ble"]
CONF_ESP32_BLE_ID = "esp32_ble_id"
CONF_SCAN_PARAMETERS = "scan_parameters"

View File

@@ -40,6 +40,8 @@ uint64_t ble_addr_to_uint64(const esp_bd_addr_t address) {
return u;
}
float ESP32BLETracker::get_setup_priority() const { return setup_priority::BLUETOOTH; }
void ESP32BLETracker::setup() {
global_esp32_ble_tracker = this;
this->scan_result_lock_ = xSemaphoreCreateMutex();
@@ -481,6 +483,7 @@ optional<ESPBLEiBeacon> ESPBLEiBeacon::from_manufacturer_data(const ServiceData
}
void ESPBTDevice::parse_scan_rst(const esp_ble_gap_cb_param_t::ble_scan_result_evt_param &param) {
this->scan_result_ = param;
for (uint8_t i = 0; i < ESP_BD_ADDR_LEN; i++)
this->address_[i] = param.bda[i];
this->address_type_ = param.ble_addr_type;
@@ -522,7 +525,7 @@ void ESPBTDevice::parse_scan_rst(const esp_ble_gap_cb_param_t::ble_scan_result_e
ESP_LOGVV(TAG, " Service UUID: %s", uuid.to_string().c_str());
}
for (auto &data : this->manufacturer_datas_) {
ESP_LOGVV(TAG, " Manufacturer data: %s", hexencode(data.data).c_str());
ESP_LOGVV(TAG, " Manufacturer data: %s", format_hex_pretty(data.data).c_str());
if (this->get_ibeacon().has_value()) {
auto ibeacon = this->get_ibeacon().value();
ESP_LOGVV(TAG, " iBeacon data:");
@@ -535,10 +538,10 @@ void ESPBTDevice::parse_scan_rst(const esp_ble_gap_cb_param_t::ble_scan_result_e
for (auto &data : this->service_datas_) {
ESP_LOGVV(TAG, " Service data:");
ESP_LOGVV(TAG, " UUID: %s", data.uuid.to_string().c_str());
ESP_LOGVV(TAG, " Data: %s", hexencode(data.data).c_str());
ESP_LOGVV(TAG, " Data: %s", format_hex_pretty(data.data).c_str());
}
ESP_LOGVV(TAG, "Adv data: %s", hexencode(param.ble_adv, param.adv_data_len + param.scan_rsp_len).c_str());
ESP_LOGVV(TAG, "Adv data: %s", format_hex_pretty(param.ble_adv, param.adv_data_len + param.scan_rsp_len).c_str());
#endif
}
void ESPBTDevice::parse_adv_(const esp_ble_gap_cb_param_t::ble_scan_result_evt_param &param) {

View File

@@ -97,6 +97,8 @@ class ESPBTDevice {
const std::vector<ServiceData> &get_service_datas() const { return service_datas_; }
const esp_ble_gap_cb_param_t::ble_scan_result_evt_param &get_scan_result() const { return scan_result_; }
optional<ESPBLEiBeacon> get_ibeacon() const {
for (auto &it : this->manufacturer_datas_) {
auto res = ESPBLEiBeacon::from_manufacturer_data(it);
@@ -121,6 +123,7 @@ class ESPBTDevice {
std::vector<ESPBTUUID> service_uuids_;
std::vector<ServiceData> manufacturer_datas_{};
std::vector<ServiceData> service_datas_{};
esp_ble_gap_cb_param_t::ble_scan_result_evt_param scan_result_{};
};
class ESP32BLETracker;
@@ -171,6 +174,7 @@ class ESP32BLETracker : public Component {
/// Setup the FreeRTOS task and the Bluetooth stack.
void setup() override;
void dump_config() override;
float get_setup_priority() const override;
void loop() override;

View File

@@ -2,10 +2,8 @@ import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import pins
from esphome.const import (
CONF_DISABLED_BY_DEFAULT,
CONF_FREQUENCY,
CONF_ID,
CONF_NAME,
CONF_PIN,
CONF_SCL,
CONF_SDA,
@@ -17,8 +15,9 @@ from esphome.const import (
)
from esphome.core import CORE
from esphome.components.esp32 import add_idf_sdkconfig_option
from esphome.cpp_helpers import setup_entity
DEPENDENCIES = ["esp32", "api"]
DEPENDENCIES = ["esp32"]
esp32_camera_ns = cg.esphome_ns.namespace("esp32_camera")
ESP32Camera = esp32_camera_ns.class_("ESP32Camera", cg.PollingComponent, cg.EntityBase)
@@ -58,16 +57,17 @@ CONF_IDLE_FRAMERATE = "idle_framerate"
CONF_JPEG_QUALITY = "jpeg_quality"
CONF_VERTICAL_FLIP = "vertical_flip"
CONF_HORIZONTAL_MIRROR = "horizontal_mirror"
CONF_AEC2 = "aec2"
CONF_AE_LEVEL = "ae_level"
CONF_AEC_VALUE = "aec_value"
CONF_SATURATION = "saturation"
CONF_TEST_PATTERN = "test_pattern"
camera_range_param = cv.int_range(min=-2, max=2)
CONFIG_SCHEMA = cv.Schema(
CONFIG_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(ESP32Camera),
cv.Required(CONF_NAME): cv.string,
cv.Optional(CONF_DISABLED_BY_DEFAULT, default=False): cv.boolean,
cv.Required(CONF_DATA_PINS): cv.All(
[pins.internal_gpio_input_pin_number], cv.Length(min=8, max=8)
),
@@ -105,6 +105,9 @@ CONFIG_SCHEMA = cv.Schema(
cv.Optional(CONF_SATURATION, default=0): camera_range_param,
cv.Optional(CONF_VERTICAL_FLIP, default=True): cv.boolean,
cv.Optional(CONF_HORIZONTAL_MIRROR, default=True): cv.boolean,
cv.Optional(CONF_AEC2, default=False): cv.boolean,
cv.Optional(CONF_AE_LEVEL, default=0): camera_range_param,
cv.Optional(CONF_AEC_VALUE, default=300): cv.int_range(min=0, max=1200),
cv.Optional(CONF_TEST_PATTERN, default=False): cv.boolean,
}
).extend(cv.COMPONENT_SCHEMA)
@@ -119,6 +122,9 @@ SETTERS = {
CONF_JPEG_QUALITY: "set_jpeg_quality",
CONF_VERTICAL_FLIP: "set_vertical_flip",
CONF_HORIZONTAL_MIRROR: "set_horizontal_mirror",
CONF_AEC2: "set_aec2",
CONF_AE_LEVEL: "set_ae_level",
CONF_AEC_VALUE: "set_aec_value",
CONF_CONTRAST: "set_contrast",
CONF_BRIGHTNESS: "set_brightness",
CONF_SATURATION: "set_saturation",
@@ -127,8 +133,8 @@ SETTERS = {
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID], config[CONF_NAME])
cg.add(var.set_disabled_by_default(config[CONF_DISABLED_BY_DEFAULT]))
var = cg.new_Pvariable(config[CONF_ID])
await setup_entity(var, config)
await cg.register_component(var, config)
for key, setter in SETTERS.items():

View File

@@ -26,6 +26,9 @@ void ESP32Camera::setup() {
sensor_t *s = esp_camera_sensor_get();
s->set_vflip(s, this->vertical_flip_);
s->set_hmirror(s, this->horizontal_mirror_);
s->set_aec2(s, this->aec2_); // 0 = disable , 1 = enable
s->set_ae_level(s, this->ae_level_); // -2 to 2
s->set_aec_value(s, this->aec_value_); // 0 to 1200
s->set_contrast(s, this->contrast_);
s->set_brightness(s, this->brightness_);
s->set_saturation(s, this->saturation_);
@@ -45,6 +48,7 @@ void ESP32Camera::dump_config() {
auto conf = this->config_;
ESP_LOGCONFIG(TAG, "ESP32 Camera:");
ESP_LOGCONFIG(TAG, " Name: %s", this->name_.c_str());
ESP_LOGCONFIG(TAG, " Internal: %s", YESNO(this->internal_));
#ifdef USE_ARDUINO
ESP_LOGCONFIG(TAG, " Board Has PSRAM: %s", YESNO(psramFound()));
#endif // USE_ARDUINO
@@ -110,9 +114,9 @@ void ESP32Camera::dump_config() {
// ESP_LOGCONFIG(TAG, " Auto White Balance: %u", st.awb);
// ESP_LOGCONFIG(TAG, " Auto White Balance Gain: %u", st.awb_gain);
// ESP_LOGCONFIG(TAG, " Auto Exposure Control: %u", st.aec);
// ESP_LOGCONFIG(TAG, " Auto Exposure Control 2: %u", st.aec2);
// ESP_LOGCONFIG(TAG, " Auto Exposure Level: %d", st.ae_level);
// ESP_LOGCONFIG(TAG, " Auto Exposure Value: %u", st.aec_value);
ESP_LOGCONFIG(TAG, " Auto Exposure Control 2: %u", st.aec2);
ESP_LOGCONFIG(TAG, " Auto Exposure Level: %d", st.ae_level);
ESP_LOGCONFIG(TAG, " Auto Exposure Value: %u", st.aec_value);
// ESP_LOGCONFIG(TAG, " AGC: %u", st.agc);
// ESP_LOGCONFIG(TAG, " AGC Gain: %u", st.agc_gain);
// ESP_LOGCONFIG(TAG, " Gain Ceiling: %u", st.gainceiling);
@@ -185,6 +189,7 @@ ESP32Camera::ESP32Camera(const std::string &name) : EntityBase(name) {
global_esp32_camera = this;
}
ESP32Camera::ESP32Camera() : ESP32Camera("") {}
void ESP32Camera::set_data_pins(std::array<uint8_t, 8> pins) {
this->config_.pin_d0 = pins[0];
this->config_.pin_d1 = pins[1];
@@ -248,6 +253,9 @@ void ESP32Camera::add_image_callback(std::function<void(std::shared_ptr<CameraIm
}
void ESP32Camera::set_vertical_flip(bool vertical_flip) { this->vertical_flip_ = vertical_flip; }
void ESP32Camera::set_horizontal_mirror(bool horizontal_mirror) { this->horizontal_mirror_ = horizontal_mirror; }
void ESP32Camera::set_aec2(bool aec2) { this->aec2_ = aec2; }
void ESP32Camera::set_ae_level(int ae_level) { this->ae_level_ = ae_level; }
void ESP32Camera::set_aec_value(uint32_t aec_value) { this->aec_value_ = aec_value; }
void ESP32Camera::set_contrast(int contrast) { this->contrast_ = contrast; }
void ESP32Camera::set_brightness(int brightness) { this->brightness_ = brightness; }
void ESP32Camera::set_saturation(int saturation) { this->saturation_ = saturation; }

View File

@@ -54,6 +54,7 @@ enum ESP32CameraFrameSize {
class ESP32Camera : public Component, public EntityBase {
public:
ESP32Camera(const std::string &name);
ESP32Camera();
void set_data_pins(std::array<uint8_t, 8> pins);
void set_vsync_pin(uint8_t pin);
void set_href_pin(uint8_t pin);
@@ -66,6 +67,9 @@ class ESP32Camera : public Component, public EntityBase {
void set_power_down_pin(uint8_t pin);
void set_vertical_flip(bool vertical_flip);
void set_horizontal_mirror(bool horizontal_mirror);
void set_aec2(bool aec2);
void set_ae_level(int ae_level);
void set_aec_value(uint32_t aec_value);
void set_contrast(int contrast);
void set_brightness(int brightness);
void set_saturation(int saturation);
@@ -90,6 +94,9 @@ class ESP32Camera : public Component, public EntityBase {
camera_config_t config_{};
bool vertical_flip_{true};
bool horizontal_mirror_{true};
bool aec2_{false};
int ae_level_{0};
uint32_t aec_value_{300};
int contrast_{0};
int brightness_{0};
int saturation_{0};

View File

@@ -0,0 +1,28 @@
import esphome.config_validation as cv
import esphome.codegen as cg
from esphome.const import CONF_ID, CONF_PORT, CONF_MODE
CODEOWNERS = ["@ayufan"]
DEPENDENCIES = ["esp32_camera"]
MULTI_CONF = True
esp32_camera_web_server_ns = cg.esphome_ns.namespace("esp32_camera_web_server")
CameraWebServer = esp32_camera_web_server_ns.class_("CameraWebServer", cg.Component)
Mode = esp32_camera_web_server_ns.enum("Mode")
MODES = {"STREAM": Mode.STREAM, "SNAPSHOT": Mode.SNAPSHOT}
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(): cv.declare_id(CameraWebServer),
cv.Required(CONF_PORT): cv.port,
cv.Required(CONF_MODE): cv.enum(MODES, upper=True),
},
).extend(cv.COMPONENT_SCHEMA)
async def to_code(config):
server = cg.new_Pvariable(config[CONF_ID])
cg.add(server.set_port(config[CONF_PORT]))
cg.add(server.set_mode(config[CONF_MODE]))
await cg.register_component(server, config)

View File

@@ -0,0 +1,248 @@
#ifdef USE_ESP32
#include "camera_web_server.h"
#include "esphome/core/application.h"
#include "esphome/core/hal.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#include "esphome/core/util.h"
#include <cstdlib>
#include <esp_http_server.h>
#include <utility>
namespace esphome {
namespace esp32_camera_web_server {
static const int IMAGE_REQUEST_TIMEOUT = 2000;
static const char *const TAG = "esp32_camera_web_server";
#define PART_BOUNDARY "123456789000000000000987654321"
#define CONTENT_TYPE "image/jpeg"
#define CONTENT_LENGTH "Content-Length"
static const char *const STREAM_HEADER = "HTTP/1.0 200 OK\r\n"
"Access-Control-Allow-Origin: *\r\n"
"Connection: close\r\n"
"Content-Type: multipart/x-mixed-replace;boundary=" PART_BOUNDARY "\r\n"
"\r\n"
"--" PART_BOUNDARY "\r\n";
static const char *const STREAM_ERROR = "Content-Type: text/plain\r\n"
"\r\n"
"No frames send.\r\n"
"--" PART_BOUNDARY "\r\n";
static const char *const STREAM_PART = "Content-Type: " CONTENT_TYPE "\r\n" CONTENT_LENGTH ": %u\r\n\r\n";
static const char *const STREAM_BOUNDARY = "\r\n"
"--" PART_BOUNDARY "\r\n";
CameraWebServer::CameraWebServer() {}
CameraWebServer::~CameraWebServer() {}
void CameraWebServer::setup() {
if (!esp32_camera::global_esp32_camera || esp32_camera::global_esp32_camera->is_failed()) {
this->mark_failed();
return;
}
this->semaphore_ = xSemaphoreCreateBinary();
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
config.server_port = this->port_;
config.ctrl_port = this->port_;
config.max_open_sockets = 1;
config.backlog_conn = 2;
config.lru_purge_enable = true;
if (httpd_start(&this->httpd_, &config) != ESP_OK) {
mark_failed();
return;
}
httpd_uri_t uri = {
.uri = "/",
.method = HTTP_GET,
.handler = [](struct httpd_req *req) { return ((CameraWebServer *) req->user_ctx)->handler_(req); },
.user_ctx = this};
httpd_register_uri_handler(this->httpd_, &uri);
esp32_camera::global_esp32_camera->add_image_callback([this](std::shared_ptr<esp32_camera::CameraImage> image) {
if (this->running_) {
this->image_ = std::move(image);
xSemaphoreGive(this->semaphore_);
}
});
}
void CameraWebServer::on_shutdown() {
this->running_ = false;
this->image_ = nullptr;
httpd_stop(this->httpd_);
this->httpd_ = nullptr;
vSemaphoreDelete(this->semaphore_);
this->semaphore_ = nullptr;
}
void CameraWebServer::dump_config() {
ESP_LOGCONFIG(TAG, "ESP32 Camera Web Server:");
ESP_LOGCONFIG(TAG, " Port: %d", this->port_);
if (this->mode_ == STREAM)
ESP_LOGCONFIG(TAG, " Mode: stream");
else
ESP_LOGCONFIG(TAG, " Mode: snapshot");
if (this->is_failed()) {
ESP_LOGE(TAG, " Setup Failed");
}
}
float CameraWebServer::get_setup_priority() const { return setup_priority::LATE; }
void CameraWebServer::loop() {
if (!this->running_) {
this->image_ = nullptr;
}
}
std::shared_ptr<esphome::esp32_camera::CameraImage> CameraWebServer::wait_for_image_() {
std::shared_ptr<esphome::esp32_camera::CameraImage> image;
image.swap(this->image_);
if (!image) {
// retry as we might still be fetching image
xSemaphoreTake(this->semaphore_, IMAGE_REQUEST_TIMEOUT / portTICK_PERIOD_MS);
image.swap(this->image_);
}
return image;
}
esp_err_t CameraWebServer::handler_(struct httpd_req *req) {
esp_err_t res = ESP_FAIL;
this->image_ = nullptr;
this->running_ = true;
switch (this->mode_) {
case STREAM:
res = this->streaming_handler_(req);
break;
case SNAPSHOT:
res = this->snapshot_handler_(req);
break;
}
this->running_ = false;
this->image_ = nullptr;
return res;
}
static esp_err_t httpd_send_all(httpd_req_t *r, const char *buf, size_t buf_len) {
int ret;
while (buf_len > 0) {
ret = httpd_send(r, buf, buf_len);
if (ret < 0) {
return ESP_FAIL;
}
buf += ret;
buf_len -= ret;
}
return ESP_OK;
}
esp_err_t CameraWebServer::streaming_handler_(struct httpd_req *req) {
esp_err_t res = ESP_OK;
char part_buf[64];
// This manually constructs HTTP response to avoid chunked encoding
// which is not supported by some clients
res = httpd_send_all(req, STREAM_HEADER, strlen(STREAM_HEADER));
if (res != ESP_OK) {
ESP_LOGW(TAG, "STREAM: failed to set HTTP header");
return res;
}
uint32_t last_frame = millis();
uint32_t frames = 0;
while (res == ESP_OK && this->running_) {
if (esp32_camera::global_esp32_camera != nullptr) {
esp32_camera::global_esp32_camera->request_stream();
}
auto image = this->wait_for_image_();
if (!image) {
ESP_LOGW(TAG, "STREAM: failed to acquire frame");
res = ESP_FAIL;
}
if (res == ESP_OK) {
size_t hlen = snprintf(part_buf, 64, STREAM_PART, image->get_data_length());
res = httpd_send_all(req, part_buf, hlen);
}
if (res == ESP_OK) {
res = httpd_send_all(req, (const char *) image->get_data_buffer(), image->get_data_length());
}
if (res == ESP_OK) {
res = httpd_send_all(req, STREAM_BOUNDARY, strlen(STREAM_BOUNDARY));
}
if (res == ESP_OK) {
frames++;
int64_t frame_time = millis() - last_frame;
last_frame = millis();
ESP_LOGD(TAG, "MJPG: %uB %ums (%.1ffps)", (uint32_t) image->get_data_length(), (uint32_t) frame_time,
1000.0 / (uint32_t) frame_time);
}
}
if (!frames) {
res = httpd_send_all(req, STREAM_ERROR, strlen(STREAM_ERROR));
}
ESP_LOGI(TAG, "STREAM: closed. Frames: %u", frames);
return res;
}
esp_err_t CameraWebServer::snapshot_handler_(struct httpd_req *req) {
esp_err_t res = ESP_OK;
if (esp32_camera::global_esp32_camera != nullptr) {
esp32_camera::global_esp32_camera->request_image();
}
auto image = this->wait_for_image_();
if (!image) {
ESP_LOGW(TAG, "SNAPSHOT: failed to acquire frame");
httpd_resp_send_500(req);
res = ESP_FAIL;
return res;
}
res = httpd_resp_set_type(req, CONTENT_TYPE);
if (res != ESP_OK) {
ESP_LOGW(TAG, "SNAPSHOT: failed to set HTTP response type");
return res;
}
httpd_resp_set_hdr(req, "Content-Disposition", "inline; filename=capture.jpg");
if (res == ESP_OK) {
res = httpd_resp_set_hdr(req, CONTENT_LENGTH, esphome::to_string(image->get_data_length()).c_str());
}
if (res == ESP_OK) {
res = httpd_resp_send(req, (const char *) image->get_data_buffer(), image->get_data_length());
}
return res;
}
} // namespace esp32_camera_web_server
} // namespace esphome
#endif // USE_ESP32

View File

@@ -0,0 +1,51 @@
#pragma once
#ifdef USE_ESP32
#include <freertos/FreeRTOS.h>
#include <freertos/semphr.h>
#include "esphome/components/esp32_camera/esp32_camera.h"
#include "esphome/core/component.h"
#include "esphome/core/helpers.h"
#include "esphome/core/preferences.h"
struct httpd_req;
namespace esphome {
namespace esp32_camera_web_server {
enum Mode { STREAM, SNAPSHOT };
class CameraWebServer : public Component {
public:
CameraWebServer();
~CameraWebServer();
void setup() override;
void on_shutdown() override;
void dump_config() override;
float get_setup_priority() const override;
void set_port(uint16_t port) { this->port_ = port; }
void set_mode(Mode mode) { this->mode_ = mode; }
void loop() override;
protected:
std::shared_ptr<esphome::esp32_camera::CameraImage> wait_for_image_();
esp_err_t handler_(struct httpd_req *req);
esp_err_t streaming_handler_(struct httpd_req *req);
esp_err_t snapshot_handler_(struct httpd_req *req);
protected:
uint16_t port_{0};
void *httpd_{nullptr};
SemaphoreHandle_t semaphore_;
std::shared_ptr<esphome::esp32_camera::CameraImage> image_;
bool running_{false};
Mode mode_{STREAM};
};
} // namespace esp32_camera_web_server
} // namespace esphome
#endif // USE_ESP32

View File

@@ -4,7 +4,7 @@ from esphome.components import binary_sensor, output, esp32_ble_server
from esphome.const import CONF_ID
AUTO_LOAD = ["binary_sensor", "output", "improv", "esp32_ble_server"]
AUTO_LOAD = ["binary_sensor", "output", "esp32_ble_server"]
CODEOWNERS = ["@jesserockz"]
CONFLICTS_WITH = ["esp32_ble_tracker", "esp32_ble_beacon"]
DEPENDENCIES = ["wifi", "esp32"]
@@ -56,6 +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.0.0")
cg.add(var.set_identify_duration(config[CONF_IDENTIFY_DURATION]))
cg.add(var.set_authorized_duration(config[CONF_AUTHORIZED_DURATION]))

View File

@@ -11,6 +11,7 @@ namespace esphome {
namespace esp32_improv {
static const char *const TAG = "esp32_improv.component";
static const char *const ESPHOME_MY_LINK = "https://my.home-assistant.io/redirect/config_flow_start?domain=esphome";
ESP32ImprovComponent::ESP32ImprovComponent() { global_improv_component = this; }
@@ -124,8 +125,13 @@ void ESP32ImprovComponent::loop() {
this->cancel_timeout("wifi-connect-timeout");
this->set_state_(improv::STATE_PROVISIONED);
std::string url = "https://my.home-assistant.io/redirect/config_flow_start?domain=esphome";
std::vector<uint8_t> data = improv::build_rpc_response(improv::WIFI_SETTINGS, {url});
std::vector<std::string> urls = {ESPHOME_MY_LINK};
#ifdef USE_WEBSERVER
auto ip = wifi::global_wifi_component->wifi_sta_ip();
std::string webserver_url = "http://" + ip.str() + ":" + to_string(WEBSERVER_PORT);
urls.push_back(webserver_url);
#endif
std::vector<uint8_t> data = improv::build_rpc_response(improv::WIFI_SETTINGS, urls);
this->send_response_(data);
this->set_timeout("end-service", 1000, [this] {
this->service_->stop();
@@ -213,7 +219,7 @@ void ESP32ImprovComponent::dump_config() {
void ESP32ImprovComponent::process_incoming_data_() {
uint8_t length = this->incoming_data_[1];
ESP_LOGD(TAG, "Processing bytes - %s", hexencode(this->incoming_data_).c_str());
ESP_LOGD(TAG, "Processing bytes - %s", format_hex_pretty(this->incoming_data_).c_str());
if (this->incoming_data_.size() - 3 == length) {
this->set_error_(improv::ERROR_NONE);
improv::ImprovCommand command = improv::parse_improv_data(this->incoming_data_);

View File

@@ -1,9 +1,8 @@
#pragma once
#include "esphome/components/binary_sensor/binary_sensor.h"
#include "esphome/components/esp32_ble_server/ble_server.h"
#include "esphome/components/esp32_ble_server/ble_characteristic.h"
#include "esphome/components/improv/improv.h"
#include "esphome/components/esp32_ble_server/ble_server.h"
#include "esphome/components/output/binary_output.h"
#include "esphome/components/wifi/wifi_component.h"
#include "esphome/core/component.h"
@@ -12,6 +11,8 @@
#ifdef USE_ESP32
#include <improv.h>
namespace esphome {
namespace esp32_improv {
@@ -48,7 +49,7 @@ class ESP32ImprovComponent : public Component, public BLEServiceComponent {
std::vector<uint8_t> incoming_data_;
wifi::WiFiAP connecting_sta_;
BLEService *service_;
std::shared_ptr<BLEService> service_;
BLECharacteristic *status_;
BLECharacteristic *error_;
BLECharacteristic *rpc_;

View File

@@ -1,6 +1,7 @@
#ifdef USE_ESP32
#include "esp32_touch.h"
#include "esphome/core/application.h"
#include "esphome/core/log.h"
#include "esphome/core/hal.h"
@@ -93,7 +94,6 @@ void ESP32TouchComponent::dump_config() {
if (this->iir_filter_enabled_()) {
ESP_LOGCONFIG(TAG, " IIR Filter: %ums", this->iir_filter_);
touch_pad_filter_start(this->iir_filter_);
} else {
ESP_LOGCONFIG(TAG, " IIR Filter DISABLED");
}
@@ -125,6 +125,8 @@ void ESP32TouchComponent::loop() {
if (should_print) {
ESP_LOGD(TAG, "Touch Pad '%s' (T%u): %u", child->get_name().c_str(), child->get_touch_pad(), value);
}
App.feed_wdt();
}
if (should_print) {

View File

@@ -4,6 +4,7 @@ from esphome.const import (
CONF_BOARD,
CONF_BOARD_FLASH_MODE,
CONF_FRAMEWORK,
CONF_SOURCE,
CONF_VERSION,
KEY_CORE,
KEY_FRAMEWORK_VERSION,
@@ -31,7 +32,7 @@ def set_core_data(config):
CORE.data[KEY_CORE][KEY_TARGET_PLATFORM] = "esp8266"
CORE.data[KEY_CORE][KEY_TARGET_FRAMEWORK] = "arduino"
CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] = cv.Version.parse(
config[CONF_FRAMEWORK][CONF_VERSION_HINT]
config[CONF_FRAMEWORK][CONF_VERSION]
)
CORE.data[KEY_ESP8266][KEY_BOARD] = config[CONF_BOARD]
return config
@@ -62,75 +63,68 @@ RECOMMENDED_ARDUINO_FRAMEWORK_VERSION = cv.Version(2, 7, 4)
# The platformio/espressif8266 version to use for arduino 2 framework versions
# - https://github.com/platformio/platform-espressif8266/releases
# - https://api.registry.platformio.org/v3/packages/platformio/platform/espressif8266
ARDUINO_2_PLATFORM_VERSION = cv.Version(2, 6, 2)
ARDUINO_2_PLATFORM_VERSION = cv.Version(2, 6, 3)
# for arduino 3 framework versions
ARDUINO_3_PLATFORM_VERSION = cv.Version(3, 0, 2)
ARDUINO_3_PLATFORM_VERSION = cv.Version(3, 2, 0)
def _arduino_check_versions(value):
value = value.copy()
lookups = {
"dev": ("https://github.com/esp8266/Arduino.git", cv.Version(3, 0, 2)),
"latest": ("", cv.Version(3, 0, 2)),
"recommended": (
_format_framework_arduino_version(RECOMMENDED_ARDUINO_FRAMEWORK_VERSION),
RECOMMENDED_ARDUINO_FRAMEWORK_VERSION,
),
"dev": (cv.Version(3, 0, 2), "https://github.com/esp8266/Arduino.git"),
"latest": (cv.Version(3, 0, 2), None),
"recommended": (RECOMMENDED_ARDUINO_FRAMEWORK_VERSION, None),
}
ver_value = value[CONF_VERSION]
default_ver_hint = None
if ver_value.lower() in lookups:
default_ver_hint = str(lookups[ver_value.lower()][1])
ver_value = lookups[ver_value.lower()][0]
if value[CONF_VERSION] in lookups:
if CONF_SOURCE in value:
raise cv.Invalid(
"Framework version needs to be explicitly specified when custom source is used."
)
version, source = lookups[value[CONF_VERSION]]
else:
with cv.suppress_invalid():
ver = cv.Version.parse(cv.version_number(value))
if ver <= cv.Version(2, 4, 1):
ver_value = f"~1.{ver.major}{ver.minor:02d}{ver.patch:02d}.0"
elif ver <= cv.Version(2, 6, 2):
ver_value = f"~2.{ver.major}{ver.minor:02d}{ver.patch:02d}.0"
else:
ver_value = f"~3.{ver.major}{ver.minor:02d}{ver.patch:02d}.0"
default_ver_hint = str(ver)
version = cv.Version.parse(cv.version_number(value[CONF_VERSION]))
source = value.get(CONF_SOURCE, None)
value[CONF_VERSION] = ver_value
value[CONF_VERSION] = str(version)
value[CONF_SOURCE] = source or _format_framework_arduino_version(version)
if CONF_VERSION_HINT not in value and default_ver_hint is None:
raise cv.Invalid("Needs a version hint to understand the framework version")
ver_hint_s = value.get(CONF_VERSION_HINT, default_ver_hint)
value[CONF_VERSION_HINT] = ver_hint_s
plat_ver = value.get(CONF_PLATFORM_VERSION)
if plat_ver is None:
ver_hint = cv.Version.parse(ver_hint_s)
if ver_hint >= cv.Version(3, 0, 0):
plat_ver = ARDUINO_3_PLATFORM_VERSION
elif ver_hint >= cv.Version(2, 5, 0):
plat_ver = ARDUINO_2_PLATFORM_VERSION
platform_version = value.get(CONF_PLATFORM_VERSION)
if platform_version is None:
if version >= cv.Version(3, 0, 0):
platform_version = _parse_platform_version(str(ARDUINO_3_PLATFORM_VERSION))
elif version >= cv.Version(2, 5, 0):
platform_version = _parse_platform_version(str(ARDUINO_2_PLATFORM_VERSION))
else:
plat_ver = cv.Version(1, 8, 0)
value[CONF_PLATFORM_VERSION] = str(plat_ver)
platform_version = _parse_platform_version(str(cv.Version(1, 8, 0)))
value[CONF_PLATFORM_VERSION] = platform_version
if cv.Version.parse(ver_hint_s) != RECOMMENDED_ARDUINO_FRAMEWORK_VERSION:
if version != RECOMMENDED_ARDUINO_FRAMEWORK_VERSION:
_LOGGER.warning(
"The selected arduino framework version is not the recommended one"
)
_LOGGER.warning(
"If there are connectivity or build issues please remove the manual version"
"The selected Arduino framework version is not the recommended one. "
"If there are connectivity or build issues please remove the manual version."
)
return value
CONF_VERSION_HINT = "version_hint"
def _parse_platform_version(value):
try:
# if platform version is a valid version constraint, prefix the default package
cv.platformio_version_constraint(value)
return f"platformio/espressif8266 @ {value}"
except cv.Invalid:
return value
CONF_PLATFORM_VERSION = "platform_version"
ARDUINO_FRAMEWORK_SCHEMA = cv.All(
cv.Schema(
{
cv.Optional(CONF_VERSION, default="recommended"): cv.string_strict,
cv.Optional(CONF_VERSION_HINT): cv.version_number,
cv.Optional(CONF_PLATFORM_VERSION): cv.string_strict,
cv.Optional(CONF_SOURCE): cv.string_strict,
cv.Optional(CONF_PLATFORM_VERSION): _parse_platform_version,
}
),
_arduino_check_versions,
@@ -157,20 +151,21 @@ CONFIG_SCHEMA = cv.All(
async def to_code(config):
cg.add(esp8266_ns.setup_preferences())
cg.add_platformio_option("lib_ldf_mode", "off")
cg.add_platformio_option("board", config[CONF_BOARD])
cg.add_build_flag("-DUSE_ESP8266")
cg.add_define("ESPHOME_BOARD", config[CONF_BOARD])
cg.add_define("ESPHOME_VARIANT", "ESP8266")
conf = config[CONF_FRAMEWORK]
cg.add_platformio_option("framework", "arduino")
cg.add_build_flag("-DUSE_ARDUINO")
cg.add_build_flag("-DUSE_ESP8266_FRAMEWORK_ARDUINO")
cg.add_platformio_option("platform", conf[CONF_PLATFORM_VERSION])
cg.add_platformio_option(
"platform_packages",
[f"platformio/framework-arduinoespressif8266 @ {conf[CONF_VERSION]}"],
)
cg.add_platformio_option(
"platform", f"platformio/espressif8266 @ {conf[CONF_PLATFORM_VERSION]}"
[f"platformio/framework-arduinoespressif8266 @ {conf[CONF_SOURCE]}"],
)
# Default for platformio is LWIP2_LOW_MEMORY with:

View File

@@ -206,61 +206,3 @@ ESP8266_BOARD_PINS = {
"wio_node": {"LED": 2, "GROVE": 15, "D0": 3, "D1": 5, "BUTTON": 0},
"xinabox_cw01": {"SDA": 2, "SCL": 14, "LED": 5, "LED_RED": 12, "LED_GREEN": 13},
}
FLASH_SIZE_1_MB = 2 ** 20
FLASH_SIZE_512_KB = FLASH_SIZE_1_MB // 2
FLASH_SIZE_2_MB = 2 * FLASH_SIZE_1_MB
FLASH_SIZE_4_MB = 4 * FLASH_SIZE_1_MB
FLASH_SIZE_16_MB = 16 * FLASH_SIZE_1_MB
ESP8266_FLASH_SIZES = {
"d1": FLASH_SIZE_4_MB,
"d1_mini": FLASH_SIZE_4_MB,
"d1_mini_lite": FLASH_SIZE_1_MB,
"d1_mini_pro": FLASH_SIZE_16_MB,
"esp01": FLASH_SIZE_512_KB,
"esp01_1m": FLASH_SIZE_1_MB,
"esp07": FLASH_SIZE_4_MB,
"esp12e": FLASH_SIZE_4_MB,
"esp210": FLASH_SIZE_4_MB,
"esp8285": FLASH_SIZE_1_MB,
"esp_wroom_02": FLASH_SIZE_2_MB,
"espduino": FLASH_SIZE_4_MB,
"espectro": FLASH_SIZE_4_MB,
"espino": FLASH_SIZE_4_MB,
"espinotee": FLASH_SIZE_4_MB,
"espmxdevkit": FLASH_SIZE_1_MB,
"espresso_lite_v1": FLASH_SIZE_4_MB,
"espresso_lite_v2": FLASH_SIZE_4_MB,
"gen4iod": FLASH_SIZE_512_KB,
"heltec_wifi_kit_8": FLASH_SIZE_4_MB,
"huzzah": FLASH_SIZE_4_MB,
"inventone": FLASH_SIZE_4_MB,
"modwifi": FLASH_SIZE_2_MB,
"nodemcu": FLASH_SIZE_4_MB,
"nodemcuv2": FLASH_SIZE_4_MB,
"oak": FLASH_SIZE_4_MB,
"phoenix_v1": FLASH_SIZE_4_MB,
"phoenix_v2": FLASH_SIZE_4_MB,
"sonoff_basic": FLASH_SIZE_1_MB,
"sonoff_s20": FLASH_SIZE_1_MB,
"sonoff_sv": FLASH_SIZE_1_MB,
"sonoff_th": FLASH_SIZE_1_MB,
"sparkfunBlynk": FLASH_SIZE_4_MB,
"thing": FLASH_SIZE_512_KB,
"thingdev": FLASH_SIZE_512_KB,
"wifi_slot": FLASH_SIZE_1_MB,
"wifiduino": FLASH_SIZE_4_MB,
"wifinfo": FLASH_SIZE_1_MB,
"wio_link": FLASH_SIZE_4_MB,
"wio_node": FLASH_SIZE_4_MB,
"xinabox_cw01": FLASH_SIZE_4_MB,
}
ESP8266_LD_SCRIPTS = {
FLASH_SIZE_512_KB: ("eagle.flash.512k0.ld", "eagle.flash.512k.ld"),
FLASH_SIZE_1_MB: ("eagle.flash.1m0.ld", "eagle.flash.1m.ld"),
FLASH_SIZE_2_MB: ("eagle.flash.2m.ld", "eagle.flash.2m.ld"),
FLASH_SIZE_4_MB: ("eagle.flash.4m.ld", "eagle.flash.4m.ld"),
FLASH_SIZE_16_MB: ("eagle.flash.16m.ld", "eagle.flash.16m14m.ld"),
}

View File

@@ -12,7 +12,7 @@ void IRAM_ATTR HOT yield() { ::yield(); }
uint32_t IRAM_ATTR HOT millis() { return ::millis(); }
void IRAM_ATTR HOT delay(uint32_t ms) { ::delay(ms); }
uint32_t IRAM_ATTR HOT micros() { return ::micros(); }
void IRAM_ATTR HOT delayMicroseconds(uint32_t us) { ::delayMicroseconds(us); }
void IRAM_ATTR HOT delayMicroseconds(uint32_t us) { delay_microseconds_safe(us); }
void arch_restart() {
ESP.restart(); // NOLINT(readability-static-accessed-through-instance)
// restart() doesn't always end execution
@@ -20,6 +20,7 @@ void arch_restart() {
yield();
}
}
void arch_init() {}
void IRAM_ATTR HOT arch_feed_wdt() {
ESP.wdtFeed(); // NOLINT(readability-static-accessed-through-instance)
}
@@ -27,7 +28,7 @@ void IRAM_ATTR HOT arch_feed_wdt() {
uint8_t progmem_read_byte(const uint8_t *addr) {
return pgm_read_byte(addr); // NOLINT
}
uint32_t arch_get_cpu_cycle_count() {
uint32_t IRAM_ATTR HOT arch_get_cpu_cycle_count() {
return ESP.getCycleCount(); // NOLINT(readability-static-accessed-through-instance)
}
uint32_t arch_get_cpu_freq_hz() { return F_CPU; }

View File

@@ -8,6 +8,29 @@ namespace esp8266 {
static const char *const TAG = "esp8266";
static int IRAM_ATTR flags_to_mode(gpio::Flags flags, uint8_t pin) {
if (flags == gpio::FLAG_INPUT) { // NOLINT(bugprone-branch-clone)
return INPUT;
} else if (flags == gpio::FLAG_OUTPUT) {
return OUTPUT;
} else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLUP)) {
if (pin == 16) {
// GPIO16 doesn't have a pullup, so pinMode would fail.
// However, sometimes this method is called with pullup mode anyway
// for example from dallas one_wire. For those cases convert this
// to a INPUT mode.
return INPUT;
}
return INPUT_PULLUP;
} else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLDOWN)) {
return INPUT_PULLDOWN_16;
} else if (flags == (gpio::FLAG_OUTPUT | gpio::FLAG_OPEN_DRAIN)) {
return OUTPUT_OPEN_DRAIN;
} else {
return 0;
}
}
struct ISRPinArg {
uint8_t pin;
bool inverted;
@@ -43,21 +66,7 @@ void ESP8266GPIOPin::attach_interrupt(void (*func)(void *), void *arg, gpio::Int
attachInterruptArg(pin_, func, arg, arduino_mode);
}
void ESP8266GPIOPin::pin_mode(gpio::Flags flags) {
uint8_t mode;
if (flags == gpio::FLAG_INPUT) {
mode = INPUT;
} else if (flags == gpio::FLAG_OUTPUT) {
mode = OUTPUT;
} else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLUP)) {
mode = INPUT_PULLUP;
} else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLDOWN)) {
mode = INPUT_PULLDOWN_16;
} else if (flags == (gpio::FLAG_OUTPUT | gpio::FLAG_OPEN_DRAIN)) {
mode = OUTPUT_OPEN_DRAIN;
} else {
return;
}
pinMode(pin_, mode); // NOLINT
pinMode(pin_, flags_to_mode(flags, pin_)); // NOLINT
}
std::string ESP8266GPIOPin::dump_summary() const {
@@ -90,6 +99,10 @@ void IRAM_ATTR ISRInternalGPIOPin::clear_interrupt() {
auto *arg = reinterpret_cast<ISRPinArg *>(arg_);
GPIO_REG_WRITE(GPIO_STATUS_W1TC_ADDRESS, 1UL << arg->pin);
}
void IRAM_ATTR ISRInternalGPIOPin::pin_mode(gpio::Flags flags) {
auto *arg = reinterpret_cast<ISRPinArg *>(arg_);
pinMode(arg->pin, flags_to_mode(flags, arg->pin)); // NOLINT
}
} // namespace esphome

View File

@@ -107,9 +107,9 @@ def validate_supports(value):
raise cv.Invalid(
"Open-drain only works with output mode", [CONF_MODE, CONF_OPEN_DRAIN]
)
if is_pullup and num == 0:
if is_pullup and num == 16:
raise cv.Invalid(
"GPIO Pin 0 does not support pullup pin mode. "
"GPIO Pin 16 does not support pullup pin mode. "
"Please choose another pin.",
[CONF_MODE, CONF_PULLUP],
)

View File

@@ -55,7 +55,7 @@ static inline bool esp_rtc_user_mem_write(uint32_t index, uint32_t value) {
extern "C" uint32_t _SPIFFS_end; // NOLINT
static const uint32_t get_esp8266_flash_sector() {
static uint32_t get_esp8266_flash_sector() {
union {
uint32_t *ptr;
uint32_t uint;
@@ -63,7 +63,7 @@ static const uint32_t get_esp8266_flash_sector() {
data.ptr = &_SPIFFS_end;
return (data.uint - 0x40200000) / SPI_FLASH_SEC_SIZE;
}
static const uint32_t get_esp8266_flash_address() { return get_esp8266_flash_sector() * SPI_FLASH_SEC_SIZE; }
static uint32_t get_esp8266_flash_address() { return get_esp8266_flash_sector() * SPI_FLASH_SEC_SIZE; }
template<class It> uint32_t calculate_crc(It first, It last, uint32_t type) {
uint32_t crc = type;

View File

@@ -168,7 +168,9 @@ void EthernetComponent::start_connect_() {
esp_err_t err;
err = tcpip_adapter_set_hostname(TCPIP_ADAPTER_IF_ETH, App.get_name().c_str());
ESPHL_ERROR_CHECK(err, "ETH set hostname error");
if (err != ERR_OK) {
ESP_LOGW(TAG, "tcpip_adapter_set_hostname failed: %s", esp_err_to_name(err));
}
tcpip_adapter_ip_info_t info;
if (this->manual_ip_.has_value()) {
@@ -182,7 +184,9 @@ void EthernetComponent::start_connect_() {
}
err = tcpip_adapter_dhcpc_stop(TCPIP_ADAPTER_IF_ETH);
ESPHL_ERROR_CHECK(err, "DHCPC stop error");
if (err != ESP_ERR_TCPIP_ADAPTER_DHCP_ALREADY_STOPPED) {
ESPHL_ERROR_CHECK(err, "DHCPC stop error");
}
err = tcpip_adapter_set_ip_info(TCPIP_ADAPTER_IF_ETH, &info);
ESPHL_ERROR_CHECK(err, "DHCPC set IP info error");

View File

@@ -12,6 +12,8 @@ from esphome.const import (
CONF_TYPE,
CONF_EXTERNAL_COMPONENTS,
CONF_PATH,
CONF_USERNAME,
CONF_PASSWORD,
)
from esphome.core import CORE
from esphome import git, loader
@@ -27,6 +29,8 @@ TYPE_LOCAL = "local"
GIT_SCHEMA = {
cv.Required(CONF_URL): cv.url,
cv.Optional(CONF_REF): cv.git_ref,
cv.Optional(CONF_USERNAME): cv.string,
cv.Optional(CONF_PASSWORD): cv.string,
}
LOCAL_SCHEMA = {
cv.Required(CONF_PATH): cv.directory,
@@ -43,19 +47,27 @@ def validate_source_shorthand(value):
# Regex for GitHub repo name with optional branch/tag
# Note: git allows other branch/tag names as well, but never seen them used before
m = re.match(
r"github://([a-zA-Z0-9\-]+)/([a-zA-Z0-9\-\._]+)(?:@([a-zA-Z0-9\-_.\./]+))?",
r"github://(?:([a-zA-Z0-9\-]+)/([a-zA-Z0-9\-\._]+)(?:@([a-zA-Z0-9\-_.\./]+))?|pr#([0-9]+))",
value,
)
if m is None:
raise cv.Invalid(
"Source is not a file system path or in expected github://username/name[@branch-or-tag] format!"
"Source is not a file system path, in expected github://username/name[@branch-or-tag] or github://pr#1234 format!"
)
conf = {
CONF_TYPE: TYPE_GIT,
CONF_URL: f"https://github.com/{m.group(1)}/{m.group(2)}.git",
}
if m.group(3):
conf[CONF_REF] = m.group(3)
if m.group(4):
conf = {
CONF_TYPE: TYPE_GIT,
CONF_URL: "https://github.com/esphome/esphome.git",
CONF_REF: f"pull/{m.group(4)}/head",
}
else:
conf = {
CONF_TYPE: TYPE_GIT,
CONF_URL: f"https://github.com/{m.group(1)}/{m.group(2)}.git",
}
if m.group(3):
conf[CONF_REF] = m.group(3)
return SOURCE_SCHEMA(conf)
@@ -91,6 +103,8 @@ def _process_git_config(config: dict, refresh) -> str:
ref=config.get(CONF_REF),
refresh=refresh,
domain=DOMAIN,
username=config.get(CONF_USERNAME),
password=config.get(CONF_PASSWORD),
)
if (repo_dir / "esphome" / "components").is_dir():

View File

@@ -32,7 +32,7 @@ void EZOSensor::update() {
}
void EZOSensor::loop() {
uint8_t buf[20];
uint8_t buf[21];
if (!(this->state_ & EZO_STATE_WAIT)) {
if (this->state_ & EZO_STATE_SEND_TEMP) {
int len = sprintf((char *) buf, "T,%0.3f", this->tempcomp_);
@@ -74,7 +74,12 @@ void EZOSensor::loop() {
if (buf[0] != 1)
return;
float val = strtof((char *) &buf[1], nullptr);
// some sensors return multiple comma-separated values, terminate string after first one
for (size_t i = 1; i < sizeof(buf) - 1; i++)
if (buf[i] == ',')
buf[i] = '\0';
float val = parse_number<float>((char *) &buf[1]).value_or(0);
this->publish_state(val);
}

View File

@@ -8,6 +8,7 @@ from esphome.const import (
CONF_LAST_FINGER_ID,
CONF_SECURITY_LEVEL,
CONF_STATUS,
ENTITY_CATEGORY_DIAGNOSTIC,
ICON_ACCOUNT,
ICON_ACCOUNT_CHECK,
ICON_DATABASE,
@@ -26,30 +27,36 @@ CONFIG_SCHEMA = cv.Schema(
icon=ICON_FINGERPRINT,
accuracy_decimals=0,
state_class=STATE_CLASS_NONE,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
cv.Optional(CONF_STATUS): sensor.sensor_schema(
accuracy_decimals=0,
state_class=STATE_CLASS_NONE,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
cv.Optional(CONF_CAPACITY): sensor.sensor_schema(
icon=ICON_DATABASE,
accuracy_decimals=0,
state_class=STATE_CLASS_NONE,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
cv.Optional(CONF_SECURITY_LEVEL): sensor.sensor_schema(
icon=ICON_SECURITY,
accuracy_decimals=0,
state_class=STATE_CLASS_NONE,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
cv.Optional(CONF_LAST_FINGER_ID): sensor.sensor_schema(
icon=ICON_ACCOUNT,
accuracy_decimals=0,
state_class=STATE_CLASS_NONE,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
cv.Optional(CONF_LAST_CONFIDENCE): sensor.sensor_schema(
icon=ICON_ACCOUNT_CHECK,
accuracy_decimals=0,
state_class=STATE_CLASS_NONE,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
}
)

View File

@@ -86,7 +86,7 @@ void Graph::draw(DisplayBuffer *buff, uint16_t x_offset, uint16_t y_offset, Colo
// Look back in trace data to best-fit into local range
float mx = NAN;
float mn = NAN;
for (int16_t i = 0; i < this->width_; i++) {
for (uint32_t i = 0; i < this->width_; i++) {
for (auto *trace : traces_) {
float v = trace->get_tracedata()->get_value(i);
if (!std::isnan(v)) {
@@ -132,7 +132,7 @@ void Graph::draw(DisplayBuffer *buff, uint16_t x_offset, uint16_t y_offset, Colo
if (!std::isnan(this->gridspacing_y_)) {
for (int y = yn; y <= ym; y++) {
int16_t py = (int16_t) roundf((this->height_ - 1) * (1.0 - (float) (y - yn) / (ym - yn)));
for (int x = 0; x < this->width_; x += 2) {
for (uint32_t x = 0; x < this->width_; x += 2) {
buff->draw_pixel_at(x_offset + x, y_offset + py, color);
}
}
@@ -147,7 +147,7 @@ void Graph::draw(DisplayBuffer *buff, uint16_t x_offset, uint16_t y_offset, Colo
ESP_LOGW(TAG, "Graphing reducing x-scale to prevent too many gridlines");
}
for (int i = 0; i <= n; i++) {
for (int y = 0; y < this->height_; y += 2) {
for (uint32_t y = 0; y < this->height_; y += 2) {
buff->draw_pixel_at(x_offset + i * (this->width_ - 1) / n, y_offset + y, color);
}
}
@@ -158,14 +158,14 @@ void Graph::draw(DisplayBuffer *buff, uint16_t x_offset, uint16_t y_offset, Colo
for (auto *trace : traces_) {
Color c = trace->get_line_color();
uint16_t thick = trace->get_line_thickness();
for (int16_t i = 0; i < this->width_; i++) {
for (uint32_t i = 0; i < this->width_; i++) {
float v = (trace->get_tracedata()->get_value(i) - ymin) / yrange;
if (!std::isnan(v) && (thick > 0)) {
int16_t x = this->width_ - 1 - i;
uint8_t b = (i % (thick * LineType::PATTERN_LENGTH)) / thick;
if (((uint8_t) trace->get_line_type() & (1 << b)) == (1 << b)) {
int16_t y = (int16_t) roundf((this->height_ - 1) * (1.0 - v)) - thick / 2;
for (int16_t t = 0; t < thick; t++) {
for (uint16_t t = 0; t < thick; t++) {
buff->draw_pixel_at(x_offset + x, y_offset + y + t, c);
}
}
@@ -179,8 +179,8 @@ void GraphLegend::init(Graph *g) {
parent_ = g;
// Determine maximum expected text and value width / height
int txtw = 0, txtos = 0, txtbl = 0, txth = 0;
int valw = 0, valos = 0, valbl = 0, valh = 0;
int txtw = 0, txth = 0;
int valw = 0, valh = 0;
int lt = 0;
for (auto *trace : g->traces_) {
std::string txtstr = trace->get_name();
@@ -320,7 +320,7 @@ void Graph::draw_legend(display::DisplayBuffer *buff, uint16_t x_offset, uint16_
if (legend_->lines_) {
uint16_t thick = trace->get_line_thickness();
for (int16_t i = 0; i < legend_->x0_ * 4 / 3; i++) {
for (int i = 0; i < legend_->x0_ * 4 / 3; i++) {
uint8_t b = (i % (thick * LineType::PATTERN_LENGTH)) / thick;
if (((uint8_t) trace->get_line_type() & (1 << b)) == (1 << b)) {
buff->vertical_line(x - legend_->x0_ * 2 / 3 + i, y + legend_->yl_ - thick / 2, thick,

View File

@@ -93,13 +93,12 @@ PV_SENSORS = {
CONF_VOLTAGE_SAMPLED_BY_SECONDARY_CPU: sensor.sensor_schema(
unit_of_measurement=UNIT_VOLT,
accuracy_decimals=0,
device_class=DEVICE_CLASS_POWER,
device_class=DEVICE_CLASS_VOLTAGE,
state_class=STATE_CLASS_MEASUREMENT,
),
CONF_INSULATION_OF_P_TO_GROUND: sensor.sensor_schema(
unit_of_measurement=UNIT_KOHM,
accuracy_decimals=0,
device_class=DEVICE_CLASS_POWER,
state_class=STATE_CLASS_MEASUREMENT,
),
}
@@ -135,7 +134,6 @@ CONFIG_SCHEMA = (
cv.Optional(CONF_REACTIVE_POWER): sensor.sensor_schema(
unit_of_measurement=UNIT_VOLT_AMPS_REACTIVE,
accuracy_decimals=2,
device_class=DEVICE_CLASS_POWER,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_ENERGY_PRODUCTION_DAY): sensor.sensor_schema(

View File

@@ -30,6 +30,7 @@ PROTOCOLS = {
"gree": Protocol.PROTOCOL_GREE,
"greeya": Protocol.PROTOCOL_GREEYAA,
"greeyan": Protocol.PROTOCOL_GREEYAN,
"greeyac": Protocol.PROTOCOL_GREEYAC,
"hisense_aud": Protocol.PROTOCOL_HISENSE_AUD,
"hitachi": Protocol.PROTOCOL_HITACHI,
"hyundai": Protocol.PROTOCOL_HYUNDAI,
@@ -111,4 +112,6 @@ def to_code(config):
cg.add(var.set_max_temperature(config[CONF_MIN_TEMPERATURE]))
cg.add(var.set_min_temperature(config[CONF_MAX_TEMPERATURE]))
cg.add_library("tonia/HeatpumpIR", "1.0.15")
# PIO isn't updating releases, so referencing the release tag directly. See:
# https://github.com/ToniA/arduino-heatpumpir/commit/0948c619d86407a4e50e8db2f3c193e0576c86fd
cg.add_library("", "", "https://github.com/ToniA/arduino-heatpumpir.git#1.0.18")

View File

@@ -25,6 +25,7 @@ const std::map<Protocol, std::function<HeatpumpIR *()>> PROTOCOL_CONSTRUCTOR_MAP
{PROTOCOL_GREE, []() { return new GreeGenericHeatpumpIR(); }}, // NOLINT
{PROTOCOL_GREEYAA, []() { return new GreeYAAHeatpumpIR(); }}, // NOLINT
{PROTOCOL_GREEYAN, []() { return new GreeYANHeatpumpIR(); }}, // NOLINT
{PROTOCOL_GREEYAC, []() { return new GreeYACHeatpumpIR(); }}, // NOLINT
{PROTOCOL_HISENSE_AUD, []() { return new HisenseHeatpumpIR(); }}, // NOLINT
{PROTOCOL_HITACHI, []() { return new HitachiHeatpumpIR(); }}, // NOLINT
{PROTOCOL_HYUNDAI, []() { return new HyundaiHeatpumpIR(); }}, // NOLINT
@@ -61,6 +62,19 @@ void HeatpumpIRClimate::setup() {
}
this->heatpump_ir_ = protocol_constructor->second();
climate_ir::ClimateIR::setup();
if (this->sensor_) {
this->sensor_->add_on_state_callback([this](float state) {
this->current_temperature = state;
IRSenderESPHome esp_sender(this->transmitter_);
this->heatpump_ir_->send(esp_sender, uint8_t(lround(this->current_temperature + 0.5)));
// current temperature changed, publish state
this->publish_state();
});
this->current_temperature = this->sensor_->state;
} else
this->current_temperature = NAN;
}
void HeatpumpIRClimate::transmit_state() {
@@ -171,8 +185,7 @@ void HeatpumpIRClimate::transmit_state() {
temperature_cmd = (uint8_t) clamp(this->target_temperature, this->min_temperature_, this->max_temperature_);
IRSenderESPHome esp_sender(0, this->transmitter_);
IRSenderESPHome esp_sender(this->transmitter_);
heatpump_ir_->send(esp_sender, power_mode_cmd, operating_mode_cmd, fan_speed_cmd, temperature_cmd, swing_v_cmd,
swing_h_cmd);
}

View File

@@ -25,6 +25,7 @@ enum Protocol {
PROTOCOL_GREE,
PROTOCOL_GREEYAA,
PROTOCOL_GREEYAN,
PROTOCOL_GREEYAC,
PROTOCOL_HISENSE_AUD,
PROTOCOL_HITACHI,
PROTOCOL_HYUNDAI,

View File

@@ -11,8 +11,8 @@ namespace heatpumpir {
class IRSenderESPHome : public IRSender {
public:
IRSenderESPHome(uint8_t pin, remote_transmitter::RemoteTransmitterComponent *transmitter)
: IRSender(pin), transmit_(transmitter->transmit()){};
IRSenderESPHome(remote_transmitter::RemoteTransmitterComponent *transmitter)
: IRSender(0), transmit_(transmitter->transmit()){};
void setFrequency(int frequency) override; // NOLINT(readability-identifier-naming)
void space(int space_length) override;
void mark(int mark_length) override;

View File

@@ -299,9 +299,7 @@ bool HitachiClimate::parse_swing_(const uint8_t remote_state[]) {
GETBITS8(remote_state[HITACHI_AC344_SWINGH_BYTE], HITACHI_AC344_SWINGH_OFFSET, HITACHI_AC344_SWINGH_SIZE);
ESP_LOGV(TAG, "SwingH: %02X %02X", remote_state[HITACHI_AC344_SWINGH_BYTE], swing_modeh);
if ((swing_modeh & 0x7) == 0x0) {
this->swing_mode = climate::CLIMATE_SWING_HORIZONTAL;
} else if ((swing_modeh & 0x3) == 0x3) {
if ((swing_modeh & 0x3) == 0x3) {
this->swing_mode = climate::CLIMATE_SWING_OFF;
} else {
this->swing_mode = climate::CLIMATE_SWING_HORIZONTAL;

View File

@@ -300,9 +300,7 @@ bool HitachiClimate::parse_swing_(const uint8_t remote_state[]) {
HITACHI_AC424_SWINGH_SIZE);
ESP_LOGV(TAG, "SwingH: %02X %02X", remote_state[HITACHI_AC424_SWINGH_BYTE], swing_modeh);
if ((swing_modeh & 0x7) == 0x0) {
this->swing_mode = climate::CLIMATE_SWING_HORIZONTAL;
} else if ((swing_modeh & 0x3) == 0x3) {
if ((swing_modeh & 0x3) == 0x3) {
this->swing_mode = climate::CLIMATE_SWING_OFF;
} else {
this->swing_mode = climate::CLIMATE_SWING_HORIZONTAL;

View File

@@ -1,6 +1,5 @@
#pragma once
#ifdef USE_ARDUINO
#include <cstdint>
namespace esphome {
@@ -13,5 +12,3 @@ class AbstractAQICalculator {
} // namespace hm3301
} // namespace esphome
#endif // USE_ARDUINO

View File

@@ -1,7 +1,5 @@
#pragma once
#ifdef USE_ARDUINO
#include "abstract_aqi_calculator.h"
namespace esphome {
@@ -33,7 +31,7 @@ class AQICalculator : public AbstractAQICalculator {
int conc_lo = array[grid_index][0];
int conc_hi = array[grid_index][1];
return ((aqi_hi - aqi_lo) / (conc_hi - conc_lo)) * (value - conc_lo) + aqi_lo;
return (value - conc_lo) * (aqi_hi - aqi_lo) / (conc_hi - conc_lo) + aqi_lo;
}
int get_grid_index_(uint16_t value, int array[AMOUNT_OF_LEVELS][2]) {
@@ -48,5 +46,3 @@ class AQICalculator : public AbstractAQICalculator {
} // namespace hm3301
} // namespace esphome
#endif // USE_ARDUINO

View File

@@ -1,7 +1,5 @@
#pragma once
#ifdef USE_ARDUINO
#include "caqi_calculator.h"
#include "aqi_calculator.h"
@@ -29,5 +27,3 @@ class AQICalculatorFactory {
} // namespace hm3301
} // namespace esphome
#endif // USE_ARDUINO

View File

@@ -1,7 +1,5 @@
#pragma once
#ifdef USE_ARDUINO
#include "esphome/core/log.h"
#include "abstract_aqi_calculator.h"
@@ -37,9 +35,7 @@ class CAQICalculator : public AbstractAQICalculator {
int conc_lo = array[grid_index][0];
int conc_hi = array[grid_index][1];
int aqi = ((aqi_hi - aqi_lo) / (conc_hi - conc_lo)) * (value - conc_lo) + aqi_lo;
return aqi;
return (value - conc_lo) * (aqi_hi - aqi_lo) / (conc_hi - conc_lo) + aqi_lo;
}
int get_grid_index_(uint16_t value, int array[AMOUNT_OF_LEVELS][2]) {
@@ -54,5 +50,3 @@ class CAQICalculator : public AbstractAQICalculator {
} // namespace hm3301
} // namespace esphome
#endif // USE_ARDUINO

View File

@@ -1,5 +1,3 @@
#ifdef USE_ARDUINO
#include "esphome/core/log.h"
#include "hm3301.h"
@@ -14,9 +12,8 @@ static const uint8_t PM_10_0_VALUE_INDEX = 7;
void HM3301Component::setup() {
ESP_LOGCONFIG(TAG, "Setting up HM3301...");
hm3301_ = make_unique<HM330X>();
error_code_ = hm3301_->init();
if (error_code_ != NO_ERROR) {
if (i2c::ERROR_OK != this->write(&SELECT_COMM_CMD, 1)) {
error_code_ = ERROR_COMM;
this->mark_failed();
return;
}
@@ -38,7 +35,7 @@ void HM3301Component::dump_config() {
float HM3301Component::get_setup_priority() const { return setup_priority::DATA; }
void HM3301Component::update() {
if (!this->read_sensor_value_(data_buffer_)) {
if (this->read(data_buffer_, 29) != i2c::ERROR_OK) {
ESP_LOGW(TAG, "Read result failed");
this->status_set_warning();
return;
@@ -87,8 +84,6 @@ void HM3301Component::update() {
this->status_clear_warning();
}
bool HM3301Component::read_sensor_value_(uint8_t *data) { return !hm3301_->read_sensor_value(data, 29); }
bool HM3301Component::validate_checksum_(const uint8_t *data) {
uint8_t sum = 0;
for (int i = 0; i < 28; i++) {
@@ -104,5 +99,3 @@ uint16_t HM3301Component::get_sensor_value_(const uint8_t *data, uint8_t i) {
} // namespace hm3301
} // namespace esphome
#endif // USE_ARDUINO

View File

@@ -1,17 +1,15 @@
#pragma once
#ifdef USE_ARDUINO
#include "esphome/core/component.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/i2c/i2c.h"
#include "aqi_calculator_factory.h"
#include <Seeed_HM330X.h>
namespace esphome {
namespace hm3301 {
static const uint8_t SELECT_COMM_CMD = 0X88;
class HM3301Component : public PollingComponent, public i2c::I2CDevice {
public:
HM3301Component() = default;
@@ -29,9 +27,12 @@ class HM3301Component : public PollingComponent, public i2c::I2CDevice {
void update() override;
protected:
std::unique_ptr<HM330X> hm3301_;
HM330XErrorCode error_code_{NO_ERROR};
enum {
NO_ERROR = 0,
ERROR_PARAM = -1,
ERROR_COMM = -2,
ERROR_OTHERS = -128,
} error_code_{NO_ERROR};
uint8_t data_buffer_[30];
@@ -43,12 +44,9 @@ class HM3301Component : public PollingComponent, public i2c::I2CDevice {
AQICalculatorType aqi_calc_type_;
AQICalculatorFactory aqi_calculator_factory_ = AQICalculatorFactory();
bool read_sensor_value_(uint8_t *);
bool validate_checksum_(const uint8_t *);
uint16_t get_sensor_value_(const uint8_t *, uint8_t);
};
} // namespace hm3301
} // namespace esphome
#endif // USE_ARDUINO

View File

@@ -84,7 +84,6 @@ CONFIG_SCHEMA = cv.All(
.extend(cv.polling_component_schema("60s"))
.extend(i2c.i2c_device_schema(0x40)),
_validate,
cv.only_with_arduino,
)
@@ -109,6 +108,3 @@ async def to_code(config):
sens = await sensor.new_sensor(config[CONF_AQI])
cg.add(var.set_aqi_sensor(sens))
cg.add(var.set_aqi_calculation_type(config[CONF_AQI][CONF_CALCULATION_TYPE]))
# https://platformio.org/lib/show/6306/Grove%20-%20Laser%20PM2.5%20Sensor%20HM3301
cg.add_library("seeed-studio/Grove - Laser PM2.5 Sensor HM3301", "1.0.3")

View File

@@ -114,8 +114,8 @@ CONFIG_SCHEMA = (
def auto_data_rate(config):
interval_sec = config[CONF_UPDATE_INTERVAL].seconds
interval_hz = 1.0 / interval_sec
interval_msec = config[CONF_UPDATE_INTERVAL].total_milliseconds
interval_hz = 1000.0 / interval_msec
for datarate in sorted(HMC5883LDatarates.keys()):
if float(datarate) >= interval_hz:
return HMC5883LDatarates[datarate]

View File

@@ -10,7 +10,7 @@ static const char *const TAG = "homeassistant.sensor";
void HomeassistantSensor::setup() {
api::global_api_server->subscribe_home_assistant_state(
this->entity_id_, this->attribute_, [this](const std::string &state) {
auto val = parse_float(state);
auto val = parse_number<float>(state);
if (!val.has_value()) {
ESP_LOGW(TAG, "Can't convert '%s' to number!", state.c_str());
this->publish_state(NAN);

View File

@@ -44,7 +44,7 @@ void HrxlMaxsonarWrComponent::check_buffer_() {
if (this->buffer_.length() == MAX_DATA_LENGTH_BYTES && this->buffer_[0] == 'R' &&
this->buffer_.back() == static_cast<char>(ASCII_CR)) {
int millimeters = strtol(this->buffer_.substr(1, MAX_DATA_LENGTH_BYTES - 2).c_str(), nullptr, 10);
int millimeters = parse_number<int>(this->buffer_.substr(1, MAX_DATA_LENGTH_BYTES - 2)).value_or(0);
float meters = float(millimeters) / 1000.0;
ESP_LOGV(TAG, "Distance from sensor: %d mm, %f m", millimeters, meters);
this->publish_state(meters);

View File

@@ -96,6 +96,8 @@ async def to_code(config):
if CORE.is_esp32:
cg.add_library("WiFiClientSecure", None)
cg.add_library("HTTPClient", None)
if CORE.is_esp8266:
cg.add_library("ESP8266HTTPClient", None)
await cg.register_component(var, config)

View File

@@ -9,8 +9,8 @@ static const char *const TAG = "htu21d";
static const uint8_t HTU21D_ADDRESS = 0x40;
static const uint8_t HTU21D_REGISTER_RESET = 0xFE;
static const uint8_t HTU21D_REGISTER_TEMPERATURE = 0xE3;
static const uint8_t HTU21D_REGISTER_HUMIDITY = 0xE5;
static const uint8_t HTU21D_REGISTER_TEMPERATURE = 0xF3;
static const uint8_t HTU21D_REGISTER_HUMIDITY = 0xF5;
static const uint8_t HTU21D_REGISTER_STATUS = 0xE7;
void HTU21DComponent::setup() {

View File

@@ -1,6 +1,7 @@
#pragma once
#include "i2c_bus.h"
#include "esphome/core/helpers.h"
#include "esphome/core/optional.h"
#include <array>
#include <vector>
@@ -32,16 +33,8 @@ class I2CRegister {
// like ntohs/htons but without including networking headers.
// ("i2c" byte order is big-endian)
inline uint16_t i2ctohs(uint16_t i2cshort) {
union {
uint16_t x;
uint8_t y[2];
} conv;
conv.x = i2cshort;
return ((uint16_t) conv.y[0] << 8) | ((uint16_t) conv.y[1] << 0);
}
inline uint16_t htoi2cs(uint16_t hostshort) { return i2ctohs(hostshort); }
inline uint16_t i2ctohs(uint16_t i2cshort) { return convert_big_endian(i2cshort); }
inline uint16_t htoi2cs(uint16_t hostshort) { return convert_big_endian(hostshort); }
class I2CDevice {
public:

View File

@@ -1,6 +1,8 @@
#pragma once
#include <cstdint>
#include <cstddef>
#include <utility>
#include <vector>
namespace esphome {
namespace i2c {
@@ -40,6 +42,20 @@ class I2CBus {
return writev(address, &buf, 1);
}
virtual ErrorCode writev(uint8_t address, WriteBuffer *buffers, size_t cnt) = 0;
protected:
void i2c_scan_() {
for (uint8_t address = 8; address < 120; address++) {
auto err = writev(address, nullptr, 0);
if (err == ERROR_OK) {
scan_results_.emplace_back(address, true);
} else if (err == ERROR_UNKNOWN) {
scan_results_.emplace_back(address, false);
}
}
}
std::vector<std::pair<uint8_t, bool>> scan_results_;
bool scan_{false};
};
} // namespace i2c

View File

@@ -2,6 +2,7 @@
#include "i2c_bus_arduino.h"
#include "esphome/core/log.h"
#include "esphome/core/helpers.h"
#include <Arduino.h>
#include <cstring>
@@ -27,6 +28,10 @@ void ArduinoI2CBus::setup() {
wire_->begin(sda_pin_, scl_pin_);
wire_->setClock(frequency_);
initialized_ = true;
if (this->scan_) {
ESP_LOGV(TAG, "Scanning i2c bus for active devices...");
this->i2c_scan_();
}
}
void ArduinoI2CBus::dump_config() {
ESP_LOGCONFIG(TAG, "I2C Bus:");
@@ -45,22 +50,20 @@ void ArduinoI2CBus::dump_config() {
break;
}
if (this->scan_) {
ESP_LOGI(TAG, "Scanning i2c bus for active devices...");
uint8_t found = 0;
for (uint8_t address = 8; address < 120; address++) {
auto err = writev(address, nullptr, 0);
if (err == ERROR_OK) {
ESP_LOGI(TAG, "Found i2c device at address 0x%02X", address);
found++;
} else if (err == ERROR_UNKNOWN) {
ESP_LOGI(TAG, "Unknown error at address 0x%02X", address);
}
}
if (found == 0) {
ESP_LOGI(TAG, "Results from i2c bus scan:");
if (scan_results_.empty()) {
ESP_LOGI(TAG, "Found no i2c devices!");
} else {
for (const auto &s : scan_results_) {
if (s.second)
ESP_LOGI(TAG, "Found i2c device at address 0x%02X", s.first);
else
ESP_LOGE(TAG, "Unknown error at address 0x%02X", s.first);
}
}
}
}
ErrorCode ArduinoI2CBus::readv(uint8_t address, ReadBuffer *buffers, size_t cnt) {
// logging is only enabled with vv level, if warnings are shown the caller
// should log them

View File

@@ -34,7 +34,6 @@ class ArduinoI2CBus : public I2CBus, public Component {
protected:
TwoWire *wire_;
bool scan_;
uint8_t sda_pin_;
uint8_t scl_pin_;
uint32_t frequency_;

View File

@@ -3,6 +3,7 @@
#include "i2c_bus_esp_idf.h"
#include "esphome/core/hal.h"
#include "esphome/core/log.h"
#include "esphome/core/helpers.h"
#include <cstring>
namespace esphome {
@@ -37,6 +38,10 @@ void IDFI2CBus::setup() {
return;
}
initialized_ = true;
if (this->scan_) {
ESP_LOGV(TAG, "Scanning i2c bus for active devices...");
this->i2c_scan_();
}
}
void IDFI2CBus::dump_config() {
ESP_LOGCONFIG(TAG, "I2C Bus:");
@@ -55,23 +60,20 @@ void IDFI2CBus::dump_config() {
break;
}
if (this->scan_) {
ESP_LOGI(TAG, "Scanning i2c bus for active devices...");
uint8_t found = 0;
for (uint8_t address = 8; address < 120; address++) {
auto err = writev(address, nullptr, 0);
if (err == ERROR_OK) {
ESP_LOGI(TAG, "Found i2c device at address 0x%02X", address);
found++;
} else if (err == ERROR_UNKNOWN) {
ESP_LOGI(TAG, "Unknown error at address 0x%02X", address);
}
}
if (found == 0) {
ESP_LOGI(TAG, "Results from i2c bus scan:");
if (scan_results_.empty()) {
ESP_LOGI(TAG, "Found no i2c devices!");
} else {
for (const auto &s : scan_results_) {
if (s.second)
ESP_LOGI(TAG, "Found i2c device at address 0x%02X", s.first);
else
ESP_LOGE(TAG, "Unknown error at address 0x%02X", s.first);
}
}
}
}
ErrorCode IDFI2CBus::readv(uint8_t address, ReadBuffer *buffers, size_t cnt) {
// logging is only enabled with vv level, if warnings are shown the caller
// should log them

View File

@@ -36,7 +36,6 @@ class IDFI2CBus : public I2CBus, public Component {
protected:
i2c_port_t port_;
bool scan_;
uint8_t sda_pin_;
bool sda_pullup_enabled_;
uint8_t scl_pin_;

View File

@@ -86,8 +86,8 @@ void ILI9341Display::update() {
void ILI9341Display::display_() {
// we will only update the changed window to the display
int w = this->x_high_ - this->x_low_ + 1;
int h = this->y_high_ - this->y_low_ + 1;
uint16_t w = this->x_high_ - this->x_low_ + 1;
uint16_t h = this->y_high_ - this->y_low_ + 1;
set_addr_window_(this->x_low_, this->y_low_, w, h);
this->start_data_();

View File

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

View File

@@ -1,91 +0,0 @@
#include "improv.h"
namespace improv {
ImprovCommand parse_improv_data(const std::vector<uint8_t> &data) {
return parse_improv_data(data.data(), data.size());
}
ImprovCommand parse_improv_data(const uint8_t *data, size_t length) {
Command command = (Command) data[0];
uint8_t data_length = data[1];
if (data_length != length - 3) {
return {.command = UNKNOWN};
}
uint8_t checksum = data[length - 1];
uint32_t calculated_checksum = 0;
for (uint8_t i = 0; i < length - 1; i++) {
calculated_checksum += data[i];
}
if ((uint8_t) calculated_checksum != checksum) {
return {.command = BAD_CHECKSUM};
}
if (command == WIFI_SETTINGS) {
uint8_t ssid_length = data[2];
uint8_t ssid_start = 3;
size_t ssid_end = ssid_start + ssid_length;
uint8_t pass_length = data[ssid_end];
size_t pass_start = ssid_end + 1;
size_t pass_end = pass_start + pass_length;
std::string ssid(data + ssid_start, data + ssid_end);
std::string password(data + pass_start, data + pass_end);
return {.command = command, .ssid = ssid, .password = password};
}
return {
.command = command,
};
}
std::vector<uint8_t> build_rpc_response(Command command, const std::vector<std::string> &datum) {
std::vector<uint8_t> out;
uint32_t length = 0;
out.push_back(command);
for (auto str : datum) {
uint8_t len = str.length();
length += len;
out.push_back(len);
out.insert(out.end(), str.begin(), str.end());
}
out.insert(out.begin() + 1, length);
uint32_t calculated_checksum = 0;
for (uint8_t byte : out) {
calculated_checksum += byte;
}
out.push_back(calculated_checksum);
return out;
}
#ifdef USE_ARDUINO
std::vector<uint8_t> build_rpc_response(Command command, const std::vector<String> &datum) {
std::vector<uint8_t> out;
uint32_t length = 0;
out.push_back(command);
for (auto str : datum) {
uint8_t len = str.length();
length += len;
out.push_back(len);
out.insert(out.end(), str.begin(), str.end());
}
out.insert(out.begin() + 1, length);
uint32_t calculated_checksum = 0;
for (uint8_t byte : out) {
calculated_checksum += byte;
}
out.push_back(calculated_checksum);
return out;
}
#endif // USE_ARDUINO
} // namespace improv

View File

@@ -1,60 +0,0 @@
#pragma once
#ifdef USE_ARDUINO
#include "WString.h"
#endif // USE_ARDUINO
#include <cstdint>
#include <string>
#include <vector>
namespace improv {
static const char *const SERVICE_UUID = "00467768-6228-2272-4663-277478268000";
static const char *const STATUS_UUID = "00467768-6228-2272-4663-277478268001";
static const char *const ERROR_UUID = "00467768-6228-2272-4663-277478268002";
static const char *const RPC_COMMAND_UUID = "00467768-6228-2272-4663-277478268003";
static const char *const RPC_RESULT_UUID = "00467768-6228-2272-4663-277478268004";
static const char *const CAPABILITIES_UUID = "00467768-6228-2272-4663-277478268005";
enum Error : uint8_t {
ERROR_NONE = 0x00,
ERROR_INVALID_RPC = 0x01,
ERROR_UNKNOWN_RPC = 0x02,
ERROR_UNABLE_TO_CONNECT = 0x03,
ERROR_NOT_AUTHORIZED = 0x04,
ERROR_UNKNOWN = 0xFF,
};
enum State : uint8_t {
STATE_STOPPED = 0x00,
STATE_AWAITING_AUTHORIZATION = 0x01,
STATE_AUTHORIZED = 0x02,
STATE_PROVISIONING = 0x03,
STATE_PROVISIONED = 0x04,
};
enum Command : uint8_t {
UNKNOWN = 0x00,
WIFI_SETTINGS = 0x01,
IDENTIFY = 0x02,
BAD_CHECKSUM = 0xFF,
};
static const uint8_t CAPABILITY_IDENTIFY = 0x01;
struct ImprovCommand {
Command command;
std::string ssid;
std::string password;
};
ImprovCommand parse_improv_data(const std::vector<uint8_t> &data);
ImprovCommand parse_improv_data(const uint8_t *data, size_t length);
std::vector<uint8_t> build_rpc_response(Command command, const std::vector<std::string> &datum);
#ifdef USE_ARDUINO
std::vector<uint8_t> build_rpc_response(Command command, const std::vector<String> &datum);
#endif // USE_ARDUINO
} // namespace improv

View File

@@ -0,0 +1,33 @@
from esphome.const import CONF_BAUD_RATE, CONF_ID, CONF_LOGGER
import esphome.codegen as cg
import esphome.config_validation as cv
import esphome.final_validate as fv
CODEOWNERS = ["@esphome/core"]
DEPENDENCIES = ["logger", "wifi"]
improv_serial_ns = cg.esphome_ns.namespace("improv_serial")
ImprovSerialComponent = improv_serial_ns.class_("ImprovSerialComponent", cg.Component)
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(): cv.declare_id(ImprovSerialComponent),
}
).extend(cv.COMPONENT_SCHEMA)
def validate_logger_baud_rate(config):
logger_conf = fv.full_config.get()[CONF_LOGGER]
if logger_conf[CONF_BAUD_RATE] == 0:
raise cv.Invalid("improv_serial requires the logger baud_rate to be not 0")
return config
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.0.0")

View File

@@ -0,0 +1,266 @@
#include "improv_serial_component.h"
#include "esphome/core/application.h"
#include "esphome/core/defines.h"
#include "esphome/core/hal.h"
#include "esphome/core/log.h"
#include "esphome/core/version.h"
#include "esphome/components/logger/logger.h"
namespace esphome {
namespace improv_serial {
static const char *const TAG = "improv_serial";
void ImprovSerialComponent::setup() {
global_improv_serial_component = this;
#ifdef USE_ARDUINO
this->hw_serial_ = logger::global_logger->get_hw_serial();
#endif
#ifdef USE_ESP_IDF
this->uart_num_ = logger::global_logger->get_uart_num();
#endif
if (wifi::global_wifi_component->has_sta()) {
this->state_ = improv::STATE_PROVISIONED;
}
}
void ImprovSerialComponent::dump_config() { ESP_LOGCONFIG(TAG, "Improv Serial:"); }
int ImprovSerialComponent::available_() {
#ifdef USE_ARDUINO
return this->hw_serial_->available();
#endif
#ifdef USE_ESP_IDF
size_t available;
uart_get_buffered_data_len(this->uart_num_, &available);
return available;
#endif
}
uint8_t ImprovSerialComponent::read_byte_() {
uint8_t data;
#ifdef USE_ARDUINO
this->hw_serial_->readBytes(&data, 1);
#endif
#ifdef USE_ESP_IDF
uart_read_bytes(this->uart_num_, &data, 1, 20 / portTICK_RATE_MS);
#endif
return data;
}
void ImprovSerialComponent::write_data_(std::vector<uint8_t> &data) {
data.push_back('\n');
#ifdef USE_ARDUINO
this->hw_serial_->write(data.data(), data.size());
#endif
#ifdef USE_ESP_IDF
uart_write_bytes(this->uart_num_, data.data(), data.size());
#endif
}
void ImprovSerialComponent::loop() {
const uint32_t now = millis();
if (now - this->last_read_byte_ > 50) {
this->rx_buffer_.clear();
this->last_read_byte_ = now;
}
while (this->available_()) {
uint8_t byte = this->read_byte_();
if (this->parse_improv_serial_byte_(byte)) {
this->last_read_byte_ = now;
} else {
this->rx_buffer_.clear();
}
}
if (this->state_ == improv::STATE_PROVISIONING) {
if (wifi::global_wifi_component->is_connected()) {
wifi::global_wifi_component->save_wifi_sta(this->connecting_sta_.get_ssid(),
this->connecting_sta_.get_password());
this->connecting_sta_ = {};
this->cancel_timeout("wifi-connect-timeout");
this->set_state_(improv::STATE_PROVISIONED);
std::vector<uint8_t> url = this->build_rpc_settings_response_(improv::WIFI_SETTINGS);
this->send_response_(url);
}
}
}
std::vector<uint8_t> ImprovSerialComponent::build_rpc_settings_response_(improv::Command command) {
std::vector<std::string> urls;
#ifdef USE_WEBSERVER
auto ip = wifi::global_wifi_component->wifi_sta_ip();
std::string webserver_url = "http://" + ip.str() + ":" + to_string(WEBSERVER_PORT);
urls.push_back(webserver_url);
#endif
std::vector<uint8_t> data = improv::build_rpc_response(command, urls, false);
return data;
}
std::vector<uint8_t> ImprovSerialComponent::build_version_info_() {
std::vector<std::string> infos = {"ESPHome", ESPHOME_VERSION, ESPHOME_VARIANT, App.get_name()};
std::vector<uint8_t> data = improv::build_rpc_response(improv::GET_DEVICE_INFO, infos, false);
return data;
};
bool ImprovSerialComponent::parse_improv_serial_byte_(uint8_t byte) {
size_t at = this->rx_buffer_.size();
this->rx_buffer_.push_back(byte);
ESP_LOGD(TAG, "Improv Serial byte: 0x%02X", byte);
const uint8_t *raw = &this->rx_buffer_[0];
if (at == 0)
return byte == 'I';
if (at == 1)
return byte == 'M';
if (at == 2)
return byte == 'P';
if (at == 3)
return byte == 'R';
if (at == 4)
return byte == 'O';
if (at == 5)
return byte == 'V';
if (at == 6)
return byte == IMPROV_SERIAL_VERSION;
if (at == 7)
return true;
uint8_t type = raw[7];
if (at == 8)
return true;
uint8_t data_len = raw[8];
if (at < 8 + data_len)
return true;
if (at == 8 + data_len)
return true;
if (at == 8 + data_len + 1) {
uint8_t checksum = 0x00;
for (size_t i = 0; i < at; i++)
checksum += raw[i];
if (checksum != byte) {
ESP_LOGW(TAG, "Error decoding Improv payload");
this->set_error_(improv::ERROR_INVALID_RPC);
return false;
}
if (type == TYPE_RPC) {
this->set_error_(improv::ERROR_NONE);
auto command = improv::parse_improv_data(&raw[9], data_len, false);
return this->parse_improv_payload_(command);
}
}
// If we got here then the command coming is is improv, but not an RPC command
return false;
}
bool ImprovSerialComponent::parse_improv_payload_(improv::ImprovCommand &command) {
switch (command.command) {
case improv::WIFI_SETTINGS: {
wifi::WiFiAP sta{};
sta.set_ssid(command.ssid);
sta.set_password(command.password);
this->connecting_sta_ = sta;
wifi::global_wifi_component->set_sta(sta);
wifi::global_wifi_component->start_scanning();
this->set_state_(improv::STATE_PROVISIONING);
ESP_LOGD(TAG, "Received Improv wifi settings ssid=%s, password=" LOG_SECRET("%s"), command.ssid.c_str(),
command.password.c_str());
auto f = std::bind(&ImprovSerialComponent::on_wifi_connect_timeout_, this);
this->set_timeout("wifi-connect-timeout", 30000, f);
return true;
}
case improv::GET_CURRENT_STATE:
this->set_state_(this->state_);
if (this->state_ == improv::STATE_PROVISIONED) {
std::vector<uint8_t> url = this->build_rpc_settings_response_(improv::GET_CURRENT_STATE);
this->send_response_(url);
}
return true;
case improv::GET_DEVICE_INFO: {
std::vector<uint8_t> info = this->build_version_info_();
this->send_response_(info);
return true;
}
default: {
ESP_LOGW(TAG, "Unknown Improv payload");
this->set_error_(improv::ERROR_UNKNOWN_RPC);
return false;
}
}
}
void ImprovSerialComponent::set_state_(improv::State state) {
this->state_ = state;
std::vector<uint8_t> data = {'I', 'M', 'P', 'R', 'O', 'V'};
data.resize(11);
data[6] = IMPROV_SERIAL_VERSION;
data[7] = TYPE_CURRENT_STATE;
data[8] = 1;
data[9] = state;
uint8_t checksum = 0x00;
for (uint8_t d : data)
checksum += d;
data[10] = checksum;
this->write_data_(data);
}
void ImprovSerialComponent::set_error_(improv::Error error) {
std::vector<uint8_t> data = {'I', 'M', 'P', 'R', 'O', 'V'};
data.resize(11);
data[6] = IMPROV_SERIAL_VERSION;
data[7] = TYPE_ERROR_STATE;
data[8] = 1;
data[9] = error;
uint8_t checksum = 0x00;
for (uint8_t d : data)
checksum += d;
data[10] = checksum;
this->write_data_(data);
}
void ImprovSerialComponent::send_response_(std::vector<uint8_t> &response) {
std::vector<uint8_t> data = {'I', 'M', 'P', 'R', 'O', 'V'};
data.resize(9);
data[6] = IMPROV_SERIAL_VERSION;
data[7] = TYPE_RPC_RESPONSE;
data[8] = response.size();
data.insert(data.end(), response.begin(), response.end());
uint8_t checksum = 0x00;
for (uint8_t d : data)
checksum += d;
data.push_back(checksum);
this->write_data_(data);
}
void ImprovSerialComponent::on_wifi_connect_timeout_() {
this->set_error_(improv::ERROR_UNABLE_TO_CONNECT);
this->set_state_(improv::STATE_AUTHORIZED);
ESP_LOGW(TAG, "Timed out trying to connect to given WiFi network");
wifi::global_wifi_component->clear_sta();
}
ImprovSerialComponent *global_improv_serial_component = // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
} // namespace improv_serial
} // namespace esphome

View File

@@ -0,0 +1,70 @@
#pragma once
#include "esphome/components/wifi/wifi_component.h"
#include "esphome/core/component.h"
#include "esphome/core/defines.h"
#include "esphome/core/helpers.h"
#include <improv.h>
#ifdef USE_ARDUINO
#include <HardwareSerial.h>
#endif
#ifdef USE_ESP_IDF
#include <driver/uart.h>
#endif
namespace esphome {
namespace improv_serial {
enum ImprovSerialType : uint8_t {
TYPE_CURRENT_STATE = 0x01,
TYPE_ERROR_STATE = 0x02,
TYPE_RPC = 0x03,
TYPE_RPC_RESPONSE = 0x04
};
static const uint8_t IMPROV_SERIAL_VERSION = 1;
class ImprovSerialComponent : public Component {
public:
void setup() override;
void loop() override;
void dump_config() override;
float get_setup_priority() const override { return setup_priority::HARDWARE; }
protected:
bool parse_improv_serial_byte_(uint8_t byte);
bool parse_improv_payload_(improv::ImprovCommand &command);
void set_state_(improv::State state);
void set_error_(improv::Error error);
void send_response_(std::vector<uint8_t> &response);
void on_wifi_connect_timeout_();
std::vector<uint8_t> build_rpc_settings_response_(improv::Command command);
std::vector<uint8_t> build_version_info_();
int available_();
uint8_t read_byte_();
void write_data_(std::vector<uint8_t> &data);
#ifdef USE_ARDUINO
HardwareSerial *hw_serial_{nullptr};
#endif
#ifdef USE_ESP_IDF
uart_port_t uart_num_;
#endif
std::vector<uint8_t> rx_buffer_;
uint32_t last_read_byte_{0};
wifi::WiFiAP connecting_sta_;
improv::State state_{improv::STATE_AUTHORIZED};
};
extern ImprovSerialComponent
*global_improv_serial_component; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
} // namespace improv_serial
} // namespace esphome

View File

@@ -9,6 +9,7 @@ from esphome.const import (
DEVICE_CLASS_BATTERY,
DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_TEMPERATURE,
ENTITY_CATEGORY_DIAGNOSTIC,
STATE_CLASS_MEASUREMENT,
UNIT_CELSIUS,
UNIT_PERCENT,
@@ -53,6 +54,7 @@ CONFIG_SCHEMA = (
accuracy_decimals=0,
device_class=DEVICE_CLASS_BATTERY,
state_class=STATE_CLASS_MEASUREMENT,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
}
)

View File

@@ -17,7 +17,7 @@ void GPIOLCDDisplay::setup() {
this->enable_pin_->setup(); // OUTPUT
this->enable_pin_->digital_write(false);
for (uint8_t i = 0; i < (this->is_four_bit_mode() ? 4 : 8); i++) {
for (uint8_t i = 0; i < (uint8_t)(this->is_four_bit_mode() ? 4u : 8u); i++) {
this->data_pins_[i]->setup(); // OUTPUT
this->data_pins_[i]->digital_write(false);
}

View File

@@ -15,17 +15,37 @@ namespace ledc {
static const char *const TAG = "ledc.output";
#ifdef USE_ESP_IDF
static const int MAX_RES_BITS = LEDC_TIMER_BIT_MAX - 1;
#if SOC_LEDC_SUPPORT_HS_MODE
// Only ESP32 has LEDC_HIGH_SPEED_MODE
inline ledc_mode_t get_speed_mode(uint8_t channel) { return channel < 8 ? LEDC_HIGH_SPEED_MODE : LEDC_LOW_SPEED_MODE; }
#else
// S2, C3, S3 only support LEDC_LOW_SPEED_MODE
// See
// https://docs.espressif.com/projects/esp-idf/en/latest/esp32c3/api-reference/peripherals/ledc.html#functionality-overview
inline ledc_mode_t get_speed_mode(uint8_t) { return LEDC_LOW_SPEED_MODE; }
#endif
#else
static const int MAX_RES_BITS = 20;
#endif
float ledc_max_frequency_for_bit_depth(uint8_t bit_depth) { return 80e6f / float(1 << bit_depth); }
float ledc_min_frequency_for_bit_depth(uint8_t bit_depth) {
const float max_div_num = ((1 << 20) - 1) / 256.0f;
float ledc_min_frequency_for_bit_depth(uint8_t bit_depth, bool low_frequency) {
const float max_div_num = ((1 << MAX_RES_BITS) - 1) / (low_frequency ? 32.0f : 256.0f);
return 80e6f / (max_div_num * float(1 << bit_depth));
}
optional<uint8_t> ledc_bit_depth_for_frequency(float frequency) {
for (int i = 20; i >= 1; i--) {
const float min_frequency = ledc_min_frequency_for_bit_depth(i);
ESP_LOGD(TAG, "Calculating resolution bit-depth for frequency %f", frequency);
for (int i = MAX_RES_BITS; i >= 1; i--) {
const float min_frequency = ledc_min_frequency_for_bit_depth(i, (frequency < 100));
const float max_frequency = ledc_max_frequency_for_bit_depth(i);
if (min_frequency <= frequency && frequency <= max_frequency)
if (min_frequency <= frequency && frequency <= max_frequency) {
ESP_LOGD(TAG, "Resolution calculated as %d", i);
return i;
}
}
return {};
}
@@ -48,7 +68,7 @@ void LEDCOutput::write_state(float state) {
ledcWrite(this->channel_, duty);
#endif
#ifdef USE_ESP_IDF
auto speed_mode = channel_ < 8 ? LEDC_HIGH_SPEED_MODE : LEDC_LOW_SPEED_MODE;
auto speed_mode = get_speed_mode(channel_);
auto chan_num = static_cast<ledc_channel_t>(channel_ % 8);
ledc_set_duty(speed_mode, chan_num, duty);
ledc_update_duty(speed_mode, chan_num);
@@ -63,11 +83,15 @@ void LEDCOutput::setup() {
ledcAttachPin(this->pin_->get_pin(), this->channel_);
#endif
#ifdef USE_ESP_IDF
auto speed_mode = channel_ < 8 ? LEDC_HIGH_SPEED_MODE : LEDC_LOW_SPEED_MODE;
auto speed_mode = get_speed_mode(channel_);
auto timer_num = static_cast<ledc_timer_t>((channel_ % 8) / 2);
auto chan_num = static_cast<ledc_channel_t>(channel_ % 8);
bit_depth_ = *ledc_bit_depth_for_frequency(frequency_);
if (bit_depth_ < 1) {
ESP_LOGW(TAG, "Frequency %f can't be achieved with any bit depth", frequency_);
this->status_set_warning();
}
ledc_timer_config_t timer_conf{};
timer_conf.speed_mode = speed_mode;
@@ -114,7 +138,7 @@ void LEDCOutput::update_frequency(float frequency) {
ESP_LOGW(TAG, "LEDC output hasn't been initialized yet!");
return;
}
auto speed_mode = channel_ < 8 ? LEDC_HIGH_SPEED_MODE : LEDC_LOW_SPEED_MODE;
auto speed_mode = get_speed_mode(channel_);
auto timer_num = static_cast<ledc_timer_t>((channel_ % 8) / 2);
ledc_timer_config_t timer_conf{};

View File

@@ -14,6 +14,7 @@ from esphome.const import (
CONF_RESTORE_MODE,
CONF_ON_TURN_OFF,
CONF_ON_TURN_ON,
CONF_ON_STATE,
CONF_TRIGGER_ID,
CONF_COLD_WHITE_COLOR_TEMPERATURE,
CONF_WARM_WHITE_COLOR_TEMPERATURE,
@@ -37,6 +38,7 @@ from .types import ( # noqa
AddressableLight,
LightTurnOnTrigger,
LightTurnOffTrigger,
LightStateTrigger,
)
CODEOWNERS = ["@esphome/core"]
@@ -69,6 +71,11 @@ LIGHT_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).ex
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(LightTurnOffTrigger),
}
),
cv.Optional(CONF_ON_STATE): auto.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(LightStateTrigger),
}
),
}
)
@@ -151,6 +158,9 @@ async def setup_light_core_(light_var, output_var, config):
for conf in config.get(CONF_ON_TURN_OFF, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], light_var)
await auto.build_automation(trigger, [], conf)
for conf in config.get(CONF_ON_STATE, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], light_var)
await auto.build_automation(trigger, [], conf)
if CONF_COLOR_CORRECT in config:
cg.add(output_var.set_correction(*config[CONF_COLOR_CORRECT]))

View File

@@ -79,7 +79,7 @@ optional<LightColorValues> AddressableLightTransformer::apply() {
// dynamically-calculated alpha values to match the look.
float denom = (1.0f - smoothed_progress);
float alpha = denom == 0.0f ? 0.0f : (smoothed_progress - this->last_transition_progress_) / denom;
float alpha = denom == 0.0f ? 1.0f : (smoothed_progress - this->last_transition_progress_) / denom;
// We need to use a low-resolution alpha here which makes the transition set in only after ~half of the length
// We solve this by accumulating the fractional part of the alpha over time.

View File

@@ -22,6 +22,12 @@ using ESPColor ESPDEPRECATED("esphome::light::ESPColor is deprecated, use esphom
/// Convert the color information from a `LightColorValues` object to a `Color` object (does not apply brightness).
Color color_from_light_color_values(LightColorValues val);
/// Use a custom state class for addressable lights, to allow type system to discriminate between addressable and
/// non-addressable lights.
class AddressableLightState : public LightState {
using LightState::LightState;
};
class AddressableLight : public LightOutput, public Component {
public:
virtual int32_t size() const = 0;
@@ -81,7 +87,7 @@ class AddressableLight : public LightOutput, public Component {
void mark_shown_() {
#ifdef USE_POWER_SUPPLY
for (const auto &c : *this) {
if (c.get().is_on()) {
if (c.get_red_raw() > 0 || c.get_green_raw() > 0 || c.get_blue_raw() > 0 || c.get_white_raw() > 0) {
this->power_.request();
return;
}

View File

@@ -167,7 +167,7 @@ class AddressableScanEffect : public AddressableLightEffect {
this->last_move_ = now;
it.all() = Color::BLACK;
for (auto i = 0; i < this->scan_width_; i++) {
for (uint32_t i = 0; i < this->scan_width_; i++) {
it[this->at_led_ + i] = current_color;
}
@@ -178,7 +178,7 @@ class AddressableScanEffect : public AddressableLightEffect {
uint32_t move_interval_{};
uint32_t scan_width_{1};
uint32_t last_move_{0};
int at_led_{0};
uint32_t at_led_{0};
bool direction_{true};
};

View File

@@ -16,24 +16,94 @@ class AddressableLightWrapper : public light::AddressableLight {
void clear_effect_data() override { this->wrapper_state_[4] = 0; }
light::LightTraits get_traits() override { return this->light_state_->get_traits(); }
light::LightTraits get_traits() override {
LightTraits traits;
// Choose which color mode to use.
// This is ordered by how closely each color mode matches the underlying RGBW data structure used in LightPartition.
ColorMode color_mode_precedence[] = {ColorMode::RGB_WHITE,
ColorMode::RGB_COLD_WARM_WHITE,
ColorMode::RGB_COLOR_TEMPERATURE,
ColorMode::RGB,
ColorMode::WHITE,
ColorMode::COLD_WARM_WHITE,
ColorMode::COLOR_TEMPERATURE,
ColorMode::BRIGHTNESS,
ColorMode::ON_OFF,
ColorMode::UNKNOWN};
LightTraits parent_traits = this->light_state_->get_traits();
for (auto cm : color_mode_precedence) {
if (parent_traits.supports_color_mode(cm)) {
this->color_mode_ = cm;
break;
}
}
// Report a color mode that's compatible with both the partition and the underlying light
switch (this->color_mode_) {
case ColorMode::RGB_WHITE:
case ColorMode::RGB_COLD_WARM_WHITE:
case ColorMode::RGB_COLOR_TEMPERATURE:
traits.set_supported_color_modes({light::ColorMode::RGB_WHITE});
break;
case ColorMode::RGB:
traits.set_supported_color_modes({light::ColorMode::RGB});
break;
case ColorMode::WHITE:
case ColorMode::COLD_WARM_WHITE:
case ColorMode::COLOR_TEMPERATURE:
case ColorMode::BRIGHTNESS:
traits.set_supported_color_modes({light::ColorMode::BRIGHTNESS});
break;
case ColorMode::ON_OFF:
traits.set_supported_color_modes({light::ColorMode::ON_OFF});
break;
default:
traits.set_supported_color_modes({light::ColorMode::UNKNOWN});
}
return traits;
}
void write_state(light::LightState *state) override {
// Don't overwrite state if the underlying light is turned on
if (this->light_state_->remote_values.is_on()) {
this->mark_shown_();
return;
}
float gamma = this->light_state_->get_gamma_correct();
float r = gamma_uncorrect(this->wrapper_state_[0] / 255.0f, gamma);
float g = gamma_uncorrect(this->wrapper_state_[1] / 255.0f, gamma);
float b = gamma_uncorrect(this->wrapper_state_[2] / 255.0f, gamma);
float w = gamma_uncorrect(this->wrapper_state_[3] / 255.0f, gamma);
float brightness = fmaxf(r, fmaxf(g, b));
auto call = this->light_state_->make_call();
call.set_state(true);
call.set_brightness_if_supported(1.0f);
call.set_color_brightness_if_supported(brightness);
call.set_red_if_supported(r);
call.set_green_if_supported(g);
call.set_blue_if_supported(b);
call.set_white_if_supported(w);
float color_brightness = fmaxf(r, fmaxf(g, b));
float brightness = fmaxf(color_brightness, w);
if (brightness == 0.0f) {
call.set_state(false);
} else {
color_brightness /= brightness;
w /= brightness;
call.set_state(true);
call.set_color_mode_if_supported(this->color_mode_);
call.set_brightness_if_supported(brightness);
call.set_color_brightness_if_supported(color_brightness);
call.set_red_if_supported(r);
call.set_green_if_supported(g);
call.set_blue_if_supported(b);
call.set_white_if_supported(w);
call.set_warm_white_if_supported(w);
call.set_cold_white_if_supported(w);
}
call.set_transition_length_if_supported(0);
call.set_publish(false);
call.set_save(false);
@@ -50,6 +120,7 @@ class AddressableLightWrapper : public light::AddressableLight {
light::LightState *light_state_;
uint8_t *wrapper_state_;
ColorMode color_mode_{ColorMode::UNKNOWN};
};
} // namespace light

View File

@@ -141,6 +141,13 @@ class LightTurnOffTrigger : public Trigger<> {
}
};
class LightStateTrigger : public Trigger<> {
public:
LightStateTrigger(LightState *a_light) {
a_light->add_new_remote_values_callback([this]() { this->trigger(); });
}
};
// This is slightly ugly, but we can't log in headers, and can't make this a static method on AddressableSet
// due to the template. It's just a temporary warning anyway.
void addressableset_warn_about_scale(const char *field);

View File

@@ -98,7 +98,7 @@ void LightCall::perform() {
// EFFECT
auto effect = this->effect_;
const char *effect_s;
if (effect == 0)
if (effect == 0u)
effect_s = "None";
else
effect_s = this->parent_->effects_[*this->effect_ - 1]->get_name().c_str();

View File

@@ -39,7 +39,15 @@ class LightTransformer {
protected:
/// The progress of this transition, on a scale of 0 to 1.
float get_progress_() { return clamp((millis() - this->start_time_) / float(this->length_), 0.0f, 1.0f); }
float get_progress_() {
uint32_t now = esphome::millis();
if (now < this->start_time_)
return 0.0f;
if (now >= this->start_time_ + this->length_)
return 1.0f;
return clamp((now - this->start_time_) / float(this->length_), 0.0f, 1.0f);
}
uint32_t start_time_;
uint32_t length_;

View File

@@ -18,10 +18,13 @@ class LightTransitionTransformer : public LightTransformer {
this->start_values_.set_brightness(0.0f);
}
// When turning light off from on state, use source state and only decrease brightness to zero.
// When turning light off from on state, use source state and only decrease brightness to zero. Use a second
// variable for transition end state, as overwriting target_values breaks LightState logic.
if (this->start_values_.is_on() && !this->target_values_.is_on()) {
this->target_values_ = LightColorValues(this->start_values_);
this->target_values_.set_brightness(0.0f);
this->end_values_ = LightColorValues(this->start_values_);
this->end_values_.set_brightness(0.0f);
} else {
this->end_values_ = LightColorValues(this->target_values_);
}
// When changing color mode, go through off state, as color modes are orthogonal and there can't be two active.
@@ -43,7 +46,7 @@ class LightTransitionTransformer : public LightTransformer {
}
LightColorValues &start = this->changing_color_mode_ && p > 0.5f ? this->intermediate_values_ : this->start_values_;
LightColorValues &end = this->changing_color_mode_ && p < 0.5f ? this->intermediate_values_ : this->target_values_;
LightColorValues &end = this->changing_color_mode_ && p < 0.5f ? this->intermediate_values_ : this->end_values_;
if (this->changing_color_mode_)
p = p < 0.5f ? p * 2 : (p - 0.5) * 2;
@@ -57,6 +60,7 @@ class LightTransitionTransformer : public LightTransformer {
static float smoothed_progress(float x) { return x * x * x * (x * (x * 6.0f - 15.0f) + 10.0f); }
bool changing_color_mode_{false};
LightColorValues end_values_{};
LightColorValues intermediate_values_{};
};
@@ -69,9 +73,7 @@ class LightFlashTransformer : public LightTransformer {
if (this->transition_length_ * 2 > this->length_)
this->transition_length_ = this->length_ / 2;
// do not create transition if length is 0
if (this->transition_length_ == 0)
return;
this->begun_lightstate_restore_ = false;
// first transition to original target
this->transformer_ = this->state_.get_output()->create_default_transition();
@@ -79,40 +81,45 @@ class LightFlashTransformer : public LightTransformer {
}
optional<LightColorValues> apply() override {
// transition transformer does not handle 0 length as progress returns nan
if (this->transition_length_ == 0)
return this->target_values_;
optional<LightColorValues> result = {};
if (this->transformer_ == nullptr && millis() > this->start_time_ + this->length_ - this->transition_length_) {
// second transition back to start value
this->transformer_ = this->state_.get_output()->create_default_transition();
this->transformer_->setup(this->state_.current_values, this->get_start_values(), this->transition_length_);
this->begun_lightstate_restore_ = true;
}
if (this->transformer_ != nullptr) {
if (!this->transformer_->is_finished()) {
return this->transformer_->apply();
} else {
result = this->transformer_->apply();
if (this->transformer_->is_finished()) {
this->transformer_->stop();
this->transformer_ = nullptr;
}
}
if (millis() > this->start_time_ + this->length_ - this->transition_length_) {
// second transition back to start value
this->transformer_ = this->state_.get_output()->create_default_transition();
this->transformer_->setup(this->state_.current_values, this->get_start_values(), this->transition_length_);
}
// once transition is complete, don't change states until next transition
return optional<LightColorValues>();
return result;
}
// Restore the original values after the flash.
void stop() override {
if (this->transformer_ != nullptr) {
this->transformer_->stop();
this->transformer_ = nullptr;
}
this->state_.current_values = this->get_start_values();
this->state_.remote_values = this->get_start_values();
this->state_.publish_state();
}
bool is_finished() override { return this->begun_lightstate_restore_ && LightTransformer::is_finished(); }
protected:
LightState &state_;
uint32_t transition_length_;
std::unique_ptr<LightTransformer> transformer_{nullptr};
bool begun_lightstate_restore_;
};
} // namespace light

View File

@@ -4,10 +4,9 @@ from esphome import automation
# Base
light_ns = cg.esphome_ns.namespace("light")
LightState = light_ns.class_("LightState", cg.EntityBase, cg.Component)
# Fake class for addressable lights
AddressableLightState = light_ns.class_("LightState", LightState)
AddressableLightState = light_ns.class_("AddressableLightState", LightState)
LightOutput = light_ns.class_("LightOutput")
AddressableLight = light_ns.class_("AddressableLight", cg.Component)
AddressableLight = light_ns.class_("AddressableLight", LightOutput, cg.Component)
AddressableLightRef = AddressableLight.operator("ref")
Color = cg.esphome_ns.class_("Color")
@@ -42,6 +41,7 @@ LightTurnOnTrigger = light_ns.class_(
LightTurnOffTrigger = light_ns.class_(
"LightTurnOffTrigger", automation.Trigger.template()
)
LightStateTrigger = light_ns.class_("LightStateTrigger", automation.Trigger.template())
# Effects
LightEffect = light_ns.class_("LightEffect")

View File

@@ -19,6 +19,8 @@ from esphome.const import (
CONF_TX_BUFFER_SIZE,
)
from esphome.core import CORE, EsphomeError, Lambda, coroutine_with_priority
from esphome.components.esp32 import get_esp32_variant
from esphome.components.esp32.const import VARIANT_ESP32S2, VARIANT_ESP32C3
CODEOWNERS = ["@esphome/core"]
logger_ns = cg.esphome_ns.namespace("logger")
@@ -52,6 +54,10 @@ LOG_LEVEL_SEVERITY = [
"VERY_VERBOSE",
]
ESP32_REDUCED_VARIANTS = [VARIANT_ESP32C3, VARIANT_ESP32S2]
UART_SELECTION_ESP32_REDUCED = ["UART0", "UART1"]
UART_SELECTION_ESP32 = ["UART0", "UART1", "UART2"]
UART_SELECTION_ESP8266 = ["UART0", "UART0_SWAP", "UART1"]
@@ -75,6 +81,8 @@ is_log_level = cv.one_of(*LOG_LEVELS, upper=True)
def uart_selection(value):
if CORE.is_esp32:
if get_esp32_variant() in ESP32_REDUCED_VARIANTS:
return cv.one_of(*UART_SELECTION_ESP32_REDUCED, upper=True)(value)
return cv.one_of(*UART_SELECTION_ESP32, upper=True)(value)
if CORE.is_esp8266:
return cv.one_of(*UART_SELECTION_ESP8266, upper=True)(value)

View File

@@ -153,13 +153,9 @@ void Logger::pre_setup() {
case UART_SELECTION_UART1:
this->hw_serial_ = &Serial1;
break;
#ifdef USE_ESP32
#if defined(USE_ESP32) && !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32S2)
case UART_SELECTION_UART2:
#if !CONFIG_IDF_TARGET_ESP32S2 && !CONFIG_IDF_TARGET_ESP32C3
// FIXME: Validate in config that UART2 can't be set for ESP32-S2 (only has
// UART0-UART1)
this->hw_serial_ = &Serial2;
#endif
break;
#endif
}
@@ -173,9 +169,11 @@ void Logger::pre_setup() {
case UART_SELECTION_UART1:
uart_num_ = UART_NUM_1;
break;
#if defined(USE_ESP32) && !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32S2)
case UART_SELECTION_UART2:
uart_num_ = UART_NUM_2;
break;
#endif
}
uart_config_t uart_config{};
uart_config.baud_rate = (int) baud_rate_;
@@ -223,7 +221,7 @@ UARTSelection Logger::get_uart() const { return this->uart_; }
void Logger::add_on_log_callback(std::function<void(int, const char *, const char *)> &&callback) {
this->log_callback_.add(std::move(callback));
}
float Logger::get_setup_priority() const { return setup_priority::HARDWARE - 1.0f; }
float Logger::get_setup_priority() const { return setup_priority::BUS + 500.0f; }
const char *const LOG_LEVELS[] = {"NONE", "ERROR", "WARN", "INFO", "CONFIG", "DEBUG", "VERBOSE", "VERY_VERBOSE"};
#ifdef USE_ESP32
const char *const UART_SELECTIONS[] = {"UART0", "UART1", "UART2"};

View File

@@ -24,7 +24,7 @@ namespace logger {
enum UARTSelection {
UART_SELECTION_UART0 = 0,
UART_SELECTION_UART1,
#ifdef USE_ESP32
#if defined(USE_ESP32) && !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32S2)
UART_SELECTION_UART2,
#endif
#ifdef USE_ESP8266

View File

@@ -97,7 +97,7 @@ void LTR390Component::read_mode_(int mode_index) {
// If there are more modes to read then begin the next
// otherwise stop
if (mode_index + 1 < this->mode_funcs_.size()) {
if (mode_index + 1 < (int) this->mode_funcs_.size()) {
this->read_mode_(mode_index + 1);
} else {
this->reading_ = false;

View File

@@ -203,16 +203,16 @@ float MAX31865Sensor::calc_temperature_(float rtd_ratio) {
rtd_resistance *= 100;
}
float rpoly = rtd_resistance;
float neg_temp = -242.02;
neg_temp += 2.2228 * rpoly;
float neg_temp = -242.02f;
neg_temp += 2.2228f * rpoly;
rpoly *= rtd_resistance; // square
neg_temp += 2.5859e-3 * rpoly;
neg_temp += 2.5859e-3f * rpoly;
rpoly *= rtd_resistance; // ^3
neg_temp -= 4.8260e-6 * rpoly;
neg_temp -= 4.8260e-6f * rpoly;
rpoly *= rtd_resistance; // ^4
neg_temp -= 2.8183e-8 * rpoly;
neg_temp -= 2.8183e-8f * rpoly;
rpoly *= rtd_resistance; // ^5
neg_temp += 1.5243e-10 * rpoly;
neg_temp += 1.5243e-10f * rpoly;
return neg_temp;
}

View File

@@ -13,10 +13,20 @@ CONF_SCROLL_DELAY = "scroll_delay"
CONF_SCROLL_ENABLE = "scroll_enable"
CONF_SCROLL_MODE = "scroll_mode"
CONF_REVERSE_ENABLE = "reverse_enable"
CONF_NUM_CHIP_LINES = "num_chip_lines"
CONF_CHIP_LINES_STYLE = "chip_lines_style"
integration_ns = cg.esphome_ns.namespace("max7219digit")
ChipLinesStyle = integration_ns.enum("ChipLinesStyle")
CHIP_LINES_STYLE = {
"ZIGZAG": ChipLinesStyle.ZIGZAG,
"SNAKE": ChipLinesStyle.SNAKE,
}
ScrollMode = integration_ns.enum("ScrollMode")
SCROLL_MODES = {
"CONTINUOUS": 0,
"STOP": 1,
"CONTINUOUS": ScrollMode.CONTINUOUS,
"STOP": ScrollMode.STOP,
}
CHIP_MODES = {
@@ -37,6 +47,10 @@ CONFIG_SCHEMA = (
{
cv.GenerateID(): cv.declare_id(MAX7219Component),
cv.Optional(CONF_NUM_CHIPS, default=4): cv.int_range(min=1, max=255),
cv.Optional(CONF_NUM_CHIP_LINES, default=1): cv.int_range(min=1, max=255),
cv.Optional(CONF_CHIP_LINES_STYLE, default="SNAKE"): cv.enum(
CHIP_LINES_STYLE, upper=True
),
cv.Optional(CONF_INTENSITY, default=15): cv.int_range(min=0, max=15),
cv.Optional(CONF_ROTATE_CHIP, default="0"): cv.enum(CHIP_MODES, upper=True),
cv.Optional(CONF_SCROLL_MODE, default="CONTINUOUS"): cv.enum(
@@ -67,6 +81,8 @@ async def to_code(config):
await display.register_display(var, config)
cg.add(var.set_num_chips(config[CONF_NUM_CHIPS]))
cg.add(var.set_num_chip_lines(config[CONF_NUM_CHIP_LINES]))
cg.add(var.set_chip_lines_style(config[CONF_CHIP_LINES_STYLE]))
cg.add(var.set_intensity(config[CONF_INTENSITY]))
cg.add(var.set_chip_orientation(config[CONF_ROTATE_CHIP]))
cg.add(var.set_scroll_speed(config[CONF_SCROLL_SPEED]))

View File

@@ -26,9 +26,12 @@ void MAX7219Component::setup() {
ESP_LOGCONFIG(TAG, "Setting up MAX7219_DIGITS...");
this->spi_setup();
this->stepsleft_ = 0;
this->max_displaybuffer_.reserve(500); // Create base space to write buffer
// Initialize buffer with 0 for display so all non written pixels are blank
this->max_displaybuffer_.resize(this->num_chips_ * 8, 0);
for (int chip_line = 0; chip_line < this->num_chip_lines_; chip_line++) {
std::vector<uint8_t> vec(1);
this->max_displaybuffer_.push_back(vec);
// Initialize buffer with 0 for display so all non written pixels are blank
this->max_displaybuffer_[chip_line].resize(get_width_internal(), 0);
}
// let's assume the user has all 8 digits connected, only important in daisy chained setups anyway
this->send_to_all_(MAX7219_REGISTER_SCAN_LIMIT, 7);
// let's use our own ASCII -> led pattern encoding
@@ -46,6 +49,8 @@ void MAX7219Component::setup() {
void MAX7219Component::dump_config() {
ESP_LOGCONFIG(TAG, "MAX7219DIGIT:");
ESP_LOGCONFIG(TAG, " Number of Chips: %u", this->num_chips_);
ESP_LOGCONFIG(TAG, " Number of Chips Lines: %u", this->num_chip_lines_);
ESP_LOGCONFIG(TAG, " Chips Lines Style : %u", this->chip_lines_style_);
ESP_LOGCONFIG(TAG, " Intensity: %u", this->intensity_);
ESP_LOGCONFIG(TAG, " Scroll Mode: %u", this->scroll_mode_);
ESP_LOGCONFIG(TAG, " Scroll Speed: %u", this->scroll_speed_);
@@ -59,19 +64,19 @@ void MAX7219Component::loop() {
uint32_t now = millis();
// check if the buffer has shrunk past the current position since last update
if ((this->max_displaybuffer_.size() >= this->old_buffer_size_ + 3) ||
(this->max_displaybuffer_.size() <= this->old_buffer_size_ - 3)) {
if ((this->max_displaybuffer_[0].size() >= this->old_buffer_size_ + 3) ||
(this->max_displaybuffer_[0].size() <= this->old_buffer_size_ - 3)) {
this->stepsleft_ = 0;
this->display();
this->old_buffer_size_ = this->max_displaybuffer_.size();
this->old_buffer_size_ = this->max_displaybuffer_[0].size();
}
// Reset the counter back to 0 when full string has been displayed.
if (this->stepsleft_ > this->max_displaybuffer_.size())
if (this->stepsleft_ > this->max_displaybuffer_[0].size())
this->stepsleft_ = 0;
// Return if there is no need to scroll or scroll is off
if (!this->scroll_ || (this->max_displaybuffer_.size() <= this->num_chips_ * 8)) {
if (!this->scroll_ || (this->max_displaybuffer_[0].size() <= (size_t) get_width_internal())) {
this->display();
return;
}
@@ -82,8 +87,8 @@ void MAX7219Component::loop() {
}
// Dwell time at end of string in case of stop at end
if (this->scroll_mode_ == 1) {
if (this->stepsleft_ >= this->max_displaybuffer_.size() - this->num_chips_ * 8 + 1) {
if (this->scroll_mode_ == ScrollMode::STOP) {
if (this->stepsleft_ >= this->max_displaybuffer_[0].size() - (size_t) get_width_internal() + 1) {
if (now - this->last_scroll_ >= this->scroll_dwell_) {
this->stepsleft_ = 0;
this->last_scroll_ = now;
@@ -107,30 +112,53 @@ void MAX7219Component::display() {
// Run this routine for the rows of every chip 8x row 0 top to 7 bottom
// Fill the pixel parameter with display data
// Send the data to the chip
for (uint8_t i = 0; i < this->num_chips_; i++) {
for (uint8_t j = 0; j < 8; j++) {
if (this->reverse_) {
pixels[j] = this->max_displaybuffer_[(this->num_chips_ - i - 1) * 8 + j];
} else {
pixels[j] = this->max_displaybuffer_[i * 8 + j];
for (uint8_t chip = 0; chip < this->num_chips_ / this->num_chip_lines_; chip++) {
for (uint8_t chip_line = 0; chip_line < this->num_chip_lines_; chip_line++) {
for (uint8_t j = 0; j < 8; j++) {
bool reverse =
chip_line % 2 != 0 && this->chip_lines_style_ == ChipLinesStyle::SNAKE ? !this->reverse_ : this->reverse_;
if (reverse) {
pixels[j] =
this->max_displaybuffer_[chip_line][(this->num_chips_ / this->num_chip_lines_ - chip - 1) * 8 + j];
} else {
pixels[j] = this->max_displaybuffer_[chip_line][chip * 8 + j];
}
}
if (chip_line % 2 != 0 && this->chip_lines_style_ == ChipLinesStyle::SNAKE)
this->orientation_ = orientation_180_();
this->send64pixels(chip_line * this->num_chips_ / this->num_chip_lines_ + chip, pixels);
if (chip_line % 2 != 0 && this->chip_lines_style_ == ChipLinesStyle::SNAKE)
this->orientation_ = orientation_180_();
}
this->send64pixels(i, pixels);
}
}
uint8_t MAX7219Component::orientation_180_() {
switch (this->orientation_) {
case 0:
return 2;
case 1:
return 3;
case 2:
return 0;
case 3:
return 1;
default:
return 0;
}
}
int MAX7219Component::get_height_internal() {
return 8; // TO BE DONE -> STACK TWO DISPLAYS ON TOP OF EACH OTHER
// TO BE DONE -> CREATE Virtual size of screen and scroll
return 8 * this->num_chip_lines_; // TO BE DONE -> CREATE Virtual size of screen and scroll
}
int MAX7219Component::get_width_internal() { return this->num_chips_ * 8; }
size_t MAX7219Component::get_buffer_length_() { return this->num_chips_ * 8; }
int MAX7219Component::get_width_internal() { return this->num_chips_ / this->num_chip_lines_ * 8; }
void HOT MAX7219Component::draw_absolute_pixel_internal(int x, int y, Color color) {
if (x + 1 > this->max_displaybuffer_.size()) { // Extend the display buffer in case required
this->max_displaybuffer_.resize(x + 1, this->bckgrnd_);
if (x + 1 > (int) this->max_displaybuffer_[0].size()) { // Extend the display buffer in case required
for (int chip_line = 0; chip_line < this->num_chip_lines_; chip_line++) {
this->max_displaybuffer_[chip_line].resize(x + 1, this->bckgrnd_);
}
}
if ((y >= this->get_height_internal()) || (y < 0) || (x < 0)) // If pixel is outside display then dont draw
@@ -140,9 +168,9 @@ void HOT MAX7219Component::draw_absolute_pixel_internal(int x, int y, Color colo
uint8_t subpos = y; // Y is starting at 0 top left
if (color.is_on()) {
this->max_displaybuffer_[pos] |= (1 << subpos);
this->max_displaybuffer_[subpos / 8][pos] |= (1 << subpos % 8);
} else {
this->max_displaybuffer_[pos] &= ~(1 << subpos);
this->max_displaybuffer_[subpos / 8][pos] &= ~(1 << subpos % 8);
}
}
@@ -158,8 +186,10 @@ void MAX7219Component::send_to_all_(uint8_t a_register, uint8_t data) {
}
void MAX7219Component::update() {
this->update_ = true;
this->max_displaybuffer_.clear();
this->max_displaybuffer_.resize(this->num_chips_ * 8, this->bckgrnd_);
for (int chip_line = 0; chip_line < this->num_chip_lines_; chip_line++) {
this->max_displaybuffer_[chip_line].clear();
this->max_displaybuffer_[chip_line].resize(get_width_internal(), this->bckgrnd_);
}
if (this->writer_local_.has_value()) // insert Labda function if available
(*this->writer_local_)(*this);
}
@@ -175,7 +205,7 @@ void MAX7219Component::turn_on_off(bool on_off) {
}
}
void MAX7219Component::scroll(bool on_off, uint8_t mode, uint16_t speed, uint16_t delay, uint16_t dwell) {
void MAX7219Component::scroll(bool on_off, ScrollMode mode, uint16_t speed, uint16_t delay, uint16_t dwell) {
this->set_scroll(on_off);
this->set_scroll_mode(mode);
this->set_scroll_speed(speed);
@@ -183,7 +213,7 @@ void MAX7219Component::scroll(bool on_off, uint8_t mode, uint16_t speed, uint16_
this->set_scroll_delay(delay);
}
void MAX7219Component::scroll(bool on_off, uint8_t mode) {
void MAX7219Component::scroll(bool on_off, ScrollMode mode) {
this->set_scroll(on_off);
this->set_scroll_mode(mode);
}
@@ -196,24 +226,26 @@ void MAX7219Component::intensity(uint8_t intensity) {
void MAX7219Component::scroll(bool on_off) { this->set_scroll(on_off); }
void MAX7219Component::scroll_left() {
if (this->update_) {
this->max_displaybuffer_.push_back(this->bckgrnd_);
for (uint16_t i = 0; i < this->stepsleft_; i++) {
this->max_displaybuffer_.push_back(this->max_displaybuffer_.front());
this->max_displaybuffer_.erase(this->max_displaybuffer_.begin());
this->update_ = false;
for (int chip_line = 0; chip_line < this->num_chip_lines_; chip_line++) {
if (this->update_) {
this->max_displaybuffer_[chip_line].push_back(this->bckgrnd_);
for (uint16_t i = 0; i < this->stepsleft_; i++) {
this->max_displaybuffer_[chip_line].push_back(this->max_displaybuffer_[chip_line].front());
this->max_displaybuffer_[chip_line].erase(this->max_displaybuffer_[chip_line].begin());
}
} else {
this->max_displaybuffer_[chip_line].push_back(this->max_displaybuffer_[chip_line].front());
this->max_displaybuffer_[chip_line].erase(this->max_displaybuffer_[chip_line].begin());
}
} else {
this->max_displaybuffer_.push_back(this->max_displaybuffer_.front());
this->max_displaybuffer_.erase(this->max_displaybuffer_.begin());
}
this->update_ = false;
this->stepsleft_++;
}
void MAX7219Component::send_char(uint8_t chip, uint8_t data) {
// get this character from PROGMEM
for (uint8_t i = 0; i < 8; i++)
this->max_displaybuffer_[chip * 8 + i] = progmem_read_byte(&MAX7219_DOT_MATRIX_FONT[data][i]);
this->max_displaybuffer_[0][chip * 8 + i] = progmem_read_byte(&MAX7219_DOT_MATRIX_FONT[data][i]);
} // end of send_char
// send one character (data) to position (chip)

View File

@@ -12,6 +12,16 @@
namespace esphome {
namespace max7219digit {
enum ChipLinesStyle {
ZIGZAG = 0,
SNAKE,
};
enum ScrollMode {
CONTINUOUS = 0,
STOP,
};
class MAX7219Component;
using max7219_writer_t = std::function<void(MAX7219Component &)>;
@@ -46,20 +56,22 @@ class MAX7219Component : public PollingComponent,
void set_intensity(uint8_t intensity) { this->intensity_ = intensity; };
void set_num_chips(uint8_t num_chips) { this->num_chips_ = num_chips; };
void set_num_chip_lines(uint8_t num_chip_lines) { this->num_chip_lines_ = num_chip_lines; };
void set_chip_lines_style(ChipLinesStyle chip_lines_style) { this->chip_lines_style_ = chip_lines_style; };
void set_chip_orientation(uint8_t rotate) { this->orientation_ = rotate; };
void set_scroll_speed(uint16_t speed) { this->scroll_speed_ = speed; };
void set_scroll_dwell(uint16_t dwell) { this->scroll_dwell_ = dwell; };
void set_scroll_delay(uint16_t delay) { this->scroll_delay_ = delay; };
void set_scroll(bool on_off) { this->scroll_ = on_off; };
void set_scroll_mode(uint8_t mode) { this->scroll_mode_ = mode; };
void set_scroll_mode(ScrollMode mode) { this->scroll_mode_ = mode; };
void set_reverse(bool on_off) { this->reverse_ = on_off; };
void send_char(uint8_t chip, uint8_t data);
void send64pixels(uint8_t chip, const uint8_t pixels[8]);
void scroll_left();
void scroll(bool on_off, uint8_t mode, uint16_t speed, uint16_t delay, uint16_t dwell);
void scroll(bool on_off, uint8_t mode);
void scroll(bool on_off, ScrollMode mode, uint16_t speed, uint16_t delay, uint16_t dwell);
void scroll(bool on_off, ScrollMode mode);
void scroll(bool on_off);
void intensity(uint8_t intensity);
@@ -84,9 +96,12 @@ class MAX7219Component : public PollingComponent,
protected:
void send_byte_(uint8_t a_register, uint8_t data);
void send_to_all_(uint8_t a_register, uint8_t data);
uint8_t orientation_180_();
uint8_t intensity_; /// Intensity of the display from 0 to 15 (most)
uint8_t num_chips_;
uint8_t num_chip_lines_;
ChipLinesStyle chip_lines_style_;
bool scroll_;
bool reverse_;
bool update_{false};
@@ -94,11 +109,11 @@ class MAX7219Component : public PollingComponent,
uint16_t scroll_delay_;
uint16_t scroll_dwell_;
uint16_t old_buffer_size_ = 0;
uint8_t scroll_mode_;
ScrollMode scroll_mode_;
bool invert_ = false;
uint8_t orientation_;
uint8_t bckgrnd_ = 0x0;
std::vector<uint8_t> max_displaybuffer_;
std::vector<std::vector<uint8_t>> max_displaybuffer_;
uint32_t last_scroll_ = 0;
uint16_t stepsleft_;
size_t get_buffer_length_();

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