From 6d341ce4e7f41c09d723f9663e61e0c0dd51d159 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 27 May 2024 07:15:05 +1200 Subject: [PATCH 01/46] [web_server_base] Bump ESPAsyncWebServer-esphome to 3.2.2 (#6797) Co-authored-by: Samuel Sieb --- esphome/components/web_server_base/__init__.py | 2 +- platformio.ini | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/web_server_base/__init__.py b/esphome/components/web_server_base/__init__.py index 1970b5a0c5..4f894619b0 100644 --- a/esphome/components/web_server_base/__init__.py +++ b/esphome/components/web_server_base/__init__.py @@ -37,4 +37,4 @@ async def to_code(config): cg.add_library("FS", None) cg.add_library("Update", None) # https://github.com/esphome/ESPAsyncWebServer/blob/master/library.json - cg.add_library("esphome/ESPAsyncWebServer-esphome", "3.2.0") + cg.add_library("esphome/ESPAsyncWebServer-esphome", "3.2.2") diff --git a/platformio.ini b/platformio.ini index 65c742cf91..fd0ae7c6d8 100644 --- a/platformio.ini +++ b/platformio.ini @@ -58,7 +58,7 @@ lib_deps = SPI ; spi (Arduino built-in) Wire ; i2c (Arduino built-int) heman/AsyncMqttClient-esphome@1.0.0 ; mqtt - esphome/ESPAsyncWebServer-esphome@3.2.0 ; web_server_base + esphome/ESPAsyncWebServer-esphome@3.2.2 ; web_server_base fastled/FastLED@3.3.2 ; fastled_base mikalhart/TinyGPSPlus@1.0.2 ; gps freekode/TM1651@1.0.1 ; tm1651 From 4125b48b86a4da0ba27704ab11f2033a8275540c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sa=C5=A1o=20Domadenik?= Date: Sun, 26 May 2024 21:23:00 +0200 Subject: [PATCH 02/46] Fix incorrect naming of the AdaFruit MagTag display. (#6810) --- esphome/components/waveshare_epaper/display.py | 4 ++-- .../components/waveshare_epaper/waveshare_epaper.cpp | 12 ++++++------ .../components/waveshare_epaper/waveshare_epaper.h | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/esphome/components/waveshare_epaper/display.py b/esphome/components/waveshare_epaper/display.py index bba60efc0a..9ad948e915 100644 --- a/esphome/components/waveshare_epaper/display.py +++ b/esphome/components/waveshare_epaper/display.py @@ -48,7 +48,7 @@ WaveshareEPaper2P9InBV3 = waveshare_epaper_ns.class_( WaveshareEPaper2P9InV2R2 = waveshare_epaper_ns.class_( "WaveshareEPaper2P9InV2R2", WaveshareEPaper ) -GDEY029T94 = waveshare_epaper_ns.class_("GDEY029T94", WaveshareEPaper) +GDEW029T5 = waveshare_epaper_ns.class_("GDEW029T5", WaveshareEPaper) WaveshareEPaper2P9InDKE = waveshare_epaper_ns.class_( "WaveshareEPaper2P9InDKE", WaveshareEPaper ) @@ -110,7 +110,7 @@ MODELS = { "2.13in-ttgo-b74": ("a", WaveshareEPaperTypeAModel.TTGO_EPAPER_2_13_IN_B74), "2.90in": ("a", WaveshareEPaperTypeAModel.WAVESHARE_EPAPER_2_9_IN), "2.90inv2": ("a", WaveshareEPaperTypeAModel.WAVESHARE_EPAPER_2_9_IN_V2), - "gdey029t94": ("c", GDEY029T94), + "gdew029t5": ("c", GDEW029T5), "2.70in": ("b", WaveshareEPaper2P7In), "2.70in-b": ("b", WaveshareEPaper2P7InB), "2.70in-bv2": ("b", WaveshareEPaper2P7InBV2), diff --git a/esphome/components/waveshare_epaper/waveshare_epaper.cpp b/esphome/components/waveshare_epaper/waveshare_epaper.cpp index 7224aa44ed..5428f4ec80 100644 --- a/esphome/components/waveshare_epaper/waveshare_epaper.cpp +++ b/esphome/components/waveshare_epaper/waveshare_epaper.cpp @@ -1514,7 +1514,7 @@ void WaveshareEPaper2P9InV2R2::set_full_update_every(uint32_t full_update_every) // - https://github.com/adafruit/Adafruit_EPD/blob/master/src/panels/ThinkInk_290_Grayscale4_T5.h // ======================================================== -void GDEY029T94::initialize() { +void GDEW029T5::initialize() { // from https://www.waveshare.com/w/upload/b/bb/2.9inch-e-paper-b-specification.pdf, page 37 // EPD hardware init start this->reset_(); @@ -1560,7 +1560,7 @@ void GDEY029T94::initialize() { // EPD hardware init end } -void HOT GDEY029T94::display() { +void HOT GDEW029T5::display() { // COMMAND DATA START TRANSMISSION 2 (B/W only) this->command(0x13); delay(2); @@ -1580,11 +1580,11 @@ void HOT GDEY029T94::display() { // NOTE: power off < deep sleep this->command(0x02); } -int GDEY029T94::get_width_internal() { return 128; } -int GDEY029T94::get_height_internal() { return 296; } -void GDEY029T94::dump_config() { +int GDEW029T5::get_width_internal() { return 128; } +int GDEW029T5::get_height_internal() { return 296; } +void GDEW029T5::dump_config() { LOG_DISPLAY("", "Waveshare E-Paper (Good Display)", this); - ESP_LOGCONFIG(TAG, " Model: 2.9in Greyscale GDEY029T94"); + ESP_LOGCONFIG(TAG, " Model: 2.9in Greyscale GDEW029T5"); LOG_PIN(" Reset Pin: ", this->reset_pin_); LOG_PIN(" DC Pin: ", this->dc_pin_); LOG_PIN(" Busy Pin: ", this->busy_pin_); diff --git a/esphome/components/waveshare_epaper/waveshare_epaper.h b/esphome/components/waveshare_epaper/waveshare_epaper.h index 3c4470c30c..4a5844ae88 100644 --- a/esphome/components/waveshare_epaper/waveshare_epaper.h +++ b/esphome/components/waveshare_epaper/waveshare_epaper.h @@ -227,7 +227,7 @@ class WaveshareEPaper2P7InBV2 : public WaveshareEPaperBWR { int get_height_internal() override; }; -class GDEY029T94 : public WaveshareEPaper { +class GDEW029T5 : public WaveshareEPaper { public: void initialize() override; From 17c6bf57cdcdffe00af8578ec1c4fd3e00e6d451 Mon Sep 17 00:00:00 2001 From: august huber Date: Sun, 26 May 2024 15:40:38 -0400 Subject: [PATCH 03/46] [tuya] add support for extended services (#6808) --- esphome/components/tuya/tuya.cpp | 24 ++++++++++++++++++++++++ esphome/components/tuya/tuya.h | 7 +++++++ 2 files changed, 31 insertions(+) diff --git a/esphome/components/tuya/tuya.cpp b/esphome/components/tuya/tuya.cpp index 1cc9681d09..402953bb3b 100644 --- a/esphome/components/tuya/tuya.cpp +++ b/esphome/components/tuya/tuya.cpp @@ -269,6 +269,30 @@ void Tuya::handle_command_(uint8_t command, uint8_t version, const uint8_t *buff ESP_LOGV(TAG, "Network status requested, reported as %i", wifi_status); break; } + case TuyaCommandType::EXTENDED_SERVICES: { + uint8_t subcommand = buffer[0]; + switch ((TuyaExtendedServicesCommandType) subcommand) { + case TuyaExtendedServicesCommandType::RESET_NOTIFICATION: { + this->send_command_( + TuyaCommand{.cmd = TuyaCommandType::EXTENDED_SERVICES, + .payload = std::vector{ + static_cast(TuyaExtendedServicesCommandType::RESET_NOTIFICATION), 0x00}}); + ESP_LOGV(TAG, "Reset status notification enabled"); + break; + } + case TuyaExtendedServicesCommandType::MODULE_RESET: { + ESP_LOGE(TAG, "EXTENDED_SERVICES::MODULE_RESET is not handled"); + break; + } + case TuyaExtendedServicesCommandType::UPDATE_IN_PROGRESS: { + ESP_LOGE(TAG, "EXTENDED_SERVICES::UPDATE_IN_PROGRESS is not handled"); + break; + } + default: + ESP_LOGE(TAG, "Invalid extended services subcommand (0x%02X) received", subcommand); + } + break; + } default: ESP_LOGE(TAG, "Invalid command (0x%02X) received", command); } diff --git a/esphome/components/tuya/tuya.h b/esphome/components/tuya/tuya.h index 7dc405e3dd..6db417d474 100644 --- a/esphome/components/tuya/tuya.h +++ b/esphome/components/tuya/tuya.h @@ -60,6 +60,13 @@ enum class TuyaCommandType : uint8_t { WIFI_RSSI = 0x24, VACUUM_MAP_UPLOAD = 0x28, GET_NETWORK_STATUS = 0x2B, + EXTENDED_SERVICES = 0x34, +}; + +enum class TuyaExtendedServicesCommandType : uint8_t { + RESET_NOTIFICATION = 0x04, + MODULE_RESET = 0x05, + UPDATE_IN_PROGRESS = 0x0A, }; enum class TuyaInitState : uint8_t { From e285196709771e22a42b2eb0c69add723d546ca5 Mon Sep 17 00:00:00 2001 From: august huber Date: Sun, 26 May 2024 15:41:29 -0400 Subject: [PATCH 04/46] fix libretiny regression from #6715 (#6806) --- esphome/components/debug/debug_libretiny.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/debug/debug_libretiny.cpp b/esphome/components/debug/debug_libretiny.cpp index 725cd870ca..c3418cf96c 100644 --- a/esphome/components/debug/debug_libretiny.cpp +++ b/esphome/components/debug/debug_libretiny.cpp @@ -12,7 +12,7 @@ std::string DebugComponent::get_reset_reason_() { return lt_get_reboot_reason_na uint32_t DebugComponent::get_free_heap_() { return lt_heap_get_free(); } void DebugComponent::get_device_info_(std::string &device_info) { - reset_reason = get_reset_reason_(); + str::string reset_reason = get_reset_reason_(); ESP_LOGD(TAG, "LibreTiny Version: %s", lt_get_version()); ESP_LOGD(TAG, "Chip: %s (%04x) @ %u MHz", lt_cpu_get_model_name(), lt_cpu_get_model(), lt_cpu_get_freq_mhz()); ESP_LOGD(TAG, "Chip ID: 0x%06X", lt_cpu_get_mac_id()); From 6e4fd428e72ec5fb4d04802de1d0f8c65f1c961d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Poczkodi?= Date: Mon, 27 May 2024 22:19:22 +0200 Subject: [PATCH 05/46] [helpers] Move Base64 string to cpp (#6819) --- esphome/core/helpers.cpp | 4 ++++ esphome/core/helpers.h | 4 ---- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index fdc0eed774..dee771d4e9 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -433,6 +433,10 @@ int8_t step_to_accuracy_decimals(float step) { return str.length() - dot_pos - 1; } +static const std::string BASE64_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789+/"; + static inline bool is_base64(char c) { return (isalnum(c) || (c == '+') || (c == '/')); } std::string base64_encode(const std::vector &buf) { return base64_encode(buf.data(), buf.size()); } diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index 809e7d6767..4af840f77b 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -435,10 +435,6 @@ std::string value_accuracy_to_string(float value, int8_t accuracy_decimals); /// Derive accuracy in decimals from an increment step. int8_t step_to_accuracy_decimals(float step); -static const std::string BASE64_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "abcdefghijklmnopqrstuvwxyz" - "0123456789+/"; - std::string base64_encode(const uint8_t *buf, size_t buf_len); std::string base64_encode(const std::vector &buf); From 54b51269ab465fb4f8f849f1f52b6b42977635c8 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 27 May 2024 07:15:05 +1200 Subject: [PATCH 06/46] [web_server_base] Bump ESPAsyncWebServer-esphome to 3.2.2 (#6797) Co-authored-by: Samuel Sieb --- esphome/components/web_server_base/__init__.py | 2 +- platformio.ini | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/web_server_base/__init__.py b/esphome/components/web_server_base/__init__.py index 1970b5a0c5..4f894619b0 100644 --- a/esphome/components/web_server_base/__init__.py +++ b/esphome/components/web_server_base/__init__.py @@ -37,4 +37,4 @@ async def to_code(config): cg.add_library("FS", None) cg.add_library("Update", None) # https://github.com/esphome/ESPAsyncWebServer/blob/master/library.json - cg.add_library("esphome/ESPAsyncWebServer-esphome", "3.2.0") + cg.add_library("esphome/ESPAsyncWebServer-esphome", "3.2.2") diff --git a/platformio.ini b/platformio.ini index d342b32b02..67a631dda8 100644 --- a/platformio.ini +++ b/platformio.ini @@ -58,7 +58,7 @@ lib_deps = SPI ; spi (Arduino built-in) Wire ; i2c (Arduino built-int) heman/AsyncMqttClient-esphome@1.0.0 ; mqtt - esphome/ESPAsyncWebServer-esphome@3.2.0 ; web_server_base + esphome/ESPAsyncWebServer-esphome@3.2.2 ; web_server_base fastled/FastLED@3.3.2 ; fastled_base mikalhart/TinyGPSPlus@1.0.2 ; gps freekode/TM1651@1.0.1 ; tm1651 From f07479419c30e3ae89fcdefff254601dbdedd36f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Poczkodi?= Date: Mon, 27 May 2024 22:19:22 +0200 Subject: [PATCH 07/46] [helpers] Move Base64 string to cpp (#6819) --- esphome/core/helpers.cpp | 4 ++++ esphome/core/helpers.h | 4 ---- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index fdc0eed774..dee771d4e9 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -433,6 +433,10 @@ int8_t step_to_accuracy_decimals(float step) { return str.length() - dot_pos - 1; } +static const std::string BASE64_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789+/"; + static inline bool is_base64(char c) { return (isalnum(c) || (c == '+') || (c == '/')); } std::string base64_encode(const std::vector &buf) { return base64_encode(buf.data(), buf.size()); } diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index 809e7d6767..4af840f77b 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -435,10 +435,6 @@ std::string value_accuracy_to_string(float value, int8_t accuracy_decimals); /// Derive accuracy in decimals from an increment step. int8_t step_to_accuracy_decimals(float step); -static const std::string BASE64_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "abcdefghijklmnopqrstuvwxyz" - "0123456789+/"; - std::string base64_encode(const uint8_t *buf, size_t buf_len); std::string base64_encode(const std::vector &buf); From 4cd4b168b44e073c3e874ba625cc10b3bb7c9bae Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 28 May 2024 08:29:19 +1200 Subject: [PATCH 08/46] Bump version to 2024.5.4 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 0c53287909..ad5858dcf3 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2024.5.3" +__version__ = "2024.5.4" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" VALID_SUBSTITUTIONS_CHARACTERS = ( From 497cf8742f495918ed368eafdeafe72d53c5b9e2 Mon Sep 17 00:00:00 2001 From: pimdo <38102897+pimdo@users.noreply.github.com> Date: Tue, 28 May 2024 02:42:59 +0100 Subject: [PATCH 09/46] Make i2s_audio compatible with IDF 5+ (#6534) * Update i2s_audio.cpp Replace usage of I2S_NUM_MAX with I2S_NUM_1 * Update i2s_audio_microphone.cpp Replace I2S_MCLK_MULTIPLE_DEFAULT with I2S_MCLK_MULTIPLE_256 * Update i2s_audio_speaker.cpp Replace I2S_MCLK_MULTIPLE_DEFAULT with I2S_MCLK_MULTIPLE_256 * Update voice_assistant.cpp Fix msg.event_type format * check SOC_I2S_NUM for c3 * use I2S_NUM_AUTO * Update i2s_audio.cpp * Couple tweaks * Why did they take away I2S_NUM_MAX Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --------- Co-authored-by: Keith Burzinski Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/i2s_audio/i2s_audio.cpp | 4 ++++ .../components/i2s_audio/microphone/i2s_audio_microphone.cpp | 2 +- esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp | 2 +- esphome/components/voice_assistant/voice_assistant.cpp | 5 +++-- 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/esphome/components/i2s_audio/i2s_audio.cpp b/esphome/components/i2s_audio/i2s_audio.cpp index c1a608c064..ad73b383fe 100644 --- a/esphome/components/i2s_audio/i2s_audio.cpp +++ b/esphome/components/i2s_audio/i2s_audio.cpp @@ -9,6 +9,10 @@ namespace i2s_audio { static const char *const TAG = "i2s_audio"; +#if defined(USE_ESP_IDF) && (ESP_IDF_VERSION_MAJOR >= 5) +static const uint8_t I2S_NUM_MAX = SOC_I2S_NUM; // because IDF 5+ took this away :( +#endif + void I2SAudioComponent::setup() { static i2s_port_t next_port_num = I2S_NUM_0; diff --git a/esphome/components/i2s_audio/microphone/i2s_audio_microphone.cpp b/esphome/components/i2s_audio/microphone/i2s_audio_microphone.cpp index 1475df0975..a672348d85 100644 --- a/esphome/components/i2s_audio/microphone/i2s_audio_microphone.cpp +++ b/esphome/components/i2s_audio/microphone/i2s_audio_microphone.cpp @@ -57,7 +57,7 @@ void I2SAudioMicrophone::start_() { .use_apll = this->use_apll_, .tx_desc_auto_clear = false, .fixed_mclk = 0, - .mclk_multiple = I2S_MCLK_MULTIPLE_DEFAULT, + .mclk_multiple = I2S_MCLK_MULTIPLE_256, .bits_per_chan = I2S_BITS_PER_CHAN_DEFAULT, }; diff --git a/esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp b/esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp index 95e63035fe..69536742cb 100644 --- a/esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp +++ b/esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp @@ -51,7 +51,7 @@ void I2SAudioSpeaker::player_task(void *params) { .use_apll = false, .tx_desc_auto_clear = true, .fixed_mclk = I2S_PIN_NO_CHANGE, - .mclk_multiple = I2S_MCLK_MULTIPLE_DEFAULT, + .mclk_multiple = I2S_MCLK_MULTIPLE_256, .bits_per_chan = I2S_BITS_PER_CHAN_DEFAULT, }; #if SOC_I2S_SUPPORTS_DAC diff --git a/esphome/components/voice_assistant/voice_assistant.cpp b/esphome/components/voice_assistant/voice_assistant.cpp index 59ba39c527..712a0ab137 100644 --- a/esphome/components/voice_assistant/voice_assistant.cpp +++ b/esphome/components/voice_assistant/voice_assistant.cpp @@ -4,6 +4,7 @@ #include "esphome/core/log.h" +#include #include namespace esphome { @@ -622,7 +623,7 @@ void VoiceAssistant::signal_stop_() { } void VoiceAssistant::on_event(const api::VoiceAssistantEventResponse &msg) { - ESP_LOGD(TAG, "Event Type: %d", msg.event_type); + ESP_LOGD(TAG, "Event Type: %" PRId32, msg.event_type); switch (msg.event_type) { case api::enums::VOICE_ASSISTANT_RUN_START: ESP_LOGD(TAG, "Assist Pipeline running"); @@ -785,7 +786,7 @@ void VoiceAssistant::on_event(const api::VoiceAssistantEventResponse &msg) { this->defer([this]() { this->stt_vad_end_trigger_->trigger(); }); break; default: - ESP_LOGD(TAG, "Unhandled event type: %d", msg.event_type); + ESP_LOGD(TAG, "Unhandled event type: %" PRId32, msg.event_type); break; } } From db6f6f0cb7d1df10ac10b37e6dac46a0747406b7 Mon Sep 17 00:00:00 2001 From: DAVe3283 Date: Tue, 28 May 2024 13:40:34 -0600 Subject: [PATCH 10/46] Fix compile errors on ESP32-C6 with latest ESP-IDF (#6822) * Use PRI macros to fix ESP32-C6 compile * Fix compile error on latest ESP-IDF framework & platform --- esphome/components/ade7880/ade7880.cpp | 23 +++++++------- .../components/ade7953_base/ade7953_base.cpp | 3 +- .../fingerprint_grow/fingerprint_grow.cpp | 14 ++++----- esphome/components/he60r/he60r.cpp | 3 +- esphome/components/mhz19/mhz19.cpp | 5 +-- .../remote_base/abbwelcome_protocol.h | 4 ++- .../remote_base/byronsx_protocol.cpp | 22 +++++++------ .../remote_base/drayton_protocol.cpp | 17 +++++----- .../remote_base/keeloq_protocol.cpp | 31 ++++++++++--------- .../uart/uart_component_esp_idf.cpp | 2 +- esphome/components/xgzp68xx/xgzp68xx.cpp | 5 +-- 11 files changed, 73 insertions(+), 56 deletions(-) diff --git a/esphome/components/ade7880/ade7880.cpp b/esphome/components/ade7880/ade7880.cpp index 31b72d51a6..a03f1227f1 100644 --- a/esphome/components/ade7880/ade7880.cpp +++ b/esphome/components/ade7880/ade7880.cpp @@ -10,6 +10,7 @@ #include "ade7880.h" #include "ade7880_registers.h" #include "esphome/core/log.h" +#include namespace esphome { namespace ade7880 { @@ -156,7 +157,7 @@ void ADE7880::update() { }); } - ESP_LOGD(TAG, "update took %u ms", millis() - start); + ESP_LOGD(TAG, "update took %" PRIu32 " ms", millis() - start); } void ADE7880::dump_config() { @@ -176,9 +177,9 @@ void ADE7880::dump_config() { LOG_SENSOR(" ", "Forward Active Energy", this->channel_a_->forward_active_energy); LOG_SENSOR(" ", "Reverse Active Energy", this->channel_a_->reverse_active_energy); ESP_LOGCONFIG(TAG, " Calibration:"); - ESP_LOGCONFIG(TAG, " Current: %u", this->channel_a_->current_gain_calibration); - ESP_LOGCONFIG(TAG, " Voltage: %d", this->channel_a_->voltage_gain_calibration); - ESP_LOGCONFIG(TAG, " Power: %d", this->channel_a_->power_gain_calibration); + ESP_LOGCONFIG(TAG, " Current: %" PRId32, this->channel_a_->current_gain_calibration); + ESP_LOGCONFIG(TAG, " Voltage: %" PRId32, this->channel_a_->voltage_gain_calibration); + ESP_LOGCONFIG(TAG, " Power: %" PRId32, this->channel_a_->power_gain_calibration); ESP_LOGCONFIG(TAG, " Phase Angle: %u", this->channel_a_->phase_angle_calibration); } @@ -192,9 +193,9 @@ void ADE7880::dump_config() { LOG_SENSOR(" ", "Forward Active Energy", this->channel_b_->forward_active_energy); LOG_SENSOR(" ", "Reverse Active Energy", this->channel_b_->reverse_active_energy); ESP_LOGCONFIG(TAG, " Calibration:"); - ESP_LOGCONFIG(TAG, " Current: %u", this->channel_b_->current_gain_calibration); - ESP_LOGCONFIG(TAG, " Voltage: %d", this->channel_b_->voltage_gain_calibration); - ESP_LOGCONFIG(TAG, " Power: %d", this->channel_b_->power_gain_calibration); + ESP_LOGCONFIG(TAG, " Current: %" PRId32, this->channel_b_->current_gain_calibration); + ESP_LOGCONFIG(TAG, " Voltage: %" PRId32, this->channel_b_->voltage_gain_calibration); + ESP_LOGCONFIG(TAG, " Power: %" PRId32, this->channel_b_->power_gain_calibration); ESP_LOGCONFIG(TAG, " Phase Angle: %u", this->channel_b_->phase_angle_calibration); } @@ -208,9 +209,9 @@ void ADE7880::dump_config() { LOG_SENSOR(" ", "Forward Active Energy", this->channel_c_->forward_active_energy); LOG_SENSOR(" ", "Reverse Active Energy", this->channel_c_->reverse_active_energy); ESP_LOGCONFIG(TAG, " Calibration:"); - ESP_LOGCONFIG(TAG, " Current: %u", this->channel_c_->current_gain_calibration); - ESP_LOGCONFIG(TAG, " Voltage: %d", this->channel_c_->voltage_gain_calibration); - ESP_LOGCONFIG(TAG, " Power: %d", this->channel_c_->power_gain_calibration); + ESP_LOGCONFIG(TAG, " Current: %" PRId32, this->channel_c_->current_gain_calibration); + ESP_LOGCONFIG(TAG, " Voltage: %" PRId32, this->channel_c_->voltage_gain_calibration); + ESP_LOGCONFIG(TAG, " Power: %" PRId32, this->channel_c_->power_gain_calibration); ESP_LOGCONFIG(TAG, " Phase Angle: %u", this->channel_c_->phase_angle_calibration); } @@ -218,7 +219,7 @@ void ADE7880::dump_config() { ESP_LOGCONFIG(TAG, " Neutral:"); LOG_SENSOR(" ", "Current", this->channel_n_->current); ESP_LOGCONFIG(TAG, " Calibration:"); - ESP_LOGCONFIG(TAG, " Current: %u", this->channel_n_->current_gain_calibration); + ESP_LOGCONFIG(TAG, " Current: %" PRId32, this->channel_n_->current_gain_calibration); } LOG_I2C_DEVICE(this); diff --git a/esphome/components/ade7953_base/ade7953_base.cpp b/esphome/components/ade7953_base/ade7953_base.cpp index 862f5567a8..79161c5676 100644 --- a/esphome/components/ade7953_base/ade7953_base.cpp +++ b/esphome/components/ade7953_base/ade7953_base.cpp @@ -1,5 +1,6 @@ #include "ade7953_base.h" #include "esphome/core/log.h" +#include namespace esphome { namespace ade7953_base { @@ -105,7 +106,7 @@ void ADE7953::update() { this->last_update_ = now; // prevent DIV/0 pf = ADE_WATTSEC_POWER_FACTOR * (diff < 10 ? 10 : diff) / 1000; - ESP_LOGVV(TAG, "ADE7953::update() diff=%d pf=%f", diff, pf); + ESP_LOGVV(TAG, "ADE7953::update() diff=%" PRIu32 " pf=%f", diff, pf); } // Apparent power diff --git a/esphome/components/fingerprint_grow/fingerprint_grow.cpp b/esphome/components/fingerprint_grow/fingerprint_grow.cpp index bd0817350a..8f58db9159 100644 --- a/esphome/components/fingerprint_grow/fingerprint_grow.cpp +++ b/esphome/components/fingerprint_grow/fingerprint_grow.cpp @@ -541,34 +541,34 @@ void FingerprintGrowComponent::dump_config() { ESP_LOGCONFIG(TAG, " Sensor Power Pin: %s", this->has_power_pin_ ? this->sensor_power_pin_->dump_summary().c_str() : "None"); if (this->idle_period_to_sleep_ms_ < UINT32_MAX) { - ESP_LOGCONFIG(TAG, " Idle Period to Sleep: %u ms", this->idle_period_to_sleep_ms_); + ESP_LOGCONFIG(TAG, " Idle Period to Sleep: %" PRIu32 " ms", this->idle_period_to_sleep_ms_); } else { ESP_LOGCONFIG(TAG, " Idle Period to Sleep: Never"); } LOG_UPDATE_INTERVAL(this); if (this->fingerprint_count_sensor_) { LOG_SENSOR(" ", "Fingerprint Count", this->fingerprint_count_sensor_); - ESP_LOGCONFIG(TAG, " Current Value: %d", (uint16_t) this->fingerprint_count_sensor_->get_state()); + ESP_LOGCONFIG(TAG, " Current Value: %u", (uint16_t) this->fingerprint_count_sensor_->get_state()); } if (this->status_sensor_) { LOG_SENSOR(" ", "Status", this->status_sensor_); - ESP_LOGCONFIG(TAG, " Current Value: %d", (uint8_t) this->status_sensor_->get_state()); + ESP_LOGCONFIG(TAG, " Current Value: %u", (uint8_t) this->status_sensor_->get_state()); } if (this->capacity_sensor_) { LOG_SENSOR(" ", "Capacity", this->capacity_sensor_); - ESP_LOGCONFIG(TAG, " Current Value: %d", (uint16_t) this->capacity_sensor_->get_state()); + ESP_LOGCONFIG(TAG, " Current Value: %u", (uint16_t) this->capacity_sensor_->get_state()); } if (this->security_level_sensor_) { LOG_SENSOR(" ", "Security Level", this->security_level_sensor_); - ESP_LOGCONFIG(TAG, " Current Value: %d", (uint8_t) this->security_level_sensor_->get_state()); + ESP_LOGCONFIG(TAG, " Current Value: %u", (uint8_t) this->security_level_sensor_->get_state()); } if (this->last_finger_id_sensor_) { LOG_SENSOR(" ", "Last Finger ID", this->last_finger_id_sensor_); - ESP_LOGCONFIG(TAG, " Current Value: %d", (uint32_t) this->last_finger_id_sensor_->get_state()); + ESP_LOGCONFIG(TAG, " Current Value: %" PRIu32, (uint32_t) this->last_finger_id_sensor_->get_state()); } if (this->last_confidence_sensor_) { LOG_SENSOR(" ", "Last Confidence", this->last_confidence_sensor_); - ESP_LOGCONFIG(TAG, " Current Value: %d", (uint32_t) this->last_confidence_sensor_->get_state()); + ESP_LOGCONFIG(TAG, " Current Value: %" PRIu32, (uint32_t) this->last_confidence_sensor_->get_state()); } } diff --git a/esphome/components/he60r/he60r.cpp b/esphome/components/he60r/he60r.cpp index d6e6122b1b..96ad1f04be 100644 --- a/esphome/components/he60r/he60r.cpp +++ b/esphome/components/he60r/he60r.cpp @@ -1,6 +1,7 @@ #include "he60r.h" #include "esphome/core/hal.h" #include "esphome/core/log.h" +#include namespace esphome { namespace he60r { @@ -127,7 +128,7 @@ void HE60rCover::update_() { if (toggles_needed_ != 0) { if ((this->counter_++ & 0x3) == 0) { toggles_needed_--; - ESP_LOGD(TAG, "Writing byte 0x30, still needed=%d", toggles_needed_); + ESP_LOGD(TAG, "Writing byte 0x30, still needed=%" PRIu32, toggles_needed_); this->write_byte(TOGGLE_BYTE); } else { this->write_byte(QUERY_BYTE); diff --git a/esphome/components/mhz19/mhz19.cpp b/esphome/components/mhz19/mhz19.cpp index 019f6cee51..574aee1bd5 100644 --- a/esphome/components/mhz19/mhz19.cpp +++ b/esphome/components/mhz19/mhz19.cpp @@ -1,5 +1,6 @@ #include "mhz19.h" #include "esphome/core/log.h" +#include namespace esphome { namespace mhz19 { @@ -32,7 +33,7 @@ void MHZ19Component::update() { uint32_t now_ms = millis(); uint32_t warmup_ms = this->warmup_seconds_ * 1000; if (now_ms < warmup_ms) { - ESP_LOGW(TAG, "MHZ19 warming up, %ds left", (warmup_ms - now_ms) / 1000); + ESP_LOGW(TAG, "MHZ19 warming up, %" PRIu32 " s left", (warmup_ms - now_ms) / 1000); this->status_set_warning(); return; } @@ -110,7 +111,7 @@ void MHZ19Component::dump_config() { ESP_LOGCONFIG(TAG, " Automatic baseline calibration disabled on boot"); } - ESP_LOGCONFIG(TAG, " Warmup seconds: %ds", this->warmup_seconds_); + ESP_LOGCONFIG(TAG, " Warmup time: %" PRIu32 " s", this->warmup_seconds_); } } // namespace mhz19 diff --git a/esphome/components/remote_base/abbwelcome_protocol.h b/esphome/components/remote_base/abbwelcome_protocol.h index 0493993926..f2d0f5b547 100644 --- a/esphome/components/remote_base/abbwelcome_protocol.h +++ b/esphome/components/remote_base/abbwelcome_protocol.h @@ -4,6 +4,7 @@ #include "esphome/core/helpers.h" #include "remote_base.h" #include +#include #include #include @@ -144,7 +145,8 @@ class ABBWelcomeData { std::string to_string(uint8_t max_print_bytes = 255) const { std::string info; if (this->is_valid()) { - info = str_sprintf(this->get_three_byte_address() ? "[%06X %s %06X] Type: %02X" : "[%04X %s %04X] Type: %02X", + info = str_sprintf(this->get_three_byte_address() ? "[%06" PRIX32 " %s %06" PRIX32 "] Type: %02X" + : "[%04" PRIX32 " %s %04" PRIX32 "] Type: %02X", this->get_source_address(), this->get_retransmission() ? "ยป" : ">", this->get_destination_address(), this->get_message_type()); if (this->get_data_size()) diff --git a/esphome/components/remote_base/byronsx_protocol.cpp b/esphome/components/remote_base/byronsx_protocol.cpp index 3096283b30..2d4c60ff8b 100644 --- a/esphome/components/remote_base/byronsx_protocol.cpp +++ b/esphome/components/remote_base/byronsx_protocol.cpp @@ -1,5 +1,6 @@ #include "byronsx_protocol.h" #include "esphome/core/log.h" +#include namespace esphome { namespace remote_base { @@ -57,7 +58,7 @@ void ByronSXProtocol::encode(RemoteTransmitData *dst, const ByronSXData &data) { out_data <<= NBITS_COMMAND; out_data |= data.command; - ESP_LOGV(TAG, "Send ByronSX: out_data %03x", out_data); + ESP_LOGV(TAG, "Send ByronSX: out_data %03" PRIx32, out_data); // Initial Mark start bit dst->mark(1 * BIT_TIME_US); @@ -90,13 +91,16 @@ optional ByronSXProtocol::decode(RemoteReceiveData src) { return {}; } - ESP_LOGVV(TAG, "%3d: %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d", src.size(), src.peek(0), - src.peek(1), src.peek(2), src.peek(3), src.peek(4), src.peek(5), src.peek(6), src.peek(7), src.peek(8), - src.peek(9), src.peek(10), src.peek(11), src.peek(12), src.peek(13), src.peek(14), src.peek(15), - src.peek(16), src.peek(17), src.peek(18), src.peek(19)); + ESP_LOGVV(TAG, + "%3" PRId32 ": %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 + " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 + " %" PRId32 " %" PRId32 " %" PRId32, + src.size(), src.peek(0), src.peek(1), src.peek(2), src.peek(3), src.peek(4), src.peek(5), src.peek(6), + src.peek(7), src.peek(8), src.peek(9), src.peek(10), src.peek(11), src.peek(12), src.peek(13), src.peek(14), + src.peek(15), src.peek(16), src.peek(17), src.peek(18), src.peek(19)); - ESP_LOGVV(TAG, " %d %d %d %d %d %d", src.peek(20), src.peek(21), src.peek(22), src.peek(23), src.peek(24), - src.peek(25)); + ESP_LOGVV(TAG, " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32, src.peek(20), + src.peek(21), src.peek(22), src.peek(23), src.peek(24), src.peek(25)); // Read data bits uint32_t out_data = 0; @@ -107,10 +111,10 @@ optional ByronSXProtocol::decode(RemoteReceiveData src) { } else if (src.expect_space(BIT_TIME_US) && src.expect_mark(2 * BIT_TIME_US)) { out_data |= 0 << bit; } else { - ESP_LOGV(TAG, "Decode ByronSX: Fail 2, %2d %08x", bit, out_data); + ESP_LOGV(TAG, "Decode ByronSX: Fail 2, %2d %08" PRIx32, bit, out_data); return {}; } - ESP_LOGVV(TAG, "Decode ByronSX: Data, %2d %08x", bit, out_data); + ESP_LOGVV(TAG, "Decode ByronSX: Data, %2d %08" PRIx32, bit, out_data); } // last bit followed by a long space diff --git a/esphome/components/remote_base/drayton_protocol.cpp b/esphome/components/remote_base/drayton_protocol.cpp index acfb7a0f16..73c0fb116c 100644 --- a/esphome/components/remote_base/drayton_protocol.cpp +++ b/esphome/components/remote_base/drayton_protocol.cpp @@ -151,12 +151,12 @@ optional DraytonProtocol::decode(RemoteReceiveData src) { // Look for sync pulse, after. If sucessful index points to space of sync symbol while (src.size() - src.get_index() >= MIN_RX_SRC) { - ESP_LOGVV(TAG, "Decode Drayton: sync search %d, %" PRId32 " %" PRId32, src.size() - src.get_index(), src.peek(), - src.peek(1)); + ESP_LOGVV(TAG, "Decode Drayton: sync search %" PRIu32 ", %" PRId32 " %" PRId32, src.size() - src.get_index(), + src.peek(), src.peek(1)); if (src.peek_mark(2 * BIT_TIME_US) && (src.peek_space(2 * BIT_TIME_US, 1) || src.peek_space(3 * BIT_TIME_US, 1))) { src.advance(1); - ESP_LOGVV(TAG, "Decode Drayton: Found SYNC, - %d", src.get_index()); + ESP_LOGVV(TAG, "Decode Drayton: Found SYNC, - %" PRIu32, src.get_index()); break; } else { src.advance(2); @@ -174,14 +174,16 @@ optional DraytonProtocol::decode(RemoteReceiveData src) { // Checks next bit to leave index pointing correctly uint32_t out_data = 0; uint8_t bit = NDATABITS - 1; - ESP_LOGVV(TAG, "Decode Drayton: first bit %d %" PRId32 ", %" PRId32, src.peek(0), src.peek(1), src.peek(2)); + ESP_LOGVV(TAG, "Decode Drayton: first bit %" PRId32 " %" PRId32 ", %" PRId32, src.peek(0), src.peek(1), + src.peek(2)); if (src.expect_space(3 * BIT_TIME_US) && (src.expect_mark(BIT_TIME_US) || src.peek_mark(2 * BIT_TIME_US))) { out_data |= 0 << bit; } else if (src.expect_space(2 * BIT_TIME_US) && src.expect_mark(BIT_TIME_US) && (src.expect_space(BIT_TIME_US) || src.peek_space(2 * BIT_TIME_US))) { out_data |= 1 << bit; } else { - ESP_LOGV(TAG, "Decode Drayton: Fail 2, - %d %d %d", src.peek(-1), src.peek(0), src.peek(1)); + ESP_LOGV(TAG, "Decode Drayton: Fail 2, - %" PRId32 " %" PRId32 " %" PRId32, src.peek(-1), src.peek(0), + src.peek(1)); continue; } @@ -202,7 +204,8 @@ optional DraytonProtocol::decode(RemoteReceiveData src) { } if (bit > 0) { - ESP_LOGVV(TAG, "Decode Drayton: Fail 3, %d %" PRId32 " %" PRId32, src.peek(-1), src.peek(0), src.peek(1)); + ESP_LOGVV(TAG, "Decode Drayton: Fail 3, %" PRId32 " %" PRId32 " %" PRId32, src.peek(-1), src.peek(0), + src.peek(1)); continue; } @@ -214,7 +217,7 @@ optional DraytonProtocol::decode(RemoteReceiveData src) { continue; } - ESP_LOGV(TAG, "Decode Drayton: Data, %2d %08x", bit, out_data); + ESP_LOGV(TAG, "Decode Drayton: Data, %2d %08" PRIx32, bit, out_data); out.channel = (uint8_t) (out_data & 0x1F); out_data >>= NBITS_CHANNEL; diff --git a/esphome/components/remote_base/keeloq_protocol.cpp b/esphome/components/remote_base/keeloq_protocol.cpp index 09d9ea4f53..72540c37f1 100644 --- a/esphome/components/remote_base/keeloq_protocol.cpp +++ b/esphome/components/remote_base/keeloq_protocol.cpp @@ -52,7 +52,7 @@ void KeeloqProtocol::encode(RemoteTransmitData *dst, const KeeloqData &data) { // Encrypted field out_data = data.encrypted; - ESP_LOGV(TAG, "Send Keeloq: Encrypted data %04x", out_data); + ESP_LOGV(TAG, "Send Keeloq: Encrypted data %04" PRIx32, out_data); for (uint32_t mask = 1, cnt = 0; cnt < NBITS_ENCRYPTED_DATA; cnt++, mask <<= 1) { if (out_data & mask) { @@ -68,7 +68,7 @@ void KeeloqProtocol::encode(RemoteTransmitData *dst, const KeeloqData &data) { out_data = (data.command & 0x0f); out_data <<= NBITS_SERIAL; out_data |= data.address; - ESP_LOGV(TAG, "Send Keeloq: Fixed data %04x", out_data); + ESP_LOGV(TAG, "Send Keeloq: Fixed data %04" PRIx32, out_data); for (uint32_t mask = 1, cnt = 0; cnt < (NBITS_FIXED_DATA - 2); cnt++, mask <<= 1) { if (out_data & mask) { @@ -111,21 +111,24 @@ optional KeeloqProtocol::decode(RemoteReceiveData src) { return {}; } - ESP_LOGVV(TAG, "%2d: %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d", src.size(), src.peek(0), - src.peek(1), src.peek(2), src.peek(3), src.peek(4), src.peek(5), src.peek(6), src.peek(7), src.peek(8), - src.peek(9), src.peek(10), src.peek(11), src.peek(12), src.peek(13), src.peek(14), src.peek(15), - src.peek(16), src.peek(17), src.peek(18), src.peek(19)); + ESP_LOGVV(TAG, + "%2" PRId32 ": %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 + " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 + " %" PRId32 " %" PRId32 " %" PRId32, + src.size(), src.peek(0), src.peek(1), src.peek(2), src.peek(3), src.peek(4), src.peek(5), src.peek(6), + src.peek(7), src.peek(8), src.peek(9), src.peek(10), src.peek(11), src.peek(12), src.peek(13), src.peek(14), + src.peek(15), src.peek(16), src.peek(17), src.peek(18), src.peek(19)); // Check preamble bits int8_t bit = NBITS_PREAMBLE - 1; while (--bit >= 0) { if (!src.expect_mark(BIT_TIME_US) || !src.expect_space(BIT_TIME_US)) { - ESP_LOGV(TAG, "Decode KeeLoq: Fail 1, %d %d", bit + 1, src.peek()); + ESP_LOGV(TAG, "Decode KeeLoq: Fail 1, %d %" PRId32, bit + 1, src.peek()); return {}; } } if (!src.expect_mark(BIT_TIME_US) || !src.expect_space(10 * BIT_TIME_US)) { - ESP_LOGV(TAG, "Decode KeeLoq: Fail 1, %d %d", bit + 1, src.peek()); + ESP_LOGV(TAG, "Decode KeeLoq: Fail 1, %d %" PRId32, bit + 1, src.peek()); return {}; } @@ -137,11 +140,11 @@ optional KeeloqProtocol::decode(RemoteReceiveData src) { } else if (src.expect_mark(BIT_TIME_US) && src.expect_space(2 * BIT_TIME_US)) { out_data |= 1 << bit; } else { - ESP_LOGV(TAG, "Decode KeeLoq: Fail 2, %d %d", src.get_index(), src.peek()); + ESP_LOGV(TAG, "Decode KeeLoq: Fail 2, %" PRIu32 " %" PRId32, src.get_index(), src.peek()); return {}; } } - ESP_LOGVV(TAG, "Decode KeeLoq: Data, %d %08x", bit, out_data); + ESP_LOGVV(TAG, "Decode KeeLoq: Data, %d %08" PRIx32, bit, out_data); out.encrypted = out_data; // Read Serial Number and Button Status @@ -152,11 +155,11 @@ optional KeeloqProtocol::decode(RemoteReceiveData src) { } else if (src.expect_mark(BIT_TIME_US) && src.expect_space(2 * BIT_TIME_US)) { out_data |= 1 << bit; } else { - ESP_LOGV(TAG, "Decode KeeLoq: Fail 3, %d %d", src.get_index(), src.peek()); + ESP_LOGV(TAG, "Decode KeeLoq: Fail 3, %" PRIu32 " %" PRId32, src.get_index(), src.peek()); return {}; } } - ESP_LOGVV(TAG, "Decode KeeLoq: Data, %2d %08x", bit, out_data); + ESP_LOGVV(TAG, "Decode KeeLoq: Data, %2d %08" PRIx32, bit, out_data); out.command = (out_data >> 28) & 0xf; out.address = out_data & 0xfffffff; @@ -166,7 +169,7 @@ optional KeeloqProtocol::decode(RemoteReceiveData src) { } else if (src.expect_mark(BIT_TIME_US) && src.expect_space(2 * BIT_TIME_US)) { out.vlow = true; } else { - ESP_LOGV(TAG, "Decode KeeLoq: Fail 4, %08x", src.peek()); + ESP_LOGV(TAG, "Decode KeeLoq: Fail 4, %" PRId32, src.peek()); return {}; } @@ -176,7 +179,7 @@ optional KeeloqProtocol::decode(RemoteReceiveData src) { } else if (src.expect_mark(BIT_TIME_US) && src.peek_space_at_least(2 * BIT_TIME_US)) { out.repeat = true; } else { - ESP_LOGV(TAG, "Decode KeeLoq: Fail 5, %08x", src.peek()); + ESP_LOGV(TAG, "Decode KeeLoq: Fail 5, %" PRId32, src.peek()); return {}; } diff --git a/esphome/components/uart/uart_component_esp_idf.cpp b/esphome/components/uart/uart_component_esp_idf.cpp index 2dd6ab105f..c66753b0c4 100644 --- a/esphome/components/uart/uart_component_esp_idf.cpp +++ b/esphome/components/uart/uart_component_esp_idf.cpp @@ -69,7 +69,7 @@ void IDFUARTComponent::setup() { this->mark_failed(); return; } - this->uart_num_ = next_uart_num++; + this->uart_num_ = static_cast(next_uart_num++); ESP_LOGCONFIG(TAG, "Setting up UART %u...", this->uart_num_); this->lock_ = xSemaphoreCreateMutex(); diff --git a/esphome/components/xgzp68xx/xgzp68xx.cpp b/esphome/components/xgzp68xx/xgzp68xx.cpp index ea3583c3c5..dff00fb696 100644 --- a/esphome/components/xgzp68xx/xgzp68xx.cpp +++ b/esphome/components/xgzp68xx/xgzp68xx.cpp @@ -3,6 +3,7 @@ #include "esphome/core/hal.h" #include "esphome/core/helpers.h" #include "esphome/components/i2c/i2c.h" +#include namespace esphome { namespace xgzp68xx { @@ -37,8 +38,8 @@ void XGZP68XXComponent::update() { temperature_raw = encode_uint16(data[3], data[4]); // Convert the pressure data to hPa - ESP_LOGV(TAG, "Got raw pressure=%d, raw temperature=%d ", pressure_raw, temperature_raw); - ESP_LOGV(TAG, "K value is %d ", this->k_value_); + ESP_LOGV(TAG, "Got raw pressure=%" PRIu32 ", raw temperature=%u", pressure_raw, temperature_raw); + ESP_LOGV(TAG, "K value is %u", this->k_value_); // The most significant bit of both pressure and temperature will be 1 to indicate a negative value. // This is directly from the datasheet, and the calculations below will handle this. From 06996def725aececb406739857f59873f72271ca Mon Sep 17 00:00:00 2001 From: DAVe3283 Date: Tue, 28 May 2024 14:44:47 -0600 Subject: [PATCH 11/46] Use uint8_t instead of uint32_t for 8-bit values on mitsubishi (#6824) This solves some printf formatting issues on ESP-IDF and saves RAM on all platforms. --- esphome/components/mitsubishi/mitsubishi.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/esphome/components/mitsubishi/mitsubishi.cpp b/esphome/components/mitsubishi/mitsubishi.cpp index 081c24a050..fd57adc586 100644 --- a/esphome/components/mitsubishi/mitsubishi.cpp +++ b/esphome/components/mitsubishi/mitsubishi.cpp @@ -6,7 +6,7 @@ namespace mitsubishi { static const char *const TAG = "mitsubishi.climate"; -const uint32_t MITSUBISHI_OFF = 0x00; +const uint8_t MITSUBISHI_OFF = 0x00; const uint8_t MITSUBISHI_MODE_AUTO = 0x20; const uint8_t MITSUBISHI_MODE_COOL = 0x18; @@ -109,8 +109,8 @@ void MitsubishiClimate::transmit_state() { // Byte 15: HVAC specfic, i.e. POWERFUL, SMART SET, PLASMA, always 0x00 // Byte 16: Constant 0x00 // Byte 17: Checksum: SUM[Byte0...Byte16] - uint32_t remote_state[18] = {0x23, 0xCB, 0x26, 0x01, 0x00, 0x20, 0x08, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + uint8_t remote_state[18] = {0x23, 0xCB, 0x26, 0x01, 0x00, 0x20, 0x08, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; switch (this->mode) { case climate::CLIMATE_MODE_HEAT: @@ -249,7 +249,7 @@ void MitsubishiClimate::transmit_state() { data->set_carrier_frequency(38000); // repeat twice - for (uint16_t r = 0; r < 2; r++) { + for (uint8_t r = 0; r < 2; r++) { // Header data->mark(MITSUBISHI_HEADER_MARK); data->space(MITSUBISHI_HEADER_SPACE); From 2b691ad5adde275544f1e7896463083a84f028af Mon Sep 17 00:00:00 2001 From: Frederik <5511687+fightforlife@users.noreply.github.com> Date: Wed, 29 May 2024 00:02:45 +0200 Subject: [PATCH 12/46] Make SPI Ethernet (W5500) compatible with ESP-IDF v5 (#6778) * change MAC PHY init order according to IDF examples * add idfv5 specific w5500 config init * esp_mac.h needs to be included directly starting with idf5 * Header fix * Couple tweaks --------- Co-authored-by: Keith Burzinski --- esphome/components/ethernet/ethernet_component.cpp | 8 ++++++-- esphome/components/ethernet/ethernet_component.h | 1 + 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/esphome/components/ethernet/ethernet_component.cpp b/esphome/components/ethernet/ethernet_component.cpp index 3af462d593..2a4e5cbad3 100644 --- a/esphome/components/ethernet/ethernet_component.cpp +++ b/esphome/components/ethernet/ethernet_component.cpp @@ -98,11 +98,15 @@ void EthernetComponent::setup() { .post_cb = nullptr, }; +#if USE_ESP_IDF && (ESP_IDF_VERSION_MAJOR >= 5) + eth_w5500_config_t w5500_config = ETH_W5500_DEFAULT_CONFIG(host, &devcfg); +#else spi_device_handle_t spi_handle = nullptr; err = spi_bus_add_device(host, &devcfg, &spi_handle); ESPHL_ERROR_CHECK(err, "SPI bus add device error"); eth_w5500_config_t w5500_config = ETH_W5500_DEFAULT_CONFIG(spi_handle); +#endif w5500_config.int_gpio_num = this->interrupt_pin_; phy_config.phy_addr = this->phy_addr_spi_; phy_config.reset_gpio_num = this->reset_pin_; @@ -614,14 +618,14 @@ void EthernetComponent::rtl8201_set_rmii_mode_(esp_eth_mac_t *mac) { err = mac->read_phy_reg(mac, this->phy_addr_, RTL8201_RMSR_REG_ADDR, &(phy_rmii_mode)); ESPHL_ERROR_CHECK(err, "Read PHY RMSR Register failed"); - ESP_LOGV(TAG, "Hardware default RTL8201 RMII Mode Register is: 0x%04X", phy_rmii_mode); + ESP_LOGV(TAG, "Hardware default RTL8201 RMII Mode Register is: 0x%04" PRIX32, phy_rmii_mode); err = mac->write_phy_reg(mac, this->phy_addr_, RTL8201_RMSR_REG_ADDR, 0x1FFA); ESPHL_ERROR_CHECK(err, "Setting Register 16 RMII Mode Setting failed"); err = mac->read_phy_reg(mac, this->phy_addr_, RTL8201_RMSR_REG_ADDR, &(phy_rmii_mode)); ESPHL_ERROR_CHECK(err, "Read PHY RMSR Register failed"); - ESP_LOGV(TAG, "Setting RTL8201 RMII Mode Register to: 0x%04X", phy_rmii_mode); + ESP_LOGV(TAG, "Setting RTL8201 RMII Mode Register to: 0x%04" PRIX32, phy_rmii_mode); err = mac->write_phy_reg(mac, this->phy_addr_, 0x1f, 0x0); ESPHL_ERROR_CHECK(err, "Setting Page 0 failed"); diff --git a/esphome/components/ethernet/ethernet_component.h b/esphome/components/ethernet/ethernet_component.h index 6276885fd1..3705e3e06a 100644 --- a/esphome/components/ethernet/ethernet_component.h +++ b/esphome/components/ethernet/ethernet_component.h @@ -10,6 +10,7 @@ #include "esp_eth.h" #include "esp_eth_mac.h" #include "esp_netif.h" +#include "esp_mac.h" namespace esphome { namespace ethernet { From ec3164f80058c43ea21d7bc5c7f62bdf7679703f Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Wed, 29 May 2024 14:36:49 +1000 Subject: [PATCH 13/46] [wake_on_lan] Make component platform independent (#6815) --- CODEOWNERS | 2 +- .../components/socket/lwip_raw_tcp_impl.cpp | 3 +- esphome/components/wake_on_lan/__init__.py | 2 +- esphome/components/wake_on_lan/button.py | 34 +++++---- .../components/wake_on_lan/wake_on_lan.cpp | 70 +++++++++++++------ esphome/components/wake_on_lan/wake_on_lan.h | 15 ++-- .../wake_on_lan/test.esp32-c3-idf.yaml | 1 + .../wake_on_lan/test.esp32-idf.yaml | 1 + 8 files changed, 84 insertions(+), 44 deletions(-) create mode 100644 tests/components/wake_on_lan/test.esp32-c3-idf.yaml create mode 100644 tests/components/wake_on_lan/test.esp32-idf.yaml diff --git a/CODEOWNERS b/CODEOWNERS index e099ca42ac..8b6143859a 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -414,7 +414,7 @@ esphome/components/veml3235/* @kbx81 esphome/components/veml7700/* @latonita esphome/components/version/* @esphome/core esphome/components/voice_assistant/* @jesserockz -esphome/components/wake_on_lan/* @willwill2will54 +esphome/components/wake_on_lan/* @clydebarrow @willwill2will54 esphome/components/waveshare_epaper/* @clydebarrow esphome/components/web_server_base/* @OttoWinter esphome/components/web_server_idf/* @dentra diff --git a/esphome/components/socket/lwip_raw_tcp_impl.cpp b/esphome/components/socket/lwip_raw_tcp_impl.cpp index bd59b81caa..1d998902ff 100644 --- a/esphome/components/socket/lwip_raw_tcp_impl.cpp +++ b/esphome/components/socket/lwip_raw_tcp_impl.cpp @@ -469,7 +469,8 @@ class LWIPRawImpl : public Socket { } ssize_t sendto(const void *buf, size_t len, int flags, const struct sockaddr *to, socklen_t tolen) override { // return ::sendto(fd_, buf, len, flags, to, tolen); - return 0; + errno = ENOSYS; + return -1; } int setblocking(bool blocking) override { if (pcb_ == nullptr) { diff --git a/esphome/components/wake_on_lan/__init__.py b/esphome/components/wake_on_lan/__init__.py index 3548fb02f4..90539e5d3c 100644 --- a/esphome/components/wake_on_lan/__init__.py +++ b/esphome/components/wake_on_lan/__init__.py @@ -1 +1 @@ -CODEOWNERS = ["@willwill2will54"] +CODEOWNERS = ["@willwill2will54", "@clydebarrow"] diff --git a/esphome/components/wake_on_lan/button.py b/esphome/components/wake_on_lan/button.py index 778ea60cfa..b09e87e811 100644 --- a/esphome/components/wake_on_lan/button.py +++ b/esphome/components/wake_on_lan/button.py @@ -2,6 +2,16 @@ import esphome.codegen as cg from esphome.components import button import esphome.config_validation as cv from esphome.const import CONF_ID +from esphome.core import CORE + +DEPENDENCIES = ["network"] + + +def AUTO_LOAD(): + if CORE.is_esp8266 or CORE.is_rp2040: + return [] + return ["socket"] + CONF_TARGET_MAC_ADDRESS = "target_mac_address" @@ -9,25 +19,19 @@ wake_on_lan_ns = cg.esphome_ns.namespace("wake_on_lan") WakeOnLanButton = wake_on_lan_ns.class_("WakeOnLanButton", button.Button, cg.Component) -DEPENDENCIES = ["network"] - -CONFIG_SCHEMA = cv.All( +CONFIG_SCHEMA = ( button.button_schema(WakeOnLanButton) .extend(cv.COMPONENT_SCHEMA) .extend( - cv.Schema( - { - cv.Required(CONF_TARGET_MAC_ADDRESS): cv.mac_address, - } - ), - ), - cv.only_with_arduino, + { + cv.Required(CONF_TARGET_MAC_ADDRESS): cv.mac_address, + } + ) ) -def to_code(config): +async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) - - yield cg.add(var.set_macaddr(*config[CONF_TARGET_MAC_ADDRESS].parts)) - yield cg.register_component(var, config) - yield button.register_button(var, config) + cg.add(var.set_macaddr(*config[CONF_TARGET_MAC_ADDRESS].parts)) + await cg.register_component(var, config) + await button.register_button(var, config) diff --git a/esphome/components/wake_on_lan/wake_on_lan.cpp b/esphome/components/wake_on_lan/wake_on_lan.cpp index f414bf6c71..080e1bbac8 100644 --- a/esphome/components/wake_on_lan/wake_on_lan.cpp +++ b/esphome/components/wake_on_lan/wake_on_lan.cpp @@ -1,5 +1,3 @@ -#ifdef USE_ARDUINO - #include "wake_on_lan.h" #include "esphome/core/log.h" #include "esphome/components/network/ip_address.h" @@ -22,40 +20,68 @@ void WakeOnLanButton::set_macaddr(uint8_t a, uint8_t b, uint8_t c, uint8_t d, ui void WakeOnLanButton::dump_config() { LOG_BUTTON("", "Wake-on-LAN Button", this); - ESP_LOGCONFIG(TAG, " Target MAC address: %02X:%02X:%02X:%02X:%02X:%02X", macaddr_[0], macaddr_[1], macaddr_[2], - macaddr_[3], macaddr_[4], macaddr_[5]); + ESP_LOGCONFIG(TAG, " Target MAC address: %02X:%02X:%02X:%02X:%02X:%02X", this->macaddr_[0], this->macaddr_[1], + this->macaddr_[2], this->macaddr_[3], this->macaddr_[4], this->macaddr_[5]); } void WakeOnLanButton::press_action() { + if (!network::is_connected()) { + ESP_LOGW(TAG, "Network not connected"); + return; + } ESP_LOGI(TAG, "Sending Wake-on-LAN Packet..."); - bool begin_status = false; - bool end_status = false; +#if defined(USE_SOCKET_IMPL_BSD_SOCKETS) || defined(USE_SOCKET_IMPL_LWIP_SOCKETS) + struct sockaddr_storage saddr {}; + auto addr_len = + socket::set_sockaddr(reinterpret_cast(&saddr), sizeof(saddr), "255.255.255.255", this->port_); + uint8_t buffer[6 + sizeof this->macaddr_ * 16]; + memcpy(buffer, PREFIX, sizeof(PREFIX)); + for (size_t i = 0; i != 16; i++) { + memcpy(buffer + i * sizeof(this->macaddr_) + sizeof(PREFIX), this->macaddr_, sizeof(this->macaddr_)); + } + if (this->broadcast_socket_->sendto(buffer, sizeof(buffer), 0, reinterpret_cast(&saddr), + addr_len) <= 0) + ESP_LOGW(TAG, "sendto() error %d", errno); +#else IPAddress broadcast = IPAddress(255, 255, 255, 255); -#ifdef USE_ESP8266 for (auto ip : esphome::network::get_ip_addresses()) { if (ip.is_ip4()) { - begin_status = this->udp_client_.beginPacketMulticast(broadcast, 9, ip, 128); - break; + if (this->udp_client_.beginPacketMulticast(broadcast, 9, ip, 128) != 0) { + this->udp_client_.write(PREFIX, 6); + for (size_t i = 0; i < 16; i++) { + this->udp_client_.write(macaddr_, 6); + } + if (this->udp_client_.endPacket() != 0) + return; + ESP_LOGW(TAG, "WOL broadcast failed"); + return; + } } } + ESP_LOGW(TAG, "No ip4 addresses to broadcast to"); #endif -#ifdef USE_ESP32 - begin_status = this->udp_client_.beginPacket(broadcast, 9); -#endif +} - if (begin_status) { - this->udp_client_.write(PREFIX, 6); - for (size_t i = 0; i < 16; i++) { - this->udp_client_.write(macaddr_, 6); - } - end_status = this->udp_client_.endPacket(); +void WakeOnLanButton::setup() { +#if defined(USE_SOCKET_IMPL_BSD_SOCKETS) || defined(USE_SOCKET_IMPL_LWIP_SOCKETS) + this->broadcast_socket_ = socket::socket(AF_INET, SOCK_DGRAM, IPPROTO_IP); + if (this->broadcast_socket_ == nullptr) { + this->mark_failed(); + this->status_set_error("Could not create socket"); + return; } - if (!begin_status || end_status) { - ESP_LOGE(TAG, "Sending Wake-on-LAN Packet Failed!"); + int enable = 1; + auto err = this->broadcast_socket_->setsockopt(SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int)); + if (err != 0) { + this->status_set_warning("Socket unable to set reuseaddr"); + // we can still continue } + err = this->broadcast_socket_->setsockopt(SOL_SOCKET, SO_BROADCAST, &enable, sizeof(int)); + if (err != 0) { + this->status_set_warning("Socket unable to set broadcast"); + } +#endif } } // namespace wake_on_lan } // namespace esphome - -#endif diff --git a/esphome/components/wake_on_lan/wake_on_lan.h b/esphome/components/wake_on_lan/wake_on_lan.h index 72f900e3fa..42cb3a9268 100644 --- a/esphome/components/wake_on_lan/wake_on_lan.h +++ b/esphome/components/wake_on_lan/wake_on_lan.h @@ -1,10 +1,12 @@ #pragma once -#ifdef USE_ARDUINO - #include "esphome/components/button/button.h" #include "esphome/core/component.h" +#if defined(USE_SOCKET_IMPL_BSD_SOCKETS) || defined(USE_SOCKET_IMPL_LWIP_SOCKETS) +#include "esphome/components/socket/socket.h" +#else #include "WiFiUdp.h" +#endif namespace esphome { namespace wake_on_lan { @@ -14,14 +16,19 @@ class WakeOnLanButton : public button::Button, public Component { void set_macaddr(uint8_t a, uint8_t b, uint8_t c, uint8_t d, uint8_t e, uint8_t f); void dump_config() override; + void setup() override; + float get_setup_priority() const override { return setup_priority::AFTER_WIFI; } protected: +#if defined(USE_SOCKET_IMPL_BSD_SOCKETS) || defined(USE_SOCKET_IMPL_LWIP_SOCKETS) + std::unique_ptr broadcast_socket_{}; +#else WiFiUDP udp_client_{}; +#endif void press_action() override; + uint16_t port_{9}; uint8_t macaddr_[6]; }; } // namespace wake_on_lan } // namespace esphome - -#endif diff --git a/tests/components/wake_on_lan/test.esp32-c3-idf.yaml b/tests/components/wake_on_lan/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..dade44d145 --- /dev/null +++ b/tests/components/wake_on_lan/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/wake_on_lan/test.esp32-idf.yaml b/tests/components/wake_on_lan/test.esp32-idf.yaml new file mode 100644 index 0000000000..dade44d145 --- /dev/null +++ b/tests/components/wake_on_lan/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml From bff24e297774d7f79deea30c9cdaed4bc23e47d4 Mon Sep 17 00:00:00 2001 From: Keith Burzinski Date: Wed, 29 May 2024 00:05:19 -0500 Subject: [PATCH 14/46] Fix a bunch of components for IDF 5 compatibility and #6802 (#6805) --- esphome/components/ade7880/ade7880.cpp | 1 + .../components/ade7953_base/ade7953_base.cpp | 1 + esphome/components/ags10/ags10.cpp | 4 +- .../components/ct_clamp/ct_clamp_sensor.cpp | 5 ++- .../fingerprint_grow/fingerprint_grow.cpp | 2 +- esphome/components/he60r/he60r.cpp | 7 ++-- esphome/components/htu31d/htu31d.cpp | 4 +- .../components/ina2xx_base/ina2xx_base.cpp | 8 ++-- esphome/components/jsn_sr04t/jsn_sr04t.cpp | 4 +- esphome/components/mhz19/mhz19.cpp | 1 + .../micro_wake_word/micro_wake_word.cpp | 3 +- .../remote_base/byronsx_protocol.cpp | 1 + .../remote_base/drayton_protocol.cpp | 2 + esphome/components/sonoff_d1/sonoff_d1.cpp | 3 +- esphome/components/weikai/weikai.cpp | 4 +- esphome/components/wl_134/wl_134.cpp | 4 +- esphome/components/xgzp68xx/xgzp68xx.cpp | 1 + .../remote_receiver/esp32-common.yaml | 40 +++++++++---------- 18 files changed, 55 insertions(+), 40 deletions(-) diff --git a/esphome/components/ade7880/ade7880.cpp b/esphome/components/ade7880/ade7880.cpp index a03f1227f1..4a45b3b321 100644 --- a/esphome/components/ade7880/ade7880.cpp +++ b/esphome/components/ade7880/ade7880.cpp @@ -10,6 +10,7 @@ #include "ade7880.h" #include "ade7880_registers.h" #include "esphome/core/log.h" + #include namespace esphome { diff --git a/esphome/components/ade7953_base/ade7953_base.cpp b/esphome/components/ade7953_base/ade7953_base.cpp index 79161c5676..2511b4e04c 100644 --- a/esphome/components/ade7953_base/ade7953_base.cpp +++ b/esphome/components/ade7953_base/ade7953_base.cpp @@ -1,5 +1,6 @@ #include "ade7953_base.h" #include "esphome/core/log.h" + #include namespace esphome { diff --git a/esphome/components/ags10/ags10.cpp b/esphome/components/ags10/ags10.cpp index dfaa00e2e9..422380da83 100644 --- a/esphome/components/ags10/ags10.cpp +++ b/esphome/components/ags10/ags10.cpp @@ -1,5 +1,7 @@ #include "ags10.h" +#include + namespace esphome { namespace ags10 { static const char *const TAG = "ags10"; @@ -35,7 +37,7 @@ void AGS10Component::setup() { auto resistance = this->read_resistance_(); if (resistance) { - ESP_LOGD(TAG, "AGS10 Sensor Resistance: 0x%08X", *resistance); + ESP_LOGD(TAG, "AGS10 Sensor Resistance: 0x%08" PRIX32, *resistance); if (this->resistance_ != nullptr) { this->resistance_->publish_state(*resistance); } diff --git a/esphome/components/ct_clamp/ct_clamp_sensor.cpp b/esphome/components/ct_clamp/ct_clamp_sensor.cpp index d555befcde..0aa0258a9b 100644 --- a/esphome/components/ct_clamp/ct_clamp_sensor.cpp +++ b/esphome/components/ct_clamp/ct_clamp_sensor.cpp @@ -1,6 +1,7 @@ #include "ct_clamp_sensor.h" #include "esphome/core/log.h" +#include #include namespace esphome { @@ -37,8 +38,8 @@ void CTClampSensor::update() { float rms_ac = 0; if (rms_ac_squared > 0) rms_ac = std::sqrt(rms_ac_squared); - ESP_LOGD(TAG, "'%s' - Raw AC Value: %.3fA after %d different samples (%d SPS)", this->name_.c_str(), rms_ac, - this->num_samples_, 1000 * this->num_samples_ / this->sample_duration_); + ESP_LOGD(TAG, "'%s' - Raw AC Value: %.3fA after %" PRIu32 " different samples (%" PRIu32 " SPS)", + this->name_.c_str(), rms_ac, this->num_samples_, 1000 * this->num_samples_ / this->sample_duration_); this->publish_state(rms_ac); }); diff --git a/esphome/components/fingerprint_grow/fingerprint_grow.cpp b/esphome/components/fingerprint_grow/fingerprint_grow.cpp index 8f58db9159..c2cab368c9 100644 --- a/esphome/components/fingerprint_grow/fingerprint_grow.cpp +++ b/esphome/components/fingerprint_grow/fingerprint_grow.cpp @@ -377,7 +377,7 @@ uint8_t FingerprintGrowComponent::transfer_(std::vector *p_data_buffer) this->write((uint8_t) (wire_length >> 8)); this->write((uint8_t) (wire_length & 0xFF)); - uint16_t sum = ((wire_length) >> 8) + ((wire_length) &0xFF) + COMMAND; + uint16_t sum = (wire_length >> 8) + (wire_length & 0xFF) + COMMAND; for (auto data : *p_data_buffer) { this->write(data); sum += data; diff --git a/esphome/components/he60r/he60r.cpp b/esphome/components/he60r/he60r.cpp index 96ad1f04be..05f3f528a5 100644 --- a/esphome/components/he60r/he60r.cpp +++ b/esphome/components/he60r/he60r.cpp @@ -1,6 +1,7 @@ #include "he60r.h" #include "esphome/core/hal.h" #include "esphome/core/log.h" + #include namespace esphome { @@ -125,10 +126,10 @@ void HE60rCover::process_rx_(uint8_t data) { } void HE60rCover::update_() { - if (toggles_needed_ != 0) { + if (this->toggles_needed_ != 0) { if ((this->counter_++ & 0x3) == 0) { - toggles_needed_--; - ESP_LOGD(TAG, "Writing byte 0x30, still needed=%" PRIu32, toggles_needed_); + this->toggles_needed_--; + ESP_LOGD(TAG, "Writing byte 0x30, still needed=%" PRIu32, this->toggles_needed_); this->write_byte(TOGGLE_BYTE); } else { this->write_byte(QUERY_BYTE); diff --git a/esphome/components/htu31d/htu31d.cpp b/esphome/components/htu31d/htu31d.cpp index 928250a5b2..bf4689d837 100644 --- a/esphome/components/htu31d/htu31d.cpp +++ b/esphome/components/htu31d/htu31d.cpp @@ -12,6 +12,8 @@ #include "esphome/core/helpers.h" #include "esphome/core/log.h" +#include + namespace esphome { namespace htu31d { @@ -204,7 +206,7 @@ uint32_t HTU31DComponent::read_serial_num_() { return 0; } - ESP_LOGD(TAG, "Found serial: 0x%X", serial); + ESP_LOGD(TAG, "Found serial: 0x%" PRIX32, serial); return serial; } diff --git a/esphome/components/ina2xx_base/ina2xx_base.cpp b/esphome/components/ina2xx_base/ina2xx_base.cpp index 5d947d0537..924bf91e5e 100644 --- a/esphome/components/ina2xx_base/ina2xx_base.cpp +++ b/esphome/components/ina2xx_base/ina2xx_base.cpp @@ -483,7 +483,7 @@ bool INA2XX::read_power_w_(float &power_out) { uint64_t power_reading{0}; auto ret = this->read_unsigned_((uint8_t) RegisterMap::REG_POWER, 3, power_reading); - ESP_LOGV(TAG, "read_power_w_ ret=%s, reading_lsb=%d", OKFAILED(ret), (uint32_t) power_reading); + ESP_LOGV(TAG, "read_power_w_ ret=%s, reading_lsb=%" PRIu32, OKFAILED(ret), (uint32_t) power_reading); if (ret) { power_out = this->cfg_.power_coeff * this->current_lsb_ * (float) power_reading; } @@ -503,8 +503,8 @@ bool INA2XX::read_energy_(double &joules_out, double &watt_hours_out) { uint64_t previous_energy = this->energy_overflows_count_ * (((uint64_t) 1) << 40); auto ret = this->read_unsigned_((uint8_t) RegisterMap::REG_ENERGY, 5, joules_reading); - ESP_LOGV(TAG, "read_energy_j_ ret=%s, reading_lsb=0x%" PRIX64 ", current_lsb=%f, overflow_cnt=%d", OKFAILED(ret), - joules_reading, this->current_lsb_, this->energy_overflows_count_); + ESP_LOGV(TAG, "read_energy_j_ ret=%s, reading_lsb=0x%" PRIX64 ", current_lsb=%f, overflow_cnt=%" PRIu32, + OKFAILED(ret), joules_reading, this->current_lsb_, this->energy_overflows_count_); if (ret) { joules_out = this->cfg_.energy_coeff * this->current_lsb_ * (double) joules_reading + (double) previous_energy; watt_hours_out = joules_out / 3600.0; @@ -528,7 +528,7 @@ bool INA2XX::read_charge_(double &coulombs_out, double &_hours_out) { auto ret = this->read_unsigned_((uint8_t) RegisterMap::REG_CHARGE, 5, raw); coulombs_reading = this->two_complement_(raw, 40); - ESP_LOGV(TAG, "read_charge_c_ ret=%d, curr_charge=%f + 39-bit overflow_cnt=%d", ret, coulombs_reading, + ESP_LOGV(TAG, "read_charge_c_ ret=%d, curr_charge=%f + 39-bit overflow_cnt=%" PRIu32, ret, coulombs_reading, this->charge_overflows_count_); if (ret) { coulombs_out = this->current_lsb_ * (double) coulombs_reading + (double) previous_charge; diff --git a/esphome/components/jsn_sr04t/jsn_sr04t.cpp b/esphome/components/jsn_sr04t/jsn_sr04t.cpp index 70e21a137d..b96bf8f762 100644 --- a/esphome/components/jsn_sr04t/jsn_sr04t.cpp +++ b/esphome/components/jsn_sr04t/jsn_sr04t.cpp @@ -2,8 +2,6 @@ #include "esphome/core/helpers.h" #include "esphome/core/log.h" -#include - // Very basic support for JSN_SR04T V3.0 distance sensor in mode 2 namespace esphome { @@ -38,7 +36,7 @@ void Jsnsr04tComponent::check_buffer_() { uint16_t distance = encode_uint16(this->buffer_[1], this->buffer_[2]); if (distance > 250) { float meters = distance / 1000.0f; - ESP_LOGV(TAG, "Distance from sensor: %" PRIu32 "mm, %.3fm", distance, meters); + ESP_LOGV(TAG, "Distance from sensor: %umm, %.3fm", distance, meters); this->publish_state(meters); } else { ESP_LOGW(TAG, "Invalid data read from sensor: %s", format_hex_pretty(this->buffer_).c_str()); diff --git a/esphome/components/mhz19/mhz19.cpp b/esphome/components/mhz19/mhz19.cpp index 574aee1bd5..c3c8120362 100644 --- a/esphome/components/mhz19/mhz19.cpp +++ b/esphome/components/mhz19/mhz19.cpp @@ -1,5 +1,6 @@ #include "mhz19.h" #include "esphome/core/log.h" + #include namespace esphome { diff --git a/esphome/components/micro_wake_word/micro_wake_word.cpp b/esphome/components/micro_wake_word/micro_wake_word.cpp index f637f8b2bb..5a89708127 100644 --- a/esphome/components/micro_wake_word/micro_wake_word.cpp +++ b/esphome/components/micro_wake_word/micro_wake_word.cpp @@ -20,6 +20,7 @@ #include #include +#include #include namespace esphome { @@ -316,7 +317,7 @@ float MicroWakeWord::perform_streaming_inference_() { return false; } - ESP_LOGV(TAG, "Streaming Inference Latency=%u ms", (millis() - prior_invoke)); + ESP_LOGV(TAG, "Streaming Inference Latency=%" PRIu32 " ms", (millis() - prior_invoke)); TfLiteTensor *output = this->streaming_interpreter_->output(0); diff --git a/esphome/components/remote_base/byronsx_protocol.cpp b/esphome/components/remote_base/byronsx_protocol.cpp index 2d4c60ff8b..6bfa4b7ff9 100644 --- a/esphome/components/remote_base/byronsx_protocol.cpp +++ b/esphome/components/remote_base/byronsx_protocol.cpp @@ -1,5 +1,6 @@ #include "byronsx_protocol.h" #include "esphome/core/log.h" + #include namespace esphome { diff --git a/esphome/components/remote_base/drayton_protocol.cpp b/esphome/components/remote_base/drayton_protocol.cpp index 73c0fb116c..da2e985af0 100644 --- a/esphome/components/remote_base/drayton_protocol.cpp +++ b/esphome/components/remote_base/drayton_protocol.cpp @@ -1,6 +1,8 @@ #include "drayton_protocol.h" #include "esphome/core/log.h" +#include + namespace esphome { namespace remote_base { diff --git a/esphome/components/sonoff_d1/sonoff_d1.cpp b/esphome/components/sonoff_d1/sonoff_d1.cpp index 6ae80296fd..e70ec7b70d 100644 --- a/esphome/components/sonoff_d1/sonoff_d1.cpp +++ b/esphome/components/sonoff_d1/sonoff_d1.cpp @@ -128,7 +128,8 @@ bool SonoffD1Output::read_ack_(const uint8_t *cmd, const size_t len) { // Expected acknowledgement from rf chip uint8_t ref_buffer[7] = {0xAA, 0x55, cmd[2], cmd[3], 0x00, 0x00, 0x00}; uint8_t buffer[sizeof(ref_buffer)] = {0}; - uint32_t pos = 0, buf_len = sizeof(ref_buffer); + uint32_t pos = 0; + size_t buf_len = sizeof(ref_buffer); // Update the reference checksum this->populate_checksum_(ref_buffer, sizeof(ref_buffer)); diff --git a/esphome/components/weikai/weikai.cpp b/esphome/components/weikai/weikai.cpp index a04bc0a574..00bce9bcff 100644 --- a/esphome/components/weikai/weikai.cpp +++ b/esphome/components/weikai/weikai.cpp @@ -375,8 +375,8 @@ void WeikaiChannel::set_baudrate_() { this->parent_->page1_ = false; // switch back to page 0 this->reg(WKREG_SPAGE) = 0; - ESP_LOGV(TAG, " Crystal=%d baudrate=%d => registers [%d %d %d]", this->parent_->crystal_, this->baud_rate_, - baud_high, baud_low, baud_dec); + ESP_LOGV(TAG, " Crystal=%" PRId32 " baudrate=%" PRId32 " => registers [%d %d %d]", this->parent_->crystal_, + this->baud_rate_, baud_high, baud_low, baud_dec); } inline bool WeikaiChannel::tx_fifo_is_not_empty_() { return this->reg(WKREG_FSR) & FSR_TFDAT; } diff --git a/esphome/components/wl_134/wl_134.cpp b/esphome/components/wl_134/wl_134.cpp index 3ffa0c63ce..403f8bd1b3 100644 --- a/esphome/components/wl_134/wl_134.cpp +++ b/esphome/components/wl_134/wl_134.cpp @@ -1,6 +1,8 @@ #include "wl_134.h" #include "esphome/core/log.h" +#include + namespace esphome { namespace wl_134 { @@ -71,7 +73,7 @@ Wl134Component::Rfid134Error Wl134Component::read_packet_() { ESP_LOGV(TAG, "isData: %s", reading.isData ? "true" : "false"); ESP_LOGV(TAG, "isAnimal: %s", reading.isAnimal ? "true" : "false"); ESP_LOGV(TAG, "Reserved0: %d", reading.reserved0); - ESP_LOGV(TAG, "Reserved1: %d", reading.reserved1); + ESP_LOGV(TAG, "Reserved1: %" PRId32, reading.reserved1); char buf[20]; sprintf(buf, "%03d%012lld", reading.country, reading.id); diff --git a/esphome/components/xgzp68xx/xgzp68xx.cpp b/esphome/components/xgzp68xx/xgzp68xx.cpp index dff00fb696..ad6217845d 100644 --- a/esphome/components/xgzp68xx/xgzp68xx.cpp +++ b/esphome/components/xgzp68xx/xgzp68xx.cpp @@ -3,6 +3,7 @@ #include "esphome/core/hal.h" #include "esphome/core/helpers.h" #include "esphome/components/i2c/i2c.h" + #include namespace esphome { diff --git a/tests/components/remote_receiver/esp32-common.yaml b/tests/components/remote_receiver/esp32-common.yaml index c3987f8cd9..7e5d2cce32 100644 --- a/tests/components/remote_receiver/esp32-common.yaml +++ b/tests/components/remote_receiver/esp32-common.yaml @@ -32,8 +32,8 @@ remote_receiver: on_coolix: then: - logger.log: - format: "on_coolix: %u %u" - args: ["x.first", "x.second"] + format: "on_coolix: %lu %lu" + args: ["long(x.first)", "long(x.second)"] on_dish: then: - logger.log: @@ -52,13 +52,13 @@ remote_receiver: on_jvc: then: - logger.log: - format: "on_jvc: %u" - args: ["x.data"] + format: "on_jvc: %lu" + args: ["long(x.data)"] on_keeloq: then: - logger.log: - format: "on_keeloq: %u %u %u" - args: ["x.encrypted", "x.address", "x.command"] + format: "on_keeloq: %lu %lu %u" + args: ["long(x.encrypted)", "long(x.address)", "x.command"] on_haier: then: - logger.log: @@ -67,13 +67,13 @@ remote_receiver: on_lg: then: - logger.log: - format: "on_lg: %u %u" - args: ["x.data", "x.nbits"] + format: "on_lg: %lu %u" + args: ["long(x.data)", "x.nbits"] on_magiquest: then: - logger.log: - format: "on_magiquest: %u %u" - args: ["x.magnitude", "x.wand_id"] + format: "on_magiquest: %u %lu" + args: ["x.magnitude", "long(x.wand_id)"] on_midea: then: - logger.log: @@ -87,13 +87,13 @@ remote_receiver: on_nexa: then: - logger.log: - format: "on_nexa: %u %u %u %u %u" - args: ["x.device", "x.group", "x.state", "x.channel", "x.level"] + format: "on_nexa: %lu %u %u %u %u" + args: ["long(x.device)", "x.group", "x.state", "x.channel", "x.level"] on_panasonic: then: - logger.log: - format: "on_panasonic: %u %u" - args: ["x.address", "x.command"] + format: "on_panasonic: %u %lu" + args: ["x.address", "long(x.command)"] on_pioneer: then: - logger.log: @@ -107,8 +107,8 @@ remote_receiver: on_raw: then: - logger.log: - format: "on_raw: %u" - args: ["x.front()"] + format: "on_raw: %lu" + args: ["long(x.front())"] on_rc5: then: - logger.log: @@ -132,13 +132,13 @@ remote_receiver: on_samsung36: then: - logger.log: - format: "on_samsung36: %u %u" - args: ["x.address", "x.command"] + format: "on_samsung36: %u %lu" + args: ["x.address", "long(x.command)"] on_sony: then: - logger.log: - format: "on_sony: %u %u" - args: ["x.data", "x.nbits"] + format: "on_sony: %lu %u" + args: ["long(x.data)", "x.nbits"] on_toshiba_ac: then: - logger.log: From 60433c5e645690f358be94c2e3136aecfd84fa59 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 29 May 2024 17:09:13 +1200 Subject: [PATCH 15/46] Bump docker/login-action from 3.1.0 to 3.2.0 (#6823) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/release.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 47dc217bf7..563d485b6a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -96,12 +96,12 @@ jobs: uses: docker/setup-qemu-action@v3.0.0 - name: Log in to docker hub - uses: docker/login-action@v3.1.0 + uses: docker/login-action@v3.2.0 with: username: ${{ secrets.DOCKER_USER }} password: ${{ secrets.DOCKER_PASSWORD }} - name: Log in to the GitHub container registry - uses: docker/login-action@v3.1.0 + uses: docker/login-action@v3.2.0 with: registry: ghcr.io username: ${{ github.actor }} @@ -188,13 +188,13 @@ jobs: - name: Log in to docker hub if: matrix.registry == 'dockerhub' - uses: docker/login-action@v3.1.0 + uses: docker/login-action@v3.2.0 with: username: ${{ secrets.DOCKER_USER }} password: ${{ secrets.DOCKER_PASSWORD }} - name: Log in to the GitHub container registry if: matrix.registry == 'ghcr' - uses: docker/login-action@v3.1.0 + uses: docker/login-action@v3.2.0 with: registry: ghcr.io username: ${{ github.actor }} From 6d5d382f3db98e2a9997fed9e62811f91bad06f0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 29 May 2024 17:10:47 +1200 Subject: [PATCH 16/46] Bump pytest-cov from 4.1.0 to 5.0.0 (#6580) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements_test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test.txt b/requirements_test.txt index ae833841ca..0052e7545c 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -6,7 +6,7 @@ pre-commit # Unit tests pytest==8.2.0 -pytest-cov==4.1.0 +pytest-cov==5.0.0 pytest-mock==3.14.0 pytest-asyncio==0.23.6 asyncmock==0.4.2 From 439fd9471863e4bf12cdc5e11b098ab0b7b96874 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 29 May 2024 17:11:02 +1200 Subject: [PATCH 17/46] Bump peter-evans/create-pull-request from 6.0.4 to 6.0.5 (#6635) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/sync-device-classes.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/sync-device-classes.yml b/.github/workflows/sync-device-classes.yml index 9a8c9d1753..e65e851f3c 100644 --- a/.github/workflows/sync-device-classes.yml +++ b/.github/workflows/sync-device-classes.yml @@ -36,7 +36,7 @@ jobs: python ./script/sync-device_class.py - name: Commit changes - uses: peter-evans/create-pull-request@v6.0.4 + uses: peter-evans/create-pull-request@v6.0.5 with: commit-message: "Synchronise Device Classes from Home Assistant" committer: esphomebot From 5ae32e81c360a73f7ebe14d330d6e9d58eac324a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 29 May 2024 17:11:34 +1200 Subject: [PATCH 18/46] Bump black from 24.4.0 to 24.4.2 (#6646) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- requirements_test.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 418716e4f4..74acfa1c1d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -3,7 +3,7 @@ # See https://pre-commit.com/hooks.html for more hooks repos: - repo: https://github.com/psf/black-pre-commit-mirror - rev: 24.2.0 + rev: 24.4.2 hooks: - id: black args: diff --git a/requirements_test.txt b/requirements_test.txt index 0052e7545c..94abe1cd76 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,6 +1,6 @@ pylint==3.1.0 flake8==7.0.0 # also change in .pre-commit-config.yaml when updating -black==24.4.0 # also change in .pre-commit-config.yaml when updating +black==24.4.2 # also change in .pre-commit-config.yaml when updating pyupgrade==3.15.2 # also change in .pre-commit-config.yaml when updating pre-commit From 854d3f2e4a919c27f723d940295f0adec1614168 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 30 May 2024 13:09:19 +1200 Subject: [PATCH 19/46] [voice_assistant] Timers (#6821) Co-authored-by: Keith Burzinski --- esphome/components/api/api.proto | 19 ++++ esphome/components/api/api_connection.cpp | 9 ++ esphome/components/api/api_connection.h | 1 + esphome/components/api/api_pb2.cpp | 92 +++++++++++++++++++ esphome/components/api/api_pb2.h | 23 +++++ esphome/components/api/api_pb2_service.cpp | 13 +++ esphome/components/api/api_pb2_service.h | 3 + .../components/voice_assistant/__init__.py | 66 +++++++++++++ .../voice_assistant/voice_assistant.cpp | 53 +++++++++++ .../voice_assistant/voice_assistant.h | 42 +++++++++ 10 files changed, 321 insertions(+) diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index 774ca7ed9b..0becec2348 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -1517,6 +1517,25 @@ message VoiceAssistantAudio { bool end = 2; } +enum VoiceAssistantTimerEvent { + VOICE_ASSISTANT_TIMER_STARTED = 0; + VOICE_ASSISTANT_TIMER_UPDATED = 1; + VOICE_ASSISTANT_TIMER_CANCELLED = 2; + VOICE_ASSISTANT_TIMER_FINISHED = 3; +} + +message VoiceAssistantTimerEventResponse { + option (id) = 115; + option (source) = SOURCE_CLIENT; + option (ifdef) = "USE_VOICE_ASSISTANT"; + + VoiceAssistantTimerEvent event_type = 1; + string timer_id = 2; + string name = 3; + uint32 total_seconds = 4; + uint32 seconds_left = 5; + bool is_active = 6; +} // ==================== ALARM CONTROL PANEL ==================== enum AlarmControlPanelState { diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 2804dba31f..253f04aa39 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -1193,6 +1193,15 @@ void APIConnection::on_voice_assistant_audio(const VoiceAssistantAudio &msg) { voice_assistant::global_voice_assistant->on_audio(msg); } }; +void APIConnection::on_voice_assistant_timer_event_response(const VoiceAssistantTimerEventResponse &msg) { + if (voice_assistant::global_voice_assistant != nullptr) { + if (voice_assistant::global_voice_assistant->get_api_connection() != this) { + return; + } + + voice_assistant::global_voice_assistant->on_timer_event(msg); + } +}; #endif diff --git a/esphome/components/api/api_connection.h b/esphome/components/api/api_connection.h index ee466c5d10..293da17fa4 100644 --- a/esphome/components/api/api_connection.h +++ b/esphome/components/api/api_connection.h @@ -150,6 +150,7 @@ class APIConnection : public APIServerConnection { void on_voice_assistant_response(const VoiceAssistantResponse &msg) override; void on_voice_assistant_event_response(const VoiceAssistantEventResponse &msg) override; void on_voice_assistant_audio(const VoiceAssistantAudio &msg) override; + void on_voice_assistant_timer_event_response(const VoiceAssistantTimerEventResponse &msg) override; #endif #ifdef USE_ALARM_CONTROL_PANEL diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index a48087e348..9db6482c49 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -475,6 +475,22 @@ template<> const char *proto_enum_to_string(enums::V } #endif #ifdef HAS_PROTO_MESSAGE_DUMP +template<> const char *proto_enum_to_string(enums::VoiceAssistantTimerEvent value) { + switch (value) { + case enums::VOICE_ASSISTANT_TIMER_STARTED: + return "VOICE_ASSISTANT_TIMER_STARTED"; + case enums::VOICE_ASSISTANT_TIMER_UPDATED: + return "VOICE_ASSISTANT_TIMER_UPDATED"; + case enums::VOICE_ASSISTANT_TIMER_CANCELLED: + return "VOICE_ASSISTANT_TIMER_CANCELLED"; + case enums::VOICE_ASSISTANT_TIMER_FINISHED: + return "VOICE_ASSISTANT_TIMER_FINISHED"; + default: + return "UNKNOWN"; + } +} +#endif +#ifdef HAS_PROTO_MESSAGE_DUMP template<> const char *proto_enum_to_string(enums::AlarmControlPanelState value) { switch (value) { case enums::ALARM_STATE_DISARMED: @@ -6857,6 +6873,82 @@ void VoiceAssistantAudio::dump_to(std::string &out) const { out.append("}"); } #endif +bool VoiceAssistantTimerEventResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { + switch (field_id) { + case 1: { + this->event_type = value.as_enum(); + return true; + } + case 4: { + this->total_seconds = value.as_uint32(); + return true; + } + case 5: { + this->seconds_left = value.as_uint32(); + return true; + } + case 6: { + this->is_active = value.as_bool(); + return true; + } + default: + return false; + } +} +bool VoiceAssistantTimerEventResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { + switch (field_id) { + case 2: { + this->timer_id = value.as_string(); + return true; + } + case 3: { + this->name = value.as_string(); + return true; + } + default: + return false; + } +} +void VoiceAssistantTimerEventResponse::encode(ProtoWriteBuffer buffer) const { + buffer.encode_enum(1, this->event_type); + buffer.encode_string(2, this->timer_id); + buffer.encode_string(3, this->name); + buffer.encode_uint32(4, this->total_seconds); + buffer.encode_uint32(5, this->seconds_left); + buffer.encode_bool(6, this->is_active); +} +#ifdef HAS_PROTO_MESSAGE_DUMP +void VoiceAssistantTimerEventResponse::dump_to(std::string &out) const { + __attribute__((unused)) char buffer[64]; + out.append("VoiceAssistantTimerEventResponse {\n"); + out.append(" event_type: "); + out.append(proto_enum_to_string(this->event_type)); + out.append("\n"); + + out.append(" timer_id: "); + out.append("'").append(this->timer_id).append("'"); + out.append("\n"); + + out.append(" name: "); + out.append("'").append(this->name).append("'"); + out.append("\n"); + + out.append(" total_seconds: "); + sprintf(buffer, "%" PRIu32, this->total_seconds); + out.append(buffer); + out.append("\n"); + + out.append(" seconds_left: "); + sprintf(buffer, "%" PRIu32, this->seconds_left); + out.append(buffer); + out.append("\n"); + + out.append(" is_active: "); + out.append(YESNO(this->is_active)); + out.append("\n"); + out.append("}"); +} +#endif bool ListEntitiesAlarmControlPanelResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { switch (field_id) { case 6: { diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index 807b150d82..54cbd20559 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -191,6 +191,12 @@ enum VoiceAssistantEvent : uint32_t { VOICE_ASSISTANT_TTS_STREAM_START = 98, VOICE_ASSISTANT_TTS_STREAM_END = 99, }; +enum VoiceAssistantTimerEvent : uint32_t { + VOICE_ASSISTANT_TIMER_STARTED = 0, + VOICE_ASSISTANT_TIMER_UPDATED = 1, + VOICE_ASSISTANT_TIMER_CANCELLED = 2, + VOICE_ASSISTANT_TIMER_FINISHED = 3, +}; enum AlarmControlPanelState : uint32_t { ALARM_STATE_DISARMED = 0, ALARM_STATE_ARMED_HOME = 1, @@ -1775,6 +1781,23 @@ class VoiceAssistantAudio : public ProtoMessage { bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; bool decode_varint(uint32_t field_id, ProtoVarInt value) override; }; +class VoiceAssistantTimerEventResponse : public ProtoMessage { + public: + enums::VoiceAssistantTimerEvent event_type{}; + std::string timer_id{}; + std::string name{}; + uint32_t total_seconds{0}; + uint32_t seconds_left{0}; + bool is_active{false}; + void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP + void dump_to(std::string &out) const override; +#endif + + protected: + bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; + bool decode_varint(uint32_t field_id, ProtoVarInt value) override; +}; class ListEntitiesAlarmControlPanelResponse : public ProtoMessage { public: std::string object_id{}; diff --git a/esphome/components/api/api_pb2_service.cpp b/esphome/components/api/api_pb2_service.cpp index 093fe917e0..7c95bb03ad 100644 --- a/esphome/components/api/api_pb2_service.cpp +++ b/esphome/components/api/api_pb2_service.cpp @@ -484,6 +484,8 @@ bool APIServerConnectionBase::send_voice_assistant_audio(const VoiceAssistantAud return this->send_message_(msg, 106); } #endif +#ifdef USE_VOICE_ASSISTANT +#endif #ifdef USE_ALARM_CONTROL_PANEL bool APIServerConnectionBase::send_list_entities_alarm_control_panel_response( const ListEntitiesAlarmControlPanelResponse &msg) { @@ -1093,6 +1095,17 @@ bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, ESP_LOGVV(TAG, "on_date_time_command_request: %s", msg.dump().c_str()); #endif this->on_date_time_command_request(msg); +#endif + break; + } + case 115: { +#ifdef USE_VOICE_ASSISTANT + VoiceAssistantTimerEventResponse msg; + msg.decode(msg_data, msg_size); +#ifdef HAS_PROTO_MESSAGE_DUMP + ESP_LOGVV(TAG, "on_voice_assistant_timer_event_response: %s", msg.dump().c_str()); +#endif + this->on_voice_assistant_timer_event_response(msg); #endif break; } diff --git a/esphome/components/api/api_pb2_service.h b/esphome/components/api/api_pb2_service.h index 196d904aca..2f8a2b3def 100644 --- a/esphome/components/api/api_pb2_service.h +++ b/esphome/components/api/api_pb2_service.h @@ -244,6 +244,9 @@ class APIServerConnectionBase : public ProtoService { bool send_voice_assistant_audio(const VoiceAssistantAudio &msg); virtual void on_voice_assistant_audio(const VoiceAssistantAudio &value){}; #endif +#ifdef USE_VOICE_ASSISTANT + virtual void on_voice_assistant_timer_event_response(const VoiceAssistantTimerEventResponse &value){}; +#endif #ifdef USE_ALARM_CONTROL_PANEL bool send_list_entities_alarm_control_panel_response(const ListEntitiesAlarmControlPanelResponse &msg); #endif diff --git a/esphome/components/voice_assistant/__init__.py b/esphome/components/voice_assistant/__init__.py index 3ba0c58ce4..c18f0a6850 100644 --- a/esphome/components/voice_assistant/__init__.py +++ b/esphome/components/voice_assistant/__init__.py @@ -44,6 +44,12 @@ CONF_VOLUME_MULTIPLIER = "volume_multiplier" CONF_WAKE_WORD = "wake_word" +CONF_ON_TIMER_STARTED = "on_timer_started" +CONF_ON_TIMER_UPDATED = "on_timer_updated" +CONF_ON_TIMER_CANCELLED = "on_timer_cancelled" +CONF_ON_TIMER_FINISHED = "on_timer_finished" +CONF_ON_TIMER_TICK = "on_timer_tick" + voice_assistant_ns = cg.esphome_ns.namespace("voice_assistant") VoiceAssistant = voice_assistant_ns.class_("VoiceAssistant", cg.Component) @@ -64,6 +70,8 @@ ConnectedCondition = voice_assistant_ns.class_( "ConnectedCondition", automation.Condition, cg.Parented.template(VoiceAssistant) ) +Timer = voice_assistant_ns.struct("Timer") + def tts_stream_validate(config): if CONF_SPEAKER not in config and ( @@ -131,6 +139,21 @@ CONFIG_SCHEMA = cv.All( single=True ), cv.Optional(CONF_ON_IDLE): automation.validate_automation(single=True), + cv.Optional(CONF_ON_TIMER_STARTED): automation.validate_automation( + single=True + ), + cv.Optional(CONF_ON_TIMER_UPDATED): automation.validate_automation( + single=True + ), + cv.Optional(CONF_ON_TIMER_CANCELLED): automation.validate_automation( + single=True + ), + cv.Optional(CONF_ON_TIMER_FINISHED): automation.validate_automation( + single=True + ), + cv.Optional(CONF_ON_TIMER_TICK): automation.validate_automation( + single=True + ), } ).extend(cv.COMPONENT_SCHEMA), tts_stream_validate, @@ -270,6 +293,49 @@ async def to_code(config): config[CONF_ON_IDLE], ) + has_timers = False + if on_timer_started := config.get(CONF_ON_TIMER_STARTED): + await automation.build_automation( + var.get_timer_started_trigger(), + [(Timer, "timer")], + on_timer_started, + ) + has_timers = True + + if on_timer_updated := config.get(CONF_ON_TIMER_UPDATED): + await automation.build_automation( + var.get_timer_updated_trigger(), + [(Timer, "timer")], + on_timer_updated, + ) + has_timers = True + + if on_timer_cancelled := config.get(CONF_ON_TIMER_CANCELLED): + await automation.build_automation( + var.get_timer_cancelled_trigger(), + [(Timer, "timer")], + on_timer_cancelled, + ) + has_timers = True + + if on_timer_finished := config.get(CONF_ON_TIMER_FINISHED): + await automation.build_automation( + var.get_timer_finished_trigger(), + [(Timer, "timer")], + on_timer_finished, + ) + has_timers = True + + if on_timer_tick := config.get(CONF_ON_TIMER_TICK): + await automation.build_automation( + var.get_timer_tick_trigger(), + [(cg.std_vector.template(Timer), "timers")], + on_timer_tick, + ) + has_timers = True + + cg.add(var.set_has_timers(has_timers)) + cg.add_define("USE_VOICE_ASSISTANT") diff --git a/esphome/components/voice_assistant/voice_assistant.cpp b/esphome/components/voice_assistant/voice_assistant.cpp index 712a0ab137..0bd8194190 100644 --- a/esphome/components/voice_assistant/voice_assistant.cpp +++ b/esphome/components/voice_assistant/voice_assistant.cpp @@ -798,12 +798,65 @@ void VoiceAssistant::on_audio(const api::VoiceAssistantAudio &msg) { this->speaker_buffer_index_ += msg.data.length(); this->speaker_buffer_size_ += msg.data.length(); this->speaker_bytes_received_ += msg.data.length(); + ESP_LOGD(TAG, "Received audio: %d bytes from API", msg.data.length()); } else { ESP_LOGE(TAG, "Cannot receive audio, buffer is full"); } #endif } +void VoiceAssistant::on_timer_event(const api::VoiceAssistantTimerEventResponse &msg) { + Timer timer = { + .id = msg.timer_id, + .name = msg.name, + .total_seconds = msg.total_seconds, + .seconds_left = msg.seconds_left, + .is_active = msg.is_active, + }; + this->timers_[timer.id] = timer; + ESP_LOGD(TAG, "Timer Event"); + ESP_LOGD(TAG, " Type: %d", msg.event_type); + ESP_LOGD(TAG, " %s", timer.to_string().c_str()); + + switch (msg.event_type) { + case api::enums::VOICE_ASSISTANT_TIMER_STARTED: + this->timer_started_trigger_->trigger(timer); + break; + case api::enums::VOICE_ASSISTANT_TIMER_UPDATED: + this->timer_updated_trigger_->trigger(timer); + break; + case api::enums::VOICE_ASSISTANT_TIMER_CANCELLED: + this->timer_cancelled_trigger_->trigger(timer); + this->timers_.erase(timer.id); + break; + case api::enums::VOICE_ASSISTANT_TIMER_FINISHED: + this->timer_finished_trigger_->trigger(timer); + this->timers_.erase(timer.id); + break; + } + + if (this->timers_.empty()) { + this->cancel_interval("timer-event"); + this->timer_tick_running_ = false; + } else if (!this->timer_tick_running_) { + this->set_interval("timer-event", 1000, [this]() { this->timer_tick_(); }); + this->timer_tick_running_ = true; + } +} + +void VoiceAssistant::timer_tick_() { + std::vector res; + res.reserve(this->timers_.size()); + for (auto &pair : this->timers_) { + auto &timer = pair.second; + if (timer.is_active && timer.seconds_left > 0) { + timer.seconds_left--; + } + res.push_back(timer); + } + this->timer_tick_trigger_->trigger(res); +} + VoiceAssistant *global_voice_assistant = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) } // namespace voice_assistant diff --git a/esphome/components/voice_assistant/voice_assistant.h b/esphome/components/voice_assistant/voice_assistant.h index 17141365d4..a160972e22 100644 --- a/esphome/components/voice_assistant/voice_assistant.h +++ b/esphome/components/voice_assistant/voice_assistant.h @@ -24,6 +24,9 @@ #include #endif +#include +#include + namespace esphome { namespace voice_assistant { @@ -36,6 +39,7 @@ enum VoiceAssistantFeature : uint32_t { FEATURE_VOICE_ASSISTANT = 1 << 0, FEATURE_SPEAKER = 1 << 1, FEATURE_API_AUDIO = 1 << 2, + FEATURE_TIMERS = 1 << 3, }; enum class State { @@ -59,6 +63,20 @@ enum AudioMode : uint8_t { AUDIO_MODE_API, }; +struct Timer { + std::string id; + std::string name; + uint32_t total_seconds; + uint32_t seconds_left; + bool is_active; + + std::string to_string() const { + return str_sprintf("Timer(id=%s, name=%s, total_seconds=%" PRIu32 ", seconds_left=%" PRIu32 ", is_active=%s)", + this->id.c_str(), this->name.c_str(), this->total_seconds, this->seconds_left, + YESNO(this->is_active)); + } +}; + class VoiceAssistant : public Component { public: void setup() override; @@ -100,6 +118,11 @@ class VoiceAssistant : public Component { flags |= VoiceAssistantFeature::FEATURE_SPEAKER; } #endif + + if (this->has_timers_) { + flags |= VoiceAssistantFeature::FEATURE_TIMERS; + } + return flags; } @@ -108,6 +131,7 @@ class VoiceAssistant : public Component { void on_event(const api::VoiceAssistantEventResponse &msg); void on_audio(const api::VoiceAssistantAudio &msg); + void on_timer_event(const api::VoiceAssistantTimerEventResponse &msg); bool is_running() const { return this->state_ != State::IDLE; } void set_continuous(bool continuous) { this->continuous_ = continuous; } @@ -150,6 +174,14 @@ class VoiceAssistant : public Component { void set_wake_word(const std::string &wake_word) { this->wake_word_ = wake_word; } + Trigger *get_timer_started_trigger() const { return this->timer_started_trigger_; } + Trigger *get_timer_updated_trigger() const { return this->timer_updated_trigger_; } + Trigger *get_timer_cancelled_trigger() const { return this->timer_cancelled_trigger_; } + Trigger *get_timer_finished_trigger() const { return this->timer_finished_trigger_; } + Trigger> *get_timer_tick_trigger() const { return this->timer_tick_trigger_; } + void set_has_timers(bool has_timers) { this->has_timers_ = has_timers; } + const std::unordered_map &get_timers() const { return this->timers_; } + protected: bool allocate_buffers_(); void clear_buffers_(); @@ -186,6 +218,16 @@ class VoiceAssistant : public Component { api::APIConnection *api_client_{nullptr}; + std::unordered_map timers_; + void timer_tick_(); + Trigger *timer_started_trigger_ = new Trigger(); + Trigger *timer_finished_trigger_ = new Trigger(); + Trigger *timer_updated_trigger_ = new Trigger(); + Trigger *timer_cancelled_trigger_ = new Trigger(); + Trigger> *timer_tick_trigger_ = new Trigger>(); + bool has_timers_{false}; + bool timer_tick_running_{false}; + microphone::Microphone *mic_{nullptr}; #ifdef USE_SPEAKER void write_speaker_(); From a7fc1a62981cf6ae51c2f44d71185c3d89071927 Mon Sep 17 00:00:00 2001 From: RFDarter Date: Thu, 30 May 2024 03:54:20 +0200 Subject: [PATCH 20/46] [web_server] add entity sorting for v3 (#6445) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- .../alarm_control_panel/__init__.py | 7 + esphome/components/binary_sensor/__init__.py | 127 ++++++----- esphome/components/button/__init__.py | 31 ++- esphome/components/climate/__init__.py | 185 ++++++++-------- esphome/components/cover/__init__.py | 67 +++--- esphome/components/datetime/__init__.py | 30 ++- esphome/components/fan/__init__.py | 137 ++++++------ esphome/components/light/__init__.py | 59 ++--- esphome/components/lock/__init__.py | 39 ++-- esphome/components/number/__init__.py | 50 +++-- esphome/components/select/__init__.py | 31 ++- esphome/components/sensor/__init__.py | 87 ++++---- esphome/components/switch/__init__.py | 43 ++-- esphome/components/text/__init__.py | 33 +-- esphome/components/text_sensor/__init__.py | 51 +++-- esphome/components/valve/__init__.py | 55 +++-- esphome/components/web_server/__init__.py | 58 +++++ esphome/components/web_server/web_server.cpp | 204 ++++++++++++------ esphome/components/web_server/web_server.h | 9 + esphome/const.py | 2 + 20 files changed, 802 insertions(+), 503 deletions(-) diff --git a/esphome/components/alarm_control_panel/__init__.py b/esphome/components/alarm_control_panel/__init__.py index 35d239c267..7ad4358011 100644 --- a/esphome/components/alarm_control_panel/__init__.py +++ b/esphome/components/alarm_control_panel/__init__.py @@ -1,5 +1,6 @@ import esphome.codegen as cg import esphome.config_validation as cv +from esphome.components import web_server from esphome import automation from esphome.automation import maybe_simple_id from esphome.core import CORE, coroutine_with_priority @@ -8,6 +9,7 @@ from esphome.const import ( CONF_ON_STATE, CONF_TRIGGER_ID, CONF_CODE, + CONF_WEB_SERVER_ID, ) from esphome.cpp_helpers import setup_entity @@ -76,6 +78,8 @@ AlarmControlPanelCondition = alarm_control_panel_ns.class_( ) ALARM_CONTROL_PANEL_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend( + web_server.WEBSERVER_SORTING_SCHEMA +).extend( { cv.GenerateID(): cv.declare_id(AlarmControlPanel), cv.Optional(CONF_ON_STATE): automation.validate_automation( @@ -185,6 +189,9 @@ async def setup_alarm_control_panel_core_(var, config): for conf in config.get(CONF_ON_READY, []): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) await automation.build_automation(trigger, [], conf) + if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None: + web_server_ = await cg.get_variable(webserver_id) + web_server.add_entity_to_sorting_list(web_server_, var, config) async def register_alarm_control_panel(var, config): diff --git a/esphome/components/binary_sensor/__init__.py b/esphome/components/binary_sensor/__init__.py index 2f788d7103..11a1887206 100644 --- a/esphome/components/binary_sensor/__init__.py +++ b/esphome/components/binary_sensor/__init__.py @@ -4,7 +4,7 @@ from esphome.cpp_generator import MockObjClass from esphome.cpp_helpers import setup_entity from esphome import automation, core from esphome.automation import Condition, maybe_simple_id -from esphome.components import mqtt +from esphome.components import mqtt, web_server from esphome.const import ( CONF_DELAY, CONF_DEVICE_CLASS, @@ -27,6 +27,7 @@ from esphome.const import ( CONF_TIMING, CONF_TRIGGER_ID, CONF_MQTT_ID, + CONF_WEB_SERVER_ID, DEVICE_CLASS_BATTERY, DEVICE_CLASS_BATTERY_CHARGING, DEVICE_CLASS_CARBON_MONOXIDE, @@ -385,70 +386,76 @@ def validate_click_timing(value): return value -BINARY_SENSOR_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend( - { - cv.GenerateID(): cv.declare_id(BinarySensor), - cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id( - mqtt.MQTTBinarySensorComponent - ), - cv.Optional(CONF_PUBLISH_INITIAL_STATE): cv.boolean, - cv.Optional(CONF_DEVICE_CLASS): validate_device_class, - cv.Optional(CONF_FILTERS): validate_filters, - cv.Optional(CONF_ON_PRESS): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(PressTrigger), - } - ), - cv.Optional(CONF_ON_RELEASE): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ReleaseTrigger), - } - ), - cv.Optional(CONF_ON_CLICK): cv.All( - automation.validate_automation( +BINARY_SENSOR_SCHEMA = ( + cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA) + .extend(cv.MQTT_COMPONENT_SCHEMA) + .extend( + { + cv.GenerateID(): cv.declare_id(BinarySensor), + cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id( + mqtt.MQTTBinarySensorComponent + ), + cv.Optional(CONF_PUBLISH_INITIAL_STATE): cv.boolean, + cv.Optional(CONF_DEVICE_CLASS): validate_device_class, + cv.Optional(CONF_FILTERS): validate_filters, + cv.Optional(CONF_ON_PRESS): automation.validate_automation( { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ClickTrigger), - cv.Optional( - CONF_MIN_LENGTH, default="50ms" - ): cv.positive_time_period_milliseconds, - cv.Optional( - CONF_MAX_LENGTH, default="350ms" - ): cv.positive_time_period_milliseconds, + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(PressTrigger), } ), - validate_click_timing, - ), - cv.Optional(CONF_ON_DOUBLE_CLICK): cv.All( - automation.validate_automation( + cv.Optional(CONF_ON_RELEASE): automation.validate_automation( { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(DoubleClickTrigger), - cv.Optional( - CONF_MIN_LENGTH, default="50ms" - ): cv.positive_time_period_milliseconds, - cv.Optional( - CONF_MAX_LENGTH, default="350ms" - ): cv.positive_time_period_milliseconds, + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ReleaseTrigger), } ), - validate_click_timing, - ), - cv.Optional(CONF_ON_MULTI_CLICK): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(MultiClickTrigger), - cv.Required(CONF_TIMING): cv.All( - [parse_multi_click_timing_str], validate_multi_click_timing + cv.Optional(CONF_ON_CLICK): cv.All( + automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ClickTrigger), + cv.Optional( + CONF_MIN_LENGTH, default="50ms" + ): cv.positive_time_period_milliseconds, + cv.Optional( + CONF_MAX_LENGTH, default="350ms" + ): cv.positive_time_period_milliseconds, + } ), - cv.Optional( - CONF_INVALID_COOLDOWN, default="1s" - ): cv.positive_time_period_milliseconds, - } - ), - cv.Optional(CONF_ON_STATE): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(StateTrigger), - } - ), - } + validate_click_timing, + ), + cv.Optional(CONF_ON_DOUBLE_CLICK): cv.All( + automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( + DoubleClickTrigger + ), + cv.Optional( + CONF_MIN_LENGTH, default="50ms" + ): cv.positive_time_period_milliseconds, + cv.Optional( + CONF_MAX_LENGTH, default="350ms" + ): cv.positive_time_period_milliseconds, + } + ), + validate_click_timing, + ), + cv.Optional(CONF_ON_MULTI_CLICK): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(MultiClickTrigger), + cv.Required(CONF_TIMING): cv.All( + [parse_multi_click_timing_str], validate_multi_click_timing + ), + cv.Optional( + CONF_INVALID_COOLDOWN, default="1s" + ): cv.positive_time_period_milliseconds, + } + ), + cv.Optional(CONF_ON_STATE): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(StateTrigger), + } + ), + } + ) ) _UNDEF = object() @@ -536,6 +543,10 @@ async def setup_binary_sensor_core_(var, config): mqtt_ = cg.new_Pvariable(mqtt_id, var) await mqtt.register_mqtt_component(mqtt_, config) + if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None: + web_server_ = await cg.get_variable(webserver_id) + web_server.add_entity_to_sorting_list(web_server_, var, config) + async def register_binary_sensor(var, config): if not CORE.has_id(config[CONF_ID]): diff --git a/esphome/components/button/__init__.py b/esphome/components/button/__init__.py index 5dcbf7ad01..773ab9d37f 100644 --- a/esphome/components/button/__init__.py +++ b/esphome/components/button/__init__.py @@ -2,7 +2,7 @@ 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.components import mqtt, web_server from esphome.const import ( CONF_DEVICE_CLASS, CONF_ENTITY_CATEGORY, @@ -11,6 +11,7 @@ from esphome.const import ( CONF_ON_PRESS, CONF_TRIGGER_ID, CONF_MQTT_ID, + CONF_WEB_SERVER_ID, DEVICE_CLASS_EMPTY, DEVICE_CLASS_IDENTIFY, DEVICE_CLASS_RESTART, @@ -43,16 +44,20 @@ ButtonPressTrigger = button_ns.class_( 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), - } - ), - } +BUTTON_SCHEMA = ( + cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_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() @@ -92,6 +97,10 @@ async def setup_button_core_(var, config): mqtt_ = cg.new_Pvariable(mqtt_id, var) await mqtt.register_mqtt_component(mqtt_, config) + if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None: + web_server_ = await cg.get_variable(webserver_id) + web_server.add_entity_to_sorting_list(web_server_, var, config) + async def register_button(var, config): if not CORE.has_id(config[CONF_ID]): diff --git a/esphome/components/climate/__init__.py b/esphome/components/climate/__init__.py index 7b0a27feae..ccd7a3da4e 100644 --- a/esphome/components/climate/__init__.py +++ b/esphome/components/climate/__init__.py @@ -2,7 +2,7 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.cpp_helpers import setup_entity from esphome import automation -from esphome.components import mqtt +from esphome.components import mqtt, web_server from esphome.const import ( CONF_ACTION_STATE_TOPIC, CONF_AWAY, @@ -44,6 +44,7 @@ from esphome.const import ( CONF_TRIGGER_ID, CONF_VISUAL, CONF_MQTT_ID, + CONF_WEB_SERVER_ID, ) from esphome.core import CORE, coroutine_with_priority @@ -150,93 +151,97 @@ VISUAL_TEMPERATURE_STEP_SCHEMA = cv.Any( ), ) -CLIMATE_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend( - { - cv.GenerateID(): cv.declare_id(Climate), - cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTClimateComponent), - cv.Optional(CONF_VISUAL, default={}): cv.Schema( - { - cv.Optional(CONF_MIN_TEMPERATURE): cv.temperature, - cv.Optional(CONF_MAX_TEMPERATURE): cv.temperature, - cv.Optional(CONF_TEMPERATURE_STEP): VISUAL_TEMPERATURE_STEP_SCHEMA, - cv.Optional(CONF_MIN_HUMIDITY): cv.percentage_int, - cv.Optional(CONF_MAX_HUMIDITY): cv.percentage_int, - } - ), - cv.Optional(CONF_ACTION_STATE_TOPIC): cv.All( - cv.requires_component("mqtt"), cv.publish_topic - ), - cv.Optional(CONF_AWAY_COMMAND_TOPIC): cv.All( - cv.requires_component("mqtt"), cv.publish_topic - ), - cv.Optional(CONF_AWAY_STATE_TOPIC): cv.All( - cv.requires_component("mqtt"), cv.publish_topic - ), - cv.Optional(CONF_CURRENT_TEMPERATURE_STATE_TOPIC): cv.All( - cv.requires_component("mqtt"), cv.publish_topic - ), - cv.Optional(CONF_CURRENT_HUMIDITY_STATE_TOPIC): cv.All( - cv.requires_component("mqtt"), cv.publish_topic - ), - cv.Optional(CONF_FAN_MODE_COMMAND_TOPIC): cv.All( - cv.requires_component("mqtt"), cv.publish_topic - ), - cv.Optional(CONF_FAN_MODE_STATE_TOPIC): cv.All( - cv.requires_component("mqtt"), cv.publish_topic - ), - cv.Optional(CONF_MODE_COMMAND_TOPIC): cv.All( - cv.requires_component("mqtt"), cv.publish_topic - ), - cv.Optional(CONF_MODE_STATE_TOPIC): cv.All( - cv.requires_component("mqtt"), cv.publish_topic - ), - cv.Optional(CONF_PRESET_COMMAND_TOPIC): cv.All( - cv.requires_component("mqtt"), cv.publish_topic - ), - cv.Optional(CONF_PRESET_STATE_TOPIC): cv.All( - cv.requires_component("mqtt"), cv.publish_topic - ), - cv.Optional(CONF_SWING_MODE_COMMAND_TOPIC): cv.All( - cv.requires_component("mqtt"), cv.publish_topic - ), - cv.Optional(CONF_SWING_MODE_STATE_TOPIC): cv.All( - cv.requires_component("mqtt"), cv.publish_topic - ), - cv.Optional(CONF_TARGET_TEMPERATURE_COMMAND_TOPIC): cv.All( - cv.requires_component("mqtt"), cv.publish_topic - ), - cv.Optional(CONF_TARGET_TEMPERATURE_STATE_TOPIC): cv.All( - cv.requires_component("mqtt"), cv.publish_topic - ), - cv.Optional(CONF_TARGET_TEMPERATURE_HIGH_COMMAND_TOPIC): cv.All( - cv.requires_component("mqtt"), cv.publish_topic - ), - cv.Optional(CONF_TARGET_TEMPERATURE_HIGH_STATE_TOPIC): cv.All( - cv.requires_component("mqtt"), cv.publish_topic - ), - cv.Optional(CONF_TARGET_TEMPERATURE_LOW_COMMAND_TOPIC): cv.All( - cv.requires_component("mqtt"), cv.publish_topic - ), - cv.Optional(CONF_TARGET_TEMPERATURE_LOW_STATE_TOPIC): cv.All( - cv.requires_component("mqtt"), cv.publish_topic - ), - cv.Optional(CONF_TARGET_HUMIDITY_COMMAND_TOPIC): cv.All( - cv.requires_component("mqtt"), cv.publish_topic - ), - cv.Optional(CONF_TARGET_HUMIDITY_STATE_TOPIC): cv.All( - cv.requires_component("mqtt"), cv.publish_topic - ), - cv.Optional(CONF_ON_CONTROL): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ControlTrigger), - } - ), - cv.Optional(CONF_ON_STATE): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(StateTrigger), - } - ), - } +CLIMATE_SCHEMA = ( + cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA) + .extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA) + .extend( + { + cv.GenerateID(): cv.declare_id(Climate), + cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTClimateComponent), + cv.Optional(CONF_VISUAL, default={}): cv.Schema( + { + cv.Optional(CONF_MIN_TEMPERATURE): cv.temperature, + cv.Optional(CONF_MAX_TEMPERATURE): cv.temperature, + cv.Optional(CONF_TEMPERATURE_STEP): VISUAL_TEMPERATURE_STEP_SCHEMA, + cv.Optional(CONF_MIN_HUMIDITY): cv.percentage_int, + cv.Optional(CONF_MAX_HUMIDITY): cv.percentage_int, + } + ), + cv.Optional(CONF_ACTION_STATE_TOPIC): cv.All( + cv.requires_component("mqtt"), cv.publish_topic + ), + cv.Optional(CONF_AWAY_COMMAND_TOPIC): cv.All( + cv.requires_component("mqtt"), cv.publish_topic + ), + cv.Optional(CONF_AWAY_STATE_TOPIC): cv.All( + cv.requires_component("mqtt"), cv.publish_topic + ), + cv.Optional(CONF_CURRENT_TEMPERATURE_STATE_TOPIC): cv.All( + cv.requires_component("mqtt"), cv.publish_topic + ), + cv.Optional(CONF_CURRENT_HUMIDITY_STATE_TOPIC): cv.All( + cv.requires_component("mqtt"), cv.publish_topic + ), + cv.Optional(CONF_FAN_MODE_COMMAND_TOPIC): cv.All( + cv.requires_component("mqtt"), cv.publish_topic + ), + cv.Optional(CONF_FAN_MODE_STATE_TOPIC): cv.All( + cv.requires_component("mqtt"), cv.publish_topic + ), + cv.Optional(CONF_MODE_COMMAND_TOPIC): cv.All( + cv.requires_component("mqtt"), cv.publish_topic + ), + cv.Optional(CONF_MODE_STATE_TOPIC): cv.All( + cv.requires_component("mqtt"), cv.publish_topic + ), + cv.Optional(CONF_PRESET_COMMAND_TOPIC): cv.All( + cv.requires_component("mqtt"), cv.publish_topic + ), + cv.Optional(CONF_PRESET_STATE_TOPIC): cv.All( + cv.requires_component("mqtt"), cv.publish_topic + ), + cv.Optional(CONF_SWING_MODE_COMMAND_TOPIC): cv.All( + cv.requires_component("mqtt"), cv.publish_topic + ), + cv.Optional(CONF_SWING_MODE_STATE_TOPIC): cv.All( + cv.requires_component("mqtt"), cv.publish_topic + ), + cv.Optional(CONF_TARGET_TEMPERATURE_COMMAND_TOPIC): cv.All( + cv.requires_component("mqtt"), cv.publish_topic + ), + cv.Optional(CONF_TARGET_TEMPERATURE_STATE_TOPIC): cv.All( + cv.requires_component("mqtt"), cv.publish_topic + ), + cv.Optional(CONF_TARGET_TEMPERATURE_HIGH_COMMAND_TOPIC): cv.All( + cv.requires_component("mqtt"), cv.publish_topic + ), + cv.Optional(CONF_TARGET_TEMPERATURE_HIGH_STATE_TOPIC): cv.All( + cv.requires_component("mqtt"), cv.publish_topic + ), + cv.Optional(CONF_TARGET_TEMPERATURE_LOW_COMMAND_TOPIC): cv.All( + cv.requires_component("mqtt"), cv.publish_topic + ), + cv.Optional(CONF_TARGET_TEMPERATURE_LOW_STATE_TOPIC): cv.All( + cv.requires_component("mqtt"), cv.publish_topic + ), + cv.Optional(CONF_TARGET_HUMIDITY_COMMAND_TOPIC): cv.All( + cv.requires_component("mqtt"), cv.publish_topic + ), + cv.Optional(CONF_TARGET_HUMIDITY_STATE_TOPIC): cv.All( + cv.requires_component("mqtt"), cv.publish_topic + ), + cv.Optional(CONF_ON_CONTROL): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ControlTrigger), + } + ), + cv.Optional(CONF_ON_STATE): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(StateTrigger), + } + ), + } + ) ) @@ -403,6 +408,10 @@ async def setup_climate_core_(var, config): trigger, [(ClimateCall.operator("ref"), "x")], conf ) + if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None: + web_server_ = await cg.get_variable(webserver_id) + web_server.add_entity_to_sorting_list(web_server_, var, config) + async def register_climate(var, config): if not CORE.has_id(config[CONF_ID]): diff --git a/esphome/components/cover/__init__.py b/esphome/components/cover/__init__.py index 8e0371017d..313b2c5928 100644 --- a/esphome/components/cover/__init__.py +++ b/esphome/components/cover/__init__.py @@ -2,7 +2,7 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome import automation from esphome.automation import maybe_simple_id, Condition -from esphome.components import mqtt +from esphome.components import mqtt, web_server from esphome.const import ( CONF_ID, CONF_DEVICE_CLASS, @@ -16,6 +16,7 @@ from esphome.const import ( CONF_TILT_STATE_TOPIC, CONF_STOP, CONF_MQTT_ID, + CONF_WEB_SERVER_ID, CONF_TRIGGER_ID, DEVICE_CLASS_AWNING, DEVICE_CLASS_BLIND, @@ -88,34 +89,38 @@ CoverClosedTrigger = cover_ns.class_( CONF_ON_CLOSED = "on_closed" -COVER_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend( - { - cv.GenerateID(): cv.declare_id(Cover), - cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTCoverComponent), - cv.Optional(CONF_DEVICE_CLASS): cv.one_of(*DEVICE_CLASSES, lower=True), - cv.Optional(CONF_POSITION_COMMAND_TOPIC): cv.All( - cv.requires_component("mqtt"), cv.subscribe_topic - ), - cv.Optional(CONF_POSITION_STATE_TOPIC): cv.All( - cv.requires_component("mqtt"), cv.subscribe_topic - ), - cv.Optional(CONF_TILT_COMMAND_TOPIC): cv.All( - cv.requires_component("mqtt"), cv.subscribe_topic - ), - cv.Optional(CONF_TILT_STATE_TOPIC): cv.All( - cv.requires_component("mqtt"), cv.subscribe_topic - ), - cv.Optional(CONF_ON_OPEN): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(CoverOpenTrigger), - } - ), - cv.Optional(CONF_ON_CLOSED): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(CoverClosedTrigger), - } - ), - } +COVER_SCHEMA = ( + cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA) + .extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA) + .extend( + { + cv.GenerateID(): cv.declare_id(Cover), + cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTCoverComponent), + cv.Optional(CONF_DEVICE_CLASS): cv.one_of(*DEVICE_CLASSES, lower=True), + cv.Optional(CONF_POSITION_COMMAND_TOPIC): cv.All( + cv.requires_component("mqtt"), cv.subscribe_topic + ), + cv.Optional(CONF_POSITION_STATE_TOPIC): cv.All( + cv.requires_component("mqtt"), cv.subscribe_topic + ), + cv.Optional(CONF_TILT_COMMAND_TOPIC): cv.All( + cv.requires_component("mqtt"), cv.subscribe_topic + ), + cv.Optional(CONF_TILT_STATE_TOPIC): cv.All( + cv.requires_component("mqtt"), cv.subscribe_topic + ), + cv.Optional(CONF_ON_OPEN): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(CoverOpenTrigger), + } + ), + cv.Optional(CONF_ON_CLOSED): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(CoverClosedTrigger), + } + ), + } + ) ) @@ -132,6 +137,10 @@ async def setup_cover_core_(var, config): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) await automation.build_automation(trigger, [], conf) + if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None: + web_server_ = await cg.get_variable(webserver_id) + web_server.add_entity_to_sorting_list(web_server_, var, config) + if (mqtt_id := config.get(CONF_MQTT_ID)) is not None: mqtt_ = cg.new_Pvariable(mqtt_id, var) await mqtt.register_mqtt_component(mqtt_, config) diff --git a/esphome/components/datetime/__init__.py b/esphome/components/datetime/__init__.py index 262f1e2315..c118216a2d 100644 --- a/esphome/components/datetime/__init__.py +++ b/esphome/components/datetime/__init__.py @@ -2,7 +2,7 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome import automation -from esphome.components import mqtt, time +from esphome.components import mqtt, web_server, time from esphome.const import ( CONF_ID, CONF_ON_TIME, @@ -11,6 +11,7 @@ from esphome.const import ( CONF_TRIGGER_ID, CONF_TYPE, CONF_MQTT_ID, + CONF_WEB_SERVER_ID, CONF_DATE, CONF_DATETIME, CONF_TIME, @@ -63,16 +64,20 @@ DATETIME_MODES = [ ] -_DATETIME_SCHEMA = cv.Schema( - { - cv.Optional(CONF_ON_VALUE): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(DateTimeStateTrigger), - } - ), - cv.GenerateID(CONF_TIME_ID): cv.use_id(time.RealTimeClock), - } -).extend(cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA)) +_DATETIME_SCHEMA = ( + cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA) + .extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA) + .extend( + { + cv.Optional(CONF_ON_VALUE): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(DateTimeStateTrigger), + } + ), + cv.GenerateID(CONF_TIME_ID): cv.use_id(time.RealTimeClock), + } + ) +) def date_schema(class_: MockObjClass) -> cv.Schema: @@ -128,6 +133,9 @@ async def setup_datetime_core_(var, config): if (mqtt_id := config.get(CONF_MQTT_ID)) is not None: mqtt_ = cg.new_Pvariable(mqtt_id, var) await mqtt.register_mqtt_component(mqtt_, config) + if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None: + web_server_ = await cg.get_variable(webserver_id) + web_server.add_entity_to_sorting_list(web_server_, var, config) for conf in config.get(CONF_ON_VALUE, []): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) await automation.build_automation(trigger, [(cg.ESPTime, "x")], conf) diff --git a/esphome/components/fan/__init__.py b/esphome/components/fan/__init__.py index 14cf6cc9c9..847a59baa1 100644 --- a/esphome/components/fan/__init__.py +++ b/esphome/components/fan/__init__.py @@ -2,10 +2,11 @@ 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.components import mqtt, web_server from esphome.const import ( CONF_ID, CONF_MQTT_ID, + CONF_WEB_SERVER_ID, CONF_OSCILLATING, CONF_OSCILLATION_COMMAND_TOPIC, CONF_OSCILLATION_STATE_TOPIC, @@ -79,67 +80,75 @@ FanPresetSetTrigger = fan_ns.class_( FanIsOnCondition = fan_ns.class_("FanIsOnCondition", automation.Condition.template()) FanIsOffCondition = fan_ns.class_("FanIsOffCondition", automation.Condition.template()) -FAN_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend( - { - cv.GenerateID(): cv.declare_id(Fan), - cv.Optional(CONF_RESTORE_MODE, default="ALWAYS_OFF"): cv.enum( - RESTORE_MODES, upper=True, space="_" - ), - cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTFanComponent), - cv.Optional(CONF_OSCILLATION_STATE_TOPIC): cv.All( - cv.requires_component("mqtt"), cv.publish_topic - ), - cv.Optional(CONF_OSCILLATION_COMMAND_TOPIC): cv.All( - cv.requires_component("mqtt"), cv.subscribe_topic - ), - cv.Optional(CONF_SPEED_LEVEL_STATE_TOPIC): cv.All( - cv.requires_component("mqtt"), cv.publish_topic - ), - cv.Optional(CONF_SPEED_LEVEL_COMMAND_TOPIC): cv.All( - cv.requires_component("mqtt"), cv.subscribe_topic - ), - cv.Optional(CONF_SPEED_STATE_TOPIC): cv.All( - cv.requires_component("mqtt"), cv.publish_topic - ), - cv.Optional(CONF_SPEED_COMMAND_TOPIC): cv.All( - cv.requires_component("mqtt"), cv.subscribe_topic - ), - cv.Optional(CONF_ON_STATE): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(FanStateTrigger), - } - ), - cv.Optional(CONF_ON_TURN_ON): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(FanTurnOnTrigger), - } - ), - cv.Optional(CONF_ON_TURN_OFF): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(FanTurnOffTrigger), - } - ), - cv.Optional(CONF_ON_DIRECTION_SET): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(FanDirectionSetTrigger), - } - ), - cv.Optional(CONF_ON_OSCILLATING_SET): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(FanOscillatingSetTrigger), - } - ), - cv.Optional(CONF_ON_SPEED_SET): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(FanSpeedSetTrigger), - } - ), - cv.Optional(CONF_ON_PRESET_SET): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(FanPresetSetTrigger), - } - ), - } +FAN_SCHEMA = ( + cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA) + .extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA) + .extend( + { + cv.GenerateID(): cv.declare_id(Fan), + cv.Optional(CONF_RESTORE_MODE, default="ALWAYS_OFF"): cv.enum( + RESTORE_MODES, upper=True, space="_" + ), + cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTFanComponent), + cv.Optional(CONF_OSCILLATION_STATE_TOPIC): cv.All( + cv.requires_component("mqtt"), cv.publish_topic + ), + cv.Optional(CONF_OSCILLATION_COMMAND_TOPIC): cv.All( + cv.requires_component("mqtt"), cv.subscribe_topic + ), + cv.Optional(CONF_SPEED_LEVEL_STATE_TOPIC): cv.All( + cv.requires_component("mqtt"), cv.publish_topic + ), + cv.Optional(CONF_SPEED_LEVEL_COMMAND_TOPIC): cv.All( + cv.requires_component("mqtt"), cv.subscribe_topic + ), + cv.Optional(CONF_SPEED_STATE_TOPIC): cv.All( + cv.requires_component("mqtt"), cv.publish_topic + ), + cv.Optional(CONF_SPEED_COMMAND_TOPIC): cv.All( + cv.requires_component("mqtt"), cv.subscribe_topic + ), + cv.Optional(CONF_ON_STATE): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(FanStateTrigger), + } + ), + cv.Optional(CONF_ON_TURN_ON): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(FanTurnOnTrigger), + } + ), + cv.Optional(CONF_ON_TURN_OFF): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(FanTurnOffTrigger), + } + ), + cv.Optional(CONF_ON_DIRECTION_SET): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( + FanDirectionSetTrigger + ), + } + ), + cv.Optional(CONF_ON_OSCILLATING_SET): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( + FanOscillatingSetTrigger + ), + } + ), + cv.Optional(CONF_ON_SPEED_SET): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(FanSpeedSetTrigger), + } + ), + cv.Optional(CONF_ON_PRESET_SET): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(FanPresetSetTrigger), + } + ), + } + ) ) _PRESET_MODES_SCHEMA = cv.All( @@ -209,6 +218,10 @@ async def setup_fan_core_(var, config): if (speed_command_topic := config.get(CONF_SPEED_COMMAND_TOPIC)) is not None: cg.add(mqtt_.set_custom_speed_command_topic(speed_command_topic)) + if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None: + web_server_ = await cg.get_variable(webserver_id) + web_server.add_entity_to_sorting_list(web_server_, var, config) + for conf in config.get(CONF_ON_STATE, []): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) await automation.build_automation(trigger, [(Fan.operator("ptr"), "x")], conf) diff --git a/esphome/components/light/__init__.py b/esphome/components/light/__init__.py index fdc4676758..161b4d8cd9 100644 --- a/esphome/components/light/__init__.py +++ b/esphome/components/light/__init__.py @@ -1,7 +1,7 @@ import esphome.codegen as cg import esphome.config_validation as cv import esphome.automation as auto -from esphome.components import mqtt, power_supply +from esphome.components import mqtt, power_supply, web_server from esphome.const import ( CONF_COLOR_CORRECT, CONF_DEFAULT_TRANSITION_LENGTH, @@ -10,6 +10,7 @@ from esphome.const import ( CONF_GAMMA_CORRECT, CONF_ID, CONF_MQTT_ID, + CONF_WEB_SERVER_ID, CONF_POWER_SUPPLY, CONF_RESTORE_MODE, CONF_ON_TURN_OFF, @@ -56,29 +57,35 @@ RESTORE_MODES = { "RESTORE_AND_ON": LightRestoreMode.LIGHT_RESTORE_AND_ON, } -LIGHT_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend( - { - cv.GenerateID(): cv.declare_id(LightState), - cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTJSONLightComponent), - cv.Optional(CONF_RESTORE_MODE, default="ALWAYS_OFF"): cv.enum( - RESTORE_MODES, upper=True, space="_" - ), - cv.Optional(CONF_ON_TURN_ON): auto.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(LightTurnOnTrigger), - } - ), - cv.Optional(CONF_ON_TURN_OFF): auto.validate_automation( - { - 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), - } - ), - } +LIGHT_SCHEMA = ( + cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA) + .extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA) + .extend( + { + cv.GenerateID(): cv.declare_id(LightState), + cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id( + mqtt.MQTTJSONLightComponent + ), + cv.Optional(CONF_RESTORE_MODE, default="ALWAYS_OFF"): cv.enum( + RESTORE_MODES, upper=True, space="_" + ), + cv.Optional(CONF_ON_TURN_ON): auto.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(LightTurnOnTrigger), + } + ), + cv.Optional(CONF_ON_TURN_OFF): auto.validate_automation( + { + 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), + } + ), + } + ) ) BINARY_LIGHT_SCHEMA = LIGHT_SCHEMA.extend( @@ -173,6 +180,10 @@ async def setup_light_core_(light_var, output_var, config): mqtt_ = cg.new_Pvariable(mqtt_id, light_var) await mqtt.register_mqtt_component(mqtt_, config) + if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None: + web_server_ = await cg.get_variable(webserver_id) + web_server.add_entity_to_sorting_list(web_server_, light_var, config) + async def register_light(output_var, config): light_var = cg.new_Pvariable(config[CONF_ID], output_var) diff --git a/esphome/components/lock/__init__.py b/esphome/components/lock/__init__.py index 457ffa278a..c2d6054ed9 100644 --- a/esphome/components/lock/__init__.py +++ b/esphome/components/lock/__init__.py @@ -2,13 +2,14 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome import automation from esphome.automation import Condition, maybe_simple_id -from esphome.components import mqtt +from esphome.components import mqtt, web_server from esphome.const import ( CONF_ID, CONF_ON_LOCK, CONF_ON_UNLOCK, CONF_TRIGGER_ID, CONF_MQTT_ID, + CONF_WEB_SERVER_ID, ) from esphome.core import CORE, coroutine_with_priority from esphome.cpp_helpers import setup_entity @@ -30,20 +31,24 @@ LockCondition = lock_ns.class_("LockCondition", Condition) LockLockTrigger = lock_ns.class_("LockLockTrigger", automation.Trigger.template()) LockUnlockTrigger = lock_ns.class_("LockUnlockTrigger", automation.Trigger.template()) -LOCK_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend( - { - cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTLockComponent), - cv.Optional(CONF_ON_LOCK): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(LockLockTrigger), - } - ), - cv.Optional(CONF_ON_UNLOCK): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(LockUnlockTrigger), - } - ), - } +LOCK_SCHEMA = ( + cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA) + .extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA) + .extend( + { + cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTLockComponent), + cv.Optional(CONF_ON_LOCK): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(LockLockTrigger), + } + ), + cv.Optional(CONF_ON_UNLOCK): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(LockUnlockTrigger), + } + ), + } + ) ) @@ -61,6 +66,10 @@ async def setup_lock_core_(var, config): mqtt_ = cg.new_Pvariable(mqtt_id, var) await mqtt.register_mqtt_component(mqtt_, config) + if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None: + web_server_ = await cg.get_variable(webserver_id) + web_server.add_entity_to_sorting_list(web_server_, var, config) + async def register_lock(var, config): if not CORE.has_id(config[CONF_ID]): diff --git a/esphome/components/number/__init__.py b/esphome/components/number/__init__.py index 8c3b15d22d..303535c138 100644 --- a/esphome/components/number/__init__.py +++ b/esphome/components/number/__init__.py @@ -2,6 +2,7 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome import automation from esphome.components import mqtt +from esphome.components import web_server from esphome.const import ( CONF_ABOVE, CONF_BELOW, @@ -18,6 +19,7 @@ from esphome.const import ( CONF_VALUE, CONF_OPERATION, CONF_CYCLE, + CONF_WEB_SERVER_ID, DEVICE_CLASS_APPARENT_POWER, DEVICE_CLASS_AQI, DEVICE_CLASS_ATMOSPHERIC_PRESSURE, @@ -167,26 +169,30 @@ NUMBER_OPERATION_OPTIONS = { validate_device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space="_") validate_unit_of_measurement = cv.string_strict -NUMBER_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend( - { - cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTNumberComponent), - cv.Optional(CONF_ON_VALUE): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(NumberStateTrigger), - } - ), - cv.Optional(CONF_ON_VALUE_RANGE): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ValueRangeTrigger), - cv.Optional(CONF_ABOVE): cv.templatable(cv.float_), - cv.Optional(CONF_BELOW): cv.templatable(cv.float_), - }, - cv.has_at_least_one_key(CONF_ABOVE, CONF_BELOW), - ), - cv.Optional(CONF_UNIT_OF_MEASUREMENT): validate_unit_of_measurement, - cv.Optional(CONF_MODE, default="AUTO"): cv.enum(NUMBER_MODES, upper=True), - cv.Optional(CONF_DEVICE_CLASS): validate_device_class, - } +NUMBER_SCHEMA = ( + cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA) + .extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA) + .extend( + { + cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTNumberComponent), + cv.Optional(CONF_ON_VALUE): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(NumberStateTrigger), + } + ), + cv.Optional(CONF_ON_VALUE_RANGE): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ValueRangeTrigger), + cv.Optional(CONF_ABOVE): cv.templatable(cv.float_), + cv.Optional(CONF_BELOW): cv.templatable(cv.float_), + }, + cv.has_at_least_one_key(CONF_ABOVE, CONF_BELOW), + ), + cv.Optional(CONF_UNIT_OF_MEASUREMENT): validate_unit_of_measurement, + cv.Optional(CONF_MODE, default="AUTO"): cv.enum(NUMBER_MODES, upper=True), + cv.Optional(CONF_DEVICE_CLASS): validate_device_class, + } + ) ) _UNDEF = object() @@ -248,6 +254,10 @@ async def setup_number_core_( mqtt_ = cg.new_Pvariable(mqtt_id, var) await mqtt.register_mqtt_component(mqtt_, config) + if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None: + web_server_ = await cg.get_variable(webserver_id) + web_server.add_entity_to_sorting_list(web_server_, var, config) + async def register_number( var, config, *, min_value: float, max_value: float, step: float diff --git a/esphome/components/select/__init__.py b/esphome/components/select/__init__.py index 1d234d5617..073fbef1d4 100644 --- a/esphome/components/select/__init__.py +++ b/esphome/components/select/__init__.py @@ -1,7 +1,7 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome import automation -from esphome.components import mqtt +from esphome.components import mqtt, web_server from esphome.const import ( CONF_ENTITY_CATEGORY, CONF_ICON, @@ -10,6 +10,7 @@ from esphome.const import ( CONF_OPTION, CONF_TRIGGER_ID, CONF_MQTT_ID, + CONF_WEB_SERVER_ID, CONF_CYCLE, CONF_MODE, CONF_OPERATION, @@ -47,16 +48,20 @@ SELECT_OPERATION_OPTIONS = { } -SELECT_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend( - { - cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTSelectComponent), - cv.GenerateID(): cv.declare_id(Select), - cv.Optional(CONF_ON_VALUE): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(SelectStateTrigger), - } - ), - } +SELECT_SCHEMA = ( + cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA) + .extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA) + .extend( + { + cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTSelectComponent), + cv.GenerateID(): cv.declare_id(Select), + cv.Optional(CONF_ON_VALUE): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(SelectStateTrigger), + } + ), + } + ) ) _UNDEF = object() @@ -99,6 +104,10 @@ async def setup_select_core_(var, config, *, options: list[str]): mqtt_ = cg.new_Pvariable(mqtt_id, var) await mqtt.register_mqtt_component(mqtt_, config) + if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None: + web_server_ = await cg.get_variable(webserver_id) + web_server.add_entity_to_sorting_list(web_server_, var, config) + async def register_select(var, config, *, options: list[str]): if not CORE.has_id(config[CONF_ID]): diff --git a/esphome/components/sensor/__init__.py b/esphome/components/sensor/__init__.py index ef5c4cc645..6077f5dc1f 100644 --- a/esphome/components/sensor/__init__.py +++ b/esphome/components/sensor/__init__.py @@ -3,7 +3,7 @@ import math import esphome.codegen as cg import esphome.config_validation as cv from esphome import automation -from esphome.components import mqtt +from esphome.components import mqtt, web_server from esphome.const import ( CONF_DEVICE_CLASS, CONF_ABOVE, @@ -31,6 +31,7 @@ from esphome.const import ( CONF_UNIT_OF_MEASUREMENT, CONF_WINDOW_SIZE, CONF_MQTT_ID, + CONF_WEB_SERVER_ID, CONF_FORCE_UPDATE, CONF_VALUE, CONF_MIN_VALUE, @@ -252,43 +253,49 @@ validate_accuracy_decimals = cv.int_ validate_icon = cv.icon validate_device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space="_") -SENSOR_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend( - { - cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTSensorComponent), - cv.GenerateID(): cv.declare_id(Sensor), - cv.Optional(CONF_UNIT_OF_MEASUREMENT): validate_unit_of_measurement, - cv.Optional(CONF_ACCURACY_DECIMALS): validate_accuracy_decimals, - cv.Optional(CONF_DEVICE_CLASS): validate_device_class, - cv.Optional(CONF_STATE_CLASS): validate_state_class, - cv.Optional(CONF_ENTITY_CATEGORY): sensor_entity_category, - cv.Optional("last_reset_type"): cv.invalid( - "last_reset_type has been removed since 2021.9.0. state_class: total_increasing should be used for total values." - ), - cv.Optional(CONF_FORCE_UPDATE, default=False): cv.boolean, - cv.Optional(CONF_EXPIRE_AFTER): cv.All( - cv.requires_component("mqtt"), - cv.Any(None, cv.positive_time_period_milliseconds), - ), - cv.Optional(CONF_FILTERS): validate_filters, - cv.Optional(CONF_ON_VALUE): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(SensorStateTrigger), - } - ), - cv.Optional(CONF_ON_RAW_VALUE): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(SensorRawStateTrigger), - } - ), - cv.Optional(CONF_ON_VALUE_RANGE): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ValueRangeTrigger), - cv.Optional(CONF_ABOVE): cv.templatable(cv.float_), - cv.Optional(CONF_BELOW): cv.templatable(cv.float_), - }, - cv.has_at_least_one_key(CONF_ABOVE, CONF_BELOW), - ), - } +SENSOR_SCHEMA = ( + cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA) + .extend(cv.MQTT_COMPONENT_SCHEMA) + .extend( + { + cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTSensorComponent), + cv.GenerateID(): cv.declare_id(Sensor), + cv.Optional(CONF_UNIT_OF_MEASUREMENT): validate_unit_of_measurement, + cv.Optional(CONF_ACCURACY_DECIMALS): validate_accuracy_decimals, + cv.Optional(CONF_DEVICE_CLASS): validate_device_class, + cv.Optional(CONF_STATE_CLASS): validate_state_class, + cv.Optional(CONF_ENTITY_CATEGORY): sensor_entity_category, + cv.Optional("last_reset_type"): cv.invalid( + "last_reset_type has been removed since 2021.9.0. state_class: total_increasing should be used for total values." + ), + cv.Optional(CONF_FORCE_UPDATE, default=False): cv.boolean, + cv.Optional(CONF_EXPIRE_AFTER): cv.All( + cv.requires_component("mqtt"), + cv.Any(None, cv.positive_time_period_milliseconds), + ), + cv.Optional(CONF_FILTERS): validate_filters, + cv.Optional(CONF_ON_VALUE): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(SensorStateTrigger), + } + ), + cv.Optional(CONF_ON_RAW_VALUE): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( + SensorRawStateTrigger + ), + } + ), + cv.Optional(CONF_ON_VALUE_RANGE): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ValueRangeTrigger), + cv.Optional(CONF_ABOVE): cv.templatable(cv.float_), + cv.Optional(CONF_BELOW): cv.templatable(cv.float_), + }, + cv.has_at_least_one_key(CONF_ABOVE, CONF_BELOW), + ), + } + ) ) _UNDEF = object() @@ -772,6 +779,10 @@ async def setup_sensor_core_(var, config): else: cg.add(mqtt_.set_expire_after(expire_after)) + if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None: + web_server_ = await cg.get_variable(webserver_id) + web_server.add_entity_to_sorting_list(web_server_, var, config) + async def register_sensor(var, config): if not CORE.has_id(config[CONF_ID]): diff --git a/esphome/components/switch/__init__.py b/esphome/components/switch/__init__.py index e997ec7ca5..3539d0e34e 100644 --- a/esphome/components/switch/__init__.py +++ b/esphome/components/switch/__init__.py @@ -2,7 +2,7 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome import automation from esphome.automation import Condition, maybe_simple_id -from esphome.components import mqtt +from esphome.components import mqtt, web_server from esphome.const import ( CONF_DEVICE_CLASS, CONF_ENTITY_CATEGORY, @@ -10,6 +10,7 @@ from esphome.const import ( CONF_ID, CONF_INVERTED, CONF_MQTT_ID, + CONF_WEB_SERVER_ID, CONF_ON_TURN_OFF, CONF_ON_TURN_ON, CONF_RESTORE_MODE, @@ -64,22 +65,26 @@ SwitchTurnOffTrigger = switch_ns.class_( validate_device_class = cv.one_of(*DEVICE_CLASSES, lower=True) -_SWITCH_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend( - { - cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTSwitchComponent), - cv.Optional(CONF_INVERTED): cv.boolean, - cv.Optional(CONF_ON_TURN_ON): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(SwitchTurnOnTrigger), - } - ), - cv.Optional(CONF_ON_TURN_OFF): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(SwitchTurnOffTrigger), - } - ), - cv.Optional(CONF_DEVICE_CLASS): validate_device_class, - } +_SWITCH_SCHEMA = ( + cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA) + .extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA) + .extend( + { + cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTSwitchComponent), + cv.Optional(CONF_INVERTED): cv.boolean, + cv.Optional(CONF_ON_TURN_ON): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(SwitchTurnOnTrigger), + } + ), + cv.Optional(CONF_ON_TURN_OFF): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(SwitchTurnOffTrigger), + } + ), + cv.Optional(CONF_DEVICE_CLASS): validate_device_class, + } + ) ) _UNDEF = object() @@ -151,6 +156,10 @@ async def setup_switch_core_(var, config): mqtt_ = cg.new_Pvariable(mqtt_id, var) await mqtt.register_mqtt_component(mqtt_, config) + if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None: + web_server_ = await cg.get_variable(webserver_id) + web_server.add_entity_to_sorting_list(web_server_, var, config) + if (device_class := config.get(CONF_DEVICE_CLASS)) is not None: cg.add(var.set_device_class(device_class)) diff --git a/esphome/components/text/__init__.py b/esphome/components/text/__init__.py index 4ca055a730..5a8e763495 100644 --- a/esphome/components/text/__init__.py +++ b/esphome/components/text/__init__.py @@ -2,13 +2,14 @@ from typing import Optional import esphome.codegen as cg import esphome.config_validation as cv from esphome import automation -from esphome.components import mqtt +from esphome.components import mqtt, web_server from esphome.const import ( CONF_ID, CONF_MODE, CONF_ON_VALUE, CONF_TRIGGER_ID, CONF_MQTT_ID, + CONF_WEB_SERVER_ID, CONF_VALUE, ) @@ -38,17 +39,21 @@ TEXT_MODES = { "PASSWORD": TextMode.TEXT_MODE_PASSWORD, # to be implemented for keys, passwords, etc. } -TEXT_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend( - { - cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTTextComponent), - cv.GenerateID(): cv.declare_id(Text), - cv.Optional(CONF_ON_VALUE): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(TextStateTrigger), - } - ), - cv.Required(CONF_MODE): cv.enum(TEXT_MODES, upper=True), - } +TEXT_SCHEMA = ( + cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA) + .extend(cv.MQTT_COMPONENT_SCHEMA) + .extend( + { + cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTTextComponent), + cv.GenerateID(): cv.declare_id(Text), + cv.Optional(CONF_ON_VALUE): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(TextStateTrigger), + } + ), + cv.Required(CONF_MODE): cv.enum(TEXT_MODES, upper=True), + } + ) ) @@ -77,6 +82,10 @@ async def setup_text_core_( mqtt_ = cg.new_Pvariable(mqtt_id, var) await mqtt.register_mqtt_component(mqtt_, config) + if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None: + web_server_ = await cg.get_variable(webserver_id) + web_server.add_entity_to_sorting_list(web_server_, var, config) + async def register_text( var, diff --git a/esphome/components/text_sensor/__init__.py b/esphome/components/text_sensor/__init__.py index 6c28b57b3d..f4e795924c 100644 --- a/esphome/components/text_sensor/__init__.py +++ b/esphome/components/text_sensor/__init__.py @@ -1,7 +1,7 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome import automation -from esphome.components import mqtt +from esphome.components import mqtt, web_server from esphome.const import ( CONF_DEVICE_CLASS, CONF_ENTITY_CATEGORY, @@ -12,6 +12,7 @@ from esphome.const import ( CONF_ON_RAW_VALUE, CONF_TRIGGER_ID, CONF_MQTT_ID, + CONF_WEB_SERVER_ID, CONF_STATE, CONF_FROM, CONF_TO, @@ -124,25 +125,31 @@ async def map_filter_to_code(config, filter_id): validate_device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space="_") -TEXT_SENSOR_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend( - { - cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTTextSensor), - cv.GenerateID(): cv.declare_id(TextSensor), - cv.Optional(CONF_DEVICE_CLASS): validate_device_class, - cv.Optional(CONF_FILTERS): validate_filters, - cv.Optional(CONF_ON_VALUE): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(TextSensorStateTrigger), - } - ), - cv.Optional(CONF_ON_RAW_VALUE): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( - TextSensorStateRawTrigger - ), - } - ), - } +TEXT_SENSOR_SCHEMA = ( + cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA) + .extend(cv.MQTT_COMPONENT_SCHEMA) + .extend( + { + cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTTextSensor), + cv.GenerateID(): cv.declare_id(TextSensor), + cv.Optional(CONF_DEVICE_CLASS): validate_device_class, + cv.Optional(CONF_FILTERS): validate_filters, + cv.Optional(CONF_ON_VALUE): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( + TextSensorStateTrigger + ), + } + ), + cv.Optional(CONF_ON_RAW_VALUE): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( + TextSensorStateRawTrigger + ), + } + ), + } + ) ) _UNDEF = object() @@ -205,6 +212,10 @@ async def setup_text_sensor_core_(var, config): mqtt_ = cg.new_Pvariable(mqtt_id, var) await mqtt.register_mqtt_component(mqtt_, config) + if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None: + web_server_ = await cg.get_variable(webserver_id) + web_server.add_entity_to_sorting_list(web_server_, var, config) + async def register_text_sensor(var, config): if not CORE.has_id(config[CONF_ID]): diff --git a/esphome/components/valve/__init__.py b/esphome/components/valve/__init__.py index ea6bfc6055..c03d13fec8 100644 --- a/esphome/components/valve/__init__.py +++ b/esphome/components/valve/__init__.py @@ -2,7 +2,7 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome import automation from esphome.automation import maybe_simple_id, Condition -from esphome.components import mqtt +from esphome.components import mqtt, web_server from esphome.const import ( CONF_DEVICE_CLASS, CONF_ID, @@ -14,6 +14,7 @@ from esphome.const import ( CONF_STATE, CONF_STOP, CONF_TRIGGER_ID, + CONF_WEB_SERVER_ID, DEVICE_CLASS_EMPTY, DEVICE_CLASS_GAS, DEVICE_CLASS_WATER, @@ -70,28 +71,32 @@ ValveClosedTrigger = valve_ns.class_( CONF_ON_CLOSED = "on_closed" -VALVE_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend( - { - cv.GenerateID(): cv.declare_id(Valve), - cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTValveComponent), - cv.Optional(CONF_DEVICE_CLASS): cv.one_of(*DEVICE_CLASSES, lower=True), - cv.Optional(CONF_POSITION_COMMAND_TOPIC): cv.All( - cv.requires_component("mqtt"), cv.subscribe_topic - ), - cv.Optional(CONF_POSITION_STATE_TOPIC): cv.All( - cv.requires_component("mqtt"), cv.subscribe_topic - ), - cv.Optional(CONF_ON_OPEN): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ValveOpenTrigger), - } - ), - cv.Optional(CONF_ON_CLOSED): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ValveClosedTrigger), - } - ), - } +VALVE_SCHEMA = ( + cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA) + .extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA) + .extend( + { + cv.GenerateID(): cv.declare_id(Valve), + cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTValveComponent), + cv.Optional(CONF_DEVICE_CLASS): cv.one_of(*DEVICE_CLASSES, lower=True), + cv.Optional(CONF_POSITION_COMMAND_TOPIC): cv.All( + cv.requires_component("mqtt"), cv.subscribe_topic + ), + cv.Optional(CONF_POSITION_STATE_TOPIC): cv.All( + cv.requires_component("mqtt"), cv.subscribe_topic + ), + cv.Optional(CONF_ON_OPEN): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ValveOpenTrigger), + } + ), + cv.Optional(CONF_ON_CLOSED): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ValveClosedTrigger), + } + ), + } + ) ) @@ -119,6 +124,10 @@ async def setup_valve_core_(var, config): mqtt_.set_custom_position_command_topic(position_command_topic_config) ) + if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None: + web_server_ = await cg.get_variable(webserver_id) + web_server.add_entity_to_sorting_list(web_server_, var, config) + async def register_valve(var, config): if not CORE.has_id(config[CONF_ID]): diff --git a/esphome/components/web_server/__init__.py b/esphome/components/web_server/__init__.py index fa614fb5a6..232ab40d10 100644 --- a/esphome/components/web_server/__init__.py +++ b/esphome/components/web_server/__init__.py @@ -1,6 +1,9 @@ +from __future__ import annotations + import gzip import esphome.codegen as cg import esphome.config_validation as cv +import esphome.final_validate as fv from esphome.components import web_server_base from esphome.components.web_server_base import CONF_WEB_SERVER_BASE_ID from esphome.const import ( @@ -19,6 +22,8 @@ from esphome.const import ( CONF_LOG, CONF_VERSION, CONF_LOCAL, + CONF_WEB_SERVER_ID, + CONF_WEB_SERVER_SORTING_WEIGHT, PLATFORM_ESP32, PLATFORM_ESP8266, PLATFORM_BK72XX, @@ -64,6 +69,46 @@ def validate_ota(config): return config +def _validate_no_sorting_weight( + webserver_version: int, config: dict, path: list[str] | None = None +) -> None: + if path is None: + path = [] + if CONF_WEB_SERVER_SORTING_WEIGHT in config: + raise cv.FinalExternalInvalid( + f"Sorting weight on entities is not supported in web_server version {webserver_version}", + path=path + [CONF_WEB_SERVER_SORTING_WEIGHT], + ) + for p, value in config.items(): + if isinstance(value, dict): + _validate_no_sorting_weight(webserver_version, value, path + [p]) + elif isinstance(value, list): + for i, item in enumerate(value): + if isinstance(item, dict): + _validate_no_sorting_weight(webserver_version, item, path + [p, i]) + + +def _final_validate_sorting_weight(config): + if (webserver_version := config.get(CONF_VERSION)) != 3: + _validate_no_sorting_weight(webserver_version, fv.full_config.get()) + + return config + + +FINAL_VALIDATE_SCHEMA = _final_validate_sorting_weight + + +WEBSERVER_SORTING_SCHEMA = cv.Schema( + { + cv.OnlyWith(CONF_WEB_SERVER_ID, "web_server"): cv.use_id(WebServer), + cv.Optional(CONF_WEB_SERVER_SORTING_WEIGHT): cv.All( + cv.requires_component("web_server"), + cv.float_, + ), + } +) + + CONFIG_SCHEMA = cv.All( cv.Schema( { @@ -108,6 +153,19 @@ CONFIG_SCHEMA = cv.All( ) +def add_entity_to_sorting_list(web_server, entity, config): + sorting_weight = 50 + if CONF_WEB_SERVER_SORTING_WEIGHT in config: + sorting_weight = config[CONF_WEB_SERVER_SORTING_WEIGHT] + + cg.add( + web_server.add_entity_to_sorting_list( + entity, + sorting_weight, + ) + ) + + def build_index_html(config) -> str: html = "" css_include = config.get(CONF_CSS_INCLUDE) diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index d72307991f..5bb36f9600 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -435,7 +435,7 @@ void WebServer::handle_sensor_request(AsyncWebServerRequest *request, const UrlM request->send(404); } std::string WebServer::sensor_json(sensor::Sensor *obj, float value, JsonDetail start_config) { - return json::build_json([obj, value, start_config](JsonObject root) { + return json::build_json([this, obj, value, start_config](JsonObject root) { std::string state; if (std::isnan(value)) { state = "NA"; @@ -446,6 +446,9 @@ std::string WebServer::sensor_json(sensor::Sensor *obj, float value, JsonDetail } set_json_icon_state_value(root, obj, "sensor-" + obj->get_object_id(), state, value, start_config); if (start_config == DETAIL_ALL) { + if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) { + root["sorting_weight"] = this->sorting_entitys_[obj].weight; + } if (!obj->get_unit_of_measurement().empty()) root["uom"] = obj->get_unit_of_measurement(); } @@ -471,8 +474,13 @@ void WebServer::handle_text_sensor_request(AsyncWebServerRequest *request, const } std::string WebServer::text_sensor_json(text_sensor::TextSensor *obj, const std::string &value, JsonDetail start_config) { - return json::build_json([obj, value, start_config](JsonObject root) { + return json::build_json([this, obj, value, start_config](JsonObject root) { set_json_icon_state_value(root, obj, "text_sensor-" + obj->get_object_id(), value, value, start_config); + if (start_config == DETAIL_ALL) { + if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) { + root["sorting_weight"] = this->sorting_entitys_[obj].weight; + } + } }); } #endif @@ -483,14 +491,6 @@ void WebServer::on_switch_update(switch_::Switch *obj, bool state) { return; this->events_.send(this->switch_json(obj, state, DETAIL_STATE).c_str(), "state"); } -std::string WebServer::switch_json(switch_::Switch *obj, bool value, JsonDetail start_config) { - return json::build_json([obj, value, start_config](JsonObject root) { - set_json_icon_state_value(root, obj, "switch-" + obj->get_object_id(), value ? "ON" : "OFF", value, start_config); - if (start_config == DETAIL_ALL) { - root["assumed_state"] = obj->assumed_state(); - } - }); -} void WebServer::handle_switch_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (switch_::Switch *obj : App.get_switches()) { if (obj->get_object_id() != match.id) @@ -515,14 +515,20 @@ void WebServer::handle_switch_request(AsyncWebServerRequest *request, const UrlM } request->send(404); } +std::string WebServer::switch_json(switch_::Switch *obj, bool value, JsonDetail start_config) { + return json::build_json([this, obj, value, start_config](JsonObject root) { + set_json_icon_state_value(root, obj, "switch-" + obj->get_object_id(), value ? "ON" : "OFF", value, start_config); + if (start_config == DETAIL_ALL) { + root["assumed_state"] = obj->assumed_state(); + if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) { + root["sorting_weight"] = this->sorting_entitys_[obj].weight; + } + } + }); +} #endif #ifdef USE_BUTTON -std::string WebServer::button_json(button::Button *obj, JsonDetail start_config) { - return json::build_json( - [obj, start_config](JsonObject root) { set_json_id(root, obj, "button-" + obj->get_object_id(), start_config); }); -} - void WebServer::handle_button_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (button::Button *obj : App.get_buttons()) { if (obj->get_object_id() != match.id) @@ -538,6 +544,16 @@ void WebServer::handle_button_request(AsyncWebServerRequest *request, const UrlM } request->send(404); } +std::string WebServer::button_json(button::Button *obj, JsonDetail start_config) { + return json::build_json([this, obj, start_config](JsonObject root) { + set_json_id(root, obj, "button-" + obj->get_object_id(), start_config); + if (start_config == DETAIL_ALL) { + if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) { + root["sorting_weight"] = this->sorting_entitys_[obj].weight; + } + } + }); +} #endif #ifdef USE_BINARY_SENSOR @@ -546,12 +562,6 @@ void WebServer::on_binary_sensor_update(binary_sensor::BinarySensor *obj, bool s return; this->events_.send(this->binary_sensor_json(obj, state, DETAIL_STATE).c_str(), "state"); } -std::string WebServer::binary_sensor_json(binary_sensor::BinarySensor *obj, bool value, JsonDetail start_config) { - return json::build_json([obj, value, start_config](JsonObject root) { - set_json_icon_state_value(root, obj, "binary_sensor-" + obj->get_object_id(), value ? "ON" : "OFF", value, - start_config); - }); -} void WebServer::handle_binary_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (binary_sensor::BinarySensor *obj : App.get_binary_sensors()) { if (obj->get_object_id() != match.id) @@ -562,6 +572,17 @@ void WebServer::handle_binary_sensor_request(AsyncWebServerRequest *request, con } request->send(404); } +std::string WebServer::binary_sensor_json(binary_sensor::BinarySensor *obj, bool value, JsonDetail start_config) { + return json::build_json([this, obj, value, start_config](JsonObject root) { + set_json_icon_state_value(root, obj, "binary_sensor-" + obj->get_object_id(), value ? "ON" : "OFF", value, + start_config); + if (start_config == DETAIL_ALL) { + if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) { + root["sorting_weight"] = this->sorting_entitys_[obj].weight; + } + } + }); +} #endif #ifdef USE_FAN @@ -570,19 +591,6 @@ void WebServer::on_fan_update(fan::Fan *obj) { return; this->events_.send(this->fan_json(obj, DETAIL_STATE).c_str(), "state"); } -std::string WebServer::fan_json(fan::Fan *obj, JsonDetail start_config) { - return json::build_json([obj, start_config](JsonObject root) { - set_json_icon_state_value(root, obj, "fan-" + obj->get_object_id(), obj->state ? "ON" : "OFF", obj->state, - start_config); - const auto traits = obj->get_traits(); - if (traits.supports_speed()) { - root["speed_level"] = obj->speed; - root["speed_count"] = traits.supported_speed_count(); - } - if (obj->get_traits().supports_oscillation()) - root["oscillation"] = obj->oscillating; - }); -} void WebServer::handle_fan_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (fan::Fan *obj : App.get_fans()) { if (obj->get_object_id() != match.id) @@ -635,6 +643,24 @@ void WebServer::handle_fan_request(AsyncWebServerRequest *request, const UrlMatc } request->send(404); } +std::string WebServer::fan_json(fan::Fan *obj, JsonDetail start_config) { + return json::build_json([this, obj, start_config](JsonObject root) { + set_json_icon_state_value(root, obj, "fan-" + obj->get_object_id(), obj->state ? "ON" : "OFF", obj->state, + start_config); + const auto traits = obj->get_traits(); + if (traits.supports_speed()) { + root["speed_level"] = obj->speed; + root["speed_count"] = traits.supported_speed_count(); + } + if (obj->get_traits().supports_oscillation()) + root["oscillation"] = obj->oscillating; + if (start_config == DETAIL_ALL) { + if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) { + root["sorting_weight"] = this->sorting_entitys_[obj].weight; + } + } + }); +} #endif #ifdef USE_LIGHT @@ -729,7 +755,7 @@ void WebServer::handle_light_request(AsyncWebServerRequest *request, const UrlMa request->send(404); } std::string WebServer::light_json(light::LightState *obj, JsonDetail start_config) { - return json::build_json([obj, start_config](JsonObject root) { + return json::build_json([this, obj, start_config](JsonObject root) { set_json_id(root, obj, "light-" + obj->get_object_id(), start_config); root["state"] = obj->remote_values.is_on() ? "ON" : "OFF"; @@ -740,6 +766,9 @@ std::string WebServer::light_json(light::LightState *obj, JsonDetail start_confi for (auto const &option : obj->get_effects()) { opt.add(option->get_name()); } + if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) { + root["sorting_weight"] = this->sorting_entitys_[obj].weight; + } } }); } @@ -803,7 +832,7 @@ void WebServer::handle_cover_request(AsyncWebServerRequest *request, const UrlMa request->send(404); } std::string WebServer::cover_json(cover::Cover *obj, JsonDetail start_config) { - return json::build_json([obj, start_config](JsonObject root) { + return json::build_json([this, obj, start_config](JsonObject root) { set_json_icon_state_value(root, obj, "cover-" + obj->get_object_id(), obj->is_fully_closed() ? "CLOSED" : "OPEN", obj->position, start_config); root["current_operation"] = cover::cover_operation_to_str(obj->current_operation); @@ -812,6 +841,11 @@ std::string WebServer::cover_json(cover::Cover *obj, JsonDetail start_config) { root["position"] = obj->position; if (obj->get_traits().get_supports_tilt()) root["tilt"] = obj->tilt; + if (start_config == DETAIL_ALL) { + if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) { + root["sorting_weight"] = this->sorting_entitys_[obj].weight; + } + } }); } #endif @@ -852,7 +886,7 @@ void WebServer::handle_number_request(AsyncWebServerRequest *request, const UrlM } std::string WebServer::number_json(number::Number *obj, float value, JsonDetail start_config) { - return json::build_json([obj, value, start_config](JsonObject root) { + return json::build_json([this, obj, value, start_config](JsonObject root) { set_json_id(root, obj, "number-" + obj->get_object_id(), start_config); if (start_config == DETAIL_ALL) { root["min_value"] = @@ -864,6 +898,9 @@ std::string WebServer::number_json(number::Number *obj, float value, JsonDetail root["mode"] = (int) obj->traits.get_mode(); if (!obj->traits.get_unit_of_measurement().empty()) root["uom"] = obj->traits.get_unit_of_measurement(); + if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) { + root["sorting_weight"] = this->sorting_entitys_[obj].weight; + } } if (std::isnan(value)) { root["value"] = "\"NaN\""; @@ -919,11 +956,16 @@ void WebServer::handle_date_request(AsyncWebServerRequest *request, const UrlMat } std::string WebServer::date_json(datetime::DateEntity *obj, JsonDetail start_config) { - return json::build_json([obj, start_config](JsonObject root) { + return json::build_json([this, obj, start_config](JsonObject root) { set_json_id(root, obj, "date-" + obj->get_object_id(), start_config); std::string value = str_sprintf("%d-%02d-%02d", obj->year, obj->month, obj->day); root["value"] = value; root["state"] = value; + if (start_config == DETAIL_ALL) { + if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) { + root["sorting_weight"] = this->sorting_entitys_[obj].weight; + } + } }); } #endif // USE_DATETIME_DATE @@ -967,11 +1009,16 @@ void WebServer::handle_time_request(AsyncWebServerRequest *request, const UrlMat request->send(404); } std::string WebServer::time_json(datetime::TimeEntity *obj, JsonDetail start_config) { - return json::build_json([obj, start_config](JsonObject root) { + return json::build_json([this, obj, start_config](JsonObject root) { set_json_id(root, obj, "time-" + obj->get_object_id(), start_config); std::string value = str_sprintf("%02d:%02d:%02d", obj->hour, obj->minute, obj->second); root["value"] = value; root["state"] = value; + if (start_config == DETAIL_ALL) { + if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) { + root["sorting_weight"] = this->sorting_entitys_[obj].weight; + } + } }); } #endif // USE_DATETIME_TIME @@ -1015,12 +1062,17 @@ void WebServer::handle_datetime_request(AsyncWebServerRequest *request, const Ur request->send(404); } std::string WebServer::datetime_json(datetime::DateTimeEntity *obj, JsonDetail start_config) { - return json::build_json([obj, start_config](JsonObject root) { + return json::build_json([this, obj, start_config](JsonObject root) { set_json_id(root, obj, "datetime-" + obj->get_object_id(), start_config); std::string value = str_sprintf("%d-%02d-%02d %02d:%02d:%02d", obj->year, obj->month, obj->day, obj->hour, obj->minute, obj->second); root["value"] = value; root["state"] = value; + if (start_config == DETAIL_ALL) { + if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) { + root["sorting_weight"] = this->sorting_entitys_[obj].weight; + } + } }); } #endif // USE_DATETIME_DATETIME @@ -1060,11 +1112,8 @@ void WebServer::handle_text_request(AsyncWebServerRequest *request, const UrlMat } std::string WebServer::text_json(text::Text *obj, const std::string &value, JsonDetail start_config) { - return json::build_json([obj, value, start_config](JsonObject root) { + return json::build_json([this, obj, value, start_config](JsonObject root) { set_json_id(root, obj, "text-" + obj->get_object_id(), start_config); - if (start_config == DETAIL_ALL) { - root["mode"] = (int) obj->traits.get_mode(); - } root["min_length"] = obj->traits.get_min_length(); root["max_length"] = obj->traits.get_max_length(); root["pattern"] = obj->traits.get_pattern(); @@ -1074,6 +1123,12 @@ std::string WebServer::text_json(text::Text *obj, const std::string &value, Json root["state"] = value; } root["value"] = value; + if (start_config == DETAIL_ALL) { + root["mode"] = (int) obj->traits.get_mode(); + if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) { + root["sorting_weight"] = this->sorting_entitys_[obj].weight; + } + } }); } #endif @@ -1119,13 +1174,16 @@ void WebServer::handle_select_request(AsyncWebServerRequest *request, const UrlM request->send(404); } std::string WebServer::select_json(select::Select *obj, const std::string &value, JsonDetail start_config) { - return json::build_json([obj, value, start_config](JsonObject root) { + return json::build_json([this, obj, value, start_config](JsonObject root) { set_json_icon_state_value(root, obj, "select-" + obj->get_object_id(), value, value, start_config); if (start_config == DETAIL_ALL) { JsonArray opt = root.createNestedArray("option"); for (auto &option : obj->traits.get_options()) { opt.add(option); } + if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) { + root["sorting_weight"] = this->sorting_entitys_[obj].weight; + } } }); } @@ -1140,7 +1198,6 @@ void WebServer::on_climate_update(climate::Climate *obj) { return; this->events_.send(this->climate_json(obj, DETAIL_STATE).c_str(), "state"); } - void WebServer::handle_climate_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (auto *obj : App.get_climates()) { if (obj->get_object_id() != match.id) @@ -1188,9 +1245,8 @@ void WebServer::handle_climate_request(AsyncWebServerRequest *request, const Url } request->send(404); } - std::string WebServer::climate_json(climate::Climate *obj, JsonDetail start_config) { - return json::build_json([obj, start_config](JsonObject root) { + return json::build_json([this, obj, start_config](JsonObject root) { set_json_id(root, obj, "climate-" + obj->get_object_id(), start_config); const auto traits = obj->get_traits(); int8_t target_accuracy = traits.get_target_temperature_accuracy_decimals(); @@ -1227,6 +1283,9 @@ std::string WebServer::climate_json(climate::Climate *obj, JsonDetail start_conf for (auto const &custom_preset : traits.get_supported_custom_presets()) opt.add(custom_preset); } + if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) { + root["sorting_weight"] = this->sorting_entitys_[obj].weight; + } } bool has_state = false; @@ -1283,12 +1342,6 @@ void WebServer::on_lock_update(lock::Lock *obj) { return; this->events_.send(this->lock_json(obj, obj->state, DETAIL_STATE).c_str(), "state"); } -std::string WebServer::lock_json(lock::Lock *obj, lock::LockState value, JsonDetail start_config) { - return json::build_json([obj, value, start_config](JsonObject root) { - set_json_icon_state_value(root, obj, "lock-" + obj->get_object_id(), lock::lock_state_to_string(value), value, - start_config); - }); -} void WebServer::handle_lock_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (lock::Lock *obj : App.get_locks()) { if (obj->get_object_id() != match.id) @@ -1313,6 +1366,17 @@ void WebServer::handle_lock_request(AsyncWebServerRequest *request, const UrlMat } request->send(404); } +std::string WebServer::lock_json(lock::Lock *obj, lock::LockState value, JsonDetail start_config) { + return json::build_json([this, obj, value, start_config](JsonObject root) { + set_json_icon_state_value(root, obj, "lock-" + obj->get_object_id(), lock::lock_state_to_string(value), value, + start_config); + if (start_config == DETAIL_ALL) { + if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) { + root["sorting_weight"] = this->sorting_entitys_[obj].weight; + } + } + }); +} #endif #ifdef USE_VALVE @@ -1366,13 +1430,16 @@ void WebServer::handle_valve_request(AsyncWebServerRequest *request, const UrlMa request->send(404); } std::string WebServer::valve_json(valve::Valve *obj, JsonDetail start_config) { - return json::build_json([obj, start_config](JsonObject root) { + return json::build_json([this, obj, start_config](JsonObject root) { set_json_icon_state_value(root, obj, "valve-" + obj->get_object_id(), obj->is_fully_closed() ? "CLOSED" : "OPEN", obj->position, start_config); root["current_operation"] = valve::valve_operation_to_str(obj->current_operation); if (obj->get_traits().get_supports_position()) root["position"] = obj->position; + if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) { + root["sorting_weight"] = this->sorting_entitys_[obj].weight; + } }); } #endif @@ -1383,15 +1450,6 @@ void WebServer::on_alarm_control_panel_update(alarm_control_panel::AlarmControlP return; this->events_.send(this->alarm_control_panel_json(obj, obj->get_state(), DETAIL_STATE).c_str(), "state"); } -std::string WebServer::alarm_control_panel_json(alarm_control_panel::AlarmControlPanel *obj, - alarm_control_panel::AlarmControlPanelState value, - JsonDetail start_config) { - return json::build_json([obj, value, start_config](JsonObject root) { - char buf[16]; - set_json_icon_state_value(root, obj, "alarm-control-panel-" + obj->get_object_id(), - PSTR_LOCAL(alarm_control_panel_state_to_string(value)), value, start_config); - }); -} void WebServer::handle_alarm_control_panel_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (alarm_control_panel::AlarmControlPanel *obj : App.get_alarm_control_panels()) { if (obj->get_object_id() != match.id) @@ -1405,6 +1463,20 @@ void WebServer::handle_alarm_control_panel_request(AsyncWebServerRequest *reques } request->send(404); } +std::string WebServer::alarm_control_panel_json(alarm_control_panel::AlarmControlPanel *obj, + alarm_control_panel::AlarmControlPanelState value, + JsonDetail start_config) { + return json::build_json([this, obj, value, start_config](JsonObject root) { + char buf[16]; + set_json_icon_state_value(root, obj, "alarm-control-panel-" + obj->get_object_id(), + PSTR_LOCAL(alarm_control_panel_state_to_string(value)), value, start_config); + if (start_config == DETAIL_ALL) { + if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) { + root["sorting_weight"] = this->sorting_entitys_[obj].weight; + } + } + }); +} #endif #ifdef USE_EVENT @@ -1709,6 +1781,10 @@ void WebServer::handleRequest(AsyncWebServerRequest *request) { bool WebServer::isRequestHandlerTrivial() { return false; } +void WebServer::add_entity_to_sorting_list(EntityBase *entity, float weight) { + this->sorting_entitys_[entity] = SortingComponents{weight}; +} + void WebServer::schedule_(std::function &&f) { #ifdef USE_ESP32 xSemaphoreTake(this->to_schedule_lock_, portMAX_DELAY); diff --git a/esphome/components/web_server/web_server.h b/esphome/components/web_server/web_server.h index dda14a7e05..0fb40e2c33 100644 --- a/esphome/components/web_server/web_server.h +++ b/esphome/components/web_server/web_server.h @@ -5,8 +5,10 @@ #include "esphome/components/web_server_base/web_server_base.h" #include "esphome/core/component.h" #include "esphome/core/controller.h" +#include "esphome/core/entity_base.h" #include +#include #ifdef USE_ESP32 #include #include @@ -39,6 +41,10 @@ struct UrlMatch { bool valid; ///< Whether this match is valid }; +struct SortingComponents { + float weight; +}; + enum JsonDetail { DETAIL_ALL, DETAIL_STATE }; /** This class allows users to create a web server with their ESP nodes. @@ -320,12 +326,15 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { /// This web handle is not trivial. bool isRequestHandlerTrivial() override; + void add_entity_to_sorting_list(EntityBase *entity, float weight); + protected: void schedule_(std::function &&f); friend ListEntitiesIterator; web_server_base::WebServerBase *base_; AsyncEventSource events_{"/events"}; ListEntitiesIterator entities_iterator_; + std::map sorting_entitys_; #if USE_WEBSERVER_VERSION == 1 const char *css_url_{nullptr}; const char *js_url_{nullptr}; diff --git a/esphome/const.py b/esphome/const.py index 616b686052..1aafffb5d0 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -904,6 +904,8 @@ CONF_WARM_WHITE = "warm_white" CONF_WARM_WHITE_COLOR_TEMPERATURE = "warm_white_color_temperature" CONF_WATCHDOG_THRESHOLD = "watchdog_threshold" CONF_WEB_SERVER = "web_server" +CONF_WEB_SERVER_ID = "web_server_id" +CONF_WEB_SERVER_SORTING_WEIGHT = "web_server_sorting_weight" CONF_WEIGHT = "weight" CONF_WHILE = "while" CONF_WHITE = "white" From c130ddbe9cabcfa88ade0d489414aecc82850548 Mon Sep 17 00:00:00 2001 From: Michael Hansen Date: Wed, 29 May 2024 20:58:13 -0500 Subject: [PATCH 21/46] [micro_wake_word] Ensure model string is Path (#6826) --- esphome/components/micro_wake_word/__init__.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/esphome/components/micro_wake_word/__init__.py b/esphome/components/micro_wake_word/__init__.py index 9073d103f1..def2808e54 100644 --- a/esphome/components/micro_wake_word/__init__.py +++ b/esphome/components/micro_wake_word/__init__.py @@ -329,11 +329,14 @@ async def to_code(config): file: Path = base_dir / h.hexdigest()[:8] / model_config[CONF_FILE] elif model_config[CONF_TYPE] == TYPE_LOCAL: - file = model_config[CONF_PATH] + file = Path(model_config[CONF_PATH]) elif model_config[CONF_TYPE] == TYPE_HTTP: file = _compute_local_file_path(model_config) / "manifest.json" + else: + raise ValueError("Unsupported config type: {model_config[CONF_TYPE]}") + manifest, data = _load_model_data(file) rhs = [HexInt(x) for x in data] From 9de8eaff24f63a0acc25fdc555babfc3b54c7b27 Mon Sep 17 00:00:00 2001 From: Erdem Date: Thu, 30 May 2024 08:31:09 +0300 Subject: [PATCH 22/46] Fix DHT reading timing for SI7021 on ESP32 (#6604) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/dht/dht.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/esphome/components/dht/dht.cpp b/esphome/components/dht/dht.cpp index 5112092073..db1c851d5f 100644 --- a/esphome/components/dht/dht.cpp +++ b/esphome/components/dht/dht.cpp @@ -86,9 +86,14 @@ bool HOT IRAM_ATTR DHT::read_sensor_(float *temperature, float *humidity, bool r if (this->model_ == DHT_MODEL_DHT11) { delayMicroseconds(18000); } else if (this->model_ == DHT_MODEL_SI7021) { +#ifdef USE_ESP8266 delayMicroseconds(500); this->pin_->digital_write(true); delayMicroseconds(40); +#else + delayMicroseconds(400); + this->pin_->digital_write(true); +#endif } else if (this->model_ == DHT_MODEL_DHT22_TYPE2) { delayMicroseconds(2000); } else if (this->model_ == DHT_MODEL_AM2120 || this->model_ == DHT_MODEL_AM2302) { From 63fc8ab10a52fc118c89218e40ccc919877a19f9 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 30 May 2024 19:59:15 +1200 Subject: [PATCH 23/46] [core] Const-ify some Component fields (#6831) --- esphome/core/component.cpp | 8 ++++---- esphome/core/component.h | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/esphome/core/component.cpp b/esphome/core/component.cpp index 594e8ff7df..ae73a451d9 100644 --- a/esphome/core/component.cpp +++ b/esphome/core/component.cpp @@ -1,11 +1,11 @@ #include "esphome/core/component.h" +#include +#include #include "esphome/core/application.h" #include "esphome/core/hal.h" #include "esphome/core/helpers.h" #include "esphome/core/log.h" -#include -#include namespace esphome { @@ -140,8 +140,8 @@ void Component::set_retry(uint32_t initial_wait_time, uint8_t max_attempts, std: float backoff_increase_factor) { // NOLINT App.scheduler.set_retry(this, "", initial_wait_time, max_attempts, std::move(f), backoff_increase_factor); } -bool Component::is_failed() { return (this->component_state_ & COMPONENT_STATE_MASK) == COMPONENT_STATE_FAILED; } -bool Component::is_ready() { +bool Component::is_failed() const { return (this->component_state_ & COMPONENT_STATE_MASK) == COMPONENT_STATE_FAILED; } +bool Component::is_ready() const { return (this->component_state_ & COMPONENT_STATE_MASK) == COMPONENT_STATE_LOOP || (this->component_state_ & COMPONENT_STATE_MASK) == COMPONENT_STATE_SETUP; } diff --git a/esphome/core/component.h b/esphome/core/component.h index e6ffe96d1e..a6bd8f81ac 100644 --- a/esphome/core/component.h +++ b/esphome/core/component.h @@ -118,9 +118,9 @@ class Component { */ virtual void mark_failed(); - bool is_failed(); + bool is_failed() const; - bool is_ready(); + bool is_ready() const; virtual bool can_proceed(); From 8aba890e6974b40fffa14271b63371d179949733 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 30 May 2024 20:00:09 +1200 Subject: [PATCH 24/46] [voice_assistant] Half the microphone ringbuffer size (#6830) --- esphome/components/voice_assistant/voice_assistant.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/voice_assistant/voice_assistant.cpp b/esphome/components/voice_assistant/voice_assistant.cpp index 0bd8194190..c2fd14fbd5 100644 --- a/esphome/components/voice_assistant/voice_assistant.cpp +++ b/esphome/components/voice_assistant/voice_assistant.cpp @@ -18,7 +18,7 @@ static const char *const TAG = "voice_assistant"; static const size_t SAMPLE_RATE_HZ = 16000; static const size_t INPUT_BUFFER_SIZE = 32 * SAMPLE_RATE_HZ / 1000; // 32ms * 16kHz / 1000ms -static const size_t BUFFER_SIZE = 1024 * SAMPLE_RATE_HZ / 1000; +static const size_t BUFFER_SIZE = 512 * SAMPLE_RATE_HZ / 1000; static const size_t SEND_BUFFER_SIZE = INPUT_BUFFER_SIZE * sizeof(int16_t); static const size_t RECEIVE_SIZE = 1024; static const size_t SPEAKER_BUFFER_SIZE = 16 * RECEIVE_SIZE; From dd2788133683cc7485008559de7d1a4b7ae21825 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 30 May 2024 20:01:39 +1200 Subject: [PATCH 25/46] [i2s_speaker] Add buffer allocation failure checks (#6829) --- .../i2s_audio/speaker/i2s_audio_speaker.cpp | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp b/esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp index 69536742cb..546c2c98f7 100644 --- a/esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp +++ b/esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp @@ -19,10 +19,27 @@ void I2SAudioSpeaker::setup() { ESP_LOGCONFIG(TAG, "Setting up I2S Audio Speaker..."); this->buffer_queue_ = xQueueCreate(BUFFER_COUNT, sizeof(DataEvent)); + if (this->buffer_queue_ == nullptr) { + ESP_LOGE(TAG, "Failed to create buffer queue"); + this->mark_failed(); + return; + } + this->event_queue_ = xQueueCreate(BUFFER_COUNT, sizeof(TaskEvent)); + if (this->event_queue_ == nullptr) { + ESP_LOGE(TAG, "Failed to create event queue"); + this->mark_failed(); + return; + } } -void I2SAudioSpeaker::start() { this->state_ = speaker::STATE_STARTING; } +void I2SAudioSpeaker::start() { + if (this->is_failed()) { + ESP_LOGE(TAG, "Cannot start audio, speaker failed to setup"); + return; + } + this->state_ = speaker::STATE_STARTING; +} void I2SAudioSpeaker::start_() { if (!this->parent_->try_lock()) { return; // Waiting for another i2s component to return lock @@ -141,6 +158,8 @@ void I2SAudioSpeaker::player_task(void *params) { } void I2SAudioSpeaker::stop() { + if (this->is_failed()) + return; if (this->state_ == speaker::STATE_STOPPED) return; if (this->state_ == speaker::STATE_STARTING) { @@ -200,6 +219,10 @@ void I2SAudioSpeaker::loop() { } size_t I2SAudioSpeaker::play(const uint8_t *data, size_t length) { + if (this->is_failed()) { + ESP_LOGE(TAG, "Cannot play audio, speaker failed to setup"); + return 0; + } if (this->state_ != speaker::STATE_RUNNING && this->state_ != speaker::STATE_STARTING) { this->start(); } From 8dfe1d52203e5ab6d07baaea74b981bf3c6cab98 Mon Sep 17 00:00:00 2001 From: Anton Viktorov Date: Thu, 30 May 2024 08:46:52 +0000 Subject: [PATCH 26/46] LTR-303, LTR-329, LTR-553, LTR-556, LTR-559, LTR-659 Series of Lite-On Light (ALS) and Proximity(PS) sensors (#6076) * LTR303 and LTR329 light sensors * LTR303 tidy up * LTR303 unused var * LTR303 tidy up + test * LTR303 auto sensitivity mode * LTR303 auto sensitivity mode tidy * LTR303 State machine version * LTR303 name fix * publish split * minor * new definitions for LTR * als-ps test * als-ps test * als-ps test * ps options * ps options * trgger bug fixed * trgger bug fixed * Minor comments * ltr303->ltr_als_ps * codeowners, tests * tidy up * tidy up * tidy up * gain enum name fix * auto gain fix * tweaks * new style tests * als/ps separate init * logd->logv * reconfiguration count changed * old-style tests removed * const py * ambient light const in vmel7700 and ltr390 * Update esphome/components/ltr_als_ps/ltr_als_ps.cpp Co-authored-by: Keith Burzinski * Apply suggestions from code review Co-authored-by: Keith Burzinski * remove commented code --------- Co-authored-by: Keith Burzinski --- CODEOWNERS | 1 + esphome/components/ltr390/sensor.py | 8 +- esphome/components/ltr_als_ps/__init__.py | 1 + esphome/components/ltr_als_ps/ltr_als_ps.cpp | 519 ++++++++++++++++++ esphome/components/ltr_als_ps/ltr_als_ps.h | 184 +++++++ .../components/ltr_als_ps/ltr_definitions.h | 275 ++++++++++ esphome/components/ltr_als_ps/sensor.py | 271 +++++++++ esphome/components/veml7700/sensor.py | 8 +- esphome/const.py | 1 + tests/components/ltr_als_ps/common.yaml | 11 + .../ltr_als_ps/test.esp32-c3-idf.yaml | 6 + .../components/ltr_als_ps/test.esp32-c3.yaml | 6 + .../components/ltr_als_ps/test.esp32-idf.yaml | 6 + tests/components/ltr_als_ps/test.esp32.yaml | 6 + tests/components/ltr_als_ps/test.esp8266.yaml | 6 + tests/components/ltr_als_ps/test.rp2040.yaml | 6 + 16 files changed, 1307 insertions(+), 8 deletions(-) create mode 100644 esphome/components/ltr_als_ps/__init__.py create mode 100644 esphome/components/ltr_als_ps/ltr_als_ps.cpp create mode 100644 esphome/components/ltr_als_ps/ltr_als_ps.h create mode 100644 esphome/components/ltr_als_ps/ltr_definitions.h create mode 100644 esphome/components/ltr_als_ps/sensor.py create mode 100644 tests/components/ltr_als_ps/common.yaml create mode 100644 tests/components/ltr_als_ps/test.esp32-c3-idf.yaml create mode 100644 tests/components/ltr_als_ps/test.esp32-c3.yaml create mode 100644 tests/components/ltr_als_ps/test.esp32-idf.yaml create mode 100644 tests/components/ltr_als_ps/test.esp32.yaml create mode 100644 tests/components/ltr_als_ps/test.esp8266.yaml create mode 100644 tests/components/ltr_als_ps/test.rp2040.yaml diff --git a/CODEOWNERS b/CODEOWNERS index 8b6143859a..0dc5339193 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -210,6 +210,7 @@ esphome/components/lilygo_t5_47/touchscreen/* @jesserockz esphome/components/lock/* @esphome/core esphome/components/logger/* @esphome/core esphome/components/ltr390/* @sjtrny +esphome/components/ltr_als_ps/* @latonita esphome/components/matrix_keypad/* @ssieb esphome/components/max31865/* @DAVe3283 esphome/components/max44009/* @berfenger diff --git a/esphome/components/ltr390/sensor.py b/esphome/components/ltr390/sensor.py index fe8cad00b6..8b2676599c 100644 --- a/esphome/components/ltr390/sensor.py +++ b/esphome/components/ltr390/sensor.py @@ -2,14 +2,15 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import i2c, sensor from esphome.const import ( - CONF_ID, + CONF_AMBIENT_LIGHT, CONF_GAIN, + CONF_ID, CONF_LIGHT, CONF_RESOLUTION, - UNIT_LUX, - ICON_BRIGHTNESS_5, DEVICE_CLASS_EMPTY, DEVICE_CLASS_ILLUMINANCE, + ICON_BRIGHTNESS_5, + UNIT_LUX, ) CODEOWNERS = ["@sjtrny"] @@ -21,7 +22,6 @@ LTR390Component = ltr390_ns.class_( "LTR390Component", cg.PollingComponent, i2c.I2CDevice ) -CONF_AMBIENT_LIGHT = "ambient_light" CONF_UV_INDEX = "uv_index" CONF_UV = "uv" CONF_WINDOW_CORRECTION_FACTOR = "window_correction_factor" diff --git a/esphome/components/ltr_als_ps/__init__.py b/esphome/components/ltr_als_ps/__init__.py new file mode 100644 index 0000000000..dd06cfffea --- /dev/null +++ b/esphome/components/ltr_als_ps/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@latonita"] diff --git a/esphome/components/ltr_als_ps/ltr_als_ps.cpp b/esphome/components/ltr_als_ps/ltr_als_ps.cpp new file mode 100644 index 0000000000..ae299c9b66 --- /dev/null +++ b/esphome/components/ltr_als_ps/ltr_als_ps.cpp @@ -0,0 +1,519 @@ +#include "ltr_als_ps.h" +#include "esphome/core/application.h" +#include "esphome/core/log.h" +#include "esphome/core/helpers.h" + +using esphome::i2c::ErrorCode; + +namespace esphome { +namespace ltr_als_ps { + +static const char *const TAG = "ltr_als_ps"; + +static const uint8_t MAX_TRIES = 5; + +template T get_next(const T (&array)[size], const T val) { + size_t i = 0; + size_t idx = -1; + while (idx == -1 && i < size) { + if (array[i] == val) { + idx = i; + break; + } + i++; + } + if (idx == -1 || i + 1 >= size) + return val; + return array[i + 1]; +} + +template T get_prev(const T (&array)[size], const T val) { + size_t i = size - 1; + size_t idx = -1; + while (idx == -1 && i > 0) { + if (array[i] == val) { + idx = i; + break; + } + i--; + } + if (idx == -1 || i == 0) + return val; + return array[i - 1]; +} + +static uint16_t get_itime_ms(IntegrationTime time) { + static const uint16_t ALS_INT_TIME[8] = {100, 50, 200, 400, 150, 250, 300, 350}; + return ALS_INT_TIME[time & 0b111]; +} + +static uint16_t get_meas_time_ms(MeasurementRepeatRate rate) { + static const uint16_t ALS_MEAS_RATE[8] = {50, 100, 200, 500, 1000, 2000, 2000, 2000}; + return ALS_MEAS_RATE[rate & 0b111]; +} + +static float get_gain_coeff(AlsGain gain) { + static const float ALS_GAIN[8] = {1, 2, 4, 8, 0, 0, 48, 96}; + return ALS_GAIN[gain & 0b111]; +} + +static float get_ps_gain_coeff(PsGain gain) { + static const float PS_GAIN[4] = {16, 0, 32, 64}; + return PS_GAIN[gain & 0b11]; +} + +void LTRAlsPsComponent::setup() { + ESP_LOGCONFIG(TAG, "Setting up LTR-303/329/55x/659"); + // As per datasheet we need to wait at least 100ms after power on to get ALS chip responsive + this->set_timeout(100, [this]() { this->state_ = State::DELAYED_SETUP; }); +} + +void LTRAlsPsComponent::dump_config() { + auto get_device_type = [](LtrType typ) { + switch (typ) { + case LtrType::LTR_TYPE_ALS_ONLY: + return "ALS only"; + case LtrType::LTR_TYPE_PS_ONLY: + return "PS only"; + case LtrType::LTR_TYPE_ALS_AND_PS: + return "ALS + PS"; + default: + return "Unknown"; + } + }; + + LOG_I2C_DEVICE(this); + ESP_LOGCONFIG(TAG, " Device type: %s", get_device_type(this->ltr_type_)); + if (this->is_als_()) { + ESP_LOGCONFIG(TAG, " Automatic mode: %s", ONOFF(this->automatic_mode_enabled_)); + ESP_LOGCONFIG(TAG, " Gain: %.0fx", get_gain_coeff(this->gain_)); + ESP_LOGCONFIG(TAG, " Integration time: %d ms", get_itime_ms(this->integration_time_)); + ESP_LOGCONFIG(TAG, " Measurement repeat rate: %d ms", get_meas_time_ms(this->repeat_rate_)); + ESP_LOGCONFIG(TAG, " Glass attenuation factor: %f", this->glass_attenuation_factor_); + LOG_SENSOR(" ", "ALS calculated lux", this->ambient_light_sensor_); + LOG_SENSOR(" ", "CH1 Infrared counts", this->infrared_counts_sensor_); + LOG_SENSOR(" ", "CH0 Visible+IR counts", this->full_spectrum_counts_sensor_); + LOG_SENSOR(" ", "Actual gain", this->actual_gain_sensor_); + } + if (this->is_ps_()) { + ESP_LOGCONFIG(TAG, " Proximity gain: %.0fx", get_ps_gain_coeff(this->ps_gain_)); + ESP_LOGCONFIG(TAG, " Proximity cooldown time: %d s", this->ps_cooldown_time_s_); + ESP_LOGCONFIG(TAG, " Proximity high threshold: %d", this->ps_threshold_high_); + ESP_LOGCONFIG(TAG, " Proximity low threshold: %d", this->ps_threshold_low_); + LOG_SENSOR(" ", "Proximity counts", this->proximity_counts_sensor_); + } + LOG_UPDATE_INTERVAL(this); + + if (this->is_failed()) { + ESP_LOGE(TAG, "Communication with I2C LTR-303/329/55x/659 failed!"); + } +} + +void LTRAlsPsComponent::update() { + ESP_LOGV(TAG, "Updating"); + if (this->is_ready() && this->state_ == State::IDLE) { + ESP_LOGV(TAG, "Initiating new data collection"); + + this->state_ = this->automatic_mode_enabled_ ? State::COLLECTING_DATA_AUTO : State::WAITING_FOR_DATA; + + this->als_readings_.ch0 = 0; + this->als_readings_.ch1 = 0; + this->als_readings_.gain = this->gain_; + this->als_readings_.integration_time = this->integration_time_; + this->als_readings_.lux = 0; + this->als_readings_.number_of_adjustments = 0; + + } else { + ESP_LOGV(TAG, "Component not ready yet"); + } +} + +void LTRAlsPsComponent::loop() { + ErrorCode err = i2c::ERROR_OK; + static uint8_t tries{0}; + + switch (this->state_) { + case State::DELAYED_SETUP: + err = this->write(nullptr, 0); + if (err != i2c::ERROR_OK) { + ESP_LOGV(TAG, "i2c connection failed"); + this->mark_failed(); + } + this->configure_reset_(); + if (this->is_als_()) { + this->configure_als_(); + this->configure_integration_time_(this->integration_time_); + } + if (this->is_ps_()) { + this->configure_ps_(); + } + + this->state_ = State::IDLE; + break; + + case State::IDLE: + if (this->is_ps_()) { + check_and_trigger_ps_(); + } + break; + + case State::WAITING_FOR_DATA: + if (this->is_als_data_ready_(this->als_readings_) == DataAvail::DATA_OK) { + tries = 0; + ESP_LOGV(TAG, "Reading sensor data having gain = %.0fx, time = %d ms", get_gain_coeff(this->als_readings_.gain), + get_itime_ms(this->als_readings_.integration_time)); + this->read_sensor_data_(this->als_readings_); + this->state_ = State::DATA_COLLECTED; + this->apply_lux_calculation_(this->als_readings_); + } else if (tries >= MAX_TRIES) { + ESP_LOGW(TAG, "Can't get data after several tries."); + tries = 0; + this->status_set_warning(); + this->state_ = State::IDLE; + return; + } else { + tries++; + } + break; + + case State::COLLECTING_DATA_AUTO: + case State::DATA_COLLECTED: + // first measurement in auto mode (COLLECTING_DATA_AUTO state) require device reconfiguration + if (this->state_ == State::COLLECTING_DATA_AUTO || this->are_adjustments_required_(this->als_readings_)) { + this->state_ = State::ADJUSTMENT_IN_PROGRESS; + ESP_LOGD(TAG, "Reconfiguring sensitivity: gain = %.0fx, time = %d ms", get_gain_coeff(this->als_readings_.gain), + get_itime_ms(this->als_readings_.integration_time)); + this->configure_integration_time_(this->als_readings_.integration_time); + this->configure_gain_(this->als_readings_.gain); + // if sensitivity adjustment needed - need to wait for first data samples after setting new parameters + this->set_timeout(2 * get_meas_time_ms(this->repeat_rate_), + [this]() { this->state_ = State::WAITING_FOR_DATA; }); + } else { + this->state_ = State::READY_TO_PUBLISH; + } + break; + + case State::ADJUSTMENT_IN_PROGRESS: + // nothing to be done, just waiting for the timeout + break; + + case State::READY_TO_PUBLISH: + this->publish_data_part_1_(this->als_readings_); + this->state_ = State::KEEP_PUBLISHING; + break; + + case State::KEEP_PUBLISHING: + this->publish_data_part_2_(this->als_readings_); + this->status_clear_warning(); + this->state_ = State::IDLE; + break; + + default: + break; + } +} + +void LTRAlsPsComponent::check_and_trigger_ps_() { + static uint32_t last_high_trigger_time{0}; + static uint32_t last_low_trigger_time{0}; + uint16_t ps_data = this->read_ps_data_(); + uint32_t now = millis(); + + if (ps_data != this->ps_readings_) { + this->ps_readings_ = ps_data; + // Higher values - object is closer to sensor + if (ps_data > this->ps_threshold_high_ && now - last_high_trigger_time >= this->ps_cooldown_time_s_ * 1000) { + last_high_trigger_time = now; + ESP_LOGV(TAG, "Proximity high threshold triggered. Value = %d, Trigger level = %d", ps_data, + this->ps_threshold_high_); + this->on_ps_high_trigger_callback_.call(); + } else if (ps_data < this->ps_threshold_low_ && now - last_low_trigger_time >= this->ps_cooldown_time_s_ * 1000) { + last_low_trigger_time = now; + ESP_LOGV(TAG, "Proximity low threshold triggered. Value = %d, Trigger level = %d", ps_data, + this->ps_threshold_low_); + this->on_ps_low_trigger_callback_.call(); + } + } +} + +bool LTRAlsPsComponent::check_part_number_() { + uint8_t manuf_id = this->reg((uint8_t) CommandRegisters::MANUFAC_ID).get(); + if (manuf_id != 0x05) { // 0x05 is Lite-On Semiconductor Corp. ID + ESP_LOGW(TAG, "Unknown manufacturer ID: 0x%02X", manuf_id); + this->mark_failed(); + return false; + } + + // Things getting not really funny here, we can't identify device type by part number ID + // ======================== ========= ===== ================= + // Device Part ID Rev Capabilities + // ======================== ========= ===== ================= + // Ltr-329/ltr-303 0x0a 0x00 Als 16b + // Ltr-553/ltr-556/ltr-556 0x09 0x02 Als 16b + Ps 11b diff nm sens + // Ltr-659 0x09 0x02 Ps 11b and ps gain + // + // There are other devices which might potentially work with default settings, + // but registers layout is different and we can't use them properly. For ex. ltr-558 + + PartIdRegister part_id{0}; + part_id.raw = this->reg((uint8_t) CommandRegisters::PART_ID).get(); + if (part_id.part_number_id != 0x0a && part_id.part_number_id != 0x09) { + ESP_LOGW(TAG, "Unknown part number ID: 0x%02X. It might not work properly.", part_id.part_number_id); + this->status_set_warning(); + return true; + } + return true; +} + +void LTRAlsPsComponent::configure_reset_() { + ESP_LOGV(TAG, "Resetting"); + + AlsControlRegister als_ctrl{0}; + als_ctrl.sw_reset = true; + this->reg((uint8_t) CommandRegisters::ALS_CONTR) = als_ctrl.raw; + delay(2); + + uint8_t tries = MAX_TRIES; + do { + ESP_LOGV(TAG, "Waiting for chip to reset"); + delay(2); + als_ctrl.raw = this->reg((uint8_t) CommandRegisters::ALS_CONTR).get(); + } while (als_ctrl.sw_reset && tries--); // while sw reset bit is on - keep waiting + + if (als_ctrl.sw_reset) { + ESP_LOGW(TAG, "Reset timed out"); + } +} + +void LTRAlsPsComponent::configure_als_() { + AlsControlRegister als_ctrl{0}; + + als_ctrl.sw_reset = false; + als_ctrl.active_mode = true; + als_ctrl.gain = this->gain_; + + ESP_LOGV(TAG, "Setting active mode and gain reg 0x%02X", als_ctrl.raw); + this->reg((uint8_t) CommandRegisters::ALS_CONTR) = als_ctrl.raw; + delay(5); + + uint8_t tries = MAX_TRIES; + do { + ESP_LOGV(TAG, "Waiting for device to become active..."); + delay(2); + als_ctrl.raw = this->reg((uint8_t) CommandRegisters::ALS_CONTR).get(); + } while (!als_ctrl.active_mode && tries--); // while active mode is not set - keep waiting + + if (!als_ctrl.active_mode) { + ESP_LOGW(TAG, "Failed to activate device"); + } +} + +void LTRAlsPsComponent::configure_ps_() { + PsMeasurementRateRegister ps_meas{0}; + ps_meas.ps_measurement_rate = PsMeasurementRate::PS_MEAS_RATE_50MS; + this->reg((uint8_t) CommandRegisters::PS_MEAS_RATE) = ps_meas.raw; + + PsControlRegister ps_ctrl{0}; + ps_ctrl.ps_mode_active = true; + ps_ctrl.ps_mode_xxx = true; + this->reg((uint8_t) CommandRegisters::PS_CONTR) = ps_ctrl.raw; +} + +uint16_t LTRAlsPsComponent::read_ps_data_() { + AlsPsStatusRegister als_status{0}; + als_status.raw = this->reg((uint8_t) CommandRegisters::ALS_PS_STATUS).get(); + if (!als_status.ps_new_data || als_status.data_invalid) { + return this->ps_readings_; + } + + uint8_t ps_low = this->reg((uint8_t) CommandRegisters::PS_DATA_0).get(); + PsData1Register ps_high; + ps_high.raw = this->reg((uint8_t) CommandRegisters::PS_DATA_1).get(); + + uint16_t val = encode_uint16(ps_high.ps_data_high, ps_low); + if (ps_high.ps_saturation_flag) { + return 0x7ff; // full 11 bit range + } + return val; +} + +void LTRAlsPsComponent::configure_gain_(AlsGain gain) { + AlsControlRegister als_ctrl{0}; + als_ctrl.active_mode = true; + als_ctrl.gain = gain; + this->reg((uint8_t) CommandRegisters::ALS_CONTR) = als_ctrl.raw; + delay(2); + + AlsControlRegister read_als_ctrl{0}; + read_als_ctrl.raw = this->reg((uint8_t) CommandRegisters::ALS_CONTR).get(); + if (read_als_ctrl.gain != gain) { + ESP_LOGW(TAG, "Failed to set gain. We will try one more time."); + this->reg((uint8_t) CommandRegisters::ALS_CONTR) = als_ctrl.raw; + delay(2); + } +} + +void LTRAlsPsComponent::configure_integration_time_(IntegrationTime time) { + MeasurementRateRegister meas{0}; + meas.measurement_repeat_rate = this->repeat_rate_; + meas.integration_time = time; + this->reg((uint8_t) CommandRegisters::MEAS_RATE) = meas.raw; + delay(2); + + MeasurementRateRegister read_meas{0}; + read_meas.raw = this->reg((uint8_t) CommandRegisters::MEAS_RATE).get(); + if (read_meas.integration_time != time) { + ESP_LOGW(TAG, "Failed to set integration time. We will try one more time."); + this->reg((uint8_t) CommandRegisters::MEAS_RATE) = meas.raw; + delay(2); + } +} + +DataAvail LTRAlsPsComponent::is_als_data_ready_(AlsReadings &data) { + AlsPsStatusRegister als_status{0}; + + als_status.raw = this->reg((uint8_t) CommandRegisters::ALS_PS_STATUS).get(); + if (!als_status.als_new_data) + return DataAvail::NO_DATA; + + if (als_status.data_invalid) { + ESP_LOGW(TAG, "Data available but not valid"); + return DataAvail::BAD_DATA; + } + ESP_LOGV(TAG, "Data ready, reported gain is %.0f", get_gain_coeff(als_status.gain)); + if (data.gain != als_status.gain) { + ESP_LOGW(TAG, "Actual gain differs from requested (%.0f)", get_gain_coeff(data.gain)); + return DataAvail::BAD_DATA; + } + return DataAvail::DATA_OK; +} + +void LTRAlsPsComponent::read_sensor_data_(AlsReadings &data) { + data.ch1 = 0; + data.ch0 = 0; + uint8_t ch1_0 = this->reg((uint8_t) CommandRegisters::ALS_DATA_CH1_0).get(); + uint8_t ch1_1 = this->reg((uint8_t) CommandRegisters::ALS_DATA_CH1_1).get(); + uint8_t ch0_0 = this->reg((uint8_t) CommandRegisters::ALS_DATA_CH0_0).get(); + uint8_t ch0_1 = this->reg((uint8_t) CommandRegisters::ALS_DATA_CH0_1).get(); + data.ch1 = encode_uint16(ch1_1, ch1_0); + data.ch0 = encode_uint16(ch0_1, ch0_0); + + ESP_LOGV(TAG, "Got sensor data: CH1 = %d, CH0 = %d", data.ch1, data.ch0); +} + +bool LTRAlsPsComponent::are_adjustments_required_(AlsReadings &data) { + if (!this->automatic_mode_enabled_) + return false; + + if (data.number_of_adjustments > 15) { + // sometimes sensors fail to change sensitivity. this prevents us from infinite loop + ESP_LOGW(TAG, "Too many sensitivity adjustments done. Apparently, sensor reconfiguration fails. Stopping."); + return false; + } + data.number_of_adjustments++; + + // Recommended thresholds as per datasheet + static const uint16_t LOW_INTENSITY_THRESHOLD = 1000; + static const uint16_t HIGH_INTENSITY_THRESHOLD = 30000; + static const AlsGain GAINS[GAINS_COUNT] = {GAIN_1, GAIN_2, GAIN_4, GAIN_8, GAIN_48, GAIN_96}; + static const IntegrationTime INT_TIMES[TIMES_COUNT] = { + INTEGRATION_TIME_50MS, INTEGRATION_TIME_100MS, INTEGRATION_TIME_150MS, INTEGRATION_TIME_200MS, + INTEGRATION_TIME_250MS, INTEGRATION_TIME_300MS, INTEGRATION_TIME_350MS, INTEGRATION_TIME_400MS}; + + if (data.ch0 <= LOW_INTENSITY_THRESHOLD) { + AlsGain next_gain = get_next(GAINS, data.gain); + if (next_gain != data.gain) { + data.gain = next_gain; + ESP_LOGV(TAG, "Low illuminance. Increasing gain."); + return true; + } + IntegrationTime next_time = get_next(INT_TIMES, data.integration_time); + if (next_time != data.integration_time) { + data.integration_time = next_time; + ESP_LOGV(TAG, "Low illuminance. Increasing integration time."); + return true; + } + } else if (data.ch0 >= HIGH_INTENSITY_THRESHOLD) { + AlsGain prev_gain = get_prev(GAINS, data.gain); + if (prev_gain != data.gain) { + data.gain = prev_gain; + ESP_LOGV(TAG, "High illuminance. Decreasing gain."); + return true; + } + IntegrationTime prev_time = get_prev(INT_TIMES, data.integration_time); + if (prev_time != data.integration_time) { + data.integration_time = prev_time; + ESP_LOGV(TAG, "High illuminance. Decreasing integration time."); + return true; + } + } else { + ESP_LOGD(TAG, "Illuminance is sufficient."); + return false; + } + ESP_LOGD(TAG, "Can't adjust sensitivity anymore."); + return false; +} + +void LTRAlsPsComponent::apply_lux_calculation_(AlsReadings &data) { + if ((data.ch0 == 0xFFFF) || (data.ch1 == 0xFFFF)) { + ESP_LOGW(TAG, "Sensors got saturated"); + data.lux = 0.0f; + return; + } + + if ((data.ch0 == 0x0000) && (data.ch1 == 0x0000)) { + ESP_LOGW(TAG, "Sensors blacked out"); + data.lux = 0.0f; + return; + } + + float ch0 = data.ch0; + float ch1 = data.ch1; + float ratio = ch1 / (ch0 + ch1); + float als_gain = get_gain_coeff(data.gain); + float als_time = ((float) get_itime_ms(data.integration_time)) / 100.0f; + float inv_pfactor = this->glass_attenuation_factor_; + float lux = 0.0f; + + if (ratio < 0.45) { + lux = (1.7743 * ch0 + 1.1059 * ch1); + } else if (ratio < 0.64 && ratio >= 0.45) { + lux = (4.2785 * ch0 - 1.9548 * ch1); + } else if (ratio < 0.85 && ratio >= 0.64) { + lux = (0.5926 * ch0 + 0.1185 * ch1); + } else { + ESP_LOGW(TAG, "Impossible ch1/(ch0 + ch1) ratio"); + lux = 0.0f; + } + lux = inv_pfactor * lux / als_gain / als_time; + data.lux = lux; + + ESP_LOGV(TAG, "Lux calculation: ratio %.3f, gain %.0fx, int time %.1f, inv_pfactor %.3f, lux %.3f", ratio, als_gain, + als_time, inv_pfactor, lux); +} + +void LTRAlsPsComponent::publish_data_part_1_(AlsReadings &data) { + if (this->proximity_counts_sensor_ != nullptr) { + this->proximity_counts_sensor_->publish_state(this->ps_readings_); + } + if (this->ambient_light_sensor_ != nullptr) { + this->ambient_light_sensor_->publish_state(data.lux); + } + if (this->infrared_counts_sensor_ != nullptr) { + this->infrared_counts_sensor_->publish_state(data.ch1); + } + if (this->full_spectrum_counts_sensor_ != nullptr) { + this->full_spectrum_counts_sensor_->publish_state(data.ch0); + } +} + +void LTRAlsPsComponent::publish_data_part_2_(AlsReadings &data) { + if (this->actual_gain_sensor_ != nullptr) { + this->actual_gain_sensor_->publish_state(get_gain_coeff(data.gain)); + } + if (this->actual_integration_time_sensor_ != nullptr) { + this->actual_integration_time_sensor_->publish_state(get_itime_ms(data.integration_time)); + } +} +} // namespace ltr_als_ps +} // namespace esphome diff --git a/esphome/components/ltr_als_ps/ltr_als_ps.h b/esphome/components/ltr_als_ps/ltr_als_ps.h new file mode 100644 index 0000000000..4cbbcea54c --- /dev/null +++ b/esphome/components/ltr_als_ps/ltr_als_ps.h @@ -0,0 +1,184 @@ +#pragma once + +#include "esphome/components/i2c/i2c.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/core/component.h" +#include "esphome/core/optional.h" +#include "esphome/core/automation.h" + +#include "ltr_definitions.h" + +namespace esphome { +namespace ltr_als_ps { + +enum DataAvail : uint8_t { NO_DATA, BAD_DATA, DATA_OK }; + +enum LtrType : uint8_t { + LTR_TYPE_UNKNOWN = 0, + LTR_TYPE_ALS_ONLY = 1, + LTR_TYPE_PS_ONLY = 2, + LTR_TYPE_ALS_AND_PS = 3, +}; + +class LTRAlsPsComponent : public PollingComponent, public i2c::I2CDevice { + public: + // + // EspHome framework functions + // + float get_setup_priority() const override { return setup_priority::DATA; } + void setup() override; + void dump_config() override; + void update() override; + void loop() override; + + // Configuration setters : General + // + void set_ltr_type(LtrType type) { this->ltr_type_ = type; } + + // Configuration setters : ALS + // + void set_als_auto_mode(bool enable) { this->automatic_mode_enabled_ = enable; } + void set_als_gain(AlsGain gain) { this->gain_ = gain; } + void set_als_integration_time(IntegrationTime time) { this->integration_time_ = time; } + void set_als_meas_repeat_rate(MeasurementRepeatRate rate) { this->repeat_rate_ = rate; } + void set_als_glass_attenuation_factor(float factor) { this->glass_attenuation_factor_ = factor; } + + // Configuration setters : PS + // + void set_ps_high_threshold(uint16_t threshold) { this->ps_threshold_high_ = threshold; } + void set_ps_low_threshold(uint16_t threshold) { this->ps_threshold_low_ = threshold; } + void set_ps_cooldown_time_s(uint16_t time) { this->ps_cooldown_time_s_ = time; } + void set_ps_gain(PsGain gain) { this->ps_gain_ = gain; } + + // Sensors setters + // + void set_ambient_light_sensor(sensor::Sensor *sensor) { this->ambient_light_sensor_ = sensor; } + void set_full_spectrum_counts_sensor(sensor::Sensor *sensor) { this->full_spectrum_counts_sensor_ = sensor; } + void set_infrared_counts_sensor(sensor::Sensor *sensor) { this->infrared_counts_sensor_ = sensor; } + void set_actual_gain_sensor(sensor::Sensor *sensor) { this->actual_gain_sensor_ = sensor; } + void set_actual_integration_time_sensor(sensor::Sensor *sensor) { this->actual_integration_time_sensor_ = sensor; } + void set_proximity_counts_sensor(sensor::Sensor *sensor) { this->proximity_counts_sensor_ = sensor; } + + protected: + // + // Internal state machine, used to split all the actions into + // small steps in loop() to make sure we are not blocking execution + // + enum class State : uint8_t { + NOT_INITIALIZED, + DELAYED_SETUP, + IDLE, + WAITING_FOR_DATA, + COLLECTING_DATA_AUTO, + DATA_COLLECTED, + ADJUSTMENT_IN_PROGRESS, + READY_TO_PUBLISH, + KEEP_PUBLISHING + } state_{State::NOT_INITIALIZED}; + + LtrType ltr_type_{LtrType::LTR_TYPE_ALS_ONLY}; + + // + // Current measurements data + // + struct AlsReadings { + uint16_t ch0{0}; + uint16_t ch1{0}; + AlsGain gain{AlsGain::GAIN_1}; + IntegrationTime integration_time{IntegrationTime::INTEGRATION_TIME_100MS}; + float lux{0.0f}; + uint8_t number_of_adjustments{0}; + } als_readings_; + uint16_t ps_readings_{0xfffe}; + + inline bool is_als_() const { + return this->ltr_type_ == LtrType::LTR_TYPE_ALS_ONLY || this->ltr_type_ == LtrType::LTR_TYPE_ALS_AND_PS; + } + inline bool is_ps_() const { + return this->ltr_type_ == LtrType::LTR_TYPE_PS_ONLY || this->ltr_type_ == LtrType::LTR_TYPE_ALS_AND_PS; + } + + // + // Device interaction and data manipulation + // + bool check_part_number_(); + + void configure_reset_(); + void configure_als_(); + void configure_integration_time_(IntegrationTime time); + void configure_gain_(AlsGain gain); + DataAvail is_als_data_ready_(AlsReadings &data); + void read_sensor_data_(AlsReadings &data); + bool are_adjustments_required_(AlsReadings &data); + void apply_lux_calculation_(AlsReadings &data); + void publish_data_part_1_(AlsReadings &data); + void publish_data_part_2_(AlsReadings &data); + + void configure_ps_(); + uint16_t read_ps_data_(); + void check_and_trigger_ps_(); + + // + // Component configuration + // + bool automatic_mode_enabled_{true}; + AlsGain gain_{AlsGain::GAIN_1}; + IntegrationTime integration_time_{IntegrationTime::INTEGRATION_TIME_100MS}; + MeasurementRepeatRate repeat_rate_{MeasurementRepeatRate::REPEAT_RATE_500MS}; + float glass_attenuation_factor_{1.0}; + + uint16_t ps_cooldown_time_s_{5}; + PsGain ps_gain_{PsGain::PS_GAIN_16}; + uint16_t ps_threshold_high_{0xffff}; + uint16_t ps_threshold_low_{0x0000}; + + // + // Sensors for publishing data + // + sensor::Sensor *infrared_counts_sensor_{nullptr}; // direct reading CH1, infrared only + sensor::Sensor *full_spectrum_counts_sensor_{nullptr}; // direct reading CH0, infrared + visible light + sensor::Sensor *ambient_light_sensor_{nullptr}; // calculated lux + sensor::Sensor *actual_gain_sensor_{nullptr}; // actual gain of reading + sensor::Sensor *actual_integration_time_sensor_{nullptr}; // actual integration time + sensor::Sensor *proximity_counts_sensor_{nullptr}; // proximity sensor + + bool is_any_als_sensor_enabled_() const { + return this->ambient_light_sensor_ != nullptr || this->full_spectrum_counts_sensor_ != nullptr || + this->infrared_counts_sensor_ != nullptr || this->actual_gain_sensor_ != nullptr || + this->actual_integration_time_sensor_ != nullptr; + } + bool is_any_ps_sensor_enabled_() const { return this->proximity_counts_sensor_ != nullptr; } + + // + // Trigger section for the automations + // + friend class LTRPsHighTrigger; + friend class LTRPsLowTrigger; + + CallbackManager on_ps_high_trigger_callback_; + CallbackManager on_ps_low_trigger_callback_; + + void add_on_ps_high_trigger_callback_(std::function callback) { + this->on_ps_high_trigger_callback_.add(std::move(callback)); + } + + void add_on_ps_low_trigger_callback_(std::function callback) { + this->on_ps_low_trigger_callback_.add(std::move(callback)); + } +}; + +class LTRPsHighTrigger : public Trigger<> { + public: + explicit LTRPsHighTrigger(LTRAlsPsComponent *parent) { + parent->add_on_ps_high_trigger_callback_([this]() { this->trigger(); }); + } +}; + +class LTRPsLowTrigger : public Trigger<> { + public: + explicit LTRPsLowTrigger(LTRAlsPsComponent *parent) { + parent->add_on_ps_low_trigger_callback_([this]() { this->trigger(); }); + } +}; +} // namespace ltr_als_ps +} // namespace esphome diff --git a/esphome/components/ltr_als_ps/ltr_definitions.h b/esphome/components/ltr_als_ps/ltr_definitions.h new file mode 100644 index 0000000000..739445e9a0 --- /dev/null +++ b/esphome/components/ltr_als_ps/ltr_definitions.h @@ -0,0 +1,275 @@ +#pragma once + +#include + +namespace esphome { +namespace ltr_als_ps { + +enum class CommandRegisters : uint8_t { + ALS_CONTR = 0x80, // ALS operation mode control and SW reset + PS_CONTR = 0x81, // PS operation mode control + PS_LED = 0x82, // PS LED pulse frequency control + PS_N_PULSES = 0x83, // PS number of pulses control + PS_MEAS_RATE = 0x84, // PS measurement rate in active mode + MEAS_RATE = 0x85, // ALS measurement rate in active mode + PART_ID = 0x86, // Part Number ID and Revision ID + MANUFAC_ID = 0x87, // Manufacturer ID + ALS_DATA_CH1_0 = 0x88, // ALS measurement CH1 data, lower byte - infrared only + ALS_DATA_CH1_1 = 0x89, // ALS measurement CH1 data, upper byte - infrared only + ALS_DATA_CH0_0 = 0x8A, // ALS measurement CH0 data, lower byte - visible + infrared + ALS_DATA_CH0_1 = 0x8B, // ALS measurement CH0 data, upper byte - visible + infrared + ALS_PS_STATUS = 0x8C, // ALS PS new data status + PS_DATA_0 = 0x8D, // PS measurement data, lower byte + PS_DATA_1 = 0x8E, // PS measurement data, upper byte + ALS_PS_INTERRUPT = 0x8F, // Interrupt status + PS_THRES_UP_0 = 0x90, // PS interrupt upper threshold, lower byte + PS_THRES_UP_1 = 0x91, // PS interrupt upper threshold, upper byte + PS_THRES_LOW_0 = 0x92, // PS interrupt lower threshold, lower byte + PS_THRES_LOW_1 = 0x93, // PS interrupt lower threshold, upper byte + PS_OFFSET_1 = 0x94, // PS offset, upper byte + PS_OFFSET_0 = 0x95, // PS offset, lower byte + // 0x96 - reserved + ALS_THRES_UP_0 = 0x97, // ALS interrupt upper threshold, lower byte + ALS_THRES_UP_1 = 0x98, // ALS interrupt upper threshold, upper byte + ALS_THRES_LOW_0 = 0x99, // ALS interrupt lower threshold, lower byte + ALS_THRES_LOW_1 = 0x9A, // ALS interrupt lower threshold, upper byte + // 0x9B - reserved + // 0x9C - reserved + // 0x9D - reserved + INTERRUPT_PERSIST = 0x9E // Interrupt persistence filter +}; + +// ALS Sensor gain levels +enum AlsGain : uint8_t { + GAIN_1 = 0, // default + GAIN_2 = 1, + GAIN_4 = 2, + GAIN_8 = 3, + GAIN_48 = 6, + GAIN_96 = 7, +}; +static const uint8_t GAINS_COUNT = 6; + +// ALS Sensor integration times +enum IntegrationTime : uint8_t { + INTEGRATION_TIME_100MS = 0, // default + INTEGRATION_TIME_50MS = 1, + INTEGRATION_TIME_200MS = 2, + INTEGRATION_TIME_400MS = 3, + INTEGRATION_TIME_150MS = 4, + INTEGRATION_TIME_250MS = 5, + INTEGRATION_TIME_300MS = 6, + INTEGRATION_TIME_350MS = 7 +}; +static const uint8_t TIMES_COUNT = 8; + +// ALS Sensor measurement repeat rate +enum MeasurementRepeatRate { + REPEAT_RATE_50MS = 0, + REPEAT_RATE_100MS = 1, + REPEAT_RATE_200MS = 2, + REPEAT_RATE_500MS = 3, // default + REPEAT_RATE_1000MS = 4, + REPEAT_RATE_2000MS = 5 +}; + +// PS Sensor gain levels +enum PsGain : uint8_t { + PS_GAIN_16 = 0, // default + PS_GAIN_32 = 2, + PS_GAIN_64 = 3, +}; + +// PS Mode +enum PsMode : uint8_t { + PS_MODE_STANDBY_00 = 0, // default + PS_MODE_STANDBY_01 = 1, + PS_MODE_ACTIVE_10 = 2, + PS_MODE_ACTIVE_11 = 3, +}; + +// LED Pulse Modulation Frequency +enum PsLedFreq : uint8_t { + PS_LED_FREQ_30KHZ = 0, + PS_LED_FREQ_40KHZ = 1, + PS_LED_FREQ_50KHZ = 2, + PS_LED_FREQ_60KHZ = 3, // default + PS_LED_FREQ_70KHZ = 4, + PS_LED_FREQ_80KHZ = 5, + PS_LED_FREQ_90KHZ = 6, + PS_LED_FREQ_100KHZ = 7, +}; + +// LED current duty +enum PsLedDuty : uint8_t { + PS_LED_DUTY_25 = 0, + PS_LED_DUTY_50 = 1, + PS_LED_DUTY_75 = 2, + PS_LED_DUTY_100 = 3, // default +}; + +// LED pulsed current level +enum PsLedCurrent : uint8_t { + PS_LED_CURRENT_5MA = 0, + PS_LED_CURRENT_10MA = 1, + PS_LED_CURRENT_20MA = 2, + PS_LED_CURRENT_50MA = 3, + PS_LED_CURRENT_100MA = 4, // default + PS_LED_CURRENT_100MA1 = 5, + PS_LED_CURRENT_100MA2 = 6, + PS_LED_CURRENT_100MA3 = 7, +}; + +// PS measurement rate +enum PsMeasurementRate : uint8_t { + PS_MEAS_RATE_50MS = 0, + PS_MEAS_RATE_70MS = 1, + PS_MEAS_RATE_100MS = 2, + PS_MEAS_RATE_200MS = 3, + PS_MEAS_RATE_500MS = 4, // default + PS_MEAS_RATE_1000MS = 5, + PS_MEAS_RATE_2000MS = 6, + PS_MEAS_RATE_2000MS1 = 7, + PS_MEAS_RATE_10MS = 8, +}; + +// +// ALS_CONTR Register (0x80) +// +union AlsControlRegister { + uint8_t raw; + struct { + bool active_mode : 1; + bool sw_reset : 1; + AlsGain gain : 3; + uint8_t reserved : 3; + } __attribute__((packed)); +}; + +// +// PS_CONTR Register (0x81) +// +union PsControlRegister { + uint8_t raw; + struct { + bool ps_mode_xxx : 1; + bool ps_mode_active : 1; + PsGain ps_gain : 2; // only LTR-659/558 + bool reserved_4 : 1; + bool ps_saturation_indicator_enable : 1; + bool reserved_6 : 1; + bool reserved_7 : 1; + } __attribute__((packed)); +}; + +// +// PS_LED Register (0x82) +// +union PsLedRegister { + uint8_t raw; + struct { + PsLedCurrent ps_led_current : 3; + PsLedDuty ps_led_duty : 2; + PsLedFreq ps_led_freq : 3; + } __attribute__((packed)); +}; + +// +// PS_N_PULSES Register (0x83) +// +union PsNPulsesRegister { + uint8_t raw; + struct { + uint8_t number_of_pulses : 4; + uint8_t reserved : 4; + } __attribute__((packed)); +}; + +// +// PS_MEAS_RATE Register (0x84) +// +union PsMeasurementRateRegister { + uint8_t raw; + struct { + PsMeasurementRate ps_measurement_rate : 4; + uint8_t reserved : 4; + } __attribute__((packed)); +}; + +// +// ALS_MEAS_RATE Register (0x85) +// +union MeasurementRateRegister { + uint8_t raw; + struct { + MeasurementRepeatRate measurement_repeat_rate : 3; + IntegrationTime integration_time : 3; + bool reserved_6 : 1; + bool reserved_7 : 1; + } __attribute__((packed)); +}; + +// +// PART_ID Register (0x86) (Read Only) +// +union PartIdRegister { + uint8_t raw; + struct { + uint8_t part_number_id : 4; + uint8_t revision_id : 4; + } __attribute__((packed)); +}; + +// +// ALS_PS_STATUS Register (0x8C) (Read Only) +// +union AlsPsStatusRegister { + uint8_t raw; + struct { + bool ps_new_data : 1; // 0 - old data, 1 - new data + bool ps_interrupt : 1; // 0 - interrupt signal not active, 1 - interrupt signal active + bool als_new_data : 1; // 0 - old data, 1 - new data + bool als_interrupt : 1; // 0 - interrupt signal not active, 1 - interrupt signal active + AlsGain gain : 3; // current ALS gain + bool data_invalid : 1; + } __attribute__((packed)); +}; + +// +// PS_DATA_1 Register (0x8E) (Read Only) +// +union PsData1Register { + uint8_t raw; + struct { + uint8_t ps_data_high : 3; + uint8_t reserved : 4; + bool ps_saturation_flag : 1; + } __attribute__((packed)); +}; + +// +// INTERRUPT Register (0x8F) (Read Only) +// +union InterruptRegister { + uint8_t raw; + struct { + bool ps_interrupt : 1; + bool als_interrupt : 1; + bool interrupt_polarity : 1; // 0 - active low (default), 1 - active high + uint8_t reserved : 5; + } __attribute__((packed)); +}; + +// +// INTERRUPT_PERSIST Register (0x9E) +// +union InterruptPersistRegister { + uint8_t raw; + struct { + uint8_t als_persist : 4; // 0 - every ALS cycle, 1 - every 2 ALS cycles, ... 15 - every 16 ALS cycles + uint8_t ps_persist : 4; // 0 - every PS cycle, 1 - every 2 PS cycles, ... 15 - every 16 PS cycles + } __attribute__((packed)); +}; + +} // namespace ltr_als_ps +} // namespace esphome diff --git a/esphome/components/ltr_als_ps/sensor.py b/esphome/components/ltr_als_ps/sensor.py new file mode 100644 index 0000000000..ac9f7e6788 --- /dev/null +++ b/esphome/components/ltr_als_ps/sensor.py @@ -0,0 +1,271 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome import automation +from esphome.components import i2c, sensor +from esphome.const import ( + CONF_ACTUAL_GAIN, + CONF_AMBIENT_LIGHT, + CONF_AUTO_MODE, + CONF_GAIN, + CONF_GLASS_ATTENUATION_FACTOR, + CONF_ID, + CONF_INTEGRATION_TIME, + CONF_NAME, + CONF_REPEAT, + CONF_TRIGGER_ID, + CONF_TYPE, + DEVICE_CLASS_DISTANCE, + DEVICE_CLASS_ILLUMINANCE, + ICON_BRIGHTNESS_5, + ICON_BRIGHTNESS_6, + ICON_TIMER, + STATE_CLASS_MEASUREMENT, + UNIT_LUX, + UNIT_MILLISECOND, +) + +CODEOWNERS = ["@latonita"] +DEPENDENCIES = ["i2c"] + +CONF_ACTUAL_INTEGRATION_TIME = "actual_integration_time" +CONF_FULL_SPECTRUM_COUNTS = "full_spectrum_counts" +CONF_INFRARED_COUNTS = "infrared_counts" +CONF_ON_PS_HIGH_THRESHOLD = "on_ps_high_threshold" +CONF_ON_PS_LOW_THRESHOLD = "on_ps_low_threshold" +CONF_PS_COOLDOWN = "ps_cooldown" +CONF_PS_COUNTS = "ps_counts" +CONF_PS_GAIN = "ps_gain" +CONF_PS_HIGH_THRESHOLD = "ps_high_threshold" +CONF_PS_LOW_THRESHOLD = "ps_low_threshold" +ICON_BRIGHTNESS_7 = "mdi:brightness-7" +ICON_GAIN = "mdi:multiplication" +ICON_PROXIMITY = "mdi:hand-wave-outline" +UNIT_COUNTS = "#" + +ltr_als_ps_ns = cg.esphome_ns.namespace("ltr_als_ps") + +LTRAlsPsComponent = ltr_als_ps_ns.class_( + "LTRAlsPsComponent", cg.PollingComponent, i2c.I2CDevice +) + +LtrType = ltr_als_ps_ns.enum("LtrType") +LTR_TYPES = { + "ALS": LtrType.LTR_TYPE_ALS_ONLY, + "PS": LtrType.LTR_TYPE_PS_ONLY, + "ALS_PS": LtrType.LTR_TYPE_ALS_AND_PS, +} + +AlsGain = ltr_als_ps_ns.enum("AlsGain") +ALS_GAINS = { + "1X": AlsGain.GAIN_1, + "2X": AlsGain.GAIN_2, + "4X": AlsGain.GAIN_4, + "8X": AlsGain.GAIN_8, + "48X": AlsGain.GAIN_48, + "96X": AlsGain.GAIN_96, +} + +IntegrationTime = ltr_als_ps_ns.enum("IntegrationTime") +INTEGRATION_TIMES = { + 50: IntegrationTime.INTEGRATION_TIME_50MS, + 100: IntegrationTime.INTEGRATION_TIME_100MS, + 150: IntegrationTime.INTEGRATION_TIME_150MS, + 200: IntegrationTime.INTEGRATION_TIME_200MS, + 250: IntegrationTime.INTEGRATION_TIME_250MS, + 300: IntegrationTime.INTEGRATION_TIME_300MS, + 350: IntegrationTime.INTEGRATION_TIME_350MS, + 400: IntegrationTime.INTEGRATION_TIME_400MS, +} + +MeasurementRepeatRate = ltr_als_ps_ns.enum("MeasurementRepeatRate") +MEASUREMENT_REPEAT_RATES = { + 50: MeasurementRepeatRate.REPEAT_RATE_50MS, + 100: MeasurementRepeatRate.REPEAT_RATE_100MS, + 200: MeasurementRepeatRate.REPEAT_RATE_200MS, + 500: MeasurementRepeatRate.REPEAT_RATE_500MS, + 1000: MeasurementRepeatRate.REPEAT_RATE_1000MS, + 2000: MeasurementRepeatRate.REPEAT_RATE_2000MS, +} + +PsGain = ltr_als_ps_ns.enum("PsGain") +PS_GAINS = { + "16X": PsGain.PS_GAIN_16, + "32X": PsGain.PS_GAIN_32, + "64X": PsGain.PS_GAIN_64, +} + +LTRPsHighTrigger = ltr_als_ps_ns.class_( + "LTRPsHighTrigger", automation.Trigger.template() +) +LTRPsLowTrigger = ltr_als_ps_ns.class_("LTRPsLowTrigger", automation.Trigger.template()) + + +def validate_integration_time(value): + value = cv.positive_time_period_milliseconds(value).total_milliseconds + return cv.enum(INTEGRATION_TIMES, int=True)(value) + + +def validate_repeat_rate(value): + value = cv.positive_time_period_milliseconds(value).total_milliseconds + return cv.enum(MEASUREMENT_REPEAT_RATES, int=True)(value) + + +def validate_time_and_repeat_rate(config): + integraton_time = config[CONF_INTEGRATION_TIME] + repeat_rate = config[CONF_REPEAT] + if integraton_time > repeat_rate: + raise cv.Invalid( + f"Measurement repeat rate ({repeat_rate}ms) shall be greater or equal to integration time ({integraton_time}ms)" + ) + return config + + +CONFIG_SCHEMA = cv.All( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(LTRAlsPsComponent), + cv.Optional(CONF_TYPE, default="ALS_PS"): cv.enum(LTR_TYPES, upper=True), + cv.Optional(CONF_AUTO_MODE, default=True): cv.boolean, + cv.Optional(CONF_GAIN, default="1X"): cv.enum(ALS_GAINS, upper=True), + cv.Optional( + CONF_INTEGRATION_TIME, default="100ms" + ): validate_integration_time, + cv.Optional(CONF_REPEAT, default="500ms"): validate_repeat_rate, + cv.Optional(CONF_GLASS_ATTENUATION_FACTOR, default=1.0): cv.float_range( + min=1.0 + ), + cv.Optional( + CONF_PS_COOLDOWN, default="5s" + ): cv.positive_time_period_seconds, + cv.Optional(CONF_PS_GAIN, default="16X"): cv.enum(PS_GAINS, upper=True), + cv.Optional(CONF_PS_HIGH_THRESHOLD, default=65535): cv.int_range( + min=0, max=65535 + ), + cv.Optional(CONF_PS_LOW_THRESHOLD, default=0): cv.int_range( + min=0, max=65535 + ), + cv.Optional(CONF_ON_PS_HIGH_THRESHOLD): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(LTRPsHighTrigger), + } + ), + cv.Optional(CONF_ON_PS_LOW_THRESHOLD): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(LTRPsLowTrigger), + } + ), + cv.Optional(CONF_AMBIENT_LIGHT): cv.maybe_simple_value( + sensor.sensor_schema( + unit_of_measurement=UNIT_LUX, + icon=ICON_BRIGHTNESS_6, + accuracy_decimals=1, + device_class=DEVICE_CLASS_ILLUMINANCE, + state_class=STATE_CLASS_MEASUREMENT, + ), + key=CONF_NAME, + ), + cv.Optional(CONF_INFRARED_COUNTS): cv.maybe_simple_value( + sensor.sensor_schema( + unit_of_measurement=UNIT_COUNTS, + icon=ICON_BRIGHTNESS_5, + accuracy_decimals=0, + device_class=DEVICE_CLASS_ILLUMINANCE, + state_class=STATE_CLASS_MEASUREMENT, + ), + key=CONF_NAME, + ), + cv.Optional(CONF_FULL_SPECTRUM_COUNTS): cv.maybe_simple_value( + sensor.sensor_schema( + unit_of_measurement=UNIT_COUNTS, + icon=ICON_BRIGHTNESS_7, + accuracy_decimals=0, + device_class=DEVICE_CLASS_ILLUMINANCE, + state_class=STATE_CLASS_MEASUREMENT, + ), + key=CONF_NAME, + ), + cv.Optional(CONF_PS_COUNTS): cv.maybe_simple_value( + sensor.sensor_schema( + unit_of_measurement=UNIT_COUNTS, + icon=ICON_PROXIMITY, + accuracy_decimals=0, + device_class=DEVICE_CLASS_DISTANCE, + state_class=STATE_CLASS_MEASUREMENT, + ), + key=CONF_NAME, + ), + cv.Optional(CONF_ACTUAL_GAIN): cv.maybe_simple_value( + sensor.sensor_schema( + icon=ICON_GAIN, + accuracy_decimals=0, + device_class=DEVICE_CLASS_ILLUMINANCE, + state_class=STATE_CLASS_MEASUREMENT, + ), + key=CONF_NAME, + ), + cv.Optional(CONF_ACTUAL_INTEGRATION_TIME): cv.maybe_simple_value( + sensor.sensor_schema( + unit_of_measurement=UNIT_MILLISECOND, + icon=ICON_TIMER, + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, + ), + key=CONF_NAME, + ), + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x29)), + validate_time_and_repeat_rate, +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await i2c.register_i2c_device(var, config) + + if als_config := config.get(CONF_AMBIENT_LIGHT): + sens = await sensor.new_sensor(als_config) + cg.add(var.set_ambient_light_sensor(sens)) + + if infrared_cnt_config := config.get(CONF_INFRARED_COUNTS): + sens = await sensor.new_sensor(infrared_cnt_config) + cg.add(var.set_infrared_counts_sensor(sens)) + + if full_spect_cnt_config := config.get(CONF_FULL_SPECTRUM_COUNTS): + sens = await sensor.new_sensor(full_spect_cnt_config) + cg.add(var.set_full_spectrum_counts_sensor(sens)) + + if act_gain_config := config.get(CONF_ACTUAL_GAIN): + sens = await sensor.new_sensor(act_gain_config) + cg.add(var.set_actual_gain_sensor(sens)) + + if act_itime_config := config.get(CONF_ACTUAL_INTEGRATION_TIME): + sens = await sensor.new_sensor(act_itime_config) + cg.add(var.set_actual_integration_time_sensor(sens)) + + if prox_cnt_config := config.get(CONF_PS_COUNTS): + sens = await sensor.new_sensor(prox_cnt_config) + cg.add(var.set_proximity_counts_sensor(sens)) + + for prox_high_tr in config.get(CONF_ON_PS_HIGH_THRESHOLD, []): + trigger = cg.new_Pvariable(prox_high_tr[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [], prox_high_tr) + + for prox_low_tr in config.get(CONF_ON_PS_LOW_THRESHOLD, []): + trigger = cg.new_Pvariable(prox_low_tr[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [], prox_low_tr) + + cg.add(var.set_ltr_type(config[CONF_TYPE])) + + cg.add(var.set_als_auto_mode(config[CONF_AUTO_MODE])) + cg.add(var.set_als_gain(config[CONF_GAIN])) + cg.add(var.set_als_integration_time(config[CONF_INTEGRATION_TIME])) + cg.add(var.set_als_meas_repeat_rate(config[CONF_REPEAT])) + cg.add(var.set_als_glass_attenuation_factor(config[CONF_GLASS_ATTENUATION_FACTOR])) + + cg.add(var.set_ps_cooldown_time_s(config[CONF_PS_COOLDOWN])) + cg.add(var.set_ps_gain(config[CONF_PS_GAIN])) + cg.add(var.set_ps_high_threshold(config[CONF_PS_HIGH_THRESHOLD])) + cg.add(var.set_ps_low_threshold(config[CONF_PS_LOW_THRESHOLD])) diff --git a/esphome/components/veml7700/sensor.py b/esphome/components/veml7700/sensor.py index 7ce05b47e4..7b0f75e70c 100644 --- a/esphome/components/veml7700/sensor.py +++ b/esphome/components/veml7700/sensor.py @@ -3,6 +3,7 @@ import esphome.config_validation as cv from esphome.components import i2c, sensor from esphome.const import ( CONF_ACTUAL_GAIN, + CONF_AMBIENT_LIGHT, CONF_AUTO_MODE, CONF_FULL_SPECTRUM, CONF_GAIN, @@ -11,13 +12,13 @@ from esphome.const import ( CONF_INFRARED, CONF_INTEGRATION_TIME, CONF_NAME, - UNIT_LUX, - UNIT_MILLISECOND, + DEVICE_CLASS_ILLUMINANCE, ICON_BRIGHTNESS_5, ICON_BRIGHTNESS_6, ICON_TIMER, - DEVICE_CLASS_ILLUMINANCE, STATE_CLASS_MEASUREMENT, + UNIT_LUX, + UNIT_MILLISECOND, ) CODEOWNERS = ["@latonita"] @@ -28,7 +29,6 @@ ICON_MULTIPLICATION = "mdi:multiplication" ICON_BRIGHTNESS_7 = "mdi:brightness-7" CONF_ACTUAL_INTEGRATION_TIME = "actual_integration_time" -CONF_AMBIENT_LIGHT = "ambient_light" CONF_AMBIENT_LIGHT_COUNTS = "ambient_light_counts" CONF_FULL_SPECTRUM_COUNTS = "full_spectrum_counts" CONF_LUX_COMPENSATION = "lux_compensation" diff --git a/esphome/const.py b/esphome/const.py index 1aafffb5d0..ed0a9b1f64 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -49,6 +49,7 @@ CONF_AFTER = "after" CONF_ALLOW_OTHER_USES = "allow_other_uses" CONF_ALPHA = "alpha" CONF_ALTITUDE = "altitude" +CONF_AMBIENT_LIGHT = "ambient_light" CONF_ANALOG = "analog" CONF_AND = "and" CONF_ANGLE = "angle" diff --git a/tests/components/ltr_als_ps/common.yaml b/tests/components/ltr_als_ps/common.yaml new file mode 100644 index 0000000000..aa5c8abed7 --- /dev/null +++ b/tests/components/ltr_als_ps/common.yaml @@ -0,0 +1,11 @@ +sensor: + - platform: ltr_als_ps + address: 0x23 + i2c_id: i2c_als_ps + gain: 1x + integration_time: 100ms + ps_cooldown: 5 s + ambient_light: "Ambient light" + full_spectrum_counts: "Full spectrum counts" + infrared_counts: "Infrared counts" + actual_gain: "Actual gain" diff --git a/tests/components/ltr_als_ps/test.esp32-c3-idf.yaml b/tests/components/ltr_als_ps/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..d64d70f018 --- /dev/null +++ b/tests/components/ltr_als_ps/test.esp32-c3-idf.yaml @@ -0,0 +1,6 @@ +i2c: + - id: i2c_als_ps + scl: 5 + sda: 4 + +<<: !include common.yaml diff --git a/tests/components/ltr_als_ps/test.esp32-c3.yaml b/tests/components/ltr_als_ps/test.esp32-c3.yaml new file mode 100644 index 0000000000..d64d70f018 --- /dev/null +++ b/tests/components/ltr_als_ps/test.esp32-c3.yaml @@ -0,0 +1,6 @@ +i2c: + - id: i2c_als_ps + scl: 5 + sda: 4 + +<<: !include common.yaml diff --git a/tests/components/ltr_als_ps/test.esp32-idf.yaml b/tests/components/ltr_als_ps/test.esp32-idf.yaml new file mode 100644 index 0000000000..2349292a64 --- /dev/null +++ b/tests/components/ltr_als_ps/test.esp32-idf.yaml @@ -0,0 +1,6 @@ +i2c: + - id: i2c_als_ps + scl: 16 + sda: 17 + +<<: !include common.yaml diff --git a/tests/components/ltr_als_ps/test.esp32.yaml b/tests/components/ltr_als_ps/test.esp32.yaml new file mode 100644 index 0000000000..2349292a64 --- /dev/null +++ b/tests/components/ltr_als_ps/test.esp32.yaml @@ -0,0 +1,6 @@ +i2c: + - id: i2c_als_ps + scl: 16 + sda: 17 + +<<: !include common.yaml diff --git a/tests/components/ltr_als_ps/test.esp8266.yaml b/tests/components/ltr_als_ps/test.esp8266.yaml new file mode 100644 index 0000000000..d64d70f018 --- /dev/null +++ b/tests/components/ltr_als_ps/test.esp8266.yaml @@ -0,0 +1,6 @@ +i2c: + - id: i2c_als_ps + scl: 5 + sda: 4 + +<<: !include common.yaml diff --git a/tests/components/ltr_als_ps/test.rp2040.yaml b/tests/components/ltr_als_ps/test.rp2040.yaml new file mode 100644 index 0000000000..d64d70f018 --- /dev/null +++ b/tests/components/ltr_als_ps/test.rp2040.yaml @@ -0,0 +1,6 @@ +i2c: + - id: i2c_als_ps + scl: 5 + sda: 4 + +<<: !include common.yaml From 2894a138e78418d2f61845fca6832053ca48292a Mon Sep 17 00:00:00 2001 From: NonaSuomy Date: Fri, 31 May 2024 01:13:11 -0400 Subject: [PATCH 27/46] Update const.py added missing millimeter (#6834) * Update const.py added missing millimeter Added missing millimeter * Fixed UNIT_MILLIMETER in multiple locations. --- esphome/components/a02yyuw/sensor.py | 4 ++-- esphome/components/hydreon_rgxx/sensor.py | 8 ++++---- esphome/components/mopeka_pro_check/sensor.py | 3 +-- esphome/components/mopeka_std_check/sensor.py | 3 +-- esphome/const.py | 1 + 5 files changed, 9 insertions(+), 10 deletions(-) diff --git a/esphome/components/a02yyuw/sensor.py b/esphome/components/a02yyuw/sensor.py index 5232b04546..d491a51be9 100644 --- a/esphome/components/a02yyuw/sensor.py +++ b/esphome/components/a02yyuw/sensor.py @@ -4,11 +4,11 @@ from esphome.const import ( STATE_CLASS_MEASUREMENT, ICON_ARROW_EXPAND_VERTICAL, DEVICE_CLASS_DISTANCE, + UNIT_MILLIMETER, ) CODEOWNERS = ["@TH-Braemer"] DEPENDENCIES = ["uart"] -UNIT_MILLIMETERS = "mm" a02yyuw_ns = cg.esphome_ns.namespace("a02yyuw") A02yyuwComponent = a02yyuw_ns.class_( @@ -17,7 +17,7 @@ A02yyuwComponent = a02yyuw_ns.class_( CONFIG_SCHEMA = sensor.sensor_schema( A02yyuwComponent, - unit_of_measurement=UNIT_MILLIMETERS, + unit_of_measurement=UNIT_MILLIMETER, icon=ICON_ARROW_EXPAND_VERTICAL, accuracy_decimals=0, state_class=STATE_CLASS_MEASUREMENT, diff --git a/esphome/components/hydreon_rgxx/sensor.py b/esphome/components/hydreon_rgxx/sensor.py index 72b74bf624..fb2099c85e 100644 --- a/esphome/components/hydreon_rgxx/sensor.py +++ b/esphome/components/hydreon_rgxx/sensor.py @@ -12,13 +12,13 @@ from esphome.const import ( STATE_CLASS_MEASUREMENT, STATE_CLASS_TOTAL_INCREASING, UNIT_CELSIUS, + UNIT_MILLIMETER, ICON_THERMOMETER, ) from . import RGModel, RG15Resolution, HydreonRGxxComponent UNIT_INTENSITY = "intensity" -UNIT_MILLIMETERS = "mm" UNIT_MILLIMETERS_PER_HOUR = "mm/h" CONF_ACC = "acc" @@ -85,19 +85,19 @@ CONFIG_SCHEMA = cv.All( ), cv.Optional(CONF_RESOLUTION): cv.enum(RG15_RESOLUTION, upper=False), cv.Optional(CONF_ACC): sensor.sensor_schema( - unit_of_measurement=UNIT_MILLIMETERS, + unit_of_measurement=UNIT_MILLIMETER, accuracy_decimals=2, device_class=DEVICE_CLASS_PRECIPITATION, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_EVENT_ACC): sensor.sensor_schema( - unit_of_measurement=UNIT_MILLIMETERS, + unit_of_measurement=UNIT_MILLIMETER, accuracy_decimals=2, device_class=DEVICE_CLASS_PRECIPITATION, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_TOTAL_ACC): sensor.sensor_schema( - unit_of_measurement=UNIT_MILLIMETERS, + unit_of_measurement=UNIT_MILLIMETER, accuracy_decimals=2, device_class=DEVICE_CLASS_PRECIPITATION, state_class=STATE_CLASS_TOTAL_INCREASING, diff --git a/esphome/components/mopeka_pro_check/sensor.py b/esphome/components/mopeka_pro_check/sensor.py index 51a515ef0c..0ba33e94de 100644 --- a/esphome/components/mopeka_pro_check/sensor.py +++ b/esphome/components/mopeka_pro_check/sensor.py @@ -12,6 +12,7 @@ from esphome.const import ( CONF_TEMPERATURE, DEVICE_CLASS_TEMPERATURE, UNIT_CELSIUS, + UNIT_MILLIMETER, STATE_CLASS_MEASUREMENT, CONF_BATTERY_LEVEL, DEVICE_CLASS_BATTERY, @@ -25,8 +26,6 @@ ICON_PROPANE_TANK = "mdi:propane-tank" TANK_TYPE_CUSTOM = "CUSTOM" -UNIT_MILLIMETER = "mm" - def small_distance(value): """small_distance is stored in mm""" diff --git a/esphome/components/mopeka_std_check/sensor.py b/esphome/components/mopeka_std_check/sensor.py index bbba798e95..ac745cf3d5 100644 --- a/esphome/components/mopeka_std_check/sensor.py +++ b/esphome/components/mopeka_std_check/sensor.py @@ -12,6 +12,7 @@ from esphome.const import ( CONF_TEMPERATURE, DEVICE_CLASS_TEMPERATURE, UNIT_CELSIUS, + UNIT_MILLIMETER, STATE_CLASS_MEASUREMENT, CONF_BATTERY_LEVEL, DEVICE_CLASS_BATTERY, @@ -26,8 +27,6 @@ ICON_PROPANE_TANK = "mdi:propane-tank" TANK_TYPE_CUSTOM = "CUSTOM" -UNIT_MILLIMETER = "mm" - def small_distance(value): """small_distance is stored in mm""" diff --git a/esphome/const.py b/esphome/const.py index ed0a9b1f64..dcb353ab1c 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1035,6 +1035,7 @@ UNIT_MICROSIEMENS_PER_CENTIMETER = "ยตS/cm" UNIT_MICROSILVERTS_PER_HOUR = "ยตSv/h" UNIT_MICROTESLA = "ยตT" UNIT_MILLIGRAMS_PER_CUBIC_METER = "mg/mยณ" +UNIT_MILLIMETER = "mm" UNIT_MILLISECOND = "ms" UNIT_MILLISIEMENS_PER_CENTIMETER = "mS/cm" UNIT_MINUTE = "min" From 1f301df51d4389f16e6a059c2ac87186c9631be8 Mon Sep 17 00:00:00 2001 From: Keith Burzinski Date: Fri, 31 May 2024 00:49:11 -0500 Subject: [PATCH 28/46] Fix log message in VA for IDF 5 (#6839) --- esphome/components/voice_assistant/voice_assistant.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/voice_assistant/voice_assistant.cpp b/esphome/components/voice_assistant/voice_assistant.cpp index c2fd14fbd5..e4fd2f2e9d 100644 --- a/esphome/components/voice_assistant/voice_assistant.cpp +++ b/esphome/components/voice_assistant/voice_assistant.cpp @@ -815,7 +815,7 @@ void VoiceAssistant::on_timer_event(const api::VoiceAssistantTimerEventResponse }; this->timers_[timer.id] = timer; ESP_LOGD(TAG, "Timer Event"); - ESP_LOGD(TAG, " Type: %d", msg.event_type); + ESP_LOGD(TAG, " Type: %" PRId32, msg.event_type); ESP_LOGD(TAG, " %s", timer.to_string().c_str()); switch (msg.event_type) { From 41e13fa6f437cac86916a99cf45ef8ce1a627d44 Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Fri, 31 May 2024 17:49:48 -0700 Subject: [PATCH 29/46] Replace random non-ascii-print characters with standard substitutes (#6840) --- esphome/components/api/custom_api_device.h | 2 +- esphome/components/ethernet/ethernet_component.cpp | 8 ++++---- esphome/components/feedback/feedback_cover.cpp | 4 ++-- esphome/components/max6956/max6956.h | 2 +- esphome/components/ntc/sensor.py | 2 +- esphome/components/wifi/wifi_component.cpp | 2 +- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/esphome/components/api/custom_api_device.h b/esphome/components/api/custom_api_device.h index 9f125a6149..845a35fc54 100644 --- a/esphome/components/api/custom_api_device.h +++ b/esphome/components/api/custom_api_device.h @@ -105,7 +105,7 @@ class CustomAPIDevice { /** Subscribe to the state (or attribute state) of an entity from Home Assistant. * * Usage: - *รฅ + * * ```cpp * void setup() override { * subscribe_homeassistant_state(&CustomNativeAPI::on_state_changed, "sensor.weather_forecast"); diff --git a/esphome/components/ethernet/ethernet_component.cpp b/esphome/components/ethernet/ethernet_component.cpp index 2a4e5cbad3..087a3b1750 100644 --- a/esphome/components/ethernet/ethernet_component.cpp +++ b/esphome/components/ethernet/ethernet_component.cpp @@ -576,11 +576,11 @@ void EthernetComponent::ksz8081_set_clock_reference_(esp_eth_mac_t *mac) { /* * Bit 7 is `RMII Reference Clock Select`. Default is `0`. * KSZ8081RNA: - * 0 - clock input to XI (Pin 8) is 25 MHz for RMII โ€“ 25 MHz clock mode. - * 1 - clock input to XI (Pin 8) is 50 MHz for RMII โ€“ 50 MHz clock mode. + * 0 - clock input to XI (Pin 8) is 25 MHz for RMII - 25 MHz clock mode. + * 1 - clock input to XI (Pin 8) is 50 MHz for RMII - 50 MHz clock mode. * KSZ8081RND: - * 0 - clock input to XI (Pin 8) is 50 MHz for RMII โ€“ 50 MHz clock mode. - * 1 - clock input to XI (Pin 8) is 25 MHz (driven clock only, not a crystal) for RMII โ€“ 25 MHz clock mode. + * 0 - clock input to XI (Pin 8) is 50 MHz for RMII - 50 MHz clock mode. + * 1 - clock input to XI (Pin 8) is 25 MHz (driven clock only, not a crystal) for RMII - 25 MHz clock mode. */ if ((phy_control_2 & (1 << 7)) != (1 << 7)) { phy_control_2 |= 1 << 7; diff --git a/esphome/components/feedback/feedback_cover.cpp b/esphome/components/feedback/feedback_cover.cpp index 117c626f58..fa3166ba65 100644 --- a/esphome/components/feedback/feedback_cover.cpp +++ b/esphome/components/feedback/feedback_cover.cpp @@ -244,7 +244,7 @@ void FeedbackCover::loop() { // update current position at requested interval, regardless of who started the movement // so that we also update UI if there was an external movement - // donยดt save intermediate positions + // don't save intermediate positions if (now - this->last_publish_time_ > this->update_interval_) { this->publish_state(false); this->last_publish_time_ = now; @@ -274,7 +274,7 @@ void FeedbackCover::control(const CoverCall &call) { if (pos == this->position) { // already at target, - // for covers with built in end stop, if we donยดt have sensors we should send the command again + // for covers with built in end stop, if we don't have sensors we should send the command again // to make sure the assumed state is not wrong if (this->has_built_in_endstop_ && ((pos == COVER_OPEN #ifdef USE_BINARY_SENSOR diff --git a/esphome/components/max6956/max6956.h b/esphome/components/max6956/max6956.h index 141164ab30..759fa45b07 100644 --- a/esphome/components/max6956/max6956.h +++ b/esphome/components/max6956/max6956.h @@ -29,7 +29,7 @@ enum MAX6956GPIORegisters { MAX6956_PORT_CONFIG_START = 0x09, // Port Configuration P7, P6, P5, P4 MAX6956_CURRENT_START = 0x12, // Current054 MAX6956_1PORT_VALUE_START = 0x20, // Port 0 only (virtual port, no action) - MAX6956_8PORTS_VALUE_START = 0x44, // 8 ports 4โ€“11 (data bits D0โ€“D7) + MAX6956_8PORTS_VALUE_START = 0x44, // 8 ports 4-11 (data bits D0-D7) }; enum MAX6956GPIOFlag { FLAG_LED = 0x20 }; diff --git a/esphome/components/ntc/sensor.py b/esphome/components/ntc/sensor.py index 06fc55fc43..961511fe00 100644 --- a/esphome/components/ntc/sensor.py +++ b/esphome/components/ntc/sensor.py @@ -100,7 +100,7 @@ def process_calibration(value): elif isinstance(value, list): if len(value) != 3: raise cv.Invalid( - "Steinhartโ€“Hart Calibration must consist of exactly three values" + "Steinhart-Hart Calibration must consist of exactly three values" ) value = cv.Schema([validate_calibration_parameter])(value) a, b, c = calc_steinhart_hart(value) diff --git a/esphome/components/wifi/wifi_component.cpp b/esphome/components/wifi/wifi_component.cpp index 2f34f2b05b..1359e0b7c5 100644 --- a/esphome/components/wifi/wifi_component.cpp +++ b/esphome/components/wifi/wifi_component.cpp @@ -58,7 +58,7 @@ void WiFiComponent::setup() { void WiFiComponent::start() { ESP_LOGCONFIG(TAG, "Starting WiFi..."); - ESP_LOGCONFIG(TAG, " Localย MAC: %s", get_mac_address_pretty().c_str()); + ESP_LOGCONFIG(TAG, " Local MAC: %s", get_mac_address_pretty().c_str()); this->last_connected_ = millis(); uint32_t hash = this->has_sta() ? fnv1_hash(App.get_compilation_time()) : 88491487UL; From 2beb1f033611c0984c3c4d11734e967cc8a1416f Mon Sep 17 00:00:00 2001 From: Simone Rossetto Date: Sat, 1 Jun 2024 04:36:51 +0200 Subject: [PATCH 30/46] Wireguard support for bk72 microcontrollers (#6842) * Bump esp_wireguard to v0.4.1 * add support for bk72 microcontrollers (thanks to kuba2k2) * fix compilation error using esp-idf v5 (thanks to kbx81) * fix crash on vpn disconnection with alive tcp connection (thanks to jefftharris) * Disable ipv6 for bk72 wireguard test * Completely remove ipv6 entry from bk72 wg test --- esphome/components/wireguard/__init__.py | 2 +- platformio.ini | 8 +-- tests/components/wireguard/test.bk72xx.yaml | 59 +++++++++++++++++++++ 3 files changed, 65 insertions(+), 4 deletions(-) create mode 100644 tests/components/wireguard/test.bk72xx.yaml diff --git a/esphome/components/wireguard/__init__.py b/esphome/components/wireguard/__init__.py index 7612c7d964..177ad6d356 100644 --- a/esphome/components/wireguard/__init__.py +++ b/esphome/components/wireguard/__init__.py @@ -122,7 +122,7 @@ async def to_code(config): # the '+1' modifier is relative to the device's own address that will # be automatically added to the provided list. cg.add_build_flag(f"-DCONFIG_WIREGUARD_MAX_SRC_IPS={len(allowed_ips) + 1}") - cg.add_library("droscy/esp_wireguard", "0.4.0") + cg.add_library("droscy/esp_wireguard", "0.4.1") await cg.register_component(var, config) diff --git a/platformio.ini b/platformio.ini index fd0ae7c6d8..6b34b2f05d 100644 --- a/platformio.ini +++ b/platformio.ini @@ -94,7 +94,7 @@ lib_deps = ESP8266mDNS ; mdns (Arduino built-in) DNSServer ; captive_portal (Arduino built-in) crankyoldgit/IRremoteESP8266@~2.8.4 ; heatpumpir - droscy/esp_wireguard@0.4.0 ; wireguard + droscy/esp_wireguard@0.4.1 ; wireguard build_flags = ${common:arduino.build_flags} -Wno-nonnull-compare @@ -124,7 +124,7 @@ lib_deps = DNSServer ; captive_portal (Arduino built-in) esphome/ESP32-audioI2S@2.0.7 ; i2s_audio crankyoldgit/IRremoteESP8266@~2.8.4 ; heatpumpir - droscy/esp_wireguard@0.4.0 ; wireguard + droscy/esp_wireguard@0.4.1 ; wireguard build_flags = ${common:arduino.build_flags} -DUSE_ESP32 @@ -143,7 +143,7 @@ framework = espidf lib_deps = ${common:idf.lib_deps} espressif/esp32-camera@1.0.0 ; esp32_camera - droscy/esp_wireguard@0.4.0 ; wireguard + droscy/esp_wireguard@0.4.1 ; wireguard build_flags = ${common:idf.build_flags} -Wno-nonnull-compare @@ -174,6 +174,8 @@ build_flags = extends = common:arduino platform = libretiny framework = arduino +lib_deps = + droscy/esp_wireguard@0.4.1 ; wireguard build_flags = ${common:arduino.build_flags} -DUSE_LIBRETINY diff --git a/tests/components/wireguard/test.bk72xx.yaml b/tests/components/wireguard/test.bk72xx.yaml new file mode 100644 index 0000000000..85325139a9 --- /dev/null +++ b/tests/components/wireguard/test.bk72xx.yaml @@ -0,0 +1,59 @@ +wifi: + ssid: "MySSID1" + password: "password1" + +time: + - platform: sntp + +wireguard: + address: 172.16.34.100 + netmask: 255.255.255.0 + # NEVER use the following keys for your vpn, they are now public! + private_key: wPBMxtNYH3mChicrbpsRpZIasIdPq3yZuthn23FbGG8= + peer_public_key: Hs2JfikvYU03/Kv3YoAs1hrUIPPTEkpsZKSPUljE9yc= + peer_preshared_key: 20fjM5GRnSolGPC5SRj9ljgIUyQfruv0B0bvLl3Yt60= + peer_endpoint: wg.server.example + peer_persistent_keepalive: 25s + peer_allowed_ips: + - 172.16.34.0/24 + - 192.168.4.0/24 + +binary_sensor: + - platform: wireguard + status: + name: 'WireGuard Status' + enabled: + name: 'WireGuard Enabled' + +sensor: + - platform: wireguard + latest_handshake: + name: 'WireGuard Latest Handshake' + +text_sensor: + - platform: wireguard + address: + name: 'WireGuard Address' + +button: + - platform: template + name: 'Toggle WireGuard' + entity_category: config + on_press: + - if: + condition: wireguard.enabled + then: + - wireguard.disable: + else: + - wireguard.enable: + + - platform: template + name: 'Log WireGuard status' + entity_category: config + on_press: + - if: + condition: wireguard.peer_online + then: + - logger.log: 'wireguard remote peer is online' + else: + - logger.log: 'wireguard remote peer is offline' From 2353b2b5e1e66874a84f5f0e1e2ae0fff5780407 Mon Sep 17 00:00:00 2001 From: "Kevin P. Fleming" Date: Sat, 1 Jun 2024 18:40:50 -0400 Subject: [PATCH 31/46] Add messages when WiFi and Ethernet components set 'warning' flag. (#6850) --- esphome/components/ethernet/ethernet_component.cpp | 2 +- esphome/components/wifi/wifi_component.cpp | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/esphome/components/ethernet/ethernet_component.cpp b/esphome/components/ethernet/ethernet_component.cpp index 087a3b1750..7672dc2dcb 100644 --- a/esphome/components/ethernet/ethernet_component.cpp +++ b/esphome/components/ethernet/ethernet_component.cpp @@ -410,7 +410,7 @@ void EthernetComponent::start_connect_() { global_eth_component->ipv6_count_ = 0; #endif /* USE_NETWORK_IPV6 */ this->connect_begin_ = millis(); - this->status_set_warning(); + this->status_set_warning("waiting for IP configuration"); esp_err_t err; err = esp_netif_set_hostname(this->eth_netif_, App.get_name().c_str()); diff --git a/esphome/components/wifi/wifi_component.cpp b/esphome/components/wifi/wifi_component.cpp index 1359e0b7c5..8c40f87879 100644 --- a/esphome/components/wifi/wifi_component.cpp +++ b/esphome/components/wifi/wifi_component.cpp @@ -135,7 +135,7 @@ void WiFiComponent::loop() { switch (this->state_) { case WIFI_COMPONENT_STATE_COOLDOWN: { - this->status_set_warning(); + this->status_set_warning("waiting to reconnect"); if (millis() - this->action_started_ > 5000) { if (this->fast_connect_ || this->retry_hidden_) { this->start_connecting(this->sta_[0], false); @@ -146,13 +146,13 @@ void WiFiComponent::loop() { break; } case WIFI_COMPONENT_STATE_STA_SCANNING: { - this->status_set_warning(); + this->status_set_warning("scanning for networks"); this->check_scanning_finished(); break; } case WIFI_COMPONENT_STATE_STA_CONNECTING: case WIFI_COMPONENT_STATE_STA_CONNECTING_2: { - this->status_set_warning(); + this->status_set_warning("associating to network"); this->check_connecting_finished(); break; } From b8d2a6f574e4f49d12d3e677220c9dd92a364aae Mon Sep 17 00:00:00 2001 From: Jimmy Hedman Date: Mon, 3 Jun 2024 05:23:49 +0200 Subject: [PATCH 32/46] [sntp] fix for ESP-IDF > 5.0 (#6769) * Make sntp work with ESP-IDF >= 5.0 * Set operatingmode on other than ESP-IDF --------- Co-authored-by: Keith Burzinski --- esphome/components/sntp/sntp_component.cpp | 23 +++++++++------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/esphome/components/sntp/sntp_component.cpp b/esphome/components/sntp/sntp_component.cpp index 6a60e8d5c1..a0e791be75 100644 --- a/esphome/components/sntp/sntp_component.cpp +++ b/esphome/components/sntp/sntp_component.cpp @@ -1,16 +1,11 @@ #include "sntp_component.h" #include "esphome/core/log.h" -#if defined(USE_ESP32) || defined(USE_LIBRETINY) -#include "lwip/apps/sntp.h" #ifdef USE_ESP_IDF #include "esp_sntp.h" -#endif -#endif -#ifdef USE_ESP8266 +#elif USE_ESP8266 #include "sntp.h" -#endif -#ifdef USE_RP2040 +#else #include "lwip/apps/sntp.h" #endif @@ -27,14 +22,14 @@ static const char *const TAG = "sntp"; void SNTPComponent::setup() { #ifndef USE_HOST ESP_LOGCONFIG(TAG, "Setting up SNTP..."); -#if defined(USE_ESP32) || defined(USE_LIBRETINY) - if (sntp_enabled()) { - sntp_stop(); +#if defined(USE_ESP_IDF) + if (esp_sntp_enabled()) { + esp_sntp_stop(); } - sntp_setoperatingmode(SNTP_OPMODE_POLL); -#endif -#ifdef USE_ESP8266 + esp_sntp_setoperatingmode(ESP_SNTP_OPMODE_POLL); +#else sntp_stop(); + sntp_setoperatingmode(SNTP_OPMODE_POLL); #endif sntp_setservername(0, strdup(this->server_1_.c_str())); @@ -45,7 +40,7 @@ void SNTPComponent::setup() { sntp_setservername(2, strdup(this->server_3_.c_str())); } #ifdef USE_ESP_IDF - sntp_set_sync_interval(this->get_update_interval()); + esp_sntp_set_sync_interval(this->get_update_interval()); #endif sntp_init(); From 05491e756bb76c43c669009ac7afb5da877b5ee1 Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Mon, 3 Jun 2024 18:34:47 -0700 Subject: [PATCH 33/46] Avoid unsafe git error when container user and file config volume permissions don't match (#6843) --- docker/Dockerfile | 3 +++ script/setup | 5 ----- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 3c661b7e1c..f6234235f7 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -100,6 +100,9 @@ RUN --mount=type=tmpfs,target=/root/.cargo if [ "$TARGETARCH$TARGETVARIANT" = "a --break-system-packages --no-cache-dir -r /requirements.txt -r /requirements_optional.txt \ && /platformio_install_deps.py /platformio.ini --libraries +# Avoid unsafe git error when container user and file config volume permissions don't match +RUN git config --system --add safe.directory '/config/*' + # ======================= docker-type image ======================= FROM base AS docker diff --git a/script/setup b/script/setup index 1a18a6a9ea..aeb1b39bc1 100755 --- a/script/setup +++ b/script/setup @@ -13,11 +13,6 @@ if [ ! -n "$DEVCONTAINER" ] && [ ! -n "$VIRTUAL_ENV" ] && [ ! "$ESPHOME_NO_VENV" source $location fi -# Avoid unsafe git error when running inside devcontainer -if [ -n "$DEVCONTAINER" ]; then - git config --global --add safe.directory "$PWD" -fi - pip3 install -r requirements.txt -r requirements_optional.txt -r requirements_test.txt -r requirements_dev.txt pip3 install setuptools wheel pip3 install -e ".[dev,test,displays]" --config-settings editable_mode=compat From 78b48209aa2456e05bc2c94357e58809d9c7403f Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Mon, 3 Jun 2024 19:57:05 -0700 Subject: [PATCH 34/46] Add Ethernet MAC address to ethernet_info (#6835) --- .../ethernet/ethernet_component.cpp | 54 +++++++++++++------ .../components/ethernet/ethernet_component.h | 4 ++ .../ethernet_info_text_sensor.cpp | 1 + .../ethernet_info/ethernet_info_text_sensor.h | 7 +++ .../components/ethernet_info/text_sensor.py | 11 ++++ tests/components/ethernet_info/common.yaml | 2 + 6 files changed, 63 insertions(+), 16 deletions(-) diff --git a/esphome/components/ethernet/ethernet_component.cpp b/esphome/components/ethernet/ethernet_component.cpp index 7672dc2dcb..6bb9732fef 100644 --- a/esphome/components/ethernet/ethernet_component.cpp +++ b/esphome/components/ethernet/ethernet_component.cpp @@ -28,6 +28,13 @@ EthernetComponent *global_eth_component; // NOLINT(cppcoreguidelines-avoid-non- return; \ } +#define ESPHL_ERROR_CHECK_RET(err, message, ret) \ + if ((err) != ESP_OK) { \ + ESP_LOGE(TAG, message ": (%d) %s", err, esp_err_to_name(err)); \ + this->mark_failed(); \ + return ret; \ + } + EthernetComponent::EthernetComponent() { global_eth_component = this; } void EthernetComponent::setup() { @@ -498,22 +505,9 @@ void EthernetComponent::dump_connect_params_() { } #endif /* USE_NETWORK_IPV6 */ - esp_err_t err; - - uint8_t mac[6]; - err = esp_eth_ioctl(this->eth_handle_, ETH_CMD_G_MAC_ADDR, &mac); - ESPHL_ERROR_CHECK(err, "ETH_CMD_G_MAC error"); - ESP_LOGCONFIG(TAG, " MAC Address: %02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); - - eth_duplex_t duplex_mode; - err = esp_eth_ioctl(this->eth_handle_, ETH_CMD_G_DUPLEX_MODE, &duplex_mode); - ESPHL_ERROR_CHECK(err, "ETH_CMD_G_DUPLEX_MODE error"); - ESP_LOGCONFIG(TAG, " Is Full Duplex: %s", YESNO(duplex_mode == ETH_DUPLEX_FULL)); - - eth_speed_t speed; - err = esp_eth_ioctl(this->eth_handle_, ETH_CMD_G_SPEED, &speed); - ESPHL_ERROR_CHECK(err, "ETH_CMD_G_SPEED error"); - ESP_LOGCONFIG(TAG, " Link Speed: %u", speed == ETH_SPEED_100M ? 100 : 10); + ESP_LOGCONFIG(TAG, " MAC Address: %s", this->get_eth_mac_address_pretty().c_str()); + ESP_LOGCONFIG(TAG, " Is Full Duplex: %s", YESNO(this->get_duplex_mode() == ETH_DUPLEX_FULL)); + ESP_LOGCONFIG(TAG, " Link Speed: %u", this->get_link_speed() == ETH_SPEED_100M ? 100 : 10); } #ifdef USE_ETHERNET_SPI @@ -546,6 +540,34 @@ std::string EthernetComponent::get_use_address() const { void EthernetComponent::set_use_address(const std::string &use_address) { this->use_address_ = use_address; } +void EthernetComponent::get_eth_mac_address_raw(uint8_t *mac) { + esp_err_t err; + err = esp_eth_ioctl(this->eth_handle_, ETH_CMD_G_MAC_ADDR, mac); + ESPHL_ERROR_CHECK(err, "ETH_CMD_G_MAC error"); +} + +std::string EthernetComponent::get_eth_mac_address_pretty() { + uint8_t mac[6]; + get_mac_address_raw(mac); + return str_snprintf("%02X:%02X:%02X:%02X:%02X:%02X", 17, mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); +} + +eth_duplex_t EthernetComponent::get_duplex_mode() { + esp_err_t err; + eth_duplex_t duplex_mode; + err = esp_eth_ioctl(this->eth_handle_, ETH_CMD_G_DUPLEX_MODE, &duplex_mode); + ESPHL_ERROR_CHECK_RET(err, "ETH_CMD_G_DUPLEX_MODE error", ETH_DUPLEX_HALF); + return duplex_mode; +} + +eth_speed_t EthernetComponent::get_link_speed() { + esp_err_t err; + eth_speed_t speed; + err = esp_eth_ioctl(this->eth_handle_, ETH_CMD_G_SPEED, &speed); + ESPHL_ERROR_CHECK_RET(err, "ETH_CMD_G_SPEED error", ETH_SPEED_10M); + return speed; +} + bool EthernetComponent::powerdown() { ESP_LOGI(TAG, "Powering down ethernet PHY"); if (this->phy_ == nullptr) { diff --git a/esphome/components/ethernet/ethernet_component.h b/esphome/components/ethernet/ethernet_component.h index 3705e3e06a..e57aa5fe12 100644 --- a/esphome/components/ethernet/ethernet_component.h +++ b/esphome/components/ethernet/ethernet_component.h @@ -74,6 +74,10 @@ class EthernetComponent : public Component { network::IPAddress get_dns_address(uint8_t num); std::string get_use_address() const; void set_use_address(const std::string &use_address); + void get_eth_mac_address_raw(uint8_t *mac); + std::string get_eth_mac_address_pretty(); + eth_duplex_t get_duplex_mode(); + eth_speed_t get_link_speed(); bool powerdown(); protected: diff --git a/esphome/components/ethernet_info/ethernet_info_text_sensor.cpp b/esphome/components/ethernet_info/ethernet_info_text_sensor.cpp index c8b2b5885b..329fb9113a 100644 --- a/esphome/components/ethernet_info/ethernet_info_text_sensor.cpp +++ b/esphome/components/ethernet_info/ethernet_info_text_sensor.cpp @@ -10,6 +10,7 @@ static const char *const TAG = "ethernet_info"; void IPAddressEthernetInfo::dump_config() { LOG_TEXT_SENSOR("", "EthernetInfo IPAddress", this); } void DNSAddressEthernetInfo::dump_config() { LOG_TEXT_SENSOR("", "EthernetInfo DNS Address", this); } +void MACAddressEthernetInfo::dump_config() { LOG_TEXT_SENSOR("", "EthernetInfo MAC Address", this); } } // namespace ethernet_info } // namespace esphome diff --git a/esphome/components/ethernet_info/ethernet_info_text_sensor.h b/esphome/components/ethernet_info/ethernet_info_text_sensor.h index 82a7dcf56e..94eed886e5 100644 --- a/esphome/components/ethernet_info/ethernet_info_text_sensor.h +++ b/esphome/components/ethernet_info/ethernet_info_text_sensor.h @@ -59,6 +59,13 @@ class DNSAddressEthernetInfo : public PollingComponent, public text_sensor::Text std::string last_results_; }; +class MACAddressEthernetInfo : public Component, public text_sensor::TextSensor { + public: + void setup() override { this->publish_state(ethernet::global_eth_component->get_eth_mac_address_pretty()); } + std::string unique_id() override { return get_mac_address() + "-ethernetinfo-mac"; } + void dump_config() override; +}; + } // namespace ethernet_info } // namespace esphome diff --git a/esphome/components/ethernet_info/text_sensor.py b/esphome/components/ethernet_info/text_sensor.py index 292673c182..a545475870 100644 --- a/esphome/components/ethernet_info/text_sensor.py +++ b/esphome/components/ethernet_info/text_sensor.py @@ -4,6 +4,7 @@ from esphome.components import text_sensor from esphome.const import ( CONF_IP_ADDRESS, CONF_DNS_ADDRESS, + CONF_MAC_ADDRESS, ENTITY_CATEGORY_DIAGNOSTIC, ) @@ -19,6 +20,10 @@ DNSAddressEthernetInfo = ethernet_info_ns.class_( "DNSAddressEthernetInfo", text_sensor.TextSensor, cg.PollingComponent ) +MACAddressEthernetInfo = ethernet_info_ns.class_( + "MACAddressEthernetInfo", text_sensor.TextSensor, cg.PollingComponent +) + CONFIG_SCHEMA = cv.Schema( { cv.Optional(CONF_IP_ADDRESS): text_sensor.text_sensor_schema( @@ -36,6 +41,9 @@ CONFIG_SCHEMA = cv.Schema( cv.Optional(CONF_DNS_ADDRESS): text_sensor.text_sensor_schema( DNSAddressEthernetInfo, entity_category=ENTITY_CATEGORY_DIAGNOSTIC ).extend(cv.polling_component_schema("1s")), + cv.Optional(CONF_MAC_ADDRESS): text_sensor.text_sensor_schema( + MACAddressEthernetInfo, entity_category=ENTITY_CATEGORY_DIAGNOSTIC + ), } ) @@ -51,3 +59,6 @@ async def to_code(config): if conf := config.get(CONF_DNS_ADDRESS): dns_info = await text_sensor.new_text_sensor(config[CONF_DNS_ADDRESS]) await cg.register_component(dns_info, config[CONF_DNS_ADDRESS]) + if conf := config.get(CONF_MAC_ADDRESS): + mac_info = await text_sensor.new_text_sensor(config[CONF_MAC_ADDRESS]) + await cg.register_component(mac_info, config[CONF_MAC_ADDRESS]) diff --git a/tests/components/ethernet_info/common.yaml b/tests/components/ethernet_info/common.yaml index dade4d7ca5..d9a6f515b1 100644 --- a/tests/components/ethernet_info/common.yaml +++ b/tests/components/ethernet_info/common.yaml @@ -17,3 +17,5 @@ text_sensor: name: IP Address dns_address: name: DNS Address + mac_address: + name: MAC Address From cdf83c5d8c1f8ff99d77b63b04177d253feecbb3 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Tue, 4 Jun 2024 13:09:46 +1000 Subject: [PATCH 35/46] Add host time platform; remove host support from sntp. (#6854) --- CODEOWNERS | 3 +- esphome/components/host/__init__.py | 2 +- esphome/components/host/time/__init__.py | 20 +++++++++++ esphome/components/host/time/host_time.h | 15 +++++++++ esphome/components/sntp/sntp_component.cpp | 4 +-- esphome/components/sntp/time.py | 39 ++++++++++++++++------ tests/components/host/common.yaml | 7 +--- tests/components/sntp/test.bk72xx.yaml | 1 + 8 files changed, 69 insertions(+), 22 deletions(-) create mode 100644 esphome/components/host/time/__init__.py create mode 100644 esphome/components/host/time/host_time.h create mode 100644 tests/components/sntp/test.bk72xx.yaml diff --git a/CODEOWNERS b/CODEOWNERS index 0dc5339193..82f61c3b80 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -167,7 +167,8 @@ esphome/components/homeassistant/* @OttoWinter esphome/components/honeywell_hih_i2c/* @Benichou34 esphome/components/honeywellabp/* @RubyBailey esphome/components/honeywellabp2_i2c/* @jpfaff -esphome/components/host/* @esphome/core +esphome/components/host/* @clydebarrow @esphome/core +esphome/components/host/time/* @clydebarrow esphome/components/hrxl_maxsonar_wr/* @netmikey esphome/components/hte501/* @Stock-M esphome/components/htu31d/* @betterengineering diff --git a/esphome/components/host/__init__.py b/esphome/components/host/__init__.py index 3bd3b6b172..39e418c9ea 100644 --- a/esphome/components/host/__init__.py +++ b/esphome/components/host/__init__.py @@ -16,7 +16,7 @@ from .const import KEY_HOST # force import gpio to register pin schema from .gpio import host_pin_to_code # noqa -CODEOWNERS = ["@esphome/core"] +CODEOWNERS = ["@esphome/core", "@clydebarrow"] AUTO_LOAD = ["network"] diff --git a/esphome/components/host/time/__init__.py b/esphome/components/host/time/__init__.py new file mode 100644 index 0000000000..76a88d98a1 --- /dev/null +++ b/esphome/components/host/time/__init__.py @@ -0,0 +1,20 @@ +import esphome.codegen as cg +from esphome.const import CONF_ID +import esphome.config_validation as cv +from esphome.components import time as time_ + +CODEOWNERS = ["@clydebarrow"] + +time_ns = cg.esphome_ns.namespace("host") +HostTime = time_ns.class_("HostTime", time_.RealTimeClock) +CONFIG_SCHEMA = time_.TIME_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(HostTime), + } +).extend(cv.COMPONENT_SCHEMA) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await time_.register_time(var, config) diff --git a/esphome/components/host/time/host_time.h b/esphome/components/host/time/host_time.h new file mode 100644 index 0000000000..4f1473b809 --- /dev/null +++ b/esphome/components/host/time/host_time.h @@ -0,0 +1,15 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/time/real_time_clock.h" + +namespace esphome { +namespace host { + +class HostTime : public time::RealTimeClock { + public: + void update() override {} +}; + +} // namespace host +} // namespace esphome diff --git a/esphome/components/sntp/sntp_component.cpp b/esphome/components/sntp/sntp_component.cpp index a0e791be75..4ded98d483 100644 --- a/esphome/components/sntp/sntp_component.cpp +++ b/esphome/components/sntp/sntp_component.cpp @@ -20,7 +20,6 @@ namespace sntp { static const char *const TAG = "sntp"; void SNTPComponent::setup() { -#ifndef USE_HOST ESP_LOGCONFIG(TAG, "Setting up SNTP..."); #if defined(USE_ESP_IDF) if (esp_sntp_enabled()) { @@ -44,7 +43,6 @@ void SNTPComponent::setup() { #endif sntp_init(); -#endif } void SNTPComponent::dump_config() { ESP_LOGCONFIG(TAG, "SNTP Time:"); @@ -54,7 +52,7 @@ void SNTPComponent::dump_config() { ESP_LOGCONFIG(TAG, " Timezone: '%s'", this->timezone_.c_str()); } void SNTPComponent::update() { -#if !defined(USE_ESP_IDF) && !defined(USE_HOST) +#if !defined(USE_ESP_IDF) // force resync if (sntp_enabled()) { sntp_stop(); diff --git a/esphome/components/sntp/time.py b/esphome/components/sntp/time.py index b1362f5421..7cc82e3dff 100644 --- a/esphome/components/sntp/time.py +++ b/esphome/components/sntp/time.py @@ -2,24 +2,41 @@ from esphome.components import time as time_ import esphome.config_validation as cv import esphome.codegen as cg from esphome.core import CORE -from esphome.const import CONF_ID, CONF_SERVERS - +from esphome.const import ( + CONF_ID, + CONF_SERVERS, + PLATFORM_ESP32, + PLATFORM_ESP8266, + PLATFORM_RP2040, + PLATFORM_RTL87XX, + PLATFORM_BK72XX, +) DEPENDENCIES = ["network"] sntp_ns = cg.esphome_ns.namespace("sntp") SNTPComponent = sntp_ns.class_("SNTPComponent", time_.RealTimeClock) - DEFAULT_SERVERS = ["0.pool.ntp.org", "1.pool.ntp.org", "2.pool.ntp.org"] -CONFIG_SCHEMA = time_.TIME_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(SNTPComponent), - cv.Optional(CONF_SERVERS, default=DEFAULT_SERVERS): cv.All( - cv.ensure_list(cv.Any(cv.domain, cv.hostname)), cv.Length(min=1, max=3) - ), - } -).extend(cv.COMPONENT_SCHEMA) +CONFIG_SCHEMA = cv.All( + time_.TIME_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(SNTPComponent), + cv.Optional(CONF_SERVERS, default=DEFAULT_SERVERS): cv.All( + cv.ensure_list(cv.Any(cv.domain, cv.hostname)), cv.Length(min=1, max=3) + ), + } + ).extend(cv.COMPONENT_SCHEMA), + cv.only_on( + [ + PLATFORM_ESP32, + PLATFORM_ESP8266, + PLATFORM_RP2040, + PLATFORM_BK72XX, + PLATFORM_RTL87XX, + ] + ), +) async def to_code(config): diff --git a/tests/components/host/common.yaml b/tests/components/host/common.yaml index 3d14c190a6..fca0c5d597 100644 --- a/tests/components/host/common.yaml +++ b/tests/components/host/common.yaml @@ -1,15 +1,10 @@ time: - - platform: sntp + - platform: host id: esptime timezone: Australia/Sydney logger: level: VERBOSE - logs: - lvgl: INFO - display: DEBUG - sensor: INFO - vnc: DEBUG host: mac_address: "62:23:45:AF:B3:DD" diff --git a/tests/components/sntp/test.bk72xx.yaml b/tests/components/sntp/test.bk72xx.yaml new file mode 100644 index 0000000000..dade44d145 --- /dev/null +++ b/tests/components/sntp/test.bk72xx.yaml @@ -0,0 +1 @@ +<<: !include common.yaml From 2d56d8d84feb8df4341725bfe0a409a44b5305a8 Mon Sep 17 00:00:00 2001 From: Keith Burzinski Date: Mon, 3 Jun 2024 22:10:44 -0500 Subject: [PATCH 36/46] [wireguard] Implement workaround for crash on IDF 5+ (#6846) --- esphome/components/wireguard/__init__.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/esphome/components/wireguard/__init__.py b/esphome/components/wireguard/__init__.py index 177ad6d356..5d5f620b51 100644 --- a/esphome/components/wireguard/__init__.py +++ b/esphome/components/wireguard/__init__.py @@ -7,7 +7,10 @@ from esphome.const import ( CONF_TIME_ID, CONF_ADDRESS, CONF_REBOOT_TIMEOUT, + KEY_CORE, + KEY_FRAMEWORK_VERSION, ) +from esphome.components.esp32 import CORE, add_idf_sdkconfig_option from esphome.components import time from esphome.core import TimePeriod from esphome import automation @@ -117,6 +120,13 @@ async def to_code(config): if config[CONF_REQUIRE_CONNECTION_TO_PROCEED]: cg.add(var.disable_auto_proceed()) + # Workaround for crash on IDF 5+ + # See https://github.com/trombik/esp_wireguard/issues/33#issuecomment-1568503651 + if CORE.using_esp_idf and CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] >= cv.Version( + 5, 0, 0 + ): + add_idf_sdkconfig_option("CONFIG_LWIP_PPP_SUPPORT", True) + # This flag is added here because the esp_wireguard library statically # set the size of its allowed_ips list at compile time using this value; # the '+1' modifier is relative to the device's own address that will From eb75778f84e32eed1f5a497471f86bb964906166 Mon Sep 17 00:00:00 2001 From: Keith Burzinski Date: Mon, 3 Jun 2024 22:18:20 -0500 Subject: [PATCH 37/46] [improv_serial] Fix for IDF 4.4.7 (#6855) --- esphome/components/improv_serial/improv_serial_component.cpp | 1 + esphome/components/improv_serial/improv_serial_component.h | 1 + 2 files changed, 2 insertions(+) diff --git a/esphome/components/improv_serial/improv_serial_component.cpp b/esphome/components/improv_serial/improv_serial_component.cpp index 40297bee68..2937720496 100644 --- a/esphome/components/improv_serial/improv_serial_component.cpp +++ b/esphome/components/improv_serial/improv_serial_component.cpp @@ -109,6 +109,7 @@ void ImprovSerialComponent::write_data_(std::vector &data) { #if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32S3) case logger::UART_SELECTION_USB_SERIAL_JTAG: usb_serial_jtag_write_bytes((char *) data.data(), data.size(), 20 / portTICK_PERIOD_MS); + usb_serial_jtag_ll_txfifo_flush(); // fixes for issue in IDF 4.4.7 break; #endif // USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32S3 default: diff --git a/esphome/components/improv_serial/improv_serial_component.h b/esphome/components/improv_serial/improv_serial_component.h index 8583d0762b..f737f93d86 100644 --- a/esphome/components/improv_serial/improv_serial_component.h +++ b/esphome/components/improv_serial/improv_serial_component.h @@ -17,6 +17,7 @@ #if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32S3) || \ defined(USE_ESP32_VARIANT_ESP32H2) #include +#include #endif #if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) #include From 69d38f61371f4f0548b50bedd1ca48784fa51028 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 5 Jun 2024 13:02:18 +1200 Subject: [PATCH 38/46] [ft5x06] Interrupt pin and code quality improvements (#6851) Co-authored-by: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> --- .../components/ft5x06/touchscreen/__init__.py | 8 +- .../ft5x06/touchscreen/ft5x06_touchscreen.cpp | 102 ++++++++++++++++++ .../ft5x06/touchscreen/ft5x06_touchscreen.h | 94 ++-------------- 3 files changed, 119 insertions(+), 85 deletions(-) create mode 100644 esphome/components/ft5x06/touchscreen/ft5x06_touchscreen.cpp diff --git a/esphome/components/ft5x06/touchscreen/__init__.py b/esphome/components/ft5x06/touchscreen/__init__.py index adeeac0d1a..4ceb50c709 100644 --- a/esphome/components/ft5x06/touchscreen/__init__.py +++ b/esphome/components/ft5x06/touchscreen/__init__.py @@ -1,8 +1,9 @@ +from esphome import pins import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import i2c, touchscreen -from esphome.const import CONF_ID +from esphome.const import CONF_ID, CONF_INTERRUPT_PIN from .. import ft5x06_ns FT5x06ButtonListener = ft5x06_ns.class_("FT5x06ButtonListener") @@ -16,6 +17,7 @@ FT5x06Touchscreen = ft5x06_ns.class_( CONFIG_SCHEMA = touchscreen.TOUCHSCREEN_SCHEMA.extend( { cv.GenerateID(): cv.declare_id(FT5x06Touchscreen), + cv.Optional(CONF_INTERRUPT_PIN): pins.internal_gpio_input_pin_schema, } ).extend(i2c.i2c_device_schema(0x48)) @@ -24,3 +26,7 @@ async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) await i2c.register_i2c_device(var, config) await touchscreen.register_touchscreen(var, config) + + if interrupt_pin := config.get(CONF_INTERRUPT_PIN): + pin = await cg.gpio_pin_expression(interrupt_pin) + cg.add(var.set_interrupt_pin(pin)) diff --git a/esphome/components/ft5x06/touchscreen/ft5x06_touchscreen.cpp b/esphome/components/ft5x06/touchscreen/ft5x06_touchscreen.cpp new file mode 100644 index 0000000000..bd603fdc10 --- /dev/null +++ b/esphome/components/ft5x06/touchscreen/ft5x06_touchscreen.cpp @@ -0,0 +1,102 @@ +#include "ft5x06_touchscreen.h" + +#include "esphome/core/log.h" + +namespace esphome { +namespace ft5x06 { + +static const char *const TAG = "ft5x06.touchscreen"; + +void FT5x06Touchscreen::setup() { + ESP_LOGCONFIG(TAG, "Setting up FT5x06 Touchscreen..."); + if (this->interrupt_pin_ != nullptr) { + this->interrupt_pin_->setup(); + this->interrupt_pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP); + this->interrupt_pin_->setup(); + this->attach_interrupt_(this->interrupt_pin_, gpio::INTERRUPT_FALLING_EDGE); + } + + // wait 200ms after reset. + this->set_timeout(200, [this] { this->continue_setup_(); }); +} + +void FT5x06Touchscreen::continue_setup_() { + uint8_t data[4]; + if (!this->set_mode_(FT5X06_OP_MODE)) + return; + + if (!this->err_check_(this->read_register(FT5X06_VENDOR_ID_REG, data, 1), "Read Vendor ID")) + return; + switch (data[0]) { + case FT5X06_ID_1: + case FT5X06_ID_2: + case FT5X06_ID_3: + this->vendor_id_ = (VendorId) data[0]; + ESP_LOGD(TAG, "Read vendor ID 0x%X", data[0]); + break; + + default: + ESP_LOGE(TAG, "Unknown vendor ID 0x%X", data[0]); + this->mark_failed(); + return; + } + // reading the chip registers to get max x/y does not seem to work. + if (this->display_ != nullptr) { + if (this->x_raw_max_ == this->x_raw_min_) { + this->x_raw_max_ = this->display_->get_native_width(); + } + if (this->y_raw_max_ == this->y_raw_min_) { + this->y_raw_max_ = this->display_->get_native_height(); + } + } + ESP_LOGCONFIG(TAG, "FT5x06 Touchscreen setup complete"); +} + +void FT5x06Touchscreen::update_touches() { + uint8_t touch_cnt; + uint8_t data[MAX_TOUCHES][6]; + + if (!this->read_byte(FT5X06_TD_STATUS, &touch_cnt) || touch_cnt > MAX_TOUCHES) { + ESP_LOGW(TAG, "Failed to read status"); + return; + } + if (touch_cnt == 0) + return; + + if (!this->read_bytes(FT5X06_TOUCH_DATA, (uint8_t *) data, touch_cnt * 6)) { + ESP_LOGW(TAG, "Failed to read touch data"); + return; + } + for (uint8_t i = 0; i != touch_cnt; i++) { + uint8_t status = data[i][0] >> 6; + uint8_t id = data[i][2] >> 3; + uint16_t x = encode_uint16(data[i][0] & 0x0F, data[i][1]); + uint16_t y = encode_uint16(data[i][2] & 0xF, data[i][3]); + + ESP_LOGD(TAG, "Read %X status, id: %d, pos %d/%d", status, id, x, y); + if (status == 0 || status == 2) { + this->add_raw_touch_position_(id, x, y); + } + } +} + +void FT5x06Touchscreen::dump_config() { + ESP_LOGCONFIG(TAG, "FT5x06 Touchscreen:"); + ESP_LOGCONFIG(TAG, " Address: 0x%02X", this->address_); + ESP_LOGCONFIG(TAG, " Vendor ID: 0x%X", (int) this->vendor_id_); +} + +bool FT5x06Touchscreen::err_check_(i2c::ErrorCode err, const char *msg) { + if (err != i2c::ERROR_OK) { + this->mark_failed(); + ESP_LOGE(TAG, "%s failed - err 0x%X", msg, err); + return false; + } + return true; +} +bool FT5x06Touchscreen::set_mode_(FTMode mode) { + return this->err_check_(this->write_register(FT5X06_MODE_REG, (uint8_t *) &mode, 1), "Set mode"); +} + +} // namespace ft5x06 +} // namespace esphome diff --git a/esphome/components/ft5x06/touchscreen/ft5x06_touchscreen.h b/esphome/components/ft5x06/touchscreen/ft5x06_touchscreen.h index 7ddd2e44d7..23e5a0c49f 100644 --- a/esphome/components/ft5x06/touchscreen/ft5x06_touchscreen.h +++ b/esphome/components/ft5x06/touchscreen/ft5x06_touchscreen.h @@ -3,14 +3,12 @@ #include "esphome/components/i2c/i2c.h" #include "esphome/components/touchscreen/touchscreen.h" #include "esphome/core/component.h" +#include "esphome/core/gpio.h" #include "esphome/core/hal.h" -#include "esphome/core/log.h" namespace esphome { namespace ft5x06 { -static const char *const TAG = "ft5x06.touchscreen"; - enum VendorId { FT5X06_ID_UNKNOWN = 0, FT5X06_ID_1 = 0x51, @@ -39,91 +37,19 @@ static const size_t MAX_TOUCHES = 5; // max number of possible touches reported class FT5x06Touchscreen : public touchscreen::Touchscreen, public i2c::I2CDevice { public: - void setup() override { - esph_log_config(TAG, "Setting up FT5x06 Touchscreen..."); - // wait 200ms after reset. - this->set_timeout(200, [this] { this->continue_setup_(); }); - } + void setup() override; + void dump_config() override; + void update_touches() override; - void continue_setup_(void) { - uint8_t data[4]; - if (!this->set_mode_(FT5X06_OP_MODE)) - return; - - if (!this->err_check_(this->read_register(FT5X06_VENDOR_ID_REG, data, 1), "Read Vendor ID")) - return; - switch (data[0]) { - case FT5X06_ID_1: - case FT5X06_ID_2: - case FT5X06_ID_3: - this->vendor_id_ = (VendorId) data[0]; - esph_log_d(TAG, "Read vendor ID 0x%X", data[0]); - break; - - default: - esph_log_e(TAG, "Unknown vendor ID 0x%X", data[0]); - this->mark_failed(); - return; - } - // reading the chip registers to get max x/y does not seem to work. - if (this->display_ != nullptr) { - if (this->x_raw_max_ == this->x_raw_min_) { - this->x_raw_max_ = this->display_->get_native_width(); - } - if (this->y_raw_max_ == this->y_raw_min_) { - this->y_raw_max_ = this->display_->get_native_height(); - } - } - esph_log_config(TAG, "FT5x06 Touchscreen setup complete"); - } - - void update_touches() override { - uint8_t touch_cnt; - uint8_t data[MAX_TOUCHES][6]; - - if (!this->read_byte(FT5X06_TD_STATUS, &touch_cnt) || touch_cnt > MAX_TOUCHES) { - esph_log_w(TAG, "Failed to read status"); - return; - } - if (touch_cnt == 0) - return; - - if (!this->read_bytes(FT5X06_TOUCH_DATA, (uint8_t *) data, touch_cnt * 6)) { - esph_log_w(TAG, "Failed to read touch data"); - return; - } - for (uint8_t i = 0; i != touch_cnt; i++) { - uint8_t status = data[i][0] >> 6; - uint8_t id = data[i][2] >> 3; - uint16_t x = encode_uint16(data[i][0] & 0x0F, data[i][1]); - uint16_t y = encode_uint16(data[i][2] & 0xF, data[i][3]); - - esph_log_d(TAG, "Read %X status, id: %d, pos %d/%d", status, id, x, y); - if (status == 0 || status == 2) { - this->add_raw_touch_position_(id, x, y); - } - } - } - - void dump_config() override { - esph_log_config(TAG, "FT5x06 Touchscreen:"); - esph_log_config(TAG, " Address: 0x%02X", this->address_); - esph_log_config(TAG, " Vendor ID: 0x%X", (int) this->vendor_id_); - } + void set_interrupt_pin(InternalGPIOPin *interrupt_pin) { this->interrupt_pin_ = interrupt_pin; } protected: - bool err_check_(i2c::ErrorCode err, const char *msg) { - if (err != i2c::ERROR_OK) { - this->mark_failed(); - esph_log_e(TAG, "%s failed - err 0x%X", msg, err); - return false; - } - return true; - } - bool set_mode_(FTMode mode) { - return this->err_check_(this->write_register(FT5X06_MODE_REG, (uint8_t *) &mode, 1), "Set mode"); - } + void continue_setup_(); + bool err_check_(i2c::ErrorCode err, const char *msg); + bool set_mode_(FTMode mode); VendorId vendor_id_{FT5X06_ID_UNKNOWN}; + + InternalGPIOPin *interrupt_pin_{nullptr}; }; } // namespace ft5x06 From ac9f57600db0f44a9a1b048e5228d6b67df29d50 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 30 May 2024 20:00:09 +1200 Subject: [PATCH 39/46] [voice_assistant] Half the microphone ringbuffer size (#6830) --- esphome/components/voice_assistant/voice_assistant.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/voice_assistant/voice_assistant.cpp b/esphome/components/voice_assistant/voice_assistant.cpp index 59ba39c527..421bca9afb 100644 --- a/esphome/components/voice_assistant/voice_assistant.cpp +++ b/esphome/components/voice_assistant/voice_assistant.cpp @@ -17,7 +17,7 @@ static const char *const TAG = "voice_assistant"; static const size_t SAMPLE_RATE_HZ = 16000; static const size_t INPUT_BUFFER_SIZE = 32 * SAMPLE_RATE_HZ / 1000; // 32ms * 16kHz / 1000ms -static const size_t BUFFER_SIZE = 1024 * SAMPLE_RATE_HZ / 1000; +static const size_t BUFFER_SIZE = 512 * SAMPLE_RATE_HZ / 1000; static const size_t SEND_BUFFER_SIZE = INPUT_BUFFER_SIZE * sizeof(int16_t); static const size_t RECEIVE_SIZE = 1024; static const size_t SPEAKER_BUFFER_SIZE = 16 * RECEIVE_SIZE; From ce4a3d995006a6f2b0a7afbee2f47c9807f54831 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 30 May 2024 20:01:39 +1200 Subject: [PATCH 40/46] [i2s_speaker] Add buffer allocation failure checks (#6829) --- .../i2s_audio/speaker/i2s_audio_speaker.cpp | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp b/esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp index 95e63035fe..7f03a43da4 100644 --- a/esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp +++ b/esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp @@ -19,10 +19,27 @@ void I2SAudioSpeaker::setup() { ESP_LOGCONFIG(TAG, "Setting up I2S Audio Speaker..."); this->buffer_queue_ = xQueueCreate(BUFFER_COUNT, sizeof(DataEvent)); + if (this->buffer_queue_ == nullptr) { + ESP_LOGE(TAG, "Failed to create buffer queue"); + this->mark_failed(); + return; + } + this->event_queue_ = xQueueCreate(BUFFER_COUNT, sizeof(TaskEvent)); + if (this->event_queue_ == nullptr) { + ESP_LOGE(TAG, "Failed to create event queue"); + this->mark_failed(); + return; + } } -void I2SAudioSpeaker::start() { this->state_ = speaker::STATE_STARTING; } +void I2SAudioSpeaker::start() { + if (this->is_failed()) { + ESP_LOGE(TAG, "Cannot start audio, speaker failed to setup"); + return; + } + this->state_ = speaker::STATE_STARTING; +} void I2SAudioSpeaker::start_() { if (!this->parent_->try_lock()) { return; // Waiting for another i2s component to return lock @@ -141,6 +158,8 @@ void I2SAudioSpeaker::player_task(void *params) { } void I2SAudioSpeaker::stop() { + if (this->is_failed()) + return; if (this->state_ == speaker::STATE_STOPPED) return; if (this->state_ == speaker::STATE_STARTING) { @@ -200,6 +219,10 @@ void I2SAudioSpeaker::loop() { } size_t I2SAudioSpeaker::play(const uint8_t *data, size_t length) { + if (this->is_failed()) { + ESP_LOGE(TAG, "Cannot play audio, speaker failed to setup"); + return 0; + } if (this->state_ != speaker::STATE_RUNNING && this->state_ != speaker::STATE_STARTING) { this->start(); } From 388b2c2de0e92f4f5a8d18379e651a266b3750fa Mon Sep 17 00:00:00 2001 From: Keith Burzinski Date: Mon, 3 Jun 2024 22:18:20 -0500 Subject: [PATCH 41/46] [improv_serial] Fix for IDF 4.4.7 (#6855) --- esphome/components/improv_serial/improv_serial_component.cpp | 1 + esphome/components/improv_serial/improv_serial_component.h | 1 + 2 files changed, 2 insertions(+) diff --git a/esphome/components/improv_serial/improv_serial_component.cpp b/esphome/components/improv_serial/improv_serial_component.cpp index 40297bee68..2937720496 100644 --- a/esphome/components/improv_serial/improv_serial_component.cpp +++ b/esphome/components/improv_serial/improv_serial_component.cpp @@ -109,6 +109,7 @@ void ImprovSerialComponent::write_data_(std::vector &data) { #if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32S3) case logger::UART_SELECTION_USB_SERIAL_JTAG: usb_serial_jtag_write_bytes((char *) data.data(), data.size(), 20 / portTICK_PERIOD_MS); + usb_serial_jtag_ll_txfifo_flush(); // fixes for issue in IDF 4.4.7 break; #endif // USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32S3 default: diff --git a/esphome/components/improv_serial/improv_serial_component.h b/esphome/components/improv_serial/improv_serial_component.h index 8583d0762b..f737f93d86 100644 --- a/esphome/components/improv_serial/improv_serial_component.h +++ b/esphome/components/improv_serial/improv_serial_component.h @@ -17,6 +17,7 @@ #if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32S3) || \ defined(USE_ESP32_VARIANT_ESP32H2) #include +#include #endif #if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) #include From 664ee56dc5c3170bab8722dea504cbf072e95ccb Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 5 Jun 2024 15:51:29 +1200 Subject: [PATCH 42/46] Bump version to 2024.5.5 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index ad5858dcf3..658773b3db 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2024.5.4" +__version__ = "2024.5.5" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" VALID_SUBSTITUTIONS_CHARACTERS = ( From 594856899a657ce5787f00d3432982869d29a8e9 Mon Sep 17 00:00:00 2001 From: Nate Clark Date: Wed, 5 Jun 2024 02:51:56 -0400 Subject: [PATCH 43/46] [ethernet] Add config option to set arbitrary PHY register values (#6836) --- esphome/components/ethernet/__init__.py | 29 ++++++++++ .../ethernet/ethernet_component.cpp | 58 +++++++------------ .../components/ethernet/ethernet_component.h | 12 +++- 3 files changed, 60 insertions(+), 39 deletions(-) diff --git a/esphome/components/ethernet/__init__.py b/esphome/components/ethernet/__init__.py index ade94cb9f5..697436415b 100644 --- a/esphome/components/ethernet/__init__.py +++ b/esphome/components/ethernet/__init__.py @@ -11,6 +11,7 @@ from esphome.components.esp32.const import ( from esphome.const import ( CONF_DOMAIN, CONF_ID, + CONF_VALUE, CONF_MANUAL_IP, CONF_STATIC_IP, CONF_TYPE, @@ -26,6 +27,8 @@ from esphome.const import ( CONF_INTERRUPT_PIN, CONF_RESET_PIN, CONF_SPI, + CONF_PAGE_ID, + CONF_ADDRESS, ) from esphome.core import CORE, coroutine_with_priority from esphome.components.network import IPAddress @@ -36,11 +39,13 @@ DEPENDENCIES = ["esp32"] AUTO_LOAD = ["network"] ethernet_ns = cg.esphome_ns.namespace("ethernet") +PHYRegister = ethernet_ns.struct("PHYRegister") CONF_PHY_ADDR = "phy_addr" CONF_MDC_PIN = "mdc_pin" CONF_MDIO_PIN = "mdio_pin" CONF_CLK_MODE = "clk_mode" CONF_POWER_PIN = "power_pin" +CONF_PHY_REGISTERS = "phy_registers" CONF_CLOCK_SPEED = "clock_speed" @@ -117,6 +122,13 @@ BASE_SCHEMA = cv.Schema( } ).extend(cv.COMPONENT_SCHEMA) +PHY_REGISTER_SCHEMA = cv.Schema( + { + cv.Required(CONF_ADDRESS): cv.hex_int, + cv.Required(CONF_VALUE): cv.hex_int, + cv.Optional(CONF_PAGE_ID): cv.hex_int, + } +) RMII_SCHEMA = BASE_SCHEMA.extend( cv.Schema( { @@ -127,6 +139,7 @@ RMII_SCHEMA = BASE_SCHEMA.extend( ), cv.Optional(CONF_PHY_ADDR, default=0): cv.int_range(min=0, max=31), cv.Optional(CONF_POWER_PIN): pins.internal_gpio_output_pin_number, + cv.Optional(CONF_PHY_REGISTERS): cv.ensure_list(PHY_REGISTER_SCHEMA), } ) ) @@ -198,6 +211,15 @@ def manual_ip(config): ) +def phy_register(address: int, value: int, page: int): + return cg.StructInitializer( + PHYRegister, + ("address", address), + ("value", value), + ("page", page), + ) + + @coroutine_with_priority(60.0) async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) @@ -225,6 +247,13 @@ async def to_code(config): cg.add(var.set_clk_mode(*CLK_MODES[config[CONF_CLK_MODE]])) if CONF_POWER_PIN in config: cg.add(var.set_power_pin(config[CONF_POWER_PIN])) + for register_value in config.get(CONF_PHY_REGISTERS, []): + reg = phy_register( + register_value.get(CONF_ADDRESS), + register_value.get(CONF_VALUE), + register_value.get(CONF_PAGE_ID), + ) + cg.add(var.add_phy_register(reg)) cg.add(var.set_type(ETHERNET_TYPES[config[CONF_TYPE]])) cg.add(var.set_use_address(config[CONF_USE_ADDRESS])) diff --git a/esphome/components/ethernet/ethernet_component.cpp b/esphome/components/ethernet/ethernet_component.cpp index 6bb9732fef..75bdd29be7 100644 --- a/esphome/components/ethernet/ethernet_component.cpp +++ b/esphome/components/ethernet/ethernet_component.cpp @@ -195,9 +195,9 @@ void EthernetComponent::setup() { // KSZ8081RNA default is incorrect. It expects a 25MHz clock instead of the 50MHz we provide. this->ksz8081_set_clock_reference_(mac); } - if (this->type_ == ETHERNET_TYPE_RTL8201 && this->clk_mode_ == EMAC_CLK_EXT_IN) { - // Change in default behavior of RTL8201FI may require register setting to enable external clock - this->rtl8201_set_rmii_mode_(mac); + + for (const auto &phy_register : this->phy_registers_) { + this->write_phy_register_(mac, phy_register); } #endif @@ -527,6 +527,7 @@ void EthernetComponent::set_clk_mode(emac_rmii_clock_mode_t clk_mode, emac_rmii_ this->clk_mode_ = clk_mode; this->clk_gpio_ = clk_gpio; } +void EthernetComponent::add_phy_register(PHYRegister register_value) { this->phy_registers_.push_back(register_value); } #endif void EthernetComponent::set_type(EthernetType type) { this->type_ = type; } void EthernetComponent::set_manual_ip(const ManualIP &manual_ip) { this->manual_ip_ = manual_ip; } @@ -613,44 +614,27 @@ void EthernetComponent::ksz8081_set_clock_reference_(esp_eth_mac_t *mac) { ESP_LOGVV(TAG, "KSZ8081 PHY Control 2: %s", format_hex_pretty((u_int8_t *) &phy_control_2, 2).c_str()); } } -constexpr uint8_t RTL8201_RMSR_REG_ADDR = 0x10; -void EthernetComponent::rtl8201_set_rmii_mode_(esp_eth_mac_t *mac) { + +void EthernetComponent::write_phy_register_(esp_eth_mac_t *mac, PHYRegister register_data) { esp_err_t err; - uint32_t phy_rmii_mode; - err = mac->write_phy_reg(mac, this->phy_addr_, 0x1f, 0x07); - ESPHL_ERROR_CHECK(err, "Setting Page 7 failed"); + constexpr uint8_t eth_phy_psr_reg_addr = 0x1F; - /* - * RTL8201 RMII Mode Setting Register (RMSR) - * Page 7 Register 16 - * - * bit 0 Reserved 0 - * bit 1 Rg_rmii_rxdsel 1 (default) - * bit 2 Rg_rmii_rxdv_sel: 0 (default) - * bit 3 RMII Mode: 1 (RMII Mode) - * bit 4~7 Rg_rmii_rx_offset: 1111 (default) - * bit 8~11 Rg_rmii_tx_offset: 1111 (default) - * bit 12 Rg_rmii_clkdir: 1 (Input) - * bit 13~15 Reserved 000 - * - * Binary: 0001 1111 1111 1010 - * Hex: 0x1FFA - * - */ + if (this->type_ == ETHERNET_TYPE_RTL8201 && register_data.page) { + ESP_LOGD(TAG, "Select PHY Register Page: 0x%02" PRIX32, register_data.page); + err = mac->write_phy_reg(mac, this->phy_addr_, eth_phy_psr_reg_addr, register_data.page); + ESPHL_ERROR_CHECK(err, "Select PHY Register page failed"); + } - err = mac->read_phy_reg(mac, this->phy_addr_, RTL8201_RMSR_REG_ADDR, &(phy_rmii_mode)); - ESPHL_ERROR_CHECK(err, "Read PHY RMSR Register failed"); - ESP_LOGV(TAG, "Hardware default RTL8201 RMII Mode Register is: 0x%04" PRIX32, phy_rmii_mode); + ESP_LOGD(TAG, "Writing to PHY Register Address: 0x%02" PRIX32, register_data.address); + ESP_LOGD(TAG, "Writing to PHY Register Value: 0x%04" PRIX32, register_data.value); + err = mac->write_phy_reg(mac, this->phy_addr_, register_data.address, register_data.value); + ESPHL_ERROR_CHECK(err, "Writing PHY Register failed"); - err = mac->write_phy_reg(mac, this->phy_addr_, RTL8201_RMSR_REG_ADDR, 0x1FFA); - ESPHL_ERROR_CHECK(err, "Setting Register 16 RMII Mode Setting failed"); - - err = mac->read_phy_reg(mac, this->phy_addr_, RTL8201_RMSR_REG_ADDR, &(phy_rmii_mode)); - ESPHL_ERROR_CHECK(err, "Read PHY RMSR Register failed"); - ESP_LOGV(TAG, "Setting RTL8201 RMII Mode Register to: 0x%04" PRIX32, phy_rmii_mode); - - err = mac->write_phy_reg(mac, this->phy_addr_, 0x1f, 0x0); - ESPHL_ERROR_CHECK(err, "Setting Page 0 failed"); + if (this->type_ == ETHERNET_TYPE_RTL8201 && register_data.page) { + ESP_LOGD(TAG, "Select PHY Register Page 0x%02" PRIX32, 0x0); + err = mac->write_phy_reg(mac, this->phy_addr_, eth_phy_psr_reg_addr, 0x0); + ESPHL_ERROR_CHECK(err, "Select PHY Register Page 0 failed"); + } } #endif diff --git a/esphome/components/ethernet/ethernet_component.h b/esphome/components/ethernet/ethernet_component.h index e57aa5fe12..f0fe6cab87 100644 --- a/esphome/components/ethernet/ethernet_component.h +++ b/esphome/components/ethernet/ethernet_component.h @@ -35,6 +35,12 @@ struct ManualIP { network::IPAddress dns2; ///< The second DNS server. 0.0.0.0 for default. }; +struct PHYRegister { + uint32_t address; + uint32_t value; + uint32_t page; +}; + enum class EthernetComponentState { STOPPED, CONNECTING, @@ -66,6 +72,7 @@ class EthernetComponent : public Component { void set_mdc_pin(uint8_t mdc_pin); void set_mdio_pin(uint8_t mdio_pin); void set_clk_mode(emac_rmii_clock_mode_t clk_mode, emac_rmii_clock_gpio_t clk_gpio); + void add_phy_register(PHYRegister register_value); #endif void set_type(EthernetType type); void set_manual_ip(const ManualIP &manual_ip); @@ -91,8 +98,8 @@ class EthernetComponent : public Component { void dump_connect_params_(); /// @brief Set `RMII Reference Clock Select` bit for KSZ8081. void ksz8081_set_clock_reference_(esp_eth_mac_t *mac); - /// @brief Set `RMII Mode Setting Register` for RTL8201. - void rtl8201_set_rmii_mode_(esp_eth_mac_t *mac); + /// @brief Set arbitratry PHY registers from config. + void write_phy_register_(esp_eth_mac_t *mac, PHYRegister register_data); std::string use_address_; #ifdef USE_ETHERNET_SPI @@ -111,6 +118,7 @@ class EthernetComponent : public Component { uint8_t mdio_pin_{18}; emac_rmii_clock_mode_t clk_mode_{EMAC_CLK_EXT_IN}; emac_rmii_clock_gpio_t clk_gpio_{EMAC_CLK_IN_GPIO}; + std::vector phy_registers_{}; #endif EthernetType type_{ETHERNET_TYPE_UNKNOWN}; optional manual_ip_{}; From f36a96c8e20c3b593f7dc48ba9f74e2c1fafe97e Mon Sep 17 00:00:00 2001 From: svxa <59204027+svxa@users.noreply.github.com> Date: Wed, 5 Jun 2024 09:35:19 +0200 Subject: [PATCH 44/46] Add carrier_frequency option to remote_transmitter.transmit_aeha (#6792) --- esphome/components/remote_base/__init__.py | 14 +++++++++++++- esphome/components/remote_base/aeha_protocol.cpp | 1 - esphome/components/remote_base/aeha_protocol.h | 2 ++ .../remote_transmitter/common-buttons.yaml | 1 + tests/test1.yaml | 1 + 5 files changed, 17 insertions(+), 2 deletions(-) diff --git a/esphome/components/remote_base/__init__.py b/esphome/components/remote_base/__init__.py index 9d49026aa3..3d1c10a092 100644 --- a/esphome/components/remote_base/__init__.py +++ b/esphome/components/remote_base/__init__.py @@ -1769,7 +1769,17 @@ def aeha_dumper(var, config): pass -@register_action("aeha", AEHAAction, AEHA_SCHEMA) +@register_action( + "aeha", + AEHAAction, + AEHA_SCHEMA.extend( + { + cv.Optional(CONF_CARRIER_FREQUENCY, default="38000Hz"): cv.All( + cv.frequency, cv.int_ + ), + } + ), +) async def aeha_action(var, config, args): template_ = await cg.templatable(config[CONF_ADDRESS], args, cg.uint16) cg.add(var.set_address(template_)) @@ -1777,6 +1787,8 @@ async def aeha_action(var, config, args): config[CONF_DATA], args, cg.std_vector.template(cg.uint8) ) cg.add(var.set_data(template_)) + templ = await cg.templatable(config[CONF_CARRIER_FREQUENCY], args, cg.uint32) + cg.add(var.set_carrier_frequency(templ)) # Haier diff --git a/esphome/components/remote_base/aeha_protocol.cpp b/esphome/components/remote_base/aeha_protocol.cpp index 40bdadf634..04fe731817 100644 --- a/esphome/components/remote_base/aeha_protocol.cpp +++ b/esphome/components/remote_base/aeha_protocol.cpp @@ -16,7 +16,6 @@ static const uint16_t BIT_ZERO_LOW_US = BITWISE; static const uint16_t TRAILER = BITWISE; void AEHAProtocol::encode(RemoteTransmitData *dst, const AEHAData &data) { - dst->set_carrier_frequency(38000); dst->reserve(2 + 32 + (data.data.size() * 2) + 1); dst->item(HEADER_HIGH_US, HEADER_LOW_US); diff --git a/esphome/components/remote_base/aeha_protocol.h b/esphome/components/remote_base/aeha_protocol.h index c41f3f8df1..51718eefcb 100644 --- a/esphome/components/remote_base/aeha_protocol.h +++ b/esphome/components/remote_base/aeha_protocol.h @@ -30,12 +30,14 @@ template class AEHAAction : public RemoteTransmitterActionBase, data) + TEMPLATABLE_VALUE(uint32_t, carrier_frequency); void set_data(const std::vector &data) { data_ = data; } void encode(RemoteTransmitData *dst, Ts... x) override { AEHAData data{}; data.address = this->address_.value(x...); data.data = this->data_.value(x...); + dst->set_carrier_frequency(this->carrier_frequency_.value(x...)); AEHAProtocol().encode(dst, data); } }; diff --git a/tests/components/remote_transmitter/common-buttons.yaml b/tests/components/remote_transmitter/common-buttons.yaml index e727017e85..c6a2453b20 100644 --- a/tests/components/remote_transmitter/common-buttons.yaml +++ b/tests/components/remote_transmitter/common-buttons.yaml @@ -118,6 +118,7 @@ button: on_press: remote_transmitter.transmit_aeha: address: 0x8008 + carrier_frequency: 36700Hz data: [ 0x00, diff --git a/tests/test1.yaml b/tests/test1.yaml index 2a20a1bb45..c49ff307e5 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -2906,6 +2906,7 @@ switch: turn_on_action: remote_transmitter.transmit_aeha: address: 0x8008 + carrier_frequency: 36700Hz data: [ 0x00, From c52d5c02791850317bc9302ef86a18fec6b01dd2 Mon Sep 17 00:00:00 2001 From: Will Rouesnel Date: Wed, 5 Jun 2024 17:52:19 +1000 Subject: [PATCH 45/46] Add `invert_position_report` to `tuya.cover` (#6020) --- esphome/components/tuya/cover/__init__.py | 3 +++ esphome/components/tuya/cover/tuya_cover.cpp | 9 ++++++--- esphome/components/tuya/cover/tuya_cover.h | 2 ++ 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/esphome/components/tuya/cover/__init__.py b/esphome/components/tuya/cover/__init__.py index f886c7030f..2dd66f814d 100644 --- a/esphome/components/tuya/cover/__init__.py +++ b/esphome/components/tuya/cover/__init__.py @@ -16,6 +16,7 @@ CONF_DIRECTION_DATAPOINT = "direction_datapoint" CONF_POSITION_DATAPOINT = "position_datapoint" CONF_POSITION_REPORT_DATAPOINT = "position_report_datapoint" CONF_INVERT_POSITION = "invert_position" +CONF_INVERT_POSITION_REPORT = "invert_position_report" TuyaCover = tuya_ns.class_("TuyaCover", cover.Cover, cg.Component) @@ -47,6 +48,7 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_MIN_VALUE, default=0): cv.int_, cv.Optional(CONF_MAX_VALUE, default=100): cv.int_, cv.Optional(CONF_INVERT_POSITION, default=False): cv.boolean, + cv.Optional(CONF_INVERT_POSITION_REPORT, default=False): cv.boolean, cv.Optional(CONF_RESTORE_MODE, default="RESTORE"): cv.enum( RESTORE_MODES, upper=True ), @@ -71,6 +73,7 @@ async def to_code(config): cg.add(var.set_min_value(config[CONF_MIN_VALUE])) cg.add(var.set_max_value(config[CONF_MAX_VALUE])) cg.add(var.set_invert_position(config[CONF_INVERT_POSITION])) + cg.add(var.set_invert_position_report(config[CONF_INVERT_POSITION_REPORT])) cg.add(var.set_restore_mode(config[CONF_RESTORE_MODE])) paren = await cg.get_variable(config[CONF_TUYA_ID]) cg.add(var.set_tuya_parent(paren)) diff --git a/esphome/components/tuya/cover/tuya_cover.cpp b/esphome/components/tuya/cover/tuya_cover.cpp index fcb961f45e..14bf937cf7 100644 --- a/esphome/components/tuya/cover/tuya_cover.cpp +++ b/esphome/components/tuya/cover/tuya_cover.cpp @@ -51,7 +51,7 @@ void TuyaCover::setup() { return; } auto pos = float(datapoint.value_uint - this->min_value_) / this->value_range_; - this->position = 1.0f - pos; + this->position = this->invert_position_report_ ? pos : 1.0f - pos; this->publish_state(); }); } @@ -62,7 +62,7 @@ void TuyaCover::control(const cover::CoverCall &call) { this->parent_->force_set_enum_datapoint_value(*this->control_id_, COMMAND_STOP); } else { auto pos = this->position; - pos = 1.0f - pos; + pos = this->invert_position_report_ ? pos : 1.0f - pos; auto position_int = static_cast(pos * this->value_range_); position_int = position_int + this->min_value_; @@ -78,7 +78,7 @@ void TuyaCover::control(const cover::CoverCall &call) { this->parent_->force_set_enum_datapoint_value(*this->control_id_, COMMAND_CLOSE); } } else { - pos = 1.0f - pos; + pos = this->invert_position_report_ ? pos : 1.0f - pos; auto position_int = static_cast(pos * this->value_range_); position_int = position_int + this->min_value_; @@ -112,6 +112,9 @@ void TuyaCover::dump_config() { ESP_LOGCONFIG(TAG, " Configured as Inverted, but direction_datapoint isn't configured"); } } + if (this->invert_position_report_) { + ESP_LOGCONFIG(TAG, " Position Reporting Inverted"); + } if (this->control_id_.has_value()) { ESP_LOGCONFIG(TAG, " Control has datapoint ID %u", *this->control_id_); } diff --git a/esphome/components/tuya/cover/tuya_cover.h b/esphome/components/tuya/cover/tuya_cover.h index 87c72b0e66..bb5a00bc59 100644 --- a/esphome/components/tuya/cover/tuya_cover.h +++ b/esphome/components/tuya/cover/tuya_cover.h @@ -25,6 +25,7 @@ class TuyaCover : public cover::Cover, public Component { void set_min_value(uint32_t min_value) { min_value_ = min_value; } void set_max_value(uint32_t max_value) { max_value_ = max_value; } void set_invert_position(bool invert_position) { invert_position_ = invert_position; } + void set_invert_position_report(bool invert_position_report) { invert_position_report_ = invert_position_report; } void set_restore_mode(TuyaCoverRestoreMode restore_mode) { restore_mode_ = restore_mode; } protected: @@ -42,6 +43,7 @@ class TuyaCover : public cover::Cover, public Component { uint32_t max_value_; uint32_t value_range_; bool invert_position_; + bool invert_position_report_; }; } // namespace tuya From cc217d8a83a049d259b163fd01e38141373b3e9d Mon Sep 17 00:00:00 2001 From: zry98 Date: Wed, 5 Jun 2024 10:11:19 +0200 Subject: [PATCH 46/46] [Tuya Climate] Support both datapoint and pins for active state (#6789) --- esphome/components/tuya/climate/__init__.py | 15 ++-- .../components/tuya/climate/tuya_climate.cpp | 76 +++++++++++-------- 2 files changed, 52 insertions(+), 39 deletions(-) diff --git a/esphome/components/tuya/climate/__init__.py b/esphome/components/tuya/climate/__init__.py index 56eb377ed7..363e7c764b 100644 --- a/esphome/components/tuya/climate/__init__.py +++ b/esphome/components/tuya/climate/__init__.py @@ -189,8 +189,6 @@ CONFIG_SCHEMA = cv.All( cv.has_at_least_one_key(CONF_TARGET_TEMPERATURE_DATAPOINT, CONF_SWITCH_DATAPOINT), validate_temperature_multipliers, validate_cooling_values, - cv.has_at_most_one_key(CONF_ACTIVE_STATE, CONF_HEATING_STATE_PIN), - cv.has_at_most_one_key(CONF_ACTIVE_STATE, CONF_COOLING_STATE_PIN), ) @@ -207,6 +205,12 @@ async def to_code(config): if switch_datapoint := config.get(CONF_SWITCH_DATAPOINT): cg.add(var.set_switch_id(switch_datapoint)) + if heating_state_pin_config := config.get(CONF_HEATING_STATE_PIN): + heating_state_pin = await cg.gpio_pin_expression(heating_state_pin_config) + cg.add(var.set_heating_state_pin(heating_state_pin)) + if cooling_state_pin_config := config.get(CONF_COOLING_STATE_PIN): + cooling_state_pin = await cg.gpio_pin_expression(cooling_state_pin_config) + cg.add(var.set_cooling_state_pin(cooling_state_pin)) if active_state_config := config.get(CONF_ACTIVE_STATE): cg.add(var.set_active_state_id(active_state_config.get(CONF_DATAPOINT))) if (heating_value := active_state_config.get(CONF_HEATING_VALUE)) is not None: @@ -217,13 +221,6 @@ async def to_code(config): cg.add(var.set_active_state_drying_value(drying_value)) if (fanonly_value := active_state_config.get(CONF_FANONLY_VALUE)) is not None: cg.add(var.set_active_state_fanonly_value(fanonly_value)) - else: - if heating_state_pin_config := config.get(CONF_HEATING_STATE_PIN): - heating_state_pin = await cg.gpio_pin_expression(heating_state_pin_config) - cg.add(var.set_heating_state_pin(heating_state_pin)) - if cooling_state_pin_config := config.get(CONF_COOLING_STATE_PIN): - cooling_state_pin = await cg.gpio_pin_expression(cooling_state_pin_config) - cg.add(var.set_cooling_state_pin(cooling_state_pin)) if target_temperature_datapoint := config.get(CONF_TARGET_TEMPERATURE_DATAPOINT): cg.add(var.set_target_temperature_id(target_temperature_datapoint)) diff --git a/esphome/components/tuya/climate/tuya_climate.cpp b/esphome/components/tuya/climate/tuya_climate.cpp index 274e19a69e..7827a4e3ab 100644 --- a/esphome/components/tuya/climate/tuya_climate.cpp +++ b/esphome/components/tuya/climate/tuya_climate.cpp @@ -24,6 +24,14 @@ void TuyaClimate::setup() { this->publish_state(); }); } + if (this->heating_state_pin_ != nullptr) { + this->heating_state_pin_->setup(); + this->heating_state_ = this->heating_state_pin_->digital_read(); + } + if (this->cooling_state_pin_ != nullptr) { + this->cooling_state_pin_->setup(); + this->cooling_state_ = this->cooling_state_pin_->digital_read(); + } if (this->active_state_id_.has_value()) { this->parent_->register_listener(*this->active_state_id_, [this](const TuyaDatapoint &datapoint) { ESP_LOGV(TAG, "MCU reported active state is: %u", datapoint.value_enum); @@ -31,15 +39,6 @@ void TuyaClimate::setup() { this->compute_state_(); this->publish_state(); }); - } else { - if (this->heating_state_pin_ != nullptr) { - this->heating_state_pin_->setup(); - this->heating_state_ = this->heating_state_pin_->digital_read(); - } - if (this->cooling_state_pin_ != nullptr) { - this->cooling_state_pin_->setup(); - this->cooling_state_ = this->cooling_state_pin_->digital_read(); - } } if (this->target_temperature_id_.has_value()) { this->parent_->register_listener(*this->target_temperature_id_, [this](const TuyaDatapoint &datapoint) { @@ -113,9 +112,6 @@ void TuyaClimate::setup() { } void TuyaClimate::loop() { - if (this->active_state_id_.has_value()) - return; - bool state_changed = false; if (this->heating_state_pin_ != nullptr) { bool heating_state = this->heating_state_pin_->digital_read(); @@ -147,14 +143,18 @@ void TuyaClimate::control(const climate::ClimateCall &call) { this->parent_->set_boolean_datapoint_value(*this->switch_id_, switch_state); const climate::ClimateMode new_mode = *call.get_mode(); - if (new_mode == climate::CLIMATE_MODE_HEAT && this->supports_heat_) { - this->parent_->set_enum_datapoint_value(*this->active_state_id_, *this->active_state_heating_value_); - } else if (new_mode == climate::CLIMATE_MODE_COOL && this->supports_cool_) { - this->parent_->set_enum_datapoint_value(*this->active_state_id_, *this->active_state_cooling_value_); - } else if (new_mode == climate::CLIMATE_MODE_DRY && this->active_state_drying_value_.has_value()) { - this->parent_->set_enum_datapoint_value(*this->active_state_id_, *this->active_state_drying_value_); - } else if (new_mode == climate::CLIMATE_MODE_FAN_ONLY && this->active_state_fanonly_value_.has_value()) { - this->parent_->set_enum_datapoint_value(*this->active_state_id_, *this->active_state_fanonly_value_); + if (this->active_state_id_.has_value()) { + if (new_mode == climate::CLIMATE_MODE_HEAT && this->supports_heat_) { + this->parent_->set_enum_datapoint_value(*this->active_state_id_, *this->active_state_heating_value_); + } else if (new_mode == climate::CLIMATE_MODE_COOL && this->supports_cool_) { + this->parent_->set_enum_datapoint_value(*this->active_state_id_, *this->active_state_cooling_value_); + } else if (new_mode == climate::CLIMATE_MODE_DRY && this->active_state_drying_value_.has_value()) { + this->parent_->set_enum_datapoint_value(*this->active_state_id_, *this->active_state_drying_value_); + } else if (new_mode == climate::CLIMATE_MODE_FAN_ONLY && this->active_state_fanonly_value_.has_value()) { + this->parent_->set_enum_datapoint_value(*this->active_state_id_, *this->active_state_fanonly_value_); + } + } else { + ESP_LOGW(TAG, "Active state (mode) datapoint not configured"); } } @@ -422,7 +422,32 @@ void TuyaClimate::compute_state_() { } climate::ClimateAction target_action = climate::CLIMATE_ACTION_IDLE; - if (this->active_state_id_.has_value()) { + if (this->heating_state_pin_ != nullptr || this->cooling_state_pin_ != nullptr) { + // Use state from input pins + if (this->heating_state_) { + target_action = climate::CLIMATE_ACTION_HEATING; + this->mode = climate::CLIMATE_MODE_HEAT; + } else if (this->cooling_state_) { + target_action = climate::CLIMATE_ACTION_COOLING; + this->mode = climate::CLIMATE_MODE_COOL; + } + if (this->active_state_id_.has_value()) { + // Both are available, use MCU datapoint as mode + if (this->supports_heat_ && this->active_state_heating_value_.has_value() && + this->active_state_ == this->active_state_heating_value_) { + this->mode = climate::CLIMATE_MODE_HEAT; + } else if (this->supports_cool_ && this->active_state_cooling_value_.has_value() && + this->active_state_ == this->active_state_cooling_value_) { + this->mode = climate::CLIMATE_MODE_COOL; + } else if (this->active_state_drying_value_.has_value() && + this->active_state_ == this->active_state_drying_value_) { + this->mode = climate::CLIMATE_MODE_DRY; + } else if (this->active_state_fanonly_value_.has_value() && + this->active_state_ == this->active_state_fanonly_value_) { + this->mode = climate::CLIMATE_MODE_FAN_ONLY; + } + } + } else if (this->active_state_id_.has_value()) { // Use state from MCU datapoint if (this->supports_heat_ && this->active_state_heating_value_.has_value() && this->active_state_ == this->active_state_heating_value_) { @@ -441,15 +466,6 @@ void TuyaClimate::compute_state_() { target_action = climate::CLIMATE_ACTION_FAN; this->mode = climate::CLIMATE_MODE_FAN_ONLY; } - } else if (this->heating_state_pin_ != nullptr || this->cooling_state_pin_ != nullptr) { - // Use state from input pins - if (this->heating_state_) { - target_action = climate::CLIMATE_ACTION_HEATING; - this->mode = climate::CLIMATE_MODE_HEAT; - } else if (this->cooling_state_) { - target_action = climate::CLIMATE_ACTION_COOLING; - this->mode = climate::CLIMATE_MODE_COOL; - } } else { // Fallback to active state calc based on temp and hysteresis const float temp_diff = this->target_temperature - this->current_temperature;