From a779592414092163f5e29299567102f9072f6475 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 13 Oct 2021 16:40:46 +1300 Subject: [PATCH 0001/3388] Bump version to 2021.10.0b1 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index a9eec3e249..ce7d363e92 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2021.10.0-dev" +__version__ = "2021.10.0b1" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" From f57980b069759037c7217336ba1aca590b9699f6 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 13 Oct 2021 22:30:29 +1300 Subject: [PATCH 0002/3388] Bump version to 2021.10.0b2 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index ce7d363e92..456b6d9209 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2021.10.0b1" +__version__ = "2021.10.0b2" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" From b3b9ccd314d7117eee216037ca1ec6c075a3573b Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Wed, 13 Oct 2021 21:53:00 +0200 Subject: [PATCH 0003/3388] Fix light state remaining on after turn off with transition (#2509) --- esphome/components/light/transformers.h | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/esphome/components/light/transformers.h b/esphome/components/light/transformers.h index 90646f4e61..c22846ceb1 100644 --- a/esphome/components/light/transformers.h +++ b/esphome/components/light/transformers.h @@ -18,10 +18,13 @@ class LightTransitionTransformer : public LightTransformer { this->start_values_.set_brightness(0.0f); } - // When turning light off from on state, use source state and only decrease brightness to zero. + // When turning light off from on state, use source state and only decrease brightness to zero. Use a second + // variable for transition end state, as overwriting target_values breaks LightState logic. if (this->start_values_.is_on() && !this->target_values_.is_on()) { - this->target_values_ = LightColorValues(this->start_values_); - this->target_values_.set_brightness(0.0f); + this->end_values_ = LightColorValues(this->start_values_); + this->end_values_.set_brightness(0.0f); + } else { + this->end_values_ = LightColorValues(this->target_values_); } // When changing color mode, go through off state, as color modes are orthogonal and there can't be two active. @@ -43,7 +46,7 @@ class LightTransitionTransformer : public LightTransformer { } LightColorValues &start = this->changing_color_mode_ && p > 0.5f ? this->intermediate_values_ : this->start_values_; - LightColorValues &end = this->changing_color_mode_ && p < 0.5f ? this->intermediate_values_ : this->target_values_; + LightColorValues &end = this->changing_color_mode_ && p < 0.5f ? this->intermediate_values_ : this->end_values_; if (this->changing_color_mode_) p = p < 0.5f ? p * 2 : (p - 0.5) * 2; @@ -57,6 +60,7 @@ class LightTransitionTransformer : public LightTransformer { static float smoothed_progress(float x) { return x * x * x * (x * (x * 6.0f - 15.0f) + 10.0f); } bool changing_color_mode_{false}; + LightColorValues end_values_{}; LightColorValues intermediate_values_{}; }; From 48ff2ffc68479963dc76ea678fb2297a9e6bdc92 Mon Sep 17 00:00:00 2001 From: Paul Monigatti Date: Thu, 14 Oct 2021 08:59:52 +1300 Subject: [PATCH 0004/3388] Fix: Light flash not restoring previous LightState (#2383) * Update light state when transformer has finished * Revert writing direct to output * Correct handling of zero-length light transformers * Allow transformers to handle zero-length transitions, and check more boundary conditions when transitioning back to start state * Removed log.h * Fixed race condition between LightFlashTransformer.apply() and is_finished() * clang-format * Step progress from 0.0f to 1.0f at t=start_time for zero-length transforms to avoid divide-by-zero --- .../components/light/addressable_light.cpp | 2 +- esphome/components/light/light_transformer.h | 10 ++++- esphome/components/light/transformers.h | 37 ++++++++++--------- 3 files changed, 30 insertions(+), 19 deletions(-) diff --git a/esphome/components/light/addressable_light.cpp b/esphome/components/light/addressable_light.cpp index f3e6c0ef1d..a8e0c7b762 100644 --- a/esphome/components/light/addressable_light.cpp +++ b/esphome/components/light/addressable_light.cpp @@ -79,7 +79,7 @@ optional AddressableLightTransformer::apply() { // dynamically-calculated alpha values to match the look. float denom = (1.0f - smoothed_progress); - float alpha = denom == 0.0f ? 0.0f : (smoothed_progress - this->last_transition_progress_) / denom; + float alpha = denom == 0.0f ? 1.0f : (smoothed_progress - this->last_transition_progress_) / denom; // We need to use a low-resolution alpha here which makes the transition set in only after ~half of the length // We solve this by accumulating the fractional part of the alpha over time. diff --git a/esphome/components/light/light_transformer.h b/esphome/components/light/light_transformer.h index dd904d0eed..35b045d5b4 100644 --- a/esphome/components/light/light_transformer.h +++ b/esphome/components/light/light_transformer.h @@ -39,7 +39,15 @@ class LightTransformer { protected: /// The progress of this transition, on a scale of 0 to 1. - float get_progress_() { return clamp((millis() - this->start_time_) / float(this->length_), 0.0f, 1.0f); } + float get_progress_() { + uint32_t now = esphome::millis(); + if (now < this->start_time_) + return 0.0f; + if (now >= this->start_time_ + this->length_) + return 1.0f; + + return clamp((now - this->start_time_) / float(this->length_), 0.0f, 1.0f); + } uint32_t start_time_; uint32_t length_; diff --git a/esphome/components/light/transformers.h b/esphome/components/light/transformers.h index c22846ceb1..a557bd39b1 100644 --- a/esphome/components/light/transformers.h +++ b/esphome/components/light/transformers.h @@ -73,9 +73,7 @@ class LightFlashTransformer : public LightTransformer { if (this->transition_length_ * 2 > this->length_) this->transition_length_ = this->length_ / 2; - // do not create transition if length is 0 - if (this->transition_length_ == 0) - return; + this->begun_lightstate_restore_ = false; // first transition to original target this->transformer_ = this->state_.get_output()->create_default_transition(); @@ -83,40 +81,45 @@ class LightFlashTransformer : public LightTransformer { } optional apply() override { - // transition transformer does not handle 0 length as progress returns nan - if (this->transition_length_ == 0) - return this->target_values_; + optional result = {}; + + if (this->transformer_ == nullptr && millis() > this->start_time_ + this->length_ - this->transition_length_) { + // second transition back to start value + this->transformer_ = this->state_.get_output()->create_default_transition(); + this->transformer_->setup(this->state_.current_values, this->get_start_values(), this->transition_length_); + this->begun_lightstate_restore_ = true; + } if (this->transformer_ != nullptr) { - if (!this->transformer_->is_finished()) { - return this->transformer_->apply(); - } else { + result = this->transformer_->apply(); + + if (this->transformer_->is_finished()) { this->transformer_->stop(); this->transformer_ = nullptr; } } - if (millis() > this->start_time_ + this->length_ - this->transition_length_) { - // second transition back to start value - this->transformer_ = this->state_.get_output()->create_default_transition(); - this->transformer_->setup(this->state_.current_values, this->get_start_values(), this->transition_length_); - } - - // once transition is complete, don't change states until next transition - return optional(); + return result; } // Restore the original values after the flash. void stop() override { + if (this->transformer_ != nullptr) { + this->transformer_->stop(); + this->transformer_ = nullptr; + } this->state_.current_values = this->get_start_values(); this->state_.remote_values = this->get_start_values(); this->state_.publish_state(); } + bool is_finished() override { return this->begun_lightstate_restore_ && LightTransformer::is_finished(); } + protected: LightState &state_; uint32_t transition_length_; std::unique_ptr transformer_{nullptr}; + bool begun_lightstate_restore_; }; } // namespace light From 5f7cef0b06b0f12db3d7131db2fbcf5b71848225 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Wed, 13 Oct 2021 22:21:43 +0200 Subject: [PATCH 0005/3388] Disallow using UART2 for logger on ESP-32 variants that lack it (#2510) --- esphome/components/esp32/__init__.py | 6 +++++- esphome/components/logger/__init__.py | 7 +++++++ esphome/components/logger/logger.cpp | 8 +++----- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index 704f9bb3e8..09eabe1fa7 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -21,12 +21,16 @@ from esphome.core import CORE, HexInt import esphome.config_validation as cv import esphome.codegen as cg -from .const import ( +from .const import ( # noqa KEY_BOARD, KEY_ESP32, KEY_SDKCONFIG_OPTIONS, KEY_VARIANT, + VARIANT_ESP32, + VARIANT_ESP32S2, + VARIANT_ESP32S3, VARIANT_ESP32C3, + VARIANT_ESP32H2, VARIANTS, ) diff --git a/esphome/components/logger/__init__.py b/esphome/components/logger/__init__.py index bc1bc6bb41..fe2a3ec8f8 100644 --- a/esphome/components/logger/__init__.py +++ b/esphome/components/logger/__init__.py @@ -19,6 +19,7 @@ from esphome.const import ( CONF_TX_BUFFER_SIZE, ) from esphome.core import CORE, EsphomeError, Lambda, coroutine_with_priority +from esphome.components.esp32 import get_esp32_variant, VARIANT_ESP32S2, VARIANT_ESP32C3 CODEOWNERS = ["@esphome/core"] logger_ns = cg.esphome_ns.namespace("logger") @@ -52,6 +53,10 @@ LOG_LEVEL_SEVERITY = [ "VERY_VERBOSE", ] +ESP32_REDUCED_VARIANTS = [VARIANT_ESP32C3, VARIANT_ESP32S2] + +UART_SELECTION_ESP32_REDUCED = ["UART0", "UART1"] + UART_SELECTION_ESP32 = ["UART0", "UART1", "UART2"] UART_SELECTION_ESP8266 = ["UART0", "UART0_SWAP", "UART1"] @@ -75,6 +80,8 @@ is_log_level = cv.one_of(*LOG_LEVELS, upper=True) def uart_selection(value): if CORE.is_esp32: + if get_esp32_variant() in ESP32_REDUCED_VARIANTS: + return cv.one_of(*UART_SELECTION_ESP32_REDUCED, upper=True)(value) return cv.one_of(*UART_SELECTION_ESP32, upper=True)(value) if CORE.is_esp8266: return cv.one_of(*UART_SELECTION_ESP8266, upper=True)(value) diff --git a/esphome/components/logger/logger.cpp b/esphome/components/logger/logger.cpp index 2d85969bf3..b38c7f1a69 100644 --- a/esphome/components/logger/logger.cpp +++ b/esphome/components/logger/logger.cpp @@ -153,13 +153,9 @@ void Logger::pre_setup() { case UART_SELECTION_UART1: this->hw_serial_ = &Serial1; break; -#ifdef USE_ESP32 +#if defined(USE_ESP32) && !CONFIG_IDF_TARGET_ESP32C3 && !CONFIG_IDF_TARGET_ESP32S2 case UART_SELECTION_UART2: -#if !CONFIG_IDF_TARGET_ESP32S2 && !CONFIG_IDF_TARGET_ESP32C3 - // FIXME: Validate in config that UART2 can't be set for ESP32-S2 (only has - // UART0-UART1) this->hw_serial_ = &Serial2; -#endif break; #endif } @@ -173,9 +169,11 @@ void Logger::pre_setup() { case UART_SELECTION_UART1: uart_num_ = UART_NUM_1; break; +#if !CONFIG_IDF_TARGET_ESP32C3 && !CONFIG_IDF_TARGET_ESP32S2 case UART_SELECTION_UART2: uart_num_ = UART_NUM_2; break; +#endif } uart_config_t uart_config{}; uart_config.baud_rate = (int) baud_rate_; From d8a6dfe5ce9ca7b5ea4d25a3a3e99ce800ca6ee2 Mon Sep 17 00:00:00 2001 From: Paul Monigatti Date: Thu, 14 Oct 2021 20:58:35 +1300 Subject: [PATCH 0006/3388] Fix BME680_BSEC compilation issue with ESP32 (#2516) --- esphome/components/bme680_bsec/__init__.py | 8 +++++++- tests/test1.yaml | 16 ++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/esphome/components/bme680_bsec/__init__.py b/esphome/components/bme680_bsec/__init__.py index d258819aa4..38da18d702 100644 --- a/esphome/components/bme680_bsec/__init__.py +++ b/esphome/components/bme680_bsec/__init__.py @@ -2,6 +2,7 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import i2c from esphome.const import CONF_ID +from esphome.core import CORE CODEOWNERS = ["@trvrnrth"] DEPENDENCIES = ["i2c"] @@ -44,7 +45,8 @@ CONFIG_SCHEMA = cv.Schema( cv.Optional( CONF_STATE_SAVE_INTERVAL, default="6hours" ): cv.positive_time_period_minutes, - } + }, + cv.only_with_arduino, ).extend(i2c.i2c_device_schema(0x76)) @@ -60,5 +62,9 @@ async def to_code(config): var.set_state_save_interval(config[CONF_STATE_SAVE_INTERVAL].total_milliseconds) ) + if CORE.is_esp32: + # Although this component does not use SPI, the BSEC library requires the SPI library + cg.add_library("SPI", None) + cg.add_define("USE_BSEC") cg.add_library("BSEC Software Library", "1.6.1480") diff --git a/tests/test1.yaml b/tests/test1.yaml index 157ccfc5d1..62fc781eca 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -265,6 +265,9 @@ wled: adalight: +bme680_bsec: + i2c_id: i2c_bus + esp32_ble_tracker: ble_client: @@ -478,6 +481,19 @@ sensor: duration: 150ms update_interval: 15s i2c_id: i2c_bus + - platform: bme680_bsec + temperature: + name: "BME680 Temperature" + pressure: + name: "BME680 Pressure" + humidity: + name: "BME680 Humidity" + iaq: + name: "BME680 IAQ" + co2_equivalent: + name: "BME680 CO2 Equivalent" + breath_voc_equivalent: + name: "BME680 Breath VOC Equivalent" - platform: bmp085 temperature: name: 'Outside Temperature' From c51d8c90214efa10ffb8c8a85da1ecb19c992b8a Mon Sep 17 00:00:00 2001 From: Dmitriy Lopatko Date: Thu, 14 Oct 2021 10:00:53 +0200 Subject: [PATCH 0007/3388] add missing include in sgp30 (#2517) --- esphome/components/sgp30/sgp30.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/esphome/components/sgp30/sgp30.cpp b/esphome/components/sgp30/sgp30.cpp index 1a64a12907..87cf0fa61a 100644 --- a/esphome/components/sgp30/sgp30.cpp +++ b/esphome/components/sgp30/sgp30.cpp @@ -1,4 +1,5 @@ #include "sgp30.h" +#include "esphome/core/hal.h" #include "esphome/core/log.h" #include "esphome/core/application.h" #include From 9b7fb829f9297e45b3ae4e641f310072bbfd9065 Mon Sep 17 00:00:00 2001 From: Paul Monigatti Date: Thu, 14 Oct 2021 21:04:50 +1300 Subject: [PATCH 0008/3388] Fix: Color modes not being correctly used in light partitions (#2513) --- .../light/addressable_light_wrapper.h | 89 +++++++++++++++++-- 1 file changed, 80 insertions(+), 9 deletions(-) diff --git a/esphome/components/light/addressable_light_wrapper.h b/esphome/components/light/addressable_light_wrapper.h index cd5bcabd47..d358502430 100644 --- a/esphome/components/light/addressable_light_wrapper.h +++ b/esphome/components/light/addressable_light_wrapper.h @@ -16,24 +16,94 @@ class AddressableLightWrapper : public light::AddressableLight { void clear_effect_data() override { this->wrapper_state_[4] = 0; } - light::LightTraits get_traits() override { return this->light_state_->get_traits(); } + light::LightTraits get_traits() override { + LightTraits traits; + + // Choose which color mode to use. + // This is ordered by how closely each color mode matches the underlying RGBW data structure used in LightPartition. + ColorMode color_mode_precedence[] = {ColorMode::RGB_WHITE, + ColorMode::RGB_COLD_WARM_WHITE, + ColorMode::RGB_COLOR_TEMPERATURE, + ColorMode::RGB, + ColorMode::WHITE, + ColorMode::COLD_WARM_WHITE, + ColorMode::COLOR_TEMPERATURE, + ColorMode::BRIGHTNESS, + ColorMode::ON_OFF, + ColorMode::UNKNOWN}; + + LightTraits parent_traits = this->light_state_->get_traits(); + for (auto cm : color_mode_precedence) { + if (parent_traits.supports_color_mode(cm)) { + this->color_mode_ = cm; + break; + } + } + + // Report a color mode that's compatible with both the partition and the underlying light + switch (this->color_mode_) { + case ColorMode::RGB_WHITE: + case ColorMode::RGB_COLD_WARM_WHITE: + case ColorMode::RGB_COLOR_TEMPERATURE: + traits.set_supported_color_modes({light::ColorMode::RGB_WHITE}); + break; + + case ColorMode::RGB: + traits.set_supported_color_modes({light::ColorMode::RGB}); + break; + + case ColorMode::WHITE: + case ColorMode::COLD_WARM_WHITE: + case ColorMode::COLOR_TEMPERATURE: + case ColorMode::BRIGHTNESS: + traits.set_supported_color_modes({light::ColorMode::BRIGHTNESS}); + break; + + case ColorMode::ON_OFF: + traits.set_supported_color_modes({light::ColorMode::ON_OFF}); + break; + + default: + traits.set_supported_color_modes({light::ColorMode::UNKNOWN}); + } + + return traits; + } void write_state(light::LightState *state) override { + // Don't overwrite state if the underlying light is turned on + if (this->light_state_->remote_values.is_on()) { + this->mark_shown_(); + return; + } + float gamma = this->light_state_->get_gamma_correct(); float r = gamma_uncorrect(this->wrapper_state_[0] / 255.0f, gamma); float g = gamma_uncorrect(this->wrapper_state_[1] / 255.0f, gamma); float b = gamma_uncorrect(this->wrapper_state_[2] / 255.0f, gamma); float w = gamma_uncorrect(this->wrapper_state_[3] / 255.0f, gamma); - float brightness = fmaxf(r, fmaxf(g, b)); auto call = this->light_state_->make_call(); - call.set_state(true); - call.set_brightness_if_supported(1.0f); - call.set_color_brightness_if_supported(brightness); - call.set_red_if_supported(r); - call.set_green_if_supported(g); - call.set_blue_if_supported(b); - call.set_white_if_supported(w); + + float color_brightness = fmaxf(r, fmaxf(g, b)); + float brightness = fmaxf(color_brightness, w); + if (brightness == 0.0f) { + call.set_state(false); + } else { + color_brightness /= brightness; + w /= brightness; + + call.set_state(true); + call.set_color_mode_if_supported(this->color_mode_); + call.set_brightness_if_supported(brightness); + call.set_color_brightness_if_supported(color_brightness); + call.set_red_if_supported(r); + call.set_green_if_supported(g); + call.set_blue_if_supported(b); + call.set_white_if_supported(w); + call.set_warm_white_if_supported(w); + call.set_cold_white_if_supported(w); + } call.set_transition_length_if_supported(0); call.set_publish(false); call.set_save(false); @@ -50,6 +120,7 @@ class AddressableLightWrapper : public light::AddressableLight { light::LightState *light_state_; uint8_t *wrapper_state_; + ColorMode color_mode_{ColorMode::UNKNOWN}; }; } // namespace light From 73940bc1bd421df14d66ac3ae67fac40e28b9775 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Thu, 14 Oct 2021 11:25:10 +0200 Subject: [PATCH 0009/3388] Don't define UART_SELECTION_UART2 when UART2 is unavailable (#2512) --- esphome/components/logger/logger.cpp | 4 ++-- esphome/components/logger/logger.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/esphome/components/logger/logger.cpp b/esphome/components/logger/logger.cpp index b38c7f1a69..97ad4c2cb9 100644 --- a/esphome/components/logger/logger.cpp +++ b/esphome/components/logger/logger.cpp @@ -153,7 +153,7 @@ void Logger::pre_setup() { case UART_SELECTION_UART1: this->hw_serial_ = &Serial1; break; -#if defined(USE_ESP32) && !CONFIG_IDF_TARGET_ESP32C3 && !CONFIG_IDF_TARGET_ESP32S2 +#if defined(USE_ESP32) && !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32S2) case UART_SELECTION_UART2: this->hw_serial_ = &Serial2; break; @@ -169,7 +169,7 @@ void Logger::pre_setup() { case UART_SELECTION_UART1: uart_num_ = UART_NUM_1; break; -#if !CONFIG_IDF_TARGET_ESP32C3 && !CONFIG_IDF_TARGET_ESP32S2 +#if defined(USE_ESP32) && !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32S2) case UART_SELECTION_UART2: uart_num_ = UART_NUM_2; break; diff --git a/esphome/components/logger/logger.h b/esphome/components/logger/logger.h index e6fa6e2058..8756bc2387 100644 --- a/esphome/components/logger/logger.h +++ b/esphome/components/logger/logger.h @@ -24,7 +24,7 @@ namespace logger { enum UARTSelection { UART_SELECTION_UART0 = 0, UART_SELECTION_UART1, -#ifdef USE_ESP32 +#if defined(USE_ESP32) && !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32S2) UART_SELECTION_UART2, #endif #ifdef USE_ESP8266 From 10c6601b0abe4138a4292679208d917f290df9b4 Mon Sep 17 00:00:00 2001 From: Paul Monigatti Date: Thu, 14 Oct 2021 23:31:52 +1300 Subject: [PATCH 0010/3388] Revert "Added test for bme680_bsec" (#2518) This reverts commit 7f6a50d291b14935b17802b4dce52135fad1e49e due to BSEC library license restrictions. --- tests/test1.yaml | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/tests/test1.yaml b/tests/test1.yaml index 62fc781eca..157ccfc5d1 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -265,9 +265,6 @@ wled: adalight: -bme680_bsec: - i2c_id: i2c_bus - esp32_ble_tracker: ble_client: @@ -481,19 +478,6 @@ sensor: duration: 150ms update_interval: 15s i2c_id: i2c_bus - - platform: bme680_bsec - temperature: - name: "BME680 Temperature" - pressure: - name: "BME680 Pressure" - humidity: - name: "BME680 Humidity" - iaq: - name: "BME680 IAQ" - co2_equivalent: - name: "BME680 CO2 Equivalent" - breath_voc_equivalent: - name: "BME680 Breath VOC Equivalent" - platform: bmp085 temperature: name: 'Outside Temperature' From d4e65eb82ad972bafdf8a0017980a7b85e4c7e12 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Fri, 15 Oct 2021 09:42:44 +1300 Subject: [PATCH 0011/3388] Bump version to 2021.10.0b3 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 456b6d9209..eacbb19a7f 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2021.10.0b2" +__version__ = "2021.10.0b3" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" From 15b5ea43a759a15d3dd0059aa58ff5c153a1ff80 Mon Sep 17 00:00:00 2001 From: Martin <25747549+martgras@users.noreply.github.com> Date: Thu, 14 Oct 2021 11:24:57 +0200 Subject: [PATCH 0012/3388] Add pressure compensation during runtime (#2493) Co-authored-by: Oxan van Leeuwen --- esphome/components/scd4x/scd4x.cpp | 133 ++++++++++++++++++----------- esphome/components/scd4x/scd4x.h | 9 +- esphome/components/scd4x/sensor.py | 9 +- 3 files changed, 97 insertions(+), 54 deletions(-) diff --git a/esphome/components/scd4x/scd4x.cpp b/esphome/components/scd4x/scd4x.cpp index c91fd5e882..eacb39edf1 100644 --- a/esphome/components/scd4x/scd4x.cpp +++ b/esphome/components/scd4x/scd4x.cpp @@ -1,4 +1,5 @@ #include "scd4x.h" +#include "esphome/core/hal.h" #include "esphome/core/log.h" namespace esphome { @@ -38,6 +39,7 @@ void SCD4XComponent::setup() { return; } + uint32_t stop_measurement_delay = 0; // In order to query the device periodic measurement must be ceased if (raw_read_status[0]) { ESP_LOGD(TAG, "Sensor has data available, stopping periodic measurement"); @@ -46,68 +48,72 @@ void SCD4XComponent::setup() { this->mark_failed(); return; } + // According to the SCD4x datasheet the sensor will only respond to other commands after waiting 500 ms after + // issuing the stop_periodic_measurement command + stop_measurement_delay = 500; } + this->set_timeout(stop_measurement_delay, [this]() { + if (!this->write_command_(SCD4X_CMD_GET_SERIAL_NUMBER)) { + ESP_LOGE(TAG, "Failed to write get serial command"); + this->error_code_ = COMMUNICATION_FAILED; + this->mark_failed(); + return; + } - if (!this->write_command_(SCD4X_CMD_GET_SERIAL_NUMBER)) { - ESP_LOGE(TAG, "Failed to write get serial command"); - this->error_code_ = COMMUNICATION_FAILED; - this->mark_failed(); - return; - } + uint16_t raw_serial_number[3]; + if (!this->read_data_(raw_serial_number, 3)) { + ESP_LOGE(TAG, "Failed to read serial number"); + this->error_code_ = SERIAL_NUMBER_IDENTIFICATION_FAILED; + this->mark_failed(); + return; + } + ESP_LOGD(TAG, "Serial number %02d.%02d.%02d", (uint16_t(raw_serial_number[0]) >> 8), + uint16_t(raw_serial_number[0] & 0xFF), (uint16_t(raw_serial_number[1]) >> 8)); - uint16_t raw_serial_number[3]; - if (!this->read_data_(raw_serial_number, 3)) { - ESP_LOGE(TAG, "Failed to read serial number"); - this->error_code_ = SERIAL_NUMBER_IDENTIFICATION_FAILED; - this->mark_failed(); - return; - } - ESP_LOGD(TAG, "Serial number %02d.%02d.%02d", (uint16_t(raw_serial_number[0]) >> 8), - uint16_t(raw_serial_number[0] & 0xFF), (uint16_t(raw_serial_number[1]) >> 8)); - - if (!this->write_command_(SCD4X_CMD_TEMPERATURE_OFFSET, - (uint16_t)(temperature_offset_ * SCD4X_TEMPERATURE_OFFSET_MULTIPLIER))) { - ESP_LOGE(TAG, "Error setting temperature offset."); - this->error_code_ = MEASUREMENT_INIT_FAILED; - this->mark_failed(); - return; - } - - // If pressure compensation available use it - // else use altitude - if (ambient_pressure_compensation_) { - if (!this->write_command_(SCD4X_CMD_AMBIENT_PRESSURE_COMPENSATION, ambient_pressure_compensation_)) { - ESP_LOGE(TAG, "Error setting ambient pressure compensation."); + if (!this->write_command_(SCD4X_CMD_TEMPERATURE_OFFSET, + (uint16_t)(temperature_offset_ * SCD4X_TEMPERATURE_OFFSET_MULTIPLIER))) { + ESP_LOGE(TAG, "Error setting temperature offset."); this->error_code_ = MEASUREMENT_INIT_FAILED; this->mark_failed(); return; } - } else { - if (!this->write_command_(SCD4X_CMD_ALTITUDE_COMPENSATION, altitude_compensation_)) { - ESP_LOGE(TAG, "Error setting altitude compensation."); + + // If pressure compensation available use it + // else use altitude + if (ambient_pressure_compensation_) { + if (!this->update_ambient_pressure_compensation_(ambient_pressure_)) { + ESP_LOGE(TAG, "Error setting ambient pressure compensation."); + this->error_code_ = MEASUREMENT_INIT_FAILED; + this->mark_failed(); + return; + } + } else { + if (!this->write_command_(SCD4X_CMD_ALTITUDE_COMPENSATION, altitude_compensation_)) { + ESP_LOGE(TAG, "Error setting altitude compensation."); + this->error_code_ = MEASUREMENT_INIT_FAILED; + this->mark_failed(); + return; + } + } + + if (!this->write_command_(SCD4X_CMD_AUTOMATIC_SELF_CALIBRATION, enable_asc_ ? 1 : 0)) { + ESP_LOGE(TAG, "Error setting automatic self calibration."); this->error_code_ = MEASUREMENT_INIT_FAILED; this->mark_failed(); return; } - } - if (!this->write_command_(SCD4X_CMD_AUTOMATIC_SELF_CALIBRATION, enable_asc_ ? 1 : 0)) { - ESP_LOGE(TAG, "Error setting automatic self calibration."); - this->error_code_ = MEASUREMENT_INIT_FAILED; - this->mark_failed(); - return; - } + // Finally start sensor measurements + if (!this->write_command_(SCD4X_CMD_START_CONTINUOUS_MEASUREMENTS)) { + ESP_LOGE(TAG, "Error starting continuous measurements."); + this->error_code_ = MEASUREMENT_INIT_FAILED; + this->mark_failed(); + return; + } - // Finally start sensor measurements - if (!this->write_command_(SCD4X_CMD_START_CONTINUOUS_MEASUREMENTS)) { - ESP_LOGE(TAG, "Error starting continuous measurements."); - this->error_code_ = MEASUREMENT_INIT_FAILED; - this->mark_failed(); - return; - } - - initialized_ = true; - ESP_LOGD(TAG, "Sensor initialized"); + initialized_ = true; + ESP_LOGD(TAG, "Sensor initialized"); + }); }); } @@ -150,6 +156,13 @@ void SCD4XComponent::update() { return; } + if (this->ambient_pressure_source_ != nullptr) { + float pressure = this->ambient_pressure_source_->state / 1000.0f; + if (!std::isnan(pressure)) { + set_ambient_pressure_compensation(this->ambient_pressure_source_->state / 1000.0f); + } + } + // Check if data is ready if (!this->write_command_(SCD4X_CMD_GET_DATA_READY_STATUS)) { this->status_set_warning(); @@ -191,6 +204,28 @@ void SCD4XComponent::update() { this->status_clear_warning(); } +// Note pressure in bar here. Convert to hPa +void SCD4XComponent::set_ambient_pressure_compensation(float pressure_in_bar) { + ambient_pressure_compensation_ = true; + uint16_t new_ambient_pressure = (uint16_t)(pressure_in_bar * 1000); + // remove millibar from comparison to avoid frequent updates +/- 10 millibar doesn't matter + if (initialized_ && (new_ambient_pressure / 10 != ambient_pressure_ / 10)) { + update_ambient_pressure_compensation_(new_ambient_pressure); + ambient_pressure_ = new_ambient_pressure; + } else { + ESP_LOGD(TAG, "ambient pressure compensation skipped - no change required"); + } +} + +bool SCD4XComponent::update_ambient_pressure_compensation_(uint16_t pressure_in_hpa) { + if (this->write_command_(SCD4X_CMD_AMBIENT_PRESSURE_COMPENSATION, pressure_in_hpa)) { + ESP_LOGD(TAG, "setting ambient pressure compensation to %d hPa", pressure_in_hpa); + return true; + } else { + ESP_LOGE(TAG, "Error setting ambient pressure compensation."); + return false; + } +} uint8_t SCD4XComponent::sht_crc_(uint8_t data1, uint8_t data2) { uint8_t bit; diff --git a/esphome/components/scd4x/scd4x.h b/esphome/components/scd4x/scd4x.h index 3c428b8623..4fe2bf14cc 100644 --- a/esphome/components/scd4x/scd4x.h +++ b/esphome/components/scd4x/scd4x.h @@ -18,10 +18,8 @@ class SCD4XComponent : public PollingComponent, public i2c::I2CDevice { void set_automatic_self_calibration(bool asc) { enable_asc_ = asc; } void set_altitude_compensation(uint16_t altitude) { altitude_compensation_ = altitude; } - void set_ambient_pressure_compensation(float pressure) { - ambient_pressure_compensation_ = true; - ambient_pressure_ = (uint16_t)(pressure * 1000); - } + void set_ambient_pressure_compensation(float pressure_in_bar); + void set_ambient_pressure_source(sensor::Sensor *pressure) { ambient_pressure_source_ = pressure; } void set_temperature_offset(float offset) { temperature_offset_ = offset; }; void set_co2_sensor(sensor::Sensor *co2) { co2_sensor_ = co2; } @@ -33,6 +31,7 @@ class SCD4XComponent : public PollingComponent, public i2c::I2CDevice { bool read_data_(uint16_t *data, uint8_t len); bool write_command_(uint16_t command); bool write_command_(uint16_t command, uint16_t data); + bool update_ambient_pressure_compensation_(uint16_t pressure_in_hpa); ERRORCODE error_code_; @@ -47,6 +46,8 @@ class SCD4XComponent : public PollingComponent, public i2c::I2CDevice { sensor::Sensor *co2_sensor_{nullptr}; sensor::Sensor *temperature_sensor_{nullptr}; sensor::Sensor *humidity_sensor_{nullptr}; + // used for compensation + sensor::Sensor *ambient_pressure_source_{nullptr}; }; } // namespace scd4x diff --git a/esphome/components/scd4x/sensor.py b/esphome/components/scd4x/sensor.py index 0b1a960f6f..3e814ffe78 100644 --- a/esphome/components/scd4x/sensor.py +++ b/esphome/components/scd4x/sensor.py @@ -29,6 +29,7 @@ CONF_AUTOMATIC_SELF_CALIBRATION = "automatic_self_calibration" CONF_ALTITUDE_COMPENSATION = "altitude_compensation" CONF_AMBIENT_PRESSURE_COMPENSATION = "ambient_pressure_compensation" CONF_TEMPERATURE_OFFSET = "temperature_offset" +CONF_AMBIENT_PRESSURE_COMPENSATION_SOURCE = "ambient_pressure_compensation_source" CONFIG_SCHEMA = ( cv.Schema( @@ -62,6 +63,9 @@ CONFIG_SCHEMA = ( ), cv.Optional(CONF_AMBIENT_PRESSURE_COMPENSATION): cv.pressure, cv.Optional(CONF_TEMPERATURE_OFFSET, default="4°C"): cv.temperature, + cv.Optional(CONF_AMBIENT_PRESSURE_COMPENSATION_SOURCE): cv.use_id( + sensor.Sensor + ), } ) .extend(cv.polling_component_schema("60s")) @@ -92,7 +96,10 @@ async def to_code(config): cg.add(getattr(var, funcName)(config[key])) for key, funcName in SENSOR_MAP.items(): - if key in config: sens = await sensor.new_sensor(config[key]) cg.add(getattr(var, funcName)(sens)) + + if CONF_AMBIENT_PRESSURE_COMPENSATION_SOURCE in config: + sens = await cg.get_variable(config[CONF_AMBIENT_PRESSURE_COMPENSATION_SOURCE]) + cg.add(var.set_ambient_pressure_source(sens)) From c3a8a044b93b9d53b1a5a2fb451ac18a5bb62196 Mon Sep 17 00:00:00 2001 From: Keith Burzinski Date: Fri, 15 Oct 2021 02:26:26 -0500 Subject: [PATCH 0013/3388] Fix Nextion HTTPClient error for ESP32 (#2524) --- esphome/components/nextion/display.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/esphome/components/nextion/display.py b/esphome/components/nextion/display.py index f4b35fd56f..d95810bfbe 100644 --- a/esphome/components/nextion/display.py +++ b/esphome/components/nextion/display.py @@ -8,7 +8,7 @@ from esphome.const import ( CONF_BRIGHTNESS, CONF_TRIGGER_ID, ) - +from esphome.core import CORE from . import Nextion, nextion_ns, nextion_ref from .base_component import ( CONF_ON_SLEEP, @@ -76,6 +76,9 @@ async def to_code(config): if CONF_TFT_URL in config: cg.add_define("USE_NEXTION_TFT_UPLOAD") cg.add(var.set_tft_url(config[CONF_TFT_URL])) + if CORE.is_esp32: + cg.add_library("WiFiClientSecure", None) + cg.add_library("HTTPClient", None) if CONF_TOUCH_SLEEP_TIMEOUT in config: cg.add(var.set_touch_sleep_timeout_internal(config[CONF_TOUCH_SLEEP_TIMEOUT])) From 98755f36212d689bbc3fc7abc227c2587fc8e282 Mon Sep 17 00:00:00 2001 From: Martin <25747549+martgras@users.noreply.github.com> Date: Fri, 15 Oct 2021 09:27:56 +0200 Subject: [PATCH 0014/3388] Fix bug in register name definition (#2526) --- esphome/components/modbus_controller/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/modbus_controller/__init__.py b/esphome/components/modbus_controller/__init__.py index 7a69029dab..6b452ea25c 100644 --- a/esphome/components/modbus_controller/__init__.py +++ b/esphome/components/modbus_controller/__init__.py @@ -38,7 +38,7 @@ ModbusRegisterType_ns = modbus_controller_ns.namespace("ModbusRegisterType") ModbusRegisterType = ModbusRegisterType_ns.enum("ModbusRegisterType") MODBUS_REGISTER_TYPE = { "coil": ModbusRegisterType.COIL, - "discrete_input": ModbusRegisterType.DISCRETE, + "discrete_input": ModbusRegisterType.DISCRETE_INPUT, "holding": ModbusRegisterType.HOLDING, "read": ModbusRegisterType.READ, } From 4dd1bf920d824aa4bae6ca6836859264e05b567f Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Fri, 15 Oct 2021 22:07:05 +0200 Subject: [PATCH 0015/3388] Replace framework version_hint with source option (#2529) --- esphome/components/esp32/__init__.py | 134 +++++++++++-------------- esphome/components/esp8266/__init__.py | 75 ++++++-------- esphome/core/config.py | 11 +- 3 files changed, 97 insertions(+), 123 deletions(-) diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index 09eabe1fa7..8a13468c76 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -7,6 +7,7 @@ from esphome.helpers import write_file_if_changed from esphome.const import ( CONF_BOARD, CONF_FRAMEWORK, + CONF_SOURCE, CONF_TYPE, CONF_VARIANT, CONF_VERSION, @@ -53,7 +54,7 @@ def set_core_data(config): elif conf[CONF_TYPE] == FRAMEWORK_ARDUINO: CORE.data[KEY_CORE][KEY_TARGET_FRAMEWORK] = "arduino" CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] = cv.Version.parse( - config[CONF_FRAMEWORK][CONF_VERSION_HINT] + config[CONF_FRAMEWORK][CONF_VERSION] ) CORE.data[KEY_ESP32][KEY_BOARD] = config[CONF_BOARD] CORE.data[KEY_ESP32][KEY_VARIANT] = config[CONF_VARIANT] @@ -94,6 +95,13 @@ def _format_framework_arduino_version(ver: cv.Version) -> str: return f"~3.{ver.major}{ver.minor:02d}{ver.patch:02d}.0" +def _format_framework_espidf_version(ver: cv.Version) -> str: + # format the given arduino (https://github.com/espressif/esp-idf/releases) version to + # a PIO platformio/framework-espidf value + # List of package versions: https://api.registry.platformio.org/v3/packages/platformio/tool/framework-espidf + return f"~3.{ver.major}{ver.minor:02d}{ver.patch:02d}.0" + + # NOTE: Keep this in mind when updating the recommended version: # * New framework historically have had some regressions, especially for WiFi. # The new version needs to be thoroughly validated before changing the @@ -123,119 +131,97 @@ ESP_IDF_PLATFORM_VERSION = cv.Version(3, 3, 2) def _arduino_check_versions(value): value = value.copy() lookups = { - "dev": ("https://github.com/espressif/arduino-esp32.git", cv.Version(2, 0, 0)), - "latest": ("", cv.Version(1, 0, 3)), - "recommended": ( - _format_framework_arduino_version(RECOMMENDED_ARDUINO_FRAMEWORK_VERSION), - RECOMMENDED_ARDUINO_FRAMEWORK_VERSION, - ), + "dev": (cv.Version(2, 0, 0), "https://github.com/espressif/arduino-esp32.git"), + "latest": (cv.Version(1, 0, 6), None), + "recommended": (RECOMMENDED_ARDUINO_FRAMEWORK_VERSION, None), } - ver_value = value[CONF_VERSION] - default_ver_hint = None - if ver_value.lower() in lookups: - default_ver_hint = str(lookups[ver_value.lower()][1]) - ver_value = lookups[ver_value.lower()][0] + + if value[CONF_VERSION] in lookups: + if CONF_SOURCE in value: + raise cv.Invalid( + "Framework version needs to be explicitly specified when custom source is used." + ) + + version, source = lookups[value[CONF_VERSION]] else: - with cv.suppress_invalid(): - ver = cv.Version.parse(cv.version_number(value)) - if ver <= cv.Version(1, 0, 3): - ver_value = f"~2.{ver.major}{ver.minor:02d}{ver.patch:02d}.0" - else: - ver_value = f"~3.{ver.major}{ver.minor:02d}{ver.patch:02d}.0" - default_ver_hint = str(ver) - value[CONF_VERSION] = ver_value + version = cv.Version.parse(cv.version_number(value[CONF_VERSION])) + source = value.get(CONF_SOURCE, None) - if CONF_VERSION_HINT not in value and default_ver_hint is None: - raise cv.Invalid("Needs a version hint to understand the framework version") + value[CONF_VERSION] = str(version) + value[CONF_SOURCE] = source or _format_framework_arduino_version(version) - ver_hint_s = value.get(CONF_VERSION_HINT, default_ver_hint) - value[CONF_VERSION_HINT] = ver_hint_s - plat_ver = value.get(CONF_PLATFORM_VERSION, ARDUINO_PLATFORM_VERSION) - value[CONF_PLATFORM_VERSION] = str(plat_ver) + platform_version = value.get(CONF_PLATFORM_VERSION, ARDUINO_PLATFORM_VERSION) + value[CONF_PLATFORM_VERSION] = str(platform_version) - if cv.Version.parse(ver_hint_s) != RECOMMENDED_ARDUINO_FRAMEWORK_VERSION: + if version != RECOMMENDED_ARDUINO_FRAMEWORK_VERSION: _LOGGER.warning( - "The selected arduino framework version is not the recommended one" - ) - _LOGGER.warning( - "If there are connectivity or build issues please remove the manual version" + "The selected Arduino framework version is not the recommended one. " + "If there are connectivity or build issues please remove the manual version." ) return value -def _format_framework_espidf_version(ver: cv.Version) -> str: - # format the given arduino (https://github.com/espressif/esp-idf/releases) version to - # a PIO platformio/framework-espidf value - # List of package versions: https://api.registry.platformio.org/v3/packages/platformio/tool/framework-espidf - return f"~3.{ver.major}{ver.minor:02d}{ver.patch:02d}.0" - - def _esp_idf_check_versions(value): value = value.copy() lookups = { - "dev": ("https://github.com/espressif/esp-idf.git", cv.Version(4, 3, 1)), - "latest": ("", cv.Version(4, 3, 0)), - "recommended": ( - _format_framework_espidf_version(RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION), - RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION, - ), + "dev": (cv.Version(4, 3, 1), "https://github.com/espressif/esp-idf.git"), + "latest": (cv.Version(4, 3, 0), None), + "recommended": (RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION, None), } - ver_value = value[CONF_VERSION] - default_ver_hint = None - if ver_value.lower() in lookups: - default_ver_hint = str(lookups[ver_value.lower()][1]) - ver_value = lookups[ver_value.lower()][0] + + if value[CONF_VERSION] in lookups: + if CONF_SOURCE in value: + raise cv.Invalid( + "Framework version needs to be explicitly specified when custom source is used." + ) + + version, source = lookups[value[CONF_VERSION]] else: - with cv.suppress_invalid(): - ver = cv.Version.parse(cv.version_number(value)) - ver_value = f"~3.{ver.major}{ver.minor:02d}{ver.patch:02d}.0" - default_ver_hint = str(ver) - value[CONF_VERSION] = ver_value + version = cv.Version.parse(cv.version_number(value[CONF_VERSION])) + source = value.get(CONF_SOURCE, None) - if CONF_VERSION_HINT not in value and default_ver_hint is None: - raise cv.Invalid("Needs a version hint to understand the framework version") + if version < cv.Version(4, 0, 0): + raise cv.Invalid("Only ESP-IDF 4.0+ is supported.") - ver_hint_s = value.get(CONF_VERSION_HINT, default_ver_hint) - value[CONF_VERSION_HINT] = ver_hint_s - if cv.Version.parse(ver_hint_s) < cv.Version(4, 0, 0): - raise cv.Invalid("Only ESP-IDF 4.0+ is supported") - if cv.Version.parse(ver_hint_s) != RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION: + value[CONF_VERSION] = str(version) + value[CONF_SOURCE] = source or _format_framework_espidf_version(version) + + platform_version = value.get(CONF_PLATFORM_VERSION, ESP_IDF_PLATFORM_VERSION) + value[CONF_PLATFORM_VERSION] = str(platform_version) + + if version != RECOMMENDED_ARDUINO_FRAMEWORK_VERSION: _LOGGER.warning( - "The selected esp-idf framework version is not the recommended one" + "The selected ESP-IDF framework version is not the recommended one. " + "If there are connectivity or build issues please remove the manual version." ) - _LOGGER.warning( - "If there are connectivity or build issues please remove the manual version" - ) - - plat_ver = value.get(CONF_PLATFORM_VERSION, ESP_IDF_PLATFORM_VERSION) - value[CONF_PLATFORM_VERSION] = str(plat_ver) return value -CONF_VERSION_HINT = "version_hint" CONF_PLATFORM_VERSION = "platform_version" + ARDUINO_FRAMEWORK_SCHEMA = cv.All( cv.Schema( { cv.Optional(CONF_VERSION, default="recommended"): cv.string_strict, - cv.Optional(CONF_VERSION_HINT): cv.version_number, + cv.Optional(CONF_SOURCE): cv.string_strict, cv.Optional(CONF_PLATFORM_VERSION): cv.string_strict, } ), _arduino_check_versions, ) + CONF_SDKCONFIG_OPTIONS = "sdkconfig_options" ESP_IDF_FRAMEWORK_SCHEMA = cv.All( cv.Schema( { cv.Optional(CONF_VERSION, default="recommended"): cv.string_strict, - cv.Optional(CONF_VERSION_HINT): cv.version_number, + cv.Optional(CONF_SOURCE): cv.string_strict, + cv.Optional(CONF_PLATFORM_VERSION): cv.string_strict, cv.Optional(CONF_SDKCONFIG_OPTIONS, default={}): { cv.string_strict: cv.string_strict }, - cv.Optional(CONF_PLATFORM_VERSION): cv.string_strict, cv.Optional(CONF_ADVANCED, default={}): cv.Schema( { cv.Optional(CONF_IGNORE_EFUSE_MAC_CRC, default=False): cv.boolean, @@ -293,7 +279,7 @@ async def to_code(config): cg.add_build_flag("-Wno-nonnull-compare") cg.add_platformio_option( "platform_packages", - [f"platformio/framework-espidf @ {conf[CONF_VERSION]}"], + [f"platformio/framework-espidf @ {conf[CONF_SOURCE]}"], ) add_idf_sdkconfig_option("CONFIG_PARTITION_TABLE_SINGLE_APP", False) add_idf_sdkconfig_option("CONFIG_PARTITION_TABLE_CUSTOM", True) @@ -323,7 +309,7 @@ async def to_code(config): cg.add_build_flag("-DUSE_ESP32_FRAMEWORK_ARDUINO") cg.add_platformio_option( "platform_packages", - [f"platformio/framework-arduinoespressif32 @ {conf[CONF_VERSION]}"], + [f"platformio/framework-arduinoespressif32 @ {conf[CONF_SOURCE]}"], ) cg.add_platformio_option("board_build.partitions", "partitions.csv") diff --git a/esphome/components/esp8266/__init__.py b/esphome/components/esp8266/__init__.py index 93a461ba1f..a5323db8bf 100644 --- a/esphome/components/esp8266/__init__.py +++ b/esphome/components/esp8266/__init__.py @@ -4,6 +4,7 @@ from esphome.const import ( CONF_BOARD, CONF_BOARD_FLASH_MODE, CONF_FRAMEWORK, + CONF_SOURCE, CONF_VERSION, KEY_CORE, KEY_FRAMEWORK_VERSION, @@ -31,7 +32,7 @@ def set_core_data(config): CORE.data[KEY_CORE][KEY_TARGET_PLATFORM] = "esp8266" CORE.data[KEY_CORE][KEY_TARGET_FRAMEWORK] = "arduino" CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] = cv.Version.parse( - config[CONF_FRAMEWORK][CONF_VERSION_HINT] + config[CONF_FRAMEWORK][CONF_VERSION] ) CORE.data[KEY_ESP8266][KEY_BOARD] = config[CONF_BOARD] return config @@ -70,66 +71,50 @@ ARDUINO_3_PLATFORM_VERSION = cv.Version(3, 0, 2) def _arduino_check_versions(value): value = value.copy() lookups = { - "dev": ("https://github.com/esp8266/Arduino.git", cv.Version(3, 0, 2)), - "latest": ("", cv.Version(3, 0, 2)), - "recommended": ( - _format_framework_arduino_version(RECOMMENDED_ARDUINO_FRAMEWORK_VERSION), - RECOMMENDED_ARDUINO_FRAMEWORK_VERSION, - ), + "dev": (cv.Version(3, 0, 2), "https://github.com/esp8266/Arduino.git"), + "latest": (cv.Version(3, 0, 2), None), + "recommended": (RECOMMENDED_ARDUINO_FRAMEWORK_VERSION, None), } - ver_value = value[CONF_VERSION] - default_ver_hint = None - if ver_value.lower() in lookups: - default_ver_hint = str(lookups[ver_value.lower()][1]) - ver_value = lookups[ver_value.lower()][0] + + if value[CONF_VERSION] in lookups: + if CONF_SOURCE in value: + raise cv.Invalid( + "Framework version needs to be explicitly specified when custom source is used." + ) + + version, source = lookups[value[CONF_VERSION]] else: - with cv.suppress_invalid(): - ver = cv.Version.parse(cv.version_number(value)) - if ver <= cv.Version(2, 4, 1): - ver_value = f"~1.{ver.major}{ver.minor:02d}{ver.patch:02d}.0" - elif ver <= cv.Version(2, 6, 2): - ver_value = f"~2.{ver.major}{ver.minor:02d}{ver.patch:02d}.0" - else: - ver_value = f"~3.{ver.major}{ver.minor:02d}{ver.patch:02d}.0" - default_ver_hint = str(ver) + version = cv.Version.parse(cv.version_number(value[CONF_VERSION])) + source = value.get(CONF_SOURCE, None) - value[CONF_VERSION] = ver_value + value[CONF_VERSION] = str(version) + value[CONF_SOURCE] = source or _format_framework_arduino_version(version) - if CONF_VERSION_HINT not in value and default_ver_hint is None: - raise cv.Invalid("Needs a version hint to understand the framework version") - - ver_hint_s = value.get(CONF_VERSION_HINT, default_ver_hint) - value[CONF_VERSION_HINT] = ver_hint_s - plat_ver = value.get(CONF_PLATFORM_VERSION) - - if plat_ver is None: - ver_hint = cv.Version.parse(ver_hint_s) - if ver_hint >= cv.Version(3, 0, 0): - plat_ver = ARDUINO_3_PLATFORM_VERSION - elif ver_hint >= cv.Version(2, 5, 0): - plat_ver = ARDUINO_2_PLATFORM_VERSION + platform_version = value.get(CONF_PLATFORM_VERSION) + if platform_version is None: + if version >= cv.Version(3, 0, 0): + platform_version = ARDUINO_3_PLATFORM_VERSION + elif version >= cv.Version(2, 5, 0): + platform_version = ARDUINO_2_PLATFORM_VERSION else: - plat_ver = cv.Version(1, 8, 0) - value[CONF_PLATFORM_VERSION] = str(plat_ver) + platform_version = cv.Version(1, 8, 0) + value[CONF_PLATFORM_VERSION] = str(platform_version) - if cv.Version.parse(ver_hint_s) != RECOMMENDED_ARDUINO_FRAMEWORK_VERSION: + if version != RECOMMENDED_ARDUINO_FRAMEWORK_VERSION: _LOGGER.warning( - "The selected arduino framework version is not the recommended one" - ) - _LOGGER.warning( - "If there are connectivity or build issues please remove the manual version" + "The selected Arduino framework version is not the recommended one. " + "If there are connectivity or build issues please remove the manual version." ) return value -CONF_VERSION_HINT = "version_hint" CONF_PLATFORM_VERSION = "platform_version" ARDUINO_FRAMEWORK_SCHEMA = cv.All( cv.Schema( { cv.Optional(CONF_VERSION, default="recommended"): cv.string_strict, - cv.Optional(CONF_VERSION_HINT): cv.version_number, + cv.Optional(CONF_SOURCE): cv.string_strict, cv.Optional(CONF_PLATFORM_VERSION): cv.string_strict, } ), @@ -167,7 +152,7 @@ async def to_code(config): cg.add_build_flag("-DUSE_ESP8266_FRAMEWORK_ARDUINO") cg.add_platformio_option( "platform_packages", - [f"platformio/framework-arduinoespressif8266 @ {conf[CONF_VERSION]}"], + [f"platformio/framework-arduinoespressif8266 @ {conf[CONF_SOURCE]}"], ) cg.add_platformio_option( "platform", f"platformio/espressif8266 @ {conf[CONF_PLATFORM_VERSION]}" diff --git a/esphome/core/config.py b/esphome/core/config.py index bbdfcf124c..c495fefddd 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -23,6 +23,7 @@ from esphome.const import ( CONF_PLATFORMIO_OPTIONS, CONF_PRIORITY, CONF_PROJECT, + CONF_SOURCE, CONF_TRIGGER_ID, CONF_TYPE, CONF_VERSION, @@ -181,10 +182,12 @@ def preload_core_config(config, result): if CONF_BOARD_FLASH_MODE in conf: plat_conf[CONF_BOARD_FLASH_MODE] = conf.pop(CONF_BOARD_FLASH_MODE) if CONF_ARDUINO_VERSION in conf: - plat_conf[CONF_FRAMEWORK] = { - CONF_TYPE: "arduino", - CONF_VERSION: conf.pop(CONF_ARDUINO_VERSION), - } + plat_conf[CONF_FRAMEWORK] = {CONF_TYPE: "arduino"} + try: + cv.Version.parse(conf[CONF_ARDUINO_VERSION]) + plat_conf[CONF_FRAMEWORK][CONF_VERSION] = conf.pop(CONF_ARDUINO_VERSION) + except ValueError: + plat_conf[CONF_FRAMEWORK][CONF_SOURCE] = conf.pop(CONF_ARDUINO_VERSION) if CONF_BOARD in conf: plat_conf[CONF_BOARD] = conf.pop(CONF_BOARD) # Insert generated target platform config to main config From f83950fd75e0c0156484c7d54ac556b762a7f636 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Sun, 17 Oct 2021 08:53:49 +0200 Subject: [PATCH 0016/3388] Fix bitshift on read in ADE7953 (#2537) --- esphome/components/ade7953/ade7953.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/esphome/components/ade7953/ade7953.h b/esphome/components/ade7953/ade7953.h index c6fb383ed8..bb160cd8eb 100644 --- a/esphome/components/ade7953/ade7953.h +++ b/esphome/components/ade7953/ade7953.h @@ -76,9 +76,9 @@ class ADE7953 : public i2c::I2CDevice, public PollingComponent { return err; *value = 0; *value |= ((uint32_t) recv[0]) << 24; - *value |= ((uint32_t) recv[1]) << 24; - *value |= ((uint32_t) recv[2]) << 24; - *value |= ((uint32_t) recv[3]) << 24; + *value |= ((uint32_t) recv[1]) << 16; + *value |= ((uint32_t) recv[2]) << 8; + *value |= ((uint32_t) recv[3]); return i2c::ERROR_OK; } From db3fa1ade72a253d4ed90d95c05f803b0d890ff3 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Sun, 17 Oct 2021 19:54:09 +1300 Subject: [PATCH 0017/3388] Allow downloading all bin files from backend in dashboard (#2514) Co-authored-by: Otto Winter --- esphome/__main__.py | 29 +++++++++++- esphome/dashboard/dashboard.py | 82 +++++++++++++++++++++++++++++----- 2 files changed, 99 insertions(+), 12 deletions(-) diff --git a/esphome/__main__.py b/esphome/__main__.py index feb95e93c7..1b9c601091 100644 --- a/esphome/__main__.py +++ b/esphome/__main__.py @@ -180,7 +180,11 @@ def compile_program(args, config): from esphome import platformio_api _LOGGER.info("Compiling app...") - return platformio_api.run_compile(config, CORE.verbose) + rc = platformio_api.run_compile(config, CORE.verbose) + if rc != 0: + return rc + idedata = platformio_api.get_idedata(config) + return 0 if idedata is not None else 1 def upload_using_esptool(config, port): @@ -458,6 +462,21 @@ def command_update_all(args): return failed +def command_idedata(args, config): + from esphome import platformio_api + import json + + logging.disable(logging.INFO) + logging.disable(logging.WARNING) + + idedata = platformio_api.get_idedata(config) + if idedata is None: + return 1 + + print(json.dumps(idedata.raw, indent=2) + "\n") + return 0 + + PRE_CONFIG_ACTIONS = { "wizard": command_wizard, "version": command_version, @@ -475,6 +494,7 @@ POST_CONFIG_ACTIONS = { "clean-mqtt": command_clean_mqtt, "mqtt-fingerprint": command_mqtt_fingerprint, "clean": command_clean, + "idedata": command_idedata, } @@ -650,6 +670,11 @@ def parse_args(argv): "configuration", help="Your YAML configuration file directories.", nargs="+" ) + parser_idedata = subparsers.add_parser("idedata") + parser_idedata.add_argument( + "configuration", help="Your YAML configuration file(s).", nargs=1 + ) + # Keep backward compatibility with the old command line format of # esphome . # @@ -762,7 +787,7 @@ def run_esphome(argv): config = read_config(dict(args.substitution) if args.substitution else {}) if config is None: - return 1 + return 2 CORE.config = config if args.command not in POST_CONFIG_ACTIONS: diff --git a/esphome/dashboard/dashboard.py b/esphome/dashboard/dashboard.py index eb698a7de1..501666b100 100644 --- a/esphome/dashboard/dashboard.py +++ b/esphome/dashboard/dashboard.py @@ -9,6 +9,7 @@ import json import logging import multiprocessing import os +from pathlib import Path import secrets import shutil import subprocess @@ -26,7 +27,7 @@ import tornado.process import tornado.web import tornado.websocket -from esphome import const, util +from esphome import const, platformio_api, util from esphome.helpers import mkdir_p, get_bool_env, run_system_command from esphome.storage_json import ( EsphomeStorageJSON, @@ -398,17 +399,45 @@ class DownloadBinaryRequestHandler(BaseHandler): @authenticated @bind_config def get(self, configuration=None): - # pylint: disable=no-value-for-parameter - storage_path = ext_storage_path(settings.config_dir, configuration) - storage_json = StorageJSON.load(storage_path) - if storage_json is None: - self.send_error() + type = self.get_argument("type", "firmware.bin") + + if type == "firmware.bin": + storage_path = ext_storage_path(settings.config_dir, configuration) + storage_json = StorageJSON.load(storage_path) + if storage_json is None: + self.send_error(404) + return + filename = f"{storage_json.name}.bin" + path = storage_json.firmware_bin_path + + else: + args = ["esphome", "idedata", settings.rel_path(configuration)] + rc, stdout, _ = run_system_command(*args) + + if rc != 0: + self.send_error(404 if rc == 2 else 500) + return + + idedata = platformio_api.IDEData(json.loads(stdout)) + + found = False + for image in idedata.extra_flash_images: + if image.path.endswith(type): + path = image.path + filename = type + found = True + break + + if not found: + self.send_error(404) + return + + self.set_header("Content-Type", "application/octet-stream") + self.set_header("Content-Disposition", f'attachment; filename="{filename}"') + if not Path(path).is_file(): + self.send_error(404) return - path = storage_json.firmware_bin_path - self.set_header("Content-Type", "application/octet-stream") - filename = f"{storage_json.name}.bin" - self.set_header("Content-Disposition", f'attachment; filename="{filename}"') with open(path, "rb") as f: while True: data = f.read(16384) @@ -418,6 +447,38 @@ class DownloadBinaryRequestHandler(BaseHandler): self.finish() +class ManifestRequestHandler(BaseHandler): + @authenticated + @bind_config + def get(self, configuration=None): + args = ["esphome", "idedata", settings.rel_path(configuration)] + rc, stdout, _ = run_system_command(*args) + + if rc != 0: + self.send_error(404 if rc == 2 else 500) + return + + idedata = platformio_api.IDEData(json.loads(stdout)) + + firmware_offset = "0x10000" if idedata.extra_flash_images else "0x0" + flash_images = [ + { + "path": f"./download.bin?configuration={configuration}&type=firmware.bin", + "offset": firmware_offset, + } + ] + [ + { + "path": f"./download.bin?configuration={configuration}&type={os.path.basename(image.path)}", + "offset": image.offset, + } + for image in idedata.extra_flash_images + ] + + self.set_header("Content-Type", "application/json") + self.write(json.dumps(flash_images)) + self.finish() + + def _list_dashboard_entries(): files = settings.list_yaml_files() return [DashboardEntry(file) for file in files] @@ -862,6 +923,7 @@ def make_app(debug=get_bool_env(ENV_DEV)): (f"{rel}info", InfoRequestHandler), (f"{rel}edit", EditRequestHandler), (f"{rel}download.bin", DownloadBinaryRequestHandler), + (f"{rel}manifest.json", ManifestRequestHandler), (f"{rel}serial-ports", SerialPortRequestHandler), (f"{rel}ping", PingRequestHandler), (f"{rel}delete", DeleteRequestHandler), From f045382d2095faa3149c5272129da521fa68ebd4 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sun, 17 Oct 2021 00:26:59 -0700 Subject: [PATCH 0018/3388] Bump dashboard to 20211015.0 (#2525) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 23a00d3755..8028007a0a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,7 +9,7 @@ pyserial==3.5 platformio==5.2.1 esptool==3.1 click==8.0.3 -esphome-dashboard==20211011.1 +esphome-dashboard==20211015.0 aioesphomeapi==9.1.5 # esp-idf requires this, but doesn't bundle it by default From 4b44280d5392d6b1f1e3722993a405bfe268d3b5 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Sun, 17 Oct 2021 20:34:07 +1300 Subject: [PATCH 0019/3388] Bump version to 2021.10.0b4 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index eacbb19a7f..b505087115 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2021.10.0b3" +__version__ = "2021.10.0b4" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" From 70b62f272eb2eeb7639f641240a20f02a1ffaefa Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Sun, 17 Oct 2021 21:01:51 +0200 Subject: [PATCH 0020/3388] Only show timestamp for dashboard access logs (#2540) --- esphome/__main__.py | 7 ++++++- esphome/log.py | 14 ++++++++++---- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/esphome/__main__.py b/esphome/__main__.py index 1b9c601091..97059154fd 100644 --- a/esphome/__main__.py +++ b/esphome/__main__.py @@ -758,7 +758,12 @@ def run_esphome(argv): args = parse_args(argv) CORE.dashboard = args.dashboard - setup_log(args.verbose, args.quiet) + setup_log( + args.verbose, + args.quiet, + # Show timestamp for dashboard access logs + args.command == "dashboard", + ) if args.deprecated_argv_suggestion is not None and args.command != "vscode": _LOGGER.warning( "Calling ESPHome with the configuration before the command is deprecated " diff --git a/esphome/log.py b/esphome/log.py index abefcf6308..e7ba0fdd82 100644 --- a/esphome/log.py +++ b/esphome/log.py @@ -49,8 +49,10 @@ def color(col: str, msg: str, reset: bool = True) -> bool: class ESPHomeLogFormatter(logging.Formatter): - def __init__(self): - super().__init__(fmt="%(asctime)s %(levelname)s %(message)s", style="%") + def __init__(self, *, include_timestamp: bool): + fmt = "%(asctime)s " if include_timestamp else "" + fmt += "%(levelname)s %(message)s" + super().__init__(fmt=fmt, style="%") def format(self, record): formatted = super().format(record) @@ -64,7 +66,9 @@ class ESPHomeLogFormatter(logging.Formatter): return f"{prefix}{formatted}{Style.RESET_ALL}" -def setup_log(debug=False, quiet=False): +def setup_log( + debug: bool = False, quiet: bool = False, include_timestamp: bool = False +) -> None: import colorama if debug: @@ -79,4 +83,6 @@ def setup_log(debug=False, quiet=False): logging.getLogger("urllib3").setLevel(logging.WARNING) colorama.init() - logging.getLogger().handlers[0].setFormatter(ESPHomeLogFormatter()) + logging.getLogger().handlers[0].setFormatter( + ESPHomeLogFormatter(include_timestamp=include_timestamp) + ) From 0524f8c677c864c7ebfbbe3bff2fad30cc949e91 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 18 Oct 2021 09:55:35 +1300 Subject: [PATCH 0021/3388] Fix const used for IDF recommended version (#2542) --- esphome/components/esp32/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index 8a13468c76..44e24b21d2 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -190,7 +190,7 @@ def _esp_idf_check_versions(value): platform_version = value.get(CONF_PLATFORM_VERSION, ESP_IDF_PLATFORM_VERSION) value[CONF_PLATFORM_VERSION] = str(platform_version) - if version != RECOMMENDED_ARDUINO_FRAMEWORK_VERSION: + if version != RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION: _LOGGER.warning( "The selected ESP-IDF framework version is not the recommended one. " "If there are connectivity or build issues please remove the manual version." From 63a9acaa19c6292610d92e1764ae6995883cd331 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 18 Oct 2021 09:56:31 +1300 Subject: [PATCH 0022/3388] Fix Bluetooth setup_priorities (#2458) Co-authored-by: Otto Winter --- esphome/components/ble_client/ble_client.cpp | 2 ++ esphome/components/ble_client/ble_client.h | 1 + esphome/components/esp32_ble_beacon/esp32_ble_beacon.cpp | 2 +- esphome/components/esp32_ble_server/ble_server.cpp | 2 +- esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp | 2 ++ esphome/components/esp32_ble_tracker/esp32_ble_tracker.h | 1 + 6 files changed, 8 insertions(+), 2 deletions(-) diff --git a/esphome/components/ble_client/ble_client.cpp b/esphome/components/ble_client/ble_client.cpp index 8ff516d735..e6cdb0c23d 100644 --- a/esphome/components/ble_client/ble_client.cpp +++ b/esphome/components/ble_client/ble_client.cpp @@ -11,6 +11,8 @@ namespace ble_client { static const char *const TAG = "ble_client"; +float BLEClient::get_setup_priority() const { return setup_priority::AFTER_BLUETOOTH; } + void BLEClient::setup() { auto ret = esp_ble_gattc_app_register(this->app_id); if (ret) { diff --git a/esphome/components/ble_client/ble_client.h b/esphome/components/ble_client/ble_client.h index 4a17ccb79b..23123914e8 100644 --- a/esphome/components/ble_client/ble_client.h +++ b/esphome/components/ble_client/ble_client.h @@ -81,6 +81,7 @@ class BLEClient : public espbt::ESPBTClient, public Component { void setup() override; void dump_config() override; void loop() override; + float get_setup_priority() const override; void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) override; diff --git a/esphome/components/esp32_ble_beacon/esp32_ble_beacon.cpp b/esphome/components/esp32_ble_beacon/esp32_ble_beacon.cpp index f6bab8e6df..955bc8595f 100644 --- a/esphome/components/esp32_ble_beacon/esp32_ble_beacon.cpp +++ b/esphome/components/esp32_ble_beacon/esp32_ble_beacon.cpp @@ -57,7 +57,7 @@ void ESP32BLEBeacon::setup() { ); } -float ESP32BLEBeacon::get_setup_priority() const { return setup_priority::DATA; } +float ESP32BLEBeacon::get_setup_priority() const { return setup_priority::BLUETOOTH; } void ESP32BLEBeacon::ble_core_task(void *params) { ble_setup(); diff --git a/esphome/components/esp32_ble_server/ble_server.cpp b/esphome/components/esp32_ble_server/ble_server.cpp index 0b91c238c3..e0fb80f94b 100644 --- a/esphome/components/esp32_ble_server/ble_server.cpp +++ b/esphome/components/esp32_ble_server/ble_server.cpp @@ -154,7 +154,7 @@ void BLEServer::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t ga } } -float BLEServer::get_setup_priority() const { return setup_priority::BLUETOOTH - 10; } +float BLEServer::get_setup_priority() const { return setup_priority::AFTER_BLUETOOTH; } void BLEServer::dump_config() { ESP_LOGCONFIG(TAG, "ESP32 BLE Server:"); } diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp index 65749f5124..303cb34aa7 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp @@ -40,6 +40,8 @@ uint64_t ble_addr_to_uint64(const esp_bd_addr_t address) { return u; } +float ESP32BLETracker::get_setup_priority() const { return setup_priority::BLUETOOTH; } + void ESP32BLETracker::setup() { global_esp32_ble_tracker = this; this->scan_result_lock_ = xSemaphoreCreateMutex(); diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h index 1308119df5..02e102f06c 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h @@ -171,6 +171,7 @@ class ESP32BLETracker : public Component { /// Setup the FreeRTOS task and the Bluetooth stack. void setup() override; void dump_config() override; + float get_setup_priority() const override; void loop() override; From 723fb7eaac75cbfcea472db58bc311f66ee6ee77 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Mon, 18 Oct 2021 02:36:18 +0200 Subject: [PATCH 0023/3388] Autodetect ESP32 variant (#2530) Co-authored-by: Otto winter --- esphome/components/esp32/__init__.py | 25 ++++-- esphome/components/esp32/boards.py | 124 ++++++++++++++++++++++++++ esphome/components/logger/__init__.py | 3 +- 3 files changed, 144 insertions(+), 8 deletions(-) diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index 44e24b21d2..e1128ff227 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -27,13 +27,10 @@ from .const import ( # noqa KEY_ESP32, KEY_SDKCONFIG_OPTIONS, KEY_VARIANT, - VARIANT_ESP32, - VARIANT_ESP32S2, - VARIANT_ESP32S3, VARIANT_ESP32C3, - VARIANT_ESP32H2, VARIANTS, ) +from .boards import BOARD_TO_VARIANT # force import gpio to register pin schema from .gpio import esp32_pin_to_code # noqa @@ -199,6 +196,21 @@ def _esp_idf_check_versions(value): return value +def _detect_variant(value): + if CONF_VARIANT not in value: + board = value[CONF_BOARD] + if board not in BOARD_TO_VARIANT: + raise cv.Invalid( + "This board is unknown, please set the variant manually", + path=[CONF_BOARD], + ) + + value = value.copy() + value[CONF_VARIANT] = BOARD_TO_VARIANT[board] + + return value + + CONF_PLATFORM_VERSION = "platform_version" ARDUINO_FRAMEWORK_SCHEMA = cv.All( @@ -250,12 +262,11 @@ CONFIG_SCHEMA = cv.All( cv.Schema( { cv.Required(CONF_BOARD): cv.string_strict, - cv.Optional(CONF_VARIANT, default="ESP32"): cv.one_of( - *VARIANTS, upper=True - ), + cv.Optional(CONF_VARIANT): cv.one_of(*VARIANTS, upper=True), cv.Optional(CONF_FRAMEWORK, default={}): FRAMEWORK_SCHEMA, } ), + _detect_variant, set_core_data, ) diff --git a/esphome/components/esp32/boards.py b/esphome/components/esp32/boards.py index ddf4bf2026..7f7bb2259f 100644 --- a/esphome/components/esp32/boards.py +++ b/esphome/components/esp32/boards.py @@ -1,3 +1,5 @@ +from .const import VARIANT_ESP32, VARIANT_ESP32S2, VARIANT_ESP32C3 + ESP32_BASE_PINS = { "TX": 1, "RX": 3, @@ -925,3 +927,125 @@ ESP32_BOARD_PINS = { }, "xinabox_cw02": {"LED": 27}, } + +""" +BOARD_TO_VARIANT generated with: + +git clone https://github.com/platformio/platform-espressif32 +for x in platform-espressif32/boards/*.json; do + mcu=$(jq -r .build.mcu <"$x"); + fname=$(basename "$x") + board="${fname%.*}" + variant=$(echo "$mcu" | tr '[:lower:]' '[:upper:]') + echo " \"$board\": VARIANT_${variant}," +done | sort +""" + +BOARD_TO_VARIANT = { + "alksesp32": VARIANT_ESP32, + "az-delivery-devkit-v4": VARIANT_ESP32, + "bpi-bit": VARIANT_ESP32, + "briki_abc_esp32": VARIANT_ESP32, + "briki_mbc-wb_esp32": VARIANT_ESP32, + "d-duino-32": VARIANT_ESP32, + "esp320": VARIANT_ESP32, + "esp32-c3-devkitm-1": VARIANT_ESP32C3, + "esp32cam": VARIANT_ESP32, + "esp32-devkitlipo": VARIANT_ESP32, + "esp32dev": VARIANT_ESP32, + "esp32doit-devkit-v1": VARIANT_ESP32, + "esp32doit-espduino": VARIANT_ESP32, + "esp32-evb": VARIANT_ESP32, + "esp32-gateway": VARIANT_ESP32, + "esp32-poe-iso": VARIANT_ESP32, + "esp32-poe": VARIANT_ESP32, + "esp32-pro": VARIANT_ESP32, + "esp32-s2-kaluga-1": VARIANT_ESP32S2, + "esp32-s2-saola-1": VARIANT_ESP32S2, + "esp32thing_plus": VARIANT_ESP32, + "esp32thing": VARIANT_ESP32, + "esp32vn-iot-uno": VARIANT_ESP32, + "espea32": VARIANT_ESP32, + "espectro32": VARIANT_ESP32, + "espino32": VARIANT_ESP32, + "esp-wrover-kit": VARIANT_ESP32, + "etboard": VARIANT_ESP32, + "featheresp32-s2": VARIANT_ESP32S2, + "featheresp32": VARIANT_ESP32, + "firebeetle32": VARIANT_ESP32, + "fm-devkit": VARIANT_ESP32, + "frogboard": VARIANT_ESP32, + "healthypi4": VARIANT_ESP32, + "heltec_wifi_kit_32_v2": VARIANT_ESP32, + "heltec_wifi_kit_32": VARIANT_ESP32, + "heltec_wifi_lora_32_V2": VARIANT_ESP32, + "heltec_wifi_lora_32": VARIANT_ESP32, + "heltec_wireless_stick_lite": VARIANT_ESP32, + "heltec_wireless_stick": VARIANT_ESP32, + "honeylemon": VARIANT_ESP32, + "hornbill32dev": VARIANT_ESP32, + "hornbill32minima": VARIANT_ESP32, + "imbrios-logsens-v1p1": VARIANT_ESP32, + "inex_openkb": VARIANT_ESP32, + "intorobot": VARIANT_ESP32, + "iotaap_magnolia": VARIANT_ESP32, + "iotbusio": VARIANT_ESP32, + "iotbusproteus": VARIANT_ESP32, + "kits-edu": VARIANT_ESP32, + "labplus_mpython": VARIANT_ESP32, + "lolin32_lite": VARIANT_ESP32, + "lolin32": VARIANT_ESP32, + "lolin_d32_pro": VARIANT_ESP32, + "lolin_d32": VARIANT_ESP32, + "lopy4": VARIANT_ESP32, + "lopy": VARIANT_ESP32, + "m5stack-atom": VARIANT_ESP32, + "m5stack-core2": VARIANT_ESP32, + "m5stack-core-esp32": VARIANT_ESP32, + "m5stack-coreink": VARIANT_ESP32, + "m5stack-fire": VARIANT_ESP32, + "m5stack-grey": VARIANT_ESP32, + "m5stack-timer-cam": VARIANT_ESP32, + "m5stick-c": VARIANT_ESP32, + "magicbit": VARIANT_ESP32, + "mgbot-iotik32a": VARIANT_ESP32, + "mgbot-iotik32b": VARIANT_ESP32, + "mhetesp32devkit": VARIANT_ESP32, + "mhetesp32minikit": VARIANT_ESP32, + "microduino-core-esp32": VARIANT_ESP32, + "nano32": VARIANT_ESP32, + "nina_w10": VARIANT_ESP32, + "node32s": VARIANT_ESP32, + "nodemcu-32s": VARIANT_ESP32, + "nscreen-32": VARIANT_ESP32, + "odroid_esp32": VARIANT_ESP32, + "onehorse32dev": VARIANT_ESP32, + "oroca_edubot": VARIANT_ESP32, + "pico32": VARIANT_ESP32, + "piranha_esp32": VARIANT_ESP32, + "pocket_32": VARIANT_ESP32, + "pycom_gpy": VARIANT_ESP32, + "qchip": VARIANT_ESP32, + "quantum": VARIANT_ESP32, + "sensesiot_weizen": VARIANT_ESP32, + "sg-o_airMon": VARIANT_ESP32, + "s_odi_ultra": VARIANT_ESP32, + "sparkfun_lora_gateway_1-channel": VARIANT_ESP32, + "tinypico": VARIANT_ESP32, + "ttgo-lora32-v1": VARIANT_ESP32, + "ttgo-lora32-v21": VARIANT_ESP32, + "ttgo-lora32-v2": VARIANT_ESP32, + "ttgo-t1": VARIANT_ESP32, + "ttgo-t7-v13-mini32": VARIANT_ESP32, + "ttgo-t7-v14-mini32": VARIANT_ESP32, + "ttgo-t-beam": VARIANT_ESP32, + "ttgo-t-watch": VARIANT_ESP32, + "turta_iot_node": VARIANT_ESP32, + "vintlabs-devkit-v1": VARIANT_ESP32, + "wemosbat": VARIANT_ESP32, + "wemos_d1_mini32": VARIANT_ESP32, + "wesp32": VARIANT_ESP32, + "widora-air": VARIANT_ESP32, + "wifiduino32": VARIANT_ESP32, + "xinabox_cw02": VARIANT_ESP32, +} diff --git a/esphome/components/logger/__init__.py b/esphome/components/logger/__init__.py index fe2a3ec8f8..20a0b0f792 100644 --- a/esphome/components/logger/__init__.py +++ b/esphome/components/logger/__init__.py @@ -19,7 +19,8 @@ from esphome.const import ( CONF_TX_BUFFER_SIZE, ) from esphome.core import CORE, EsphomeError, Lambda, coroutine_with_priority -from esphome.components.esp32 import get_esp32_variant, VARIANT_ESP32S2, VARIANT_ESP32C3 +from esphome.components.esp32 import get_esp32_variant +from esphome.components.esp32.const import VARIANT_ESP32S2, VARIANT_ESP32C3 CODEOWNERS = ["@esphome/core"] logger_ns = cg.esphome_ns.namespace("logger") From b5734c2b208e7b5285baa75d94307162944f0493 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 18 Oct 2021 15:31:01 +1300 Subject: [PATCH 0024/3388] Bump version to 2021.10.0b5 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index b505087115..39616ca062 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2021.10.0b4" +__version__ = "2021.10.0b5" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" From 4a1e50fed171e44eb8f80cbd6e3ab9db7b394e2a Mon Sep 17 00:00:00 2001 From: Maurice Makaay Date: Fri, 15 Oct 2021 22:06:32 +0200 Subject: [PATCH 0025/3388] OTA firmware MD5 check + password support for esp-idf (#2507) Co-authored-by: Maurice Makaay --- CODEOWNERS | 1 + esphome/components/md5/__init__.py | 1 + esphome/components/md5/md5.cpp | 51 ++++++++++++++++ esphome/components/md5/md5.h | 58 +++++++++++++++++++ esphome/components/ota/__init__.py | 12 +--- .../ota/ota_backend_arduino_esp32.h | 1 + .../components/ota/ota_backend_esp_idf.cpp | 12 +++- esphome/components/ota/ota_backend_esp_idf.h | 3 + esphome/components/ota/ota_component.cpp | 27 ++++----- esphome/core/defines.h | 1 + 10 files changed, 139 insertions(+), 28 deletions(-) create mode 100644 esphome/components/md5/__init__.py create mode 100644 esphome/components/md5/md5.cpp create mode 100644 esphome/components/md5/md5.h diff --git a/CODEOWNERS b/CODEOWNERS index 4c3084d463..a7cf3a1b68 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -89,6 +89,7 @@ esphome/components/mcp23x17_base/* @jesserockz esphome/components/mcp23xxx_base/* @jesserockz esphome/components/mcp2515/* @danielschramm @mvturnho esphome/components/mcp9808/* @k7hpn +esphome/components/md5/* @esphome/core esphome/components/mdns/* @esphome/core esphome/components/midea/* @dudanov esphome/components/mitsubishi/* @RubyBailey diff --git a/esphome/components/md5/__init__.py b/esphome/components/md5/__init__.py new file mode 100644 index 0000000000..f70ffa9520 --- /dev/null +++ b/esphome/components/md5/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@esphome/core"] diff --git a/esphome/components/md5/md5.cpp b/esphome/components/md5/md5.cpp new file mode 100644 index 0000000000..c6ff783439 --- /dev/null +++ b/esphome/components/md5/md5.cpp @@ -0,0 +1,51 @@ +#include +#include +#include "md5.h" +#include "esphome/core/helpers.h" + +namespace esphome { +namespace md5 { + +void MD5Digest::init() { + memset(this->digest_, 0, 16); + MD5Init(&this->ctx_); +} + +void MD5Digest::add(const uint8_t *data, size_t len) { MD5Update(&this->ctx_, data, len); } + +void MD5Digest::calculate() { MD5Final(this->digest_, &this->ctx_); } + +void MD5Digest::get_bytes(uint8_t *output) { memcpy(output, this->digest_, 16); } + +void MD5Digest::get_hex(char *output) { + for (size_t i = 0; i < 16; i++) { + sprintf(output + i * 2, "%02x", this->digest_[i]); + } +} + +bool MD5Digest::equals_bytes(const char *expected) { + for (size_t i = 0; i < 16; i++) { + if (expected[i] != this->digest_[i]) { + return false; + } + } + return true; +} + +bool MD5Digest::equals_hex(const char *expected) { + for (size_t i = 0; i < 16; i++) { + auto high = parse_hex(expected[i * 2]); + auto low = parse_hex(expected[i * 2 + 1]); + if (!high.has_value() || !low.has_value()) { + return false; + } + auto value = (*high << 4) | *low; + if (value != this->digest_[i]) { + return false; + } + } + return true; +} + +} // namespace md5 +} // namespace esphome diff --git a/esphome/components/md5/md5.h b/esphome/components/md5/md5.h new file mode 100644 index 0000000000..e40f419347 --- /dev/null +++ b/esphome/components/md5/md5.h @@ -0,0 +1,58 @@ +#pragma once + +#include "esphome/core/defines.h" + +#ifdef USE_ESP_IDF +#include "esp32/rom/md5_hash.h" +#define MD5_CTX_TYPE MD5Context +#endif + +#if defined(USE_ARDUINO) && defined(USE_ESP32) +#include "rom/md5_hash.h" +#define MD5_CTX_TYPE MD5Context +#endif + +#if defined(USE_ARDUINO) && defined(USE_ESP8266) +#include +#define MD5_CTX_TYPE md5_context_t +#endif + +namespace esphome { +namespace md5 { + +class MD5Digest { + public: + MD5Digest() = default; + ~MD5Digest() = default; + + /// Initialize a new MD5 digest computation. + void init(); + + /// Add bytes of data for the digest. + void add(const uint8_t *data, size_t len); + void add(const char *data, size_t len) { this->add((const uint8_t *) data, len); } + + /// Compute the digest, based on the provided data. + void calculate(); + + /// Retrieve the MD5 digest as bytes. + /// The output must be able to hold 16 bytes or more. + void get_bytes(uint8_t *output); + + /// Retrieve the MD5 digest as hex characters. + /// The output must be able to hold 32 bytes or more. + void get_hex(char *output); + + /// Compare the digest against a provided byte-encoded digest (16 bytes). + bool equals_bytes(const char *expected); + + /// Compare the digest against a provided hex-encoded digest (32 bytes). + bool equals_hex(const char *expected); + + protected: + MD5_CTX_TYPE ctx_{}; + uint8_t digest_[16]; +}; + +} // namespace md5 +} // namespace esphome diff --git a/esphome/components/ota/__init__.py b/esphome/components/ota/__init__.py index bcfb28979d..53b282c43e 100644 --- a/esphome/components/ota/__init__.py +++ b/esphome/components/ota/__init__.py @@ -15,7 +15,7 @@ from esphome.core import CORE, coroutine_with_priority CODEOWNERS = ["@esphome/core"] DEPENDENCIES = ["network"] -AUTO_LOAD = ["socket"] +AUTO_LOAD = ["socket", "md5"] CONF_ON_STATE_CHANGE = "on_state_change" CONF_ON_BEGIN = "on_begin" @@ -35,20 +35,12 @@ OTAEndTrigger = ota_ns.class_("OTAEndTrigger", automation.Trigger.template()) OTAErrorTrigger = ota_ns.class_("OTAErrorTrigger", automation.Trigger.template()) -def validate_password_support(value): - if CORE.using_arduino: - return value - if CORE.using_esp_idf: - raise cv.Invalid("Password support is not implemented yet for ESP-IDF") - raise NotImplementedError - - CONFIG_SCHEMA = cv.Schema( { cv.GenerateID(): cv.declare_id(OTAComponent), cv.Optional(CONF_SAFE_MODE, default=True): cv.boolean, cv.SplitDefault(CONF_PORT, esp8266=8266, esp32=3232): cv.port, - cv.Optional(CONF_PASSWORD): cv.All(cv.string, validate_password_support), + cv.Optional(CONF_PASSWORD): cv.string, cv.Optional( CONF_REBOOT_TIMEOUT, default="5min" ): cv.positive_time_period_milliseconds, diff --git a/esphome/components/ota/ota_backend_arduino_esp32.h b/esphome/components/ota/ota_backend_arduino_esp32.h index 8343bdf94f..6b712502fb 100644 --- a/esphome/components/ota/ota_backend_arduino_esp32.h +++ b/esphome/components/ota/ota_backend_arduino_esp32.h @@ -9,6 +9,7 @@ namespace esphome { namespace ota { class ArduinoESP32OTABackend : public OTABackend { + public: OTAResponseTypes begin(size_t image_size) override; void set_update_md5(const char *md5) override; OTAResponseTypes write(uint8_t *data, size_t len) override; diff --git a/esphome/components/ota/ota_backend_esp_idf.cpp b/esphome/components/ota/ota_backend_esp_idf.cpp index 4eb17d82f1..336b3798d9 100644 --- a/esphome/components/ota/ota_backend_esp_idf.cpp +++ b/esphome/components/ota/ota_backend_esp_idf.cpp @@ -4,6 +4,7 @@ #include "ota_backend_esp_idf.h" #include "ota_component.h" #include +#include "esphome/components/md5/md5.h" namespace esphome { namespace ota { @@ -24,15 +25,15 @@ OTAResponseTypes IDFOTABackend::begin(size_t image_size) { } return OTA_RESPONSE_ERROR_UNKNOWN; } + this->md5_.init(); return OTA_RESPONSE_OK; } -void IDFOTABackend::set_update_md5(const char *md5) { - // pass -} +void IDFOTABackend::set_update_md5(const char *expected_md5) { memcpy(this->expected_bin_md5_, expected_md5, 32); } OTAResponseTypes IDFOTABackend::write(uint8_t *data, size_t len) { esp_err_t err = esp_ota_write(this->update_handle_, data, len); + this->md5_.add(data, len); if (err != ESP_OK) { if (err == ESP_ERR_OTA_VALIDATE_FAILED) { return OTA_RESPONSE_ERROR_MAGIC; @@ -45,6 +46,11 @@ OTAResponseTypes IDFOTABackend::write(uint8_t *data, size_t len) { } OTAResponseTypes IDFOTABackend::end() { + this->md5_.calculate(); + if (!this->md5_.equals_hex(this->expected_bin_md5_)) { + this->abort(); + return OTA_RESPONSE_ERROR_UPDATE_END; + } esp_err_t err = esp_ota_end(this->update_handle_); this->update_handle_ = 0; if (err == ESP_OK) { diff --git a/esphome/components/ota/ota_backend_esp_idf.h b/esphome/components/ota/ota_backend_esp_idf.h index d6e2e2742a..49c6e124fa 100644 --- a/esphome/components/ota/ota_backend_esp_idf.h +++ b/esphome/components/ota/ota_backend_esp_idf.h @@ -5,6 +5,7 @@ #include "ota_component.h" #include "ota_backend.h" #include +#include "esphome/components/md5/md5.h" namespace esphome { namespace ota { @@ -20,6 +21,8 @@ class IDFOTABackend : public OTABackend { private: esp_ota_handle_t update_handle_{0}; const esp_partition_t *partition_; + md5::MD5Digest md5_{}; + char expected_bin_md5_[32]; }; } // namespace ota diff --git a/esphome/components/ota/ota_component.cpp b/esphome/components/ota/ota_component.cpp index 9ad3814f5c..89bee17452 100644 --- a/esphome/components/ota/ota_component.cpp +++ b/esphome/components/ota/ota_component.cpp @@ -8,15 +8,12 @@ #include "esphome/core/application.h" #include "esphome/core/hal.h" #include "esphome/core/util.h" +#include "esphome/components/md5/md5.h" #include "esphome/components/network/util.h" #include #include -#ifdef USE_OTA_PASSWORD -#include -#endif - namespace esphome { namespace ota { @@ -173,12 +170,12 @@ void OTAComponent::handle_() { if (!this->password_.empty()) { buf[0] = OTA_RESPONSE_REQUEST_AUTH; this->writeall_(buf, 1); - MD5Builder md5_builder{}; - md5_builder.begin(); + md5::MD5Digest md5{}; + md5.init(); sprintf(sbuf, "%08X", random_uint32()); - md5_builder.add(sbuf); - md5_builder.calculate(); - md5_builder.getChars(sbuf); + md5.add(sbuf, 8); + md5.calculate(); + md5.get_hex(sbuf); ESP_LOGV(TAG, "Auth: Nonce is %s", sbuf); // Send nonce, 32 bytes hex MD5 @@ -188,10 +185,10 @@ void OTAComponent::handle_() { } // prepare challenge - md5_builder.begin(); - md5_builder.add(this->password_.c_str()); + md5.init(); + md5.add(this->password_.c_str(), this->password_.length()); // add nonce - md5_builder.add(sbuf); + md5.add(sbuf, 32); // Receive cnonce, 32 bytes hex MD5 if (!this->readall_(buf, 32)) { @@ -201,11 +198,11 @@ void OTAComponent::handle_() { sbuf[32] = '\0'; ESP_LOGV(TAG, "Auth: CNonce is %s", sbuf); // add cnonce - md5_builder.add(sbuf); + md5.add(sbuf, 32); // calculate result - md5_builder.calculate(); - md5_builder.getChars(sbuf); + md5.calculate(); + md5.get_hex(sbuf); ESP_LOGV(TAG, "Auth: Result is %s", sbuf); // Receive result, 32 bytes hex MD5 diff --git a/esphome/core/defines.h b/esphome/core/defines.h index 7c2261920a..b44987a768 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -26,6 +26,7 @@ #define USE_LOGGER #define USE_MDNS #define USE_NUMBER +#define USE_OTA_PASSWORD #define USE_OTA_STATE_CALLBACK #define USE_POWER_SUPPLY #define USE_PROMETHEUS From ecd115851fd4ed9d82a1db6865130fcbeb73eec1 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 18 Oct 2021 21:26:36 +1300 Subject: [PATCH 0026/3388] Bump version to 2021.10.0b6 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 39616ca062..6bf9ef2a64 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2021.10.0b5" +__version__ = "2021.10.0b6" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" From 430598b7a1d07f9c2f702a9f1314bae7f671f5af Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 19 Oct 2021 15:16:39 +1300 Subject: [PATCH 0027/3388] Bump dashboard to 20211019.0 (#2549) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 8028007a0a..665ba27ed6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,7 +9,7 @@ pyserial==3.5 platformio==5.2.1 esptool==3.1 click==8.0.3 -esphome-dashboard==20211015.0 +esphome-dashboard==20211019.0 aioesphomeapi==9.1.5 # esp-idf requires this, but doesn't bundle it by default From 3e9c7f2e9ffdd356f254b054d3792646de7b66e0 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 19 Oct 2021 15:47:31 +1300 Subject: [PATCH 0028/3388] Bump version to 2021.10.0b7 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 6bf9ef2a64..f83fc7a651 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2021.10.0b6" +__version__ = "2021.10.0b7" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" From 3a760fbb4460a91cff240b65f62151eec17b41d2 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Tue, 19 Oct 2021 12:56:49 +0200 Subject: [PATCH 0029/3388] Fix ADC pin validation on ESP32-C3 (#2551) --- esphome/components/adc/sensor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/adc/sensor.py b/esphome/components/adc/sensor.py index 9a0407d0f4..26ef504c1a 100644 --- a/esphome/components/adc/sensor.py +++ b/esphome/components/adc/sensor.py @@ -35,7 +35,7 @@ def validate_adc_pin(value): if is_esp32c3(): if not (0 <= value <= 4): # ADC1 raise cv.Invalid("ESP32-C3: Only pins 0 though 4 support ADC.") - if not (32 <= value <= 39): # ADC1 + elif not (32 <= value <= 39): # ADC1 raise cv.Invalid("ESP32: Only pins 32 though 39 support ADC.") elif CORE.is_esp8266: from esphome.components.esp8266.gpio import CONF_ANALOG From 1b0e60374bd997a69ea0ba63bc02d4e3a5c6bcf6 Mon Sep 17 00:00:00 2001 From: Martin <25747549+martgras@users.noreply.github.com> Date: Tue, 19 Oct 2021 23:10:24 +0200 Subject: [PATCH 0030/3388] ignore exception when not waiting for a response (#2552) --- esphome/components/modbus/modbus.cpp | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/esphome/components/modbus/modbus.cpp b/esphome/components/modbus/modbus.cpp index 1f6d868baf..45c5bfb603 100644 --- a/esphome/components/modbus/modbus.cpp +++ b/esphome/components/modbus/modbus.cpp @@ -96,23 +96,27 @@ bool Modbus::parse_modbus_byte_(uint8_t byte) { ESP_LOGW(TAG, "Modbus CRC Check failed! %02X!=%02X", computed_crc, remote_crc); return false; } - - waiting_for_response = 0; std::vector data(this->rx_buffer_.begin() + data_offset, this->rx_buffer_.begin() + data_offset + data_len); - bool found = false; for (auto *device : this->devices_) { if (device->address_ == address) { // Is it an error response? if ((function_code & 0x80) == 0x80) { - ESP_LOGW(TAG, "Modbus error function code: 0x%X exception: %d", function_code, raw[2]); - device->on_modbus_error(function_code & 0x7F, raw[2]); + ESP_LOGD(TAG, "Modbus error function code: 0x%X exception: %d", function_code, raw[2]); + if (waiting_for_response != 0) { + device->on_modbus_error(function_code & 0x7F, raw[2]); + } else { + // Ignore modbus exception not related to a pending command + ESP_LOGD(TAG, "Ignoring Modbus error - not expecting a response"); + } } else { device->on_modbus_data(data); } found = true; } } + waiting_for_response = 0; + if (!found) { ESP_LOGW(TAG, "Got Modbus frame from unknown address 0x%02X! ", address); } @@ -196,6 +200,7 @@ void Modbus::send_raw(const std::vector &payload) { if (this->flow_control_pin_ != nullptr) this->flow_control_pin_->digital_write(false); waiting_for_response = payload[0]; + ESP_LOGV(TAG, "Modbus write raw: %s", hexencode(payload).c_str()); last_send_ = millis(); } From e10ab1da78658ef75ec8ebc328fa9b3c21fd775b Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 20 Oct 2021 10:14:30 +1300 Subject: [PATCH 0031/3388] Bump version to 2021.10.0b8 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index f83fc7a651..ad95096b94 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2021.10.0b7" +__version__ = "2021.10.0b8" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" From ea0977abb4451c35a39e076c5ba92e3f8666c8ed Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 20 Oct 2021 10:53:49 +1300 Subject: [PATCH 0032/3388] Bump dashboard to 20211020.0 (#2556) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 665ba27ed6..120c71ff69 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,7 +9,7 @@ pyserial==3.5 platformio==5.2.1 esptool==3.1 click==8.0.3 -esphome-dashboard==20211019.0 +esphome-dashboard==20211020.0 aioesphomeapi==9.1.5 # esp-idf requires this, but doesn't bundle it by default From 7feffa64f3788db14e48d724b0b8405ff3c5a92f Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 20 Oct 2021 11:00:02 +1300 Subject: [PATCH 0033/3388] Bump version to 2021.10.0b9 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index ad95096b94..4ade772cc1 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2021.10.0b8" +__version__ = "2021.10.0b9" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" From 9ac365feef5dad4412af5c271aa6f6c02be6e5b5 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 20 Oct 2021 13:25:00 +1300 Subject: [PATCH 0034/3388] Fix HA addon so it does not have logout button (#2558) --- esphome/dashboard/dashboard.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/dashboard/dashboard.py b/esphome/dashboard/dashboard.py index 501666b100..63378a38b5 100644 --- a/esphome/dashboard/dashboard.py +++ b/esphome/dashboard/dashboard.py @@ -597,7 +597,7 @@ class MainRequestHandler(BaseHandler): get_template_path("index"), begin=begin, **template_args(), - login_enabled=settings.using_auth, + login_enabled=settings.using_password, ) From b9f66373c1b5ffc78dc15b87dbbde884d830bbeb Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 20 Oct 2021 16:17:00 +1300 Subject: [PATCH 0035/3388] Bump esphome-dashboard to 20211020.1 (#2559) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 120c71ff69..16f23f4b7d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,7 +9,7 @@ pyserial==3.5 platformio==5.2.1 esptool==3.1 click==8.0.3 -esphome-dashboard==20211020.0 +esphome-dashboard==20211020.1 aioesphomeapi==9.1.5 # esp-idf requires this, but doesn't bundle it by default From 8456a8cecb3125c318accc407aec3b71e135fb75 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 20 Oct 2021 16:39:24 +1300 Subject: [PATCH 0036/3388] Bump version to 2021.10.0b10 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 4ade772cc1..f0951cbdaa 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2021.10.0b9" +__version__ = "2021.10.0b10" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" From dad244fb7a998d52e1900f5692ea97d5d65f252d Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 21 Oct 2021 06:45:10 +1300 Subject: [PATCH 0037/3388] A few esp32_ble_server/improv fixes (#2562) --- .../components/esp32_ble_server/ble_server.cpp | 17 +++++++++-------- .../components/esp32_ble_server/ble_server.h | 14 ++++++++------ .../esp32_improv/esp32_improv_component.h | 2 +- 3 files changed, 18 insertions(+), 15 deletions(-) diff --git a/esphome/components/esp32_ble_server/ble_server.cpp b/esphome/components/esp32_ble_server/ble_server.cpp index e0fb80f94b..15bea07021 100644 --- a/esphome/components/esp32_ble_server/ble_server.cpp +++ b/esphome/components/esp32_ble_server/ble_server.cpp @@ -98,19 +98,20 @@ bool BLEServer::create_device_characteristics_() { return true; } -BLEService *BLEServer::create_service(const uint8_t *uuid, bool advertise) { +std::shared_ptr BLEServer::create_service(const uint8_t *uuid, bool advertise) { return this->create_service(ESPBTUUID::from_raw(uuid), advertise); } -BLEService *BLEServer::create_service(uint16_t uuid, bool advertise) { +std::shared_ptr BLEServer::create_service(uint16_t uuid, bool advertise) { return this->create_service(ESPBTUUID::from_uint16(uuid), advertise); } -BLEService *BLEServer::create_service(const std::string &uuid, bool advertise) { +std::shared_ptr BLEServer::create_service(const std::string &uuid, bool advertise) { return this->create_service(ESPBTUUID::from_raw(uuid), advertise); } -BLEService *BLEServer::create_service(ESPBTUUID uuid, bool advertise, uint16_t num_handles, uint8_t inst_id) { +std::shared_ptr BLEServer::create_service(ESPBTUUID uuid, bool advertise, uint16_t num_handles, + uint8_t inst_id) { ESP_LOGV(TAG, "Creating service - %s", uuid.to_string().c_str()); - BLEService *service = new BLEService(uuid, num_handles, inst_id); // NOLINT(cppcoreguidelines-owning-memory) - this->services_.push_back(service); + std::shared_ptr service = std::make_shared(uuid, num_handles, inst_id); + this->services_.emplace_back(service); if (advertise) { esp32_ble::global_ble->get_advertising()->add_service_uuid(uuid); } @@ -149,12 +150,12 @@ void BLEServer::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t ga break; } - for (auto *service : this->services_) { + for (const auto &service : this->services_) { service->gatts_event_handler(event, gatts_if, param); } } -float BLEServer::get_setup_priority() const { return setup_priority::AFTER_BLUETOOTH; } +float BLEServer::get_setup_priority() const { return setup_priority::AFTER_BLUETOOTH + 10; } void BLEServer::dump_config() { ESP_LOGCONFIG(TAG, "ESP32 BLE Server:"); } diff --git a/esphome/components/esp32_ble_server/ble_server.h b/esphome/components/esp32_ble_server/ble_server.h index 9f7e8b8fc0..d275eeab01 100644 --- a/esphome/components/esp32_ble_server/ble_server.h +++ b/esphome/components/esp32_ble_server/ble_server.h @@ -11,6 +11,7 @@ #include "esphome/core/preferences.h" #include +#include #ifdef USE_ESP32 @@ -43,10 +44,11 @@ class BLEServer : public Component { void set_manufacturer(const std::string &manufacturer) { this->manufacturer_ = manufacturer; } void set_model(const std::string &model) { this->model_ = model; } - BLEService *create_service(const uint8_t *uuid, bool advertise = false); - BLEService *create_service(uint16_t uuid, bool advertise = false); - BLEService *create_service(const std::string &uuid, bool advertise = false); - BLEService *create_service(ESPBTUUID uuid, bool advertise = false, uint16_t num_handles = 15, uint8_t inst_id = 0); + std::shared_ptr create_service(const uint8_t *uuid, bool advertise = false); + std::shared_ptr create_service(uint16_t uuid, bool advertise = false); + std::shared_ptr create_service(const std::string &uuid, bool advertise = false); + std::shared_ptr create_service(ESPBTUUID uuid, bool advertise = false, uint16_t num_handles = 15, + uint8_t inst_id = 0); esp_gatt_if_t get_gatts_if() { return this->gatts_if_; } uint32_t get_connected_client_count() { return this->connected_clients_; } @@ -74,8 +76,8 @@ class BLEServer : public Component { uint32_t connected_clients_{0}; std::map clients_; - std::vector services_; - BLEService *device_information_service_; + std::vector> services_; + std::shared_ptr device_information_service_; std::vector service_components_; diff --git a/esphome/components/esp32_improv/esp32_improv_component.h b/esphome/components/esp32_improv/esp32_improv_component.h index 53cda5f399..3a5d150fbe 100644 --- a/esphome/components/esp32_improv/esp32_improv_component.h +++ b/esphome/components/esp32_improv/esp32_improv_component.h @@ -48,7 +48,7 @@ class ESP32ImprovComponent : public Component, public BLEServiceComponent { std::vector incoming_data_; wifi::WiFiAP connecting_sta_; - BLEService *service_; + std::shared_ptr service_; BLECharacteristic *status_; BLECharacteristic *error_; BLECharacteristic *rpc_; From 95593eeeabe091cc80ea485ef57d469b69765bd5 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 21 Oct 2021 07:08:34 +1300 Subject: [PATCH 0038/3388] Bump esphome-dashboard to 20211021.0 (#2564) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 16f23f4b7d..63804e89e8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,7 +9,7 @@ pyserial==3.5 platformio==5.2.1 esptool==3.1 click==8.0.3 -esphome-dashboard==20211020.1 +esphome-dashboard==20211021.0 aioesphomeapi==9.1.5 # esp-idf requires this, but doesn't bundle it by default From 996ec59d286d0888c48313439feb404b6c9818fb Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Wed, 20 Oct 2021 20:15:09 +0200 Subject: [PATCH 0039/3388] Move running process log line to debug level (#2565) --- esphome/util.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/util.py b/esphome/util.py index 527e370ad8..0f168cade3 100644 --- a/esphome/util.py +++ b/esphome/util.py @@ -178,7 +178,7 @@ def run_external_command( orig_argv = sys.argv orig_exit = sys.exit # mock sys.exit full_cmd = " ".join(shlex_quote(x) for x in cmd) - _LOGGER.info("Running: %s", full_cmd) + _LOGGER.debug("Running: %s", full_cmd) orig_stdout = sys.stdout sys.stdout = RedirectText(sys.stdout, filter_lines=filter_lines) @@ -214,7 +214,7 @@ def run_external_command( def run_external_process(*cmd, **kwargs): full_cmd = " ".join(shlex_quote(x) for x in cmd) - _LOGGER.info("Running: %s", full_cmd) + _LOGGER.debug("Running: %s", full_cmd) filter_lines = kwargs.get("filter_lines") capture_stdout = kwargs.get("capture_stdout", False) From 3af297aa7660b2dd6f5f10bd8185619d24d511d1 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Wed, 20 Oct 2021 20:31:13 +0200 Subject: [PATCH 0040/3388] Revert nextion clang-tidy changes (#2566) --- .../binary_sensor/nextion_binarysensor.cpp | 4 +- .../binary_sensor/nextion_binarysensor.h | 3 +- esphome/components/nextion/nextion.cpp | 73 ++++++++++++------- esphome/components/nextion/nextion.h | 11 ++- esphome/components/nextion/nextion_base.h | 9 +-- .../nextion/nextion_component_base.h | 3 +- esphome/components/nextion/nextion_upload.cpp | 14 ++-- .../nextion/sensor/nextion_sensor.cpp | 8 +- .../nextion/sensor/nextion_sensor.h | 5 +- .../nextion/switch/nextion_switch.cpp | 4 +- .../nextion/switch/nextion_switch.h | 5 +- .../text_sensor/nextion_textsensor.cpp | 4 +- .../nextion/text_sensor/nextion_textsensor.h | 5 +- 13 files changed, 78 insertions(+), 70 deletions(-) diff --git a/esphome/components/nextion/binary_sensor/nextion_binarysensor.cpp b/esphome/components/nextion/binary_sensor/nextion_binarysensor.cpp index c5bfa78efe..bf6e74cb38 100644 --- a/esphome/components/nextion/binary_sensor/nextion_binarysensor.cpp +++ b/esphome/components/nextion/binary_sensor/nextion_binarysensor.cpp @@ -33,7 +33,7 @@ void NextionBinarySensor::update() { if (this->variable_name_.empty()) // This is a touch component return; - this->nextion_->add_to_get_queue(shared_from_this()); + this->nextion_->add_to_get_queue(this); } void NextionBinarySensor::set_state(bool state, bool publish, bool send_to_nextion) { @@ -48,7 +48,7 @@ void NextionBinarySensor::set_state(bool state, bool publish, bool send_to_nexti this->needs_to_send_update_ = true; } else { this->needs_to_send_update_ = false; - this->nextion_->add_no_result_to_queue_with_set(shared_from_this(), (int) state); + this->nextion_->add_no_result_to_queue_with_set(this, (int) state); } } diff --git a/esphome/components/nextion/binary_sensor/nextion_binarysensor.h b/esphome/components/nextion/binary_sensor/nextion_binarysensor.h index b86ee74013..b6b23ada85 100644 --- a/esphome/components/nextion/binary_sensor/nextion_binarysensor.h +++ b/esphome/components/nextion/binary_sensor/nextion_binarysensor.h @@ -10,8 +10,7 @@ class NextionBinarySensor; class NextionBinarySensor : public NextionComponent, public binary_sensor::BinarySensorInitiallyOff, - public PollingComponent, - public std::enable_shared_from_this { + public PollingComponent { public: NextionBinarySensor(NextionBase *nextion) { this->nextion_ = nextion; } diff --git a/esphome/components/nextion/nextion.cpp b/esphome/components/nextion/nextion.cpp index d56c370412..f23f55c9bb 100644 --- a/esphome/components/nextion/nextion.cpp +++ b/esphome/components/nextion/nextion.cpp @@ -196,7 +196,7 @@ void Nextion::print_queue_members_() { ESP_LOGN(TAG, "print_queue_members_ (top 10) size %zu", this->nextion_queue_.size()); ESP_LOGN(TAG, "*******************************************"); int count = 0; - for (auto &i : this->nextion_queue_) { + for (auto *i : this->nextion_queue_) { if (count++ == 10) break; @@ -257,9 +257,8 @@ bool Nextion::remove_from_q_(bool report_empty) { return false; } - auto nb = std::move(this->nextion_queue_.front()); - this->nextion_queue_.pop_front(); - auto &component = nb->component; + NextionQueue *nb = this->nextion_queue_.front(); + NextionComponentBase *component = nb->component; ESP_LOGN(TAG, "Removing %s from the queue", component->get_variable_name().c_str()); @@ -267,8 +266,10 @@ bool Nextion::remove_from_q_(bool report_empty) { if (component->get_variable_name() == "sleep_wake") { this->is_sleeping_ = false; } + delete component; // NOLINT(cppcoreguidelines-owning-memory) } - + delete nb; // NOLINT(cppcoreguidelines-owning-memory) + this->nextion_queue_.pop_front(); return true; } @@ -357,7 +358,7 @@ void Nextion::process_nextion_commands_() { int index = 0; int found = -1; for (auto &nb : this->nextion_queue_) { - auto &component = nb->component; + NextionComponentBase *component = nb->component; if (component->get_queue_type() == NextionQueueType::WAVEFORM_SENSOR) { ESP_LOGW(TAG, "Nextion reported invalid Waveform ID %d or Channel # %d was used!", @@ -368,6 +369,9 @@ void Nextion::process_nextion_commands_() { found = index; + delete component; // NOLINT(cppcoreguidelines-owning-memory) + delete nb; // NOLINT(cppcoreguidelines-owning-memory) + break; } ++index; @@ -464,9 +468,8 @@ void Nextion::process_nextion_commands_() { break; } - auto nb = std::move(this->nextion_queue_.front()); - this->nextion_queue_.pop_front(); - auto &component = nb->component; + NextionQueue *nb = this->nextion_queue_.front(); + NextionComponentBase *component = nb->component; if (component->get_queue_type() != NextionQueueType::TEXT_SENSOR) { ESP_LOGE(TAG, "ERROR: Received string return but next in queue \"%s\" is not a text sensor", @@ -477,6 +480,9 @@ void Nextion::process_nextion_commands_() { component->set_state_from_string(to_process, true, false); } + delete nb; // NOLINT(cppcoreguidelines-owning-memory) + this->nextion_queue_.pop_front(); + break; } // 0x71 0x01 0x02 0x03 0x04 0xFF 0xFF 0xFF @@ -505,9 +511,8 @@ void Nextion::process_nextion_commands_() { ++dataindex; } - auto nb = std::move(this->nextion_queue_.front()); - this->nextion_queue_.pop_front(); - auto &component = nb->component; + NextionQueue *nb = this->nextion_queue_.front(); + NextionComponentBase *component = nb->component; if (component->get_queue_type() != NextionQueueType::SENSOR && component->get_queue_type() != NextionQueueType::BINARY_SENSOR && @@ -521,6 +526,9 @@ void Nextion::process_nextion_commands_() { component->set_state_from_int(value, true, false); } + delete nb; // NOLINT(cppcoreguidelines-owning-memory) + this->nextion_queue_.pop_front(); + break; } @@ -682,7 +690,7 @@ void Nextion::process_nextion_commands_() { int index = 0; int found = -1; for (auto &nb : this->nextion_queue_) { - auto &component = nb->component; + auto component = nb->component; if (component->get_queue_type() == NextionQueueType::WAVEFORM_SENSOR) { size_t buffer_to_send = component->get_wave_buffer().size() < 255 ? component->get_wave_buffer().size() : 255; // ADDT command can only send 255 @@ -699,6 +707,8 @@ void Nextion::process_nextion_commands_() { component->get_wave_buffer().begin() + buffer_to_send); } found = index; + delete component; // NOLINT(cppcoreguidelines-owning-memory) + delete nb; // NOLINT(cppcoreguidelines-owning-memory) break; } ++index; @@ -727,7 +737,7 @@ void Nextion::process_nextion_commands_() { if (!this->nextion_queue_.empty() && this->nextion_queue_.front()->queue_time + this->max_q_age_ms_ < ms) { for (int i = 0; i < this->nextion_queue_.size(); i++) { - auto &component = this->nextion_queue_[i]->component; + NextionComponentBase *component = this->nextion_queue_[i]->component; if (this->nextion_queue_[i]->queue_time + this->max_q_age_ms_ < ms) { if (this->nextion_queue_[i]->queue_time == 0) ESP_LOGD(TAG, "Removing old queue type \"%s\" name \"%s\" queue_time 0", @@ -744,8 +754,11 @@ void Nextion::process_nextion_commands_() { if (component->get_variable_name() == "sleep_wake") { this->is_sleeping_ = false; } + delete component; // NOLINT(cppcoreguidelines-owning-memory) } + delete this->nextion_queue_[i]; // NOLINT(cppcoreguidelines-owning-memory) + this->nextion_queue_.erase(this->nextion_queue_.begin() + i); i--; @@ -899,16 +912,18 @@ uint16_t Nextion::recv_ret_string_(std::string &response, uint32_t timeout, bool * @param variable_name Name for the queue */ void Nextion::add_no_result_to_queue_(const std::string &variable_name) { - auto nextion_queue = make_unique(); + // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) + nextion::NextionQueue *nextion_queue = new nextion::NextionQueue; - nextion_queue->component = make_unique(); + // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) + nextion_queue->component = new nextion::NextionComponentBase; nextion_queue->component->set_variable_name(variable_name); nextion_queue->queue_time = millis(); - ESP_LOGN(TAG, "Add to queue type: NORESULT component %s", nextion_queue->component->get_variable_name().c_str()); + this->nextion_queue_.push_back(nextion_queue); - this->nextion_queue_.push_back(std::move(nextion_queue)); + ESP_LOGN(TAG, "Add to queue type: NORESULT component %s", nextion_queue->component->get_variable_name().c_str()); } /** @@ -979,7 +994,7 @@ bool Nextion::add_no_result_to_queue_with_printf_(const std::string &variable_na * @param is_sleep_safe The command is safe to send when the Nextion is sleeping */ -void Nextion::add_no_result_to_queue_with_set(std::shared_ptr component, int state_value) { +void Nextion::add_no_result_to_queue_with_set(NextionComponentBase *component, int state_value) { this->add_no_result_to_queue_with_set(component->get_variable_name(), component->get_variable_name_to_send(), state_value); } @@ -1007,8 +1022,7 @@ void Nextion::add_no_result_to_queue_with_set_internal_(const std::string &varia * @param state_value String value to set * @param is_sleep_safe The command is safe to send when the Nextion is sleeping */ -void Nextion::add_no_result_to_queue_with_set(std::shared_ptr component, - const std::string &state_value) { +void Nextion::add_no_result_to_queue_with_set(NextionComponentBase *component, const std::string &state_value) { this->add_no_result_to_queue_with_set(component->get_variable_name(), component->get_variable_name_to_send(), state_value); } @@ -1028,11 +1042,12 @@ void Nextion::add_no_result_to_queue_with_set_internal_(const std::string &varia state_value.c_str()); } -void Nextion::add_to_get_queue(std::shared_ptr component) { +void Nextion::add_to_get_queue(NextionComponentBase *component) { if ((!this->is_setup() && !this->ignore_is_setup_)) return; - auto nextion_queue = make_unique(); + // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) + nextion::NextionQueue *nextion_queue = new nextion::NextionQueue; nextion_queue->component = component; nextion_queue->queue_time = millis(); @@ -1043,7 +1058,7 @@ void Nextion::add_to_get_queue(std::shared_ptr component) std::string command = "get " + component->get_variable_name_to_send(); if (this->send_command_(command)) { - this->nextion_queue_.push_back(std::move(nextion_queue)); + this->nextion_queue_.push_back(nextion_queue); } } @@ -1055,13 +1070,15 @@ void Nextion::add_to_get_queue(std::shared_ptr component) * @param buffer_to_send The buffer size * @param buffer_size The buffer data */ -void Nextion::add_addt_command_to_queue(std::shared_ptr component) { +void Nextion::add_addt_command_to_queue(NextionComponentBase *component) { if ((!this->is_setup() && !this->ignore_is_setup_) || this->is_sleeping()) return; - auto nextion_queue = make_unique(); + // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) + nextion::NextionQueue *nextion_queue = new nextion::NextionQueue; - nextion_queue->component = std::make_shared(); + // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) + nextion_queue->component = new nextion::NextionComponentBase; nextion_queue->queue_time = millis(); size_t buffer_to_send = component->get_wave_buffer_size() < 255 ? component->get_wave_buffer_size() @@ -1070,7 +1087,7 @@ void Nextion::add_addt_command_to_queue(std::shared_ptr co std::string command = "addt " + to_string(component->get_component_id()) + "," + to_string(component->get_wave_channel_id()) + "," + to_string(buffer_to_send); if (this->send_command_(command)) { - this->nextion_queue_.push_back(std::move(nextion_queue)); + this->nextion_queue_.push_back(nextion_queue); } } diff --git a/esphome/components/nextion/nextion.h b/esphome/components/nextion/nextion.h index 1bee41f6cf..285b3ac9a3 100644 --- a/esphome/components/nextion/nextion.h +++ b/esphome/components/nextion/nextion.h @@ -707,18 +707,17 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe void set_nextion_sensor_state(NextionQueueType queue_type, const std::string &name, float state); void set_nextion_text_state(const std::string &name, const std::string &state); - void add_no_result_to_queue_with_set(std::shared_ptr component, int state_value) override; + void add_no_result_to_queue_with_set(NextionComponentBase *component, int state_value) override; void add_no_result_to_queue_with_set(const std::string &variable_name, const std::string &variable_name_to_send, int state_value) override; - void add_no_result_to_queue_with_set(std::shared_ptr component, - const std::string &state_value) override; + void add_no_result_to_queue_with_set(NextionComponentBase *component, const std::string &state_value) override; void add_no_result_to_queue_with_set(const std::string &variable_name, const std::string &variable_name_to_send, const std::string &state_value) override; - void add_to_get_queue(std::shared_ptr component) override; + void add_to_get_queue(NextionComponentBase *component) override; - void add_addt_command_to_queue(std::shared_ptr component) override; + void add_addt_command_to_queue(NextionComponentBase *component) override; void update_components_by_prefix(const std::string &prefix); @@ -729,7 +728,7 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe void set_auto_wake_on_touch_internal(bool auto_wake_on_touch) { this->auto_wake_on_touch_ = auto_wake_on_touch; } protected: - std::deque> nextion_queue_; + std::deque nextion_queue_; uint16_t recv_ret_string_(std::string &response, uint32_t timeout, bool recv_flag); void all_components_send_state_(bool force_update = false); uint64_t comok_sent_ = 0; diff --git a/esphome/components/nextion/nextion_base.h b/esphome/components/nextion/nextion_base.h index d91c70c960..a24fd74060 100644 --- a/esphome/components/nextion/nextion_base.h +++ b/esphome/components/nextion/nextion_base.h @@ -24,19 +24,18 @@ class NextionBase; class NextionBase { public: - virtual void add_no_result_to_queue_with_set(std::shared_ptr component, int state_value) = 0; + virtual void add_no_result_to_queue_with_set(NextionComponentBase *component, int state_value) = 0; virtual void add_no_result_to_queue_with_set(const std::string &variable_name, const std::string &variable_name_to_send, int state_value) = 0; - virtual void add_no_result_to_queue_with_set(std::shared_ptr component, - const std::string &state_value) = 0; + virtual void add_no_result_to_queue_with_set(NextionComponentBase *component, const std::string &state_value) = 0; virtual void add_no_result_to_queue_with_set(const std::string &variable_name, const std::string &variable_name_to_send, const std::string &state_value) = 0; - virtual void add_addt_command_to_queue(std::shared_ptr component) = 0; + virtual void add_addt_command_to_queue(NextionComponentBase *component) = 0; - virtual void add_to_get_queue(std::shared_ptr component) = 0; + virtual void add_to_get_queue(NextionComponentBase *component) = 0; virtual void set_component_background_color(const char *component, Color color) = 0; virtual void set_component_pressed_background_color(const char *component, Color color) = 0; diff --git a/esphome/components/nextion/nextion_component_base.h b/esphome/components/nextion/nextion_component_base.h index 2725d5a30c..71ad803bc4 100644 --- a/esphome/components/nextion/nextion_component_base.h +++ b/esphome/components/nextion/nextion_component_base.h @@ -1,6 +1,5 @@ #pragma once #include -#include #include "esphome/core/defines.h" namespace esphome { @@ -23,7 +22,7 @@ class NextionComponentBase; class NextionQueue { public: virtual ~NextionQueue() = default; - std::shared_ptr component; + NextionComponentBase *component; uint32_t queue_time = 0; }; diff --git a/esphome/components/nextion/nextion_upload.cpp b/esphome/components/nextion/nextion_upload.cpp index cebdbec31a..cd1c073320 100644 --- a/esphome/components/nextion/nextion_upload.cpp +++ b/esphome/components/nextion/nextion_upload.cpp @@ -281,12 +281,14 @@ void Nextion::upload_tft() { #endif // NOLINTNEXTLINE(readability-static-accessed-through-instance) ESP_LOGD(TAG, "Allocating buffer size %d, Heap size is %u", chunk_size, ESP.getFreeHeap()); - this->transfer_buffer_ = new (std::nothrow) uint8_t[chunk_size]; // NOLINT(cppcoreguidelines-owning-memory) - if (this->transfer_buffer_ == nullptr) { // Try a smaller size + // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) + this->transfer_buffer_ = new (std::nothrow) uint8_t[chunk_size]; + if (this->transfer_buffer_ == nullptr) { // Try a smaller size ESP_LOGD(TAG, "Could not allocate buffer size: %d trying 4096 instead", chunk_size); chunk_size = 4096; ESP_LOGD(TAG, "Allocating %d buffer", chunk_size); - this->transfer_buffer_ = new (std::nothrow) uint8_t[chunk_size]; // NOLINT(cppcoreguidelines-owning-memory) + // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) + this->transfer_buffer_ = new uint8_t[chunk_size]; if (!this->transfer_buffer_) this->upload_end_(); @@ -330,7 +332,8 @@ void Nextion::upload_end_() { WiFiClient *Nextion::get_wifi_client_() { if (this->tft_url_.compare(0, 6, "https:") == 0) { if (this->wifi_client_secure_ == nullptr) { - this->wifi_client_secure_ = new BearSSL::WiFiClientSecure(); // NOLINT(cppcoreguidelines-owning-memory) + // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) + this->wifi_client_secure_ = new BearSSL::WiFiClientSecure(); this->wifi_client_secure_->setInsecure(); this->wifi_client_secure_->setBufferSizes(512, 512); } @@ -338,7 +341,8 @@ WiFiClient *Nextion::get_wifi_client_() { } if (this->wifi_client_ == nullptr) { - this->wifi_client_ = new WiFiClient(); // NOLINT(cppcoreguidelines-owning-memory) + // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) + this->wifi_client_ = new WiFiClient(); } return this->wifi_client_; } diff --git a/esphome/components/nextion/sensor/nextion_sensor.cpp b/esphome/components/nextion/sensor/nextion_sensor.cpp index e983ebcc6f..4b7532d32d 100644 --- a/esphome/components/nextion/sensor/nextion_sensor.cpp +++ b/esphome/components/nextion/sensor/nextion_sensor.cpp @@ -34,7 +34,7 @@ void NextionSensor::update() { return; if (this->wave_chan_id_ == UINT8_MAX) { - this->nextion_->add_to_get_queue(shared_from_this()); + this->nextion_->add_to_get_queue(this); } else { if (this->send_last_value_) { this->add_to_wave_buffer(this->last_value_); @@ -62,9 +62,9 @@ void NextionSensor::set_state(float state, bool publish, bool send_to_nextion) { double to_multiply = pow(10, this->precision_); int state_value = (int) (state * to_multiply); - this->nextion_->add_no_result_to_queue_with_set(shared_from_this(), (int) state_value); + this->nextion_->add_no_result_to_queue_with_set(this, (int) state_value); } else { - this->nextion_->add_no_result_to_queue_with_set(shared_from_this(), (int) state); + this->nextion_->add_no_result_to_queue_with_set(this, (int) state); } } } @@ -103,7 +103,7 @@ void NextionSensor::wave_update_() { buffer_to_send, this->wave_buffer_.size(), this->component_id_, this->wave_chan_id_); #endif - this->nextion_->add_addt_command_to_queue(shared_from_this()); + this->nextion_->add_addt_command_to_queue(this); } } // namespace nextion diff --git a/esphome/components/nextion/sensor/nextion_sensor.h b/esphome/components/nextion/sensor/nextion_sensor.h index 068ff0451b..e4dde9a513 100644 --- a/esphome/components/nextion/sensor/nextion_sensor.h +++ b/esphome/components/nextion/sensor/nextion_sensor.h @@ -8,10 +8,7 @@ namespace esphome { namespace nextion { class NextionSensor; -class NextionSensor : public NextionComponent, - public sensor::Sensor, - public PollingComponent, - public std::enable_shared_from_this { +class NextionSensor : public NextionComponent, public sensor::Sensor, public PollingComponent { public: NextionSensor(NextionBase *nextion) { this->nextion_ = nextion; } void send_state_to_nextion() override { this->set_state(this->state, false, true); }; diff --git a/esphome/components/nextion/switch/nextion_switch.cpp b/esphome/components/nextion/switch/nextion_switch.cpp index 0bd958e0d8..1f32ad3425 100644 --- a/esphome/components/nextion/switch/nextion_switch.cpp +++ b/esphome/components/nextion/switch/nextion_switch.cpp @@ -20,7 +20,7 @@ void NextionSwitch::process_bool(const std::string &variable_name, bool on) { void NextionSwitch::update() { if (!this->nextion_->is_setup()) return; - this->nextion_->add_to_get_queue(shared_from_this()); + this->nextion_->add_to_get_queue(this); } void NextionSwitch::set_state(bool state, bool publish, bool send_to_nextion) { @@ -32,7 +32,7 @@ void NextionSwitch::set_state(bool state, bool publish, bool send_to_nextion) { this->needs_to_send_update_ = true; } else { this->needs_to_send_update_ = false; - this->nextion_->add_no_result_to_queue_with_set(shared_from_this(), (int) state); + this->nextion_->add_no_result_to_queue_with_set(this, (int) state); } } if (publish) { diff --git a/esphome/components/nextion/switch/nextion_switch.h b/esphome/components/nextion/switch/nextion_switch.h index d7783e5c51..1548287473 100644 --- a/esphome/components/nextion/switch/nextion_switch.h +++ b/esphome/components/nextion/switch/nextion_switch.h @@ -8,10 +8,7 @@ namespace esphome { namespace nextion { class NextionSwitch; -class NextionSwitch : public NextionComponent, - public switch_::Switch, - public PollingComponent, - public std::enable_shared_from_this { +class NextionSwitch : public NextionComponent, public switch_::Switch, public PollingComponent { public: NextionSwitch(NextionBase *nextion) { this->nextion_ = nextion; } diff --git a/esphome/components/nextion/text_sensor/nextion_textsensor.cpp b/esphome/components/nextion/text_sensor/nextion_textsensor.cpp index fa7cb35025..08f032df74 100644 --- a/esphome/components/nextion/text_sensor/nextion_textsensor.cpp +++ b/esphome/components/nextion/text_sensor/nextion_textsensor.cpp @@ -18,7 +18,7 @@ void NextionTextSensor::process_text(const std::string &variable_name, const std void NextionTextSensor::update() { if (!this->nextion_->is_setup()) return; - this->nextion_->add_to_get_queue(shared_from_this()); + this->nextion_->add_to_get_queue(this); } void NextionTextSensor::set_state(const std::string &state, bool publish, bool send_to_nextion) { @@ -29,7 +29,7 @@ void NextionTextSensor::set_state(const std::string &state, bool publish, bool s if (this->nextion_->is_sleeping() || !this->visible_) { this->needs_to_send_update_ = true; } else { - this->nextion_->add_no_result_to_queue_with_set(shared_from_this(), state); + this->nextion_->add_no_result_to_queue_with_set(this, state); } } diff --git a/esphome/components/nextion/text_sensor/nextion_textsensor.h b/esphome/components/nextion/text_sensor/nextion_textsensor.h index 762797727d..5716d0a008 100644 --- a/esphome/components/nextion/text_sensor/nextion_textsensor.h +++ b/esphome/components/nextion/text_sensor/nextion_textsensor.h @@ -8,10 +8,7 @@ namespace esphome { namespace nextion { class NextionTextSensor; -class NextionTextSensor : public NextionComponent, - public text_sensor::TextSensor, - public PollingComponent, - public std::enable_shared_from_this { +class NextionTextSensor : public NextionComponent, public text_sensor::TextSensor, public PollingComponent { public: NextionTextSensor(NextionBase *nextion) { this->nextion_ = nextion; } void update() override; From 56cc31e8e752572f3233c6e68ef4f832d6b3132d Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 21 Oct 2021 07:32:27 +1300 Subject: [PATCH 0041/3388] Bump version to 2021.10.0b11 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index f0951cbdaa..2c1bd219d7 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2021.10.0b10" +__version__ = "2021.10.0b11" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" From 625463d87168bcc563896355e531c4b9a18bef34 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 21 Oct 2021 07:57:10 +1300 Subject: [PATCH 0042/3388] Bump version to 2021.10.0 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 2c1bd219d7..59441e23c8 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2021.10.0b11" +__version__ = "2021.10.0" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" From 332c9e891b4abb7c72c324f02d0b8750e6199b1f Mon Sep 17 00:00:00 2001 From: Maurice Makaay Date: Thu, 21 Oct 2021 12:23:21 +0200 Subject: [PATCH 0043/3388] Fix MDNS for ESP8266 devices (#2571) Co-authored-by: Maurice Makaay Co-authored-by: Otto winter Co-authored-by: Maurice Makaay --- esphome/components/mdns/mdns_component.cpp | 6 +++--- esphome/components/mdns/mdns_component.h | 4 ++++ esphome/components/mdns/mdns_esp8266.cpp | 16 ++++++++++++++-- 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/esphome/components/mdns/mdns_component.cpp b/esphome/components/mdns/mdns_component.cpp index 372d980eb0..631af9eba9 100644 --- a/esphome/components/mdns/mdns_component.cpp +++ b/esphome/components/mdns/mdns_component.cpp @@ -23,7 +23,7 @@ std::vector MDNSComponent::compile_services_() { #ifdef USE_API if (api::global_api_server != nullptr) { MDNSService service{}; - service.service_type = "esphomelib"; + service.service_type = "_esphomelib"; service.proto = "_tcp"; service.port = api::global_api_server->get_port(); service.txt_records.push_back({"version", ESPHOME_VERSION}); @@ -57,7 +57,7 @@ std::vector MDNSComponent::compile_services_() { #ifdef USE_PROMETHEUS { MDNSService service{}; - service.service_type = "prometheus-http"; + service.service_type = "_prometheus-http"; service.proto = "_tcp"; service.port = WEBSERVER_PORT; res.push_back(service); @@ -68,7 +68,7 @@ std::vector MDNSComponent::compile_services_() { // Publish "http" service if not using native API // This is just to have *some* mDNS service so that .local resolution works MDNSService service{}; - service.service_type = "http"; + service.service_type = "_http"; service.proto = "_tcp"; service.port = WEBSERVER_PORT; service.txt_records.push_back({"version", ESPHOME_VERSION}); diff --git a/esphome/components/mdns/mdns_component.h b/esphome/components/mdns/mdns_component.h index 985947d99c..679fb1a768 100644 --- a/esphome/components/mdns/mdns_component.h +++ b/esphome/components/mdns/mdns_component.h @@ -13,7 +13,11 @@ struct MDNSTXTRecord { }; struct MDNSService { + // service name _including_ underscore character prefix + // as defined in RFC6763 Section 7 std::string service_type; + // second label indicating protocol _including_ underscore character prefix + // as defined in RFC6763 Section 7, like "_tcp" or "_udp" std::string proto; uint16_t port; std::vector txt_records; diff --git a/esphome/components/mdns/mdns_esp8266.cpp b/esphome/components/mdns/mdns_esp8266.cpp index 48f31f1bbf..1a73e4b5b3 100644 --- a/esphome/components/mdns/mdns_esp8266.cpp +++ b/esphome/components/mdns/mdns_esp8266.cpp @@ -17,9 +17,21 @@ void MDNSComponent::setup() { auto services = compile_services_(); for (const auto &service : services) { - MDNS.addService(service.service_type.c_str(), service.proto.c_str(), service.port); + // Strip the leading underscore from the proto and service_type. While it is + // part of the wire protocol to have an underscore, and for example ESP-IDF + // expects the underscore to be there, the ESP8266 implementation always adds + // the underscore itself. + auto proto = service.proto.c_str(); + while (*proto == '_') { + proto++; + } + auto service_type = service.service_type.c_str(); + while (*service_type == '_') { + service_type++; + } + MDNS.addService(service_type, proto, service.port); for (const auto &record : service.txt_records) { - MDNS.addServiceTxt(service.service_type.c_str(), service.proto.c_str(), record.key.c_str(), record.value.c_str()); + MDNS.addServiceTxt(service_type, proto, record.key.c_str(), record.value.c_str()); } } } From f6935a4b4b61769d8b13320985753e97278d345f Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Thu, 21 Oct 2021 12:23:37 +0200 Subject: [PATCH 0044/3388] Fix ESP8266 GPIO0 Pullup Validation (#2572) --- esphome/components/esp8266/gpio.py | 4 ++-- tests/test3.yaml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/esphome/components/esp8266/gpio.py b/esphome/components/esp8266/gpio.py index 0ebfbd6f69..fa5c94dff5 100644 --- a/esphome/components/esp8266/gpio.py +++ b/esphome/components/esp8266/gpio.py @@ -107,9 +107,9 @@ def validate_supports(value): raise cv.Invalid( "Open-drain only works with output mode", [CONF_MODE, CONF_OPEN_DRAIN] ) - if is_pullup and num == 0: + if is_pullup and num == 16: raise cv.Invalid( - "GPIO Pin 0 does not support pullup pin mode. " + "GPIO Pin 16 does not support pullup pin mode. " "Please choose another pin.", [CONF_MODE, CONF_PULLUP], ) diff --git a/tests/test3.yaml b/tests/test3.yaml index 73e314c94c..f7cba5e787 100644 --- a/tests/test3.yaml +++ b/tests/test3.yaml @@ -1150,7 +1150,7 @@ servo: ttp229_lsf: ttp229_bsf: - sdo_pin: D0 + sdo_pin: D2 scl_pin: D1 sim800l: From 2f32833a22e7d91b11221688f09c2a5c06aa8651 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Thu, 21 Oct 2021 12:24:01 +0200 Subject: [PATCH 0045/3388] Fix wifi ble coexistence check (#2573) --- esphome/components/wifi/__init__.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/esphome/components/wifi/__init__.py b/esphome/components/wifi/__init__.py index 19e4046711..faf3cca280 100644 --- a/esphome/components/wifi/__init__.py +++ b/esphome/components/wifi/__init__.py @@ -159,8 +159,15 @@ def final_validate_power_esp32_ble(value): "esp32_ble_server", "esp32_ble_tracker", ]: + if conflicting not in fv.full_config.get(): + continue + try: - cv.require_framework_version(esp32_arduino=cv.Version(1, 0, 5))(None) + # Only arduino 1.0.5+ and esp-idf impacted + cv.require_framework_version( + esp32_arduino=cv.Version(1, 0, 5), + esp_idf=cv.Version(4, 0, 0), + )(None) except cv.Invalid: pass else: From e7baa42e6309949db5b1f7b22813f04f96ec3060 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Thu, 21 Oct 2021 14:20:23 +0200 Subject: [PATCH 0046/3388] Arduino global delay/millis/... symbols workaround (#2575) --- esphome/core/config.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/esphome/core/config.py b/esphome/core/config.py index c495fefddd..3c53d81784 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -206,6 +206,31 @@ def include_file(path, basename): cg.add_global(cg.RawStatement(f'#include "{basename}"')) +ARDUINO_GLUE_CODE = """\ +#define yield() esphome::yield() +#define millis() esphome::millis() +#define delay(x) esphome::delay(x) +#define delayMicroseconds(x) esphome::delayMicroseconds(x) +""" + + +@coroutine_with_priority(-999.0) +async def add_arduino_global_workaround(): + # The Arduino framework defined these itself in the global + # namespace. For the esphome codebase that is not a problem, + # but when custom code + # 1. writes `millis()` for example AND + # 2. has `using namespace esphome;` like our guides suggest + # Then the compiler will complain that the call is ambiguous + # Define a hacky macro so that the call is never ambiguous + # and always uses the esphome namespace one. + # See also https://github.com/esphome/issues/issues/2510 + # Priority -999 so that it runs before adding includes, as those + # also might reference these symbols + for line in ARDUINO_GLUE_CODE.splitlines(): + cg.add_global(cg.RawStatement(line)) + + @coroutine_with_priority(-1000.0) async def add_includes(includes): # Add includes at the very end, so that the included files can access global variables @@ -287,6 +312,9 @@ async def to_code(config): cg.add_build_flag("-Wno-unused-but-set-variable") cg.add_build_flag("-Wno-sign-compare") + if CORE.using_arduino: + CORE.add_job(add_arduino_global_workaround) + if config[CONF_INCLUDES]: CORE.add_job(add_includes, config[CONF_INCLUDES]) From 7b8d826704715792f5fba2d8e8e9540b3cb86d46 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Thu, 21 Oct 2021 14:20:57 +0200 Subject: [PATCH 0047/3388] Fix ESP8266 OTA adds unnecessary Update library (#2579) --- esphome/components/ota/__init__.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/esphome/components/ota/__init__.py b/esphome/components/ota/__init__.py index 53b282c43e..b3d3b7ad23 100644 --- a/esphome/components/ota/__init__.py +++ b/esphome/components/ota/__init__.py @@ -90,9 +90,7 @@ async def to_code(config): ) cg.add(RawExpression(f"if ({condition}) return")) - if CORE.is_esp8266: - cg.add_library("Update", None) - elif CORE.is_esp32 and CORE.using_arduino: + if CORE.is_esp32 and CORE.using_arduino: cg.add_library("Update", None) use_state_callback = False From eae3d72a4da02f03929ddc2cc6d40e7b0c81c0d4 Mon Sep 17 00:00:00 2001 From: Otto winter Date: Thu, 21 Oct 2021 14:23:08 +0200 Subject: [PATCH 0048/3388] Bump version to 2021.10.1 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 59441e23c8..f07c83b32d 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2021.10.0" +__version__ = "2021.10.1" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" From ab07ee57c6f96025c2dbaefc5a642a05f689b0b3 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Thu, 21 Oct 2021 14:39:36 +0200 Subject: [PATCH 0049/3388] Fix ESP8266 dallas GPIO16 INPUT_PULLUP (#2581) --- esphome/components/esp8266/gpio.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/esphome/components/esp8266/gpio.cpp b/esphome/components/esp8266/gpio.cpp index cb703c18e1..7805889b40 100644 --- a/esphome/components/esp8266/gpio.cpp +++ b/esphome/components/esp8266/gpio.cpp @@ -50,6 +50,13 @@ void ESP8266GPIOPin::pin_mode(gpio::Flags flags) { mode = OUTPUT; } else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLUP)) { mode = INPUT_PULLUP; + if (pin_ == 16) { + // GPIO16 doesn't have a pullup, so pinMode would fail. + // However, sometimes this method is called with pullup mode anyway + // for example from dallas one_wire. For those cases convert this + // to a INPUT mode. + mode = INPUT; + } } else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLDOWN)) { mode = INPUT_PULLDOWN_16; } else if (flags == (gpio::FLAG_OUTPUT | gpio::FLAG_OPEN_DRAIN)) { From f0aba6ceb282155f69c2928e9e896eea0e924ac8 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Thu, 21 Oct 2021 14:53:08 +0200 Subject: [PATCH 0050/3388] Fix platformio version in Dockerfile doesn't match requirements (#2582) --- docker/Dockerfile | 2 +- requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index e66c3e1d95..7928ff8901 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -43,7 +43,7 @@ RUN \ # Ubuntu python3-pip is missing wheel pip3 install --no-cache-dir \ wheel==0.36.2 \ - platformio==5.2.0 \ + platformio==5.2.1 \ # Change some platformio settings && platformio settings set enable_telemetry No \ && platformio settings set check_libraries_interval 1000000 \ diff --git a/requirements.txt b/requirements.txt index 63804e89e8..d0ee047f3a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,7 +6,7 @@ tornado==6.1 tzlocal==3.0 # from time tzdata>=2021.1 # from time pyserial==3.5 -platformio==5.2.1 +platformio==5.2.1 # When updating platformio, also update Dockerfile esptool==3.1 click==8.0.3 esphome-dashboard==20211021.0 From 7d9d9fcf3601e1058e4c47b43481d1bab8dc63ae Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Thu, 21 Oct 2021 15:29:32 +0200 Subject: [PATCH 0051/3388] Fix platformio_install_deps no longer installing all lib_deps (#2584) --- docker/platformio_install_deps.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/docker/platformio_install_deps.py b/docker/platformio_install_deps.py index 5625bd4d01..c7b11cf321 100755 --- a/docker/platformio_install_deps.py +++ b/docker/platformio_install_deps.py @@ -8,6 +8,23 @@ import sys config = configparser.ConfigParser(inline_comment_prefixes=(';', )) config.read(sys.argv[1]) -libs = [x for x in config['common']['lib_deps'].splitlines() if len(x) != 0] + +libs = [] +# Extract from every lib_deps key in all sections +for section in config.sections(): + conf = config[section] + if "lib_deps" not in conf: + continue + for lib_dep in conf["lib_deps"].splitlines(): + if not lib_dep: + # Empty line or comment + continue + if lib_dep.startswith("${"): + # Extending from another section + continue + if "@" not in lib_dep: + # No version pinned, this is an internal lib + continue + libs.append(lib_dep) subprocess.check_call(['platformio', 'lib', '-g', 'install', *libs]) From eed0c18d65e2f732128e48661218abc6a499b6dd Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Thu, 21 Oct 2021 18:57:03 +0200 Subject: [PATCH 0052/3388] Fix HeatpumpIR pin (#2585) --- platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index 9cc7477d51..6a8b342314 100644 --- a/platformio.ini +++ b/platformio.ini @@ -49,7 +49,7 @@ lib_deps = glmnet/Dsmr@0.5 ; dsmr rweather/Crypto@0.2.0 ; dsmr dudanov/MideaUART@1.1.8 ; midea - tonia/HeatpumpIR@^1.0.15 ; heatpumpir + tonia/HeatpumpIR@1.0.15 ; heatpumpir build_flags = ${common.build_flags} -DUSE_ARDUINO From ab3440142112e956ab40676088c8e7fb2bc584c7 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Thu, 21 Oct 2021 18:59:49 +0200 Subject: [PATCH 0053/3388] Fix PlatformIO version for latest Arduino framework (#2590) --- esphome/components/esp8266/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/esp8266/__init__.py b/esphome/components/esp8266/__init__.py index a5323db8bf..ddaeee6ab7 100644 --- a/esphome/components/esp8266/__init__.py +++ b/esphome/components/esp8266/__init__.py @@ -65,7 +65,7 @@ RECOMMENDED_ARDUINO_FRAMEWORK_VERSION = cv.Version(2, 7, 4) # - https://api.registry.platformio.org/v3/packages/platformio/platform/espressif8266 ARDUINO_2_PLATFORM_VERSION = cv.Version(2, 6, 2) # for arduino 3 framework versions -ARDUINO_3_PLATFORM_VERSION = cv.Version(3, 0, 2) +ARDUINO_3_PLATFORM_VERSION = cv.Version(3, 2, 0) def _arduino_check_versions(value): From ed0b34b2fe03d71cc28694c9ec39446fd888c9bc Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Thu, 21 Oct 2021 19:55:19 +0200 Subject: [PATCH 0054/3388] Fix pin/component switchup in SX1509 pin configuration (#2593) --- esphome/components/sx1509/__init__.py | 4 ++-- esphome/core/helpers.cpp | 1 + tests/test4.yaml | 10 ++++++++++ 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/esphome/components/sx1509/__init__.py b/esphome/components/sx1509/__init__.py index f1b7d5f424..879ced2fb3 100644 --- a/esphome/components/sx1509/__init__.py +++ b/esphome/components/sx1509/__init__.py @@ -80,8 +80,8 @@ def validate_mode(value): CONF_SX1509 = "sx1509" SX1509_PIN_SCHEMA = cv.All( { - cv.GenerateID(): cv.declare_id(SX1509Component), - cv.Required(CONF_SX1509): cv.use_id(SX1509GPIOPin), + cv.GenerateID(): cv.declare_id(SX1509GPIOPin), + cv.Required(CONF_SX1509): cv.use_id(SX1509Component), cv.Required(CONF_NUMBER): cv.int_range(min=0, max=15), cv.Optional(CONF_MODE, default={}): cv.All( { diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index 780df3ca6d..bc97259a71 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -358,6 +358,7 @@ template T clamp(const T val, const T min, const T max) { return max; return val; } +template uint8_t clamp(uint8_t, uint8_t, uint8_t); template float clamp(float, float, float); template int clamp(int, int, int); diff --git a/tests/test4.yaml b/tests/test4.yaml index 4f2025ad74..bc249c5ecb 100644 --- a/tests/test4.yaml +++ b/tests/test4.yaml @@ -59,6 +59,10 @@ tuya: pipsolar: id: inverter0 +sx1509: + - id: sx1509_hub + address: 0x3E + sensor: - platform: homeassistant entity_id: sensor.hello_world @@ -209,6 +213,7 @@ sensor: - or: - throttle: "20min" - delta: 0.02 + # # platform sensor.apds9960 requires component apds9960 # @@ -308,6 +313,11 @@ binary_sensor: y_max: 212 on_state: - lambda: 'ESP_LOGI("main", "key0: %s", (x ? "touch" : "release"));' + - platform: gpio + name: GPIO SX1509 test + pin: + sx1509: sx1509_hub + number: 3 climate: - platform: tuya From 115bca98f16ef99c718d563ec25f395d0aef744b Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Thu, 21 Oct 2021 19:56:47 +0200 Subject: [PATCH 0055/3388] Fix old-style `arduino_version` on ESP8266 and with magic values (#2591) --- esphome/const.py | 5 ++++- esphome/core/config.py | 9 +++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/esphome/const.py b/esphome/const.py index f07c83b32d..ce401b9f73 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -4,7 +4,10 @@ __version__ = "2021.10.1" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" -TARGET_PLATFORMS = ["esp32", "esp8266"] +PLATFORM_ESP32 = "esp32" +PLATFORM_ESP8266 = "esp8266" + +TARGET_PLATFORMS = [PLATFORM_ESP32, PLATFORM_ESP8266] TARGET_FRAMEWORKS = ["arduino", "esp-idf"] # See also https://github.com/platformio/platform-espressif8266/releases diff --git a/esphome/core/config.py b/esphome/core/config.py index 3c53d81784..235e0eeb84 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -29,6 +29,7 @@ from esphome.const import ( CONF_VERSION, KEY_CORE, TARGET_PLATFORMS, + PLATFORM_ESP8266, ) from esphome.core import CORE, coroutine_with_priority from esphome.helpers import copy_file_if_changed, walk_files @@ -182,9 +183,13 @@ def preload_core_config(config, result): if CONF_BOARD_FLASH_MODE in conf: plat_conf[CONF_BOARD_FLASH_MODE] = conf.pop(CONF_BOARD_FLASH_MODE) if CONF_ARDUINO_VERSION in conf: - plat_conf[CONF_FRAMEWORK] = {CONF_TYPE: "arduino"} + plat_conf[CONF_FRAMEWORK] = {} + if plat != PLATFORM_ESP8266: + plat_conf[CONF_FRAMEWORK][CONF_TYPE] = "arduino" + try: - cv.Version.parse(conf[CONF_ARDUINO_VERSION]) + if conf[CONF_ARDUINO_VERSION] not in ("recommended", "latest", "dev"): + cv.Version.parse(conf[CONF_ARDUINO_VERSION]) plat_conf[CONF_FRAMEWORK][CONF_VERSION] = conf.pop(CONF_ARDUINO_VERSION) except ValueError: plat_conf[CONF_FRAMEWORK][CONF_SOURCE] = conf.pop(CONF_ARDUINO_VERSION) From 68cbe58d005903c88736725f44991c718b355109 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 21 Oct 2021 20:01:03 +0200 Subject: [PATCH 0056/3388] Bump esphome-dashboard from 20211021.0 to 20211021.1 (#2594) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index d0ee047f3a..51bbb445d7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,7 +9,7 @@ pyserial==3.5 platformio==5.2.1 # When updating platformio, also update Dockerfile esptool==3.1 click==8.0.3 -esphome-dashboard==20211021.0 +esphome-dashboard==20211021.1 aioesphomeapi==9.1.5 # esp-idf requires this, but doesn't bundle it by default From bbcd52396745f3a33da705c403da66f322228d52 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Thu, 21 Oct 2021 20:07:37 +0200 Subject: [PATCH 0057/3388] Fix validation of addressable light IDs (#2588) --- esphome/components/light/addressable_light.h | 6 ++++++ esphome/components/light/types.py | 5 ++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/esphome/components/light/addressable_light.h b/esphome/components/light/addressable_light.h index fea7508515..2b2c0ca7e4 100644 --- a/esphome/components/light/addressable_light.h +++ b/esphome/components/light/addressable_light.h @@ -22,6 +22,12 @@ using ESPColor ESPDEPRECATED("esphome::light::ESPColor is deprecated, use esphom /// Convert the color information from a `LightColorValues` object to a `Color` object (does not apply brightness). Color color_from_light_color_values(LightColorValues val); +/// Use a custom state class for addressable lights, to allow type system to discriminate between addressable and +/// non-addressable lights. +class AddressableLightState : public LightState { + using LightState::LightState; +}; + class AddressableLight : public LightOutput, public Component { public: virtual int32_t size() const = 0; diff --git a/esphome/components/light/types.py b/esphome/components/light/types.py index cf544e5435..bc20cd5555 100644 --- a/esphome/components/light/types.py +++ b/esphome/components/light/types.py @@ -4,10 +4,9 @@ from esphome import automation # Base light_ns = cg.esphome_ns.namespace("light") LightState = light_ns.class_("LightState", cg.EntityBase, cg.Component) -# Fake class for addressable lights -AddressableLightState = light_ns.class_("LightState", LightState) +AddressableLightState = light_ns.class_("AddressableLightState", LightState) LightOutput = light_ns.class_("LightOutput") -AddressableLight = light_ns.class_("AddressableLight", cg.Component) +AddressableLight = light_ns.class_("AddressableLight", LightOutput, cg.Component) AddressableLightRef = AddressableLight.operator("ref") Color = cg.esphome_ns.class_("Color") From f93e7d4e3a63f946ecaef103bf239d9295dbaae1 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Fri, 22 Oct 2021 10:46:44 +0200 Subject: [PATCH 0058/3388] Fix socket connection closed not detected (#2587) --- esphome/components/api/api_connection.cpp | 2 + esphome/components/api/api_frame_helper.cpp | 48 ++++++++++++++----- esphome/components/api/api_frame_helper.h | 1 + esphome/components/ota/ota_component.cpp | 9 ++++ .../components/socket/lwip_raw_tcp_impl.cpp | 8 +++- 5 files changed, 53 insertions(+), 15 deletions(-) diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 47171ba50f..c87ccf4dc0 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -78,6 +78,8 @@ void APIConnection::loop() { on_fatal_error(); if (err == APIError::SOCKET_READ_FAILED && errno == ECONNRESET) { ESP_LOGW(TAG, "%s: Connection reset", client_info_.c_str()); + } else if (err == APIError::CONNECTION_CLOSED) { + ESP_LOGW(TAG, "%s: Connection closed", client_info_.c_str()); } else { ESP_LOGW(TAG, "%s: Reading failed: %s errno=%d", client_info_.c_str(), api_error_to_str(err), errno); } diff --git a/esphome/components/api/api_frame_helper.cpp b/esphome/components/api/api_frame_helper.cpp index 4971272f41..c0e37ec90d 100644 --- a/esphome/components/api/api_frame_helper.cpp +++ b/esphome/components/api/api_frame_helper.cpp @@ -10,7 +10,7 @@ namespace api { static const char *const TAG = "api.socket"; -/// Is the given return value (from read/write syscalls) a wouldblock error? +/// Is the given return value (from write syscalls) a wouldblock error? bool is_would_block(ssize_t ret) { if (ret == -1) { return errno == EWOULDBLOCK || errno == EAGAIN; @@ -64,6 +64,8 @@ const char *api_error_to_str(APIError err) { return "HANDSHAKESTATE_SPLIT_FAILED"; } else if (err == APIError::BAD_HANDSHAKE_ERROR_BYTE) { return "BAD_HANDSHAKE_ERROR_BYTE"; + } else if (err == APIError::CONNECTION_CLOSED) { + return "CONNECTION_CLOSED"; } return "UNKNOWN"; } @@ -185,12 +187,17 @@ APIError APINoiseFrameHelper::try_read_frame_(ParsedFrame *frame) { // no header information yet size_t to_read = 3 - rx_header_buf_len_; ssize_t received = socket_->read(&rx_header_buf_[rx_header_buf_len_], to_read); - if (is_would_block(received)) { - return APIError::WOULD_BLOCK; - } else if (received == -1) { + if (received == -1) { + if (errno == EWOULDBLOCK || errno == EAGAIN) { + return APIError::WOULD_BLOCK; + } state_ = State::FAILED; HELPER_LOG("Socket read failed with errno %d", errno); return APIError::SOCKET_READ_FAILED; + } else if (received == 0) { + state_ = State::FAILED; + HELPER_LOG("Connection closed"); + return APIError::CONNECTION_CLOSED; } rx_header_buf_len_ += received; if (received != to_read) { @@ -227,12 +234,17 @@ APIError APINoiseFrameHelper::try_read_frame_(ParsedFrame *frame) { // more data to read size_t to_read = msg_size - rx_buf_len_; ssize_t received = socket_->read(&rx_buf_[rx_buf_len_], to_read); - if (is_would_block(received)) { - return APIError::WOULD_BLOCK; - } else if (received == -1) { + if (received == -1) { + if (errno == EWOULDBLOCK || errno == EAGAIN) { + return APIError::WOULD_BLOCK; + } state_ = State::FAILED; HELPER_LOG("Socket read failed with errno %d", errno); return APIError::SOCKET_READ_FAILED; + } else if (received == 0) { + state_ = State::FAILED; + HELPER_LOG("Connection closed"); + return APIError::CONNECTION_CLOSED; } rx_buf_len_ += received; if (received != to_read) { @@ -778,12 +790,17 @@ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) { while (!rx_header_parsed_) { uint8_t data; ssize_t received = socket_->read(&data, 1); - if (is_would_block(received)) { - return APIError::WOULD_BLOCK; - } else if (received == -1) { + if (received == -1) { + if (errno == EWOULDBLOCK || errno == EAGAIN) { + return APIError::WOULD_BLOCK; + } state_ = State::FAILED; HELPER_LOG("Socket read failed with errno %d", errno); return APIError::SOCKET_READ_FAILED; + } else if (received == 0) { + state_ = State::FAILED; + HELPER_LOG("Connection closed"); + return APIError::CONNECTION_CLOSED; } rx_header_buf_.push_back(data); @@ -824,12 +841,17 @@ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) { // more data to read size_t to_read = rx_header_parsed_len_ - rx_buf_len_; ssize_t received = socket_->read(&rx_buf_[rx_buf_len_], to_read); - if (is_would_block(received)) { - return APIError::WOULD_BLOCK; - } else if (received == -1) { + if (received == -1) { + if (errno == EWOULDBLOCK || errno == EAGAIN) { + return APIError::WOULD_BLOCK; + } state_ = State::FAILED; HELPER_LOG("Socket read failed with errno %d", errno); return APIError::SOCKET_READ_FAILED; + } else if (received == 0) { + state_ = State::FAILED; + HELPER_LOG("Connection closed"); + return APIError::CONNECTION_CLOSED; } rx_buf_len_ += received; if (received != to_read) { diff --git a/esphome/components/api/api_frame_helper.h b/esphome/components/api/api_frame_helper.h index 7fdb26fd40..57e3c961d5 100644 --- a/esphome/components/api/api_frame_helper.h +++ b/esphome/components/api/api_frame_helper.h @@ -53,6 +53,7 @@ enum class APIError : int { HANDSHAKESTATE_SETUP_FAILED = 1019, HANDSHAKESTATE_SPLIT_FAILED = 1020, BAD_HANDSHAKE_ERROR_BYTE = 1021, + CONNECTION_CLOSED = 1022, }; const char *api_error_to_str(APIError err); diff --git a/esphome/components/ota/ota_component.cpp b/esphome/components/ota/ota_component.cpp index 89bee17452..6d51087882 100644 --- a/esphome/components/ota/ota_component.cpp +++ b/esphome/components/ota/ota_component.cpp @@ -275,6 +275,12 @@ void OTAComponent::handle_() { } ESP_LOGW(TAG, "Error receiving data for update, errno: %d", errno); goto error; + } else if (read == 0) { + // $ man recv + // "When a stream socket peer has performed an orderly shutdown, the return value will + // be 0 (the traditional "end-of-file" return)." + ESP_LOGW(TAG, "Remote end closed connection"); + goto error; } error_code = backend->write(buf, read); @@ -362,6 +368,9 @@ bool OTAComponent::readall_(uint8_t *buf, size_t len) { } ESP_LOGW(TAG, "Failed to read %d bytes of data, errno: %d", len, errno); return false; + } else if (read == 0) { + ESP_LOGW(TAG, "Remote closed connection"); + return false; } else { at += read; } diff --git a/esphome/components/socket/lwip_raw_tcp_impl.cpp b/esphome/components/socket/lwip_raw_tcp_impl.cpp index 54dfddac3f..922d895ff4 100644 --- a/esphome/components/socket/lwip_raw_tcp_impl.cpp +++ b/esphome/components/socket/lwip_raw_tcp_impl.cpp @@ -320,8 +320,7 @@ class LWIPRawImpl : public Socket { return -1; } if (rx_closed_ && rx_buf_ == nullptr) { - errno = ECONNRESET; - return -1; + return 0; } if (len == 0) { return 0; @@ -366,6 +365,11 @@ class LWIPRawImpl : public Socket { read += copysize; } + if (read == 0) { + errno = EWOULDBLOCK; + return -1; + } + return read; } ssize_t readv(const struct iovec *iov, int iovcnt) override { From 42873dd37ca231dc1dd33f8f8cc72ac4bc10f541 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Fri, 22 Oct 2021 12:12:07 +0200 Subject: [PATCH 0059/3388] Bump noise-c from 0.1.3 to 0.1.4 (#2602) --- esphome/components/api/__init__.py | 2 +- platformio.ini | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/api/__init__.py b/esphome/components/api/__init__.py index b0608a69dd..6b2e7fd06b 100644 --- a/esphome/components/api/__init__.py +++ b/esphome/components/api/__init__.py @@ -121,7 +121,7 @@ async def to_code(config): decoded = base64.b64decode(conf[CONF_KEY]) cg.add(var.set_noise_psk(list(decoded))) cg.add_define("USE_API_NOISE") - cg.add_library("esphome/noise-c", "0.1.3") + cg.add_library("esphome/noise-c", "0.1.4") else: cg.add_define("USE_API_PLAINTEXT") diff --git a/platformio.ini b/platformio.ini index 6a8b342314..ac5144fc37 100644 --- a/platformio.ini +++ b/platformio.ini @@ -26,7 +26,7 @@ build_flags = [common] lib_deps = - esphome/noise-c@0.1.3 ; api + esphome/noise-c@0.1.4 ; api makuna/NeoPixelBus@2.6.7 ; neopixelbus build_flags = -DESPHOME_LOG_LEVEL=ESPHOME_LOG_LEVEL_VERY_VERBOSE From 5be52f71f9c036d78fa1fe6799650899a6180794 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Fri, 22 Oct 2021 13:02:55 +0200 Subject: [PATCH 0060/3388] Add OTA upload compression for ESP8266 (#2601) --- esphome/components/ota/ota_backend.h | 1 + .../ota/ota_backend_arduino_esp32.h | 1 + .../ota/ota_backend_arduino_esp8266.h | 1 + esphome/components/ota/ota_backend_esp_idf.h | 1 + esphome/components/ota/ota_component.cpp | 9 +++- esphome/components/ota/ota_component.h | 1 + esphome/espota2.py | 44 ++++++++++++------- 7 files changed, 42 insertions(+), 16 deletions(-) diff --git a/esphome/components/ota/ota_backend.h b/esphome/components/ota/ota_backend.h index c253e009c6..5c5b61a278 100644 --- a/esphome/components/ota/ota_backend.h +++ b/esphome/components/ota/ota_backend.h @@ -12,6 +12,7 @@ class OTABackend { virtual OTAResponseTypes write(uint8_t *data, size_t len) = 0; virtual OTAResponseTypes end() = 0; virtual void abort() = 0; + virtual bool supports_compression() = 0; }; } // namespace ota diff --git a/esphome/components/ota/ota_backend_arduino_esp32.h b/esphome/components/ota/ota_backend_arduino_esp32.h index 6b712502fb..f86a70d678 100644 --- a/esphome/components/ota/ota_backend_arduino_esp32.h +++ b/esphome/components/ota/ota_backend_arduino_esp32.h @@ -15,6 +15,7 @@ class ArduinoESP32OTABackend : public OTABackend { OTAResponseTypes write(uint8_t *data, size_t len) override; OTAResponseTypes end() override; void abort() override; + bool supports_compression() override { return false; } }; } // namespace ota diff --git a/esphome/components/ota/ota_backend_arduino_esp8266.h b/esphome/components/ota/ota_backend_arduino_esp8266.h index d1195af911..cf29a90fc1 100644 --- a/esphome/components/ota/ota_backend_arduino_esp8266.h +++ b/esphome/components/ota/ota_backend_arduino_esp8266.h @@ -16,6 +16,7 @@ class ArduinoESP8266OTABackend : public OTABackend { OTAResponseTypes write(uint8_t *data, size_t len) override; OTAResponseTypes end() override; void abort() override; + bool supports_compression() override { return true; } }; } // namespace ota diff --git a/esphome/components/ota/ota_backend_esp_idf.h b/esphome/components/ota/ota_backend_esp_idf.h index 49c6e124fa..af09d0d693 100644 --- a/esphome/components/ota/ota_backend_esp_idf.h +++ b/esphome/components/ota/ota_backend_esp_idf.h @@ -17,6 +17,7 @@ class IDFOTABackend : public OTABackend { OTAResponseTypes write(uint8_t *data, size_t len) override; OTAResponseTypes end() override; void abort() override; + bool supports_compression() override { return false; } private: esp_ota_handle_t update_handle_{0}; diff --git a/esphome/components/ota/ota_component.cpp b/esphome/components/ota/ota_component.cpp index 6d51087882..e49c108320 100644 --- a/esphome/components/ota/ota_component.cpp +++ b/esphome/components/ota/ota_component.cpp @@ -104,6 +104,8 @@ void OTAComponent::loop() { } } +static const uint8_t FEATURE_SUPPORTS_COMPRESSION = 0x01; + void OTAComponent::handle_() { OTAResponseTypes error_code = OTA_RESPONSE_ERROR_UNKNOWN; bool update_started = false; @@ -154,6 +156,8 @@ void OTAComponent::handle_() { buf[1] = OTA_VERSION_1_0; this->writeall_(buf, 2); + backend = make_ota_backend(); + // Read features - 1 byte if (!this->readall_(buf, 1)) { ESP_LOGW(TAG, "Reading features failed!"); @@ -164,6 +168,10 @@ void OTAComponent::handle_() { // Acknowledge header - 1 byte buf[0] = OTA_RESPONSE_HEADER_OK; + if ((ota_features & FEATURE_SUPPORTS_COMPRESSION) != 0 && backend->supports_compression()) { + buf[0] = OTA_RESPONSE_SUPPORTS_COMPRESSION; + } + this->writeall_(buf, 1); #ifdef USE_OTA_PASSWORD @@ -241,7 +249,6 @@ void OTAComponent::handle_() { } ESP_LOGV(TAG, "OTA size is %u bytes", ota_size); - backend = make_ota_backend(); error_code = backend->begin(ota_size); if (error_code != OTA_RESPONSE_OK) goto error; diff --git a/esphome/components/ota/ota_component.h b/esphome/components/ota/ota_component.h index e08e187df6..5647d52eeb 100644 --- a/esphome/components/ota/ota_component.h +++ b/esphome/components/ota/ota_component.h @@ -19,6 +19,7 @@ enum OTAResponseTypes { OTA_RESPONSE_BIN_MD5_OK = 67, OTA_RESPONSE_RECEIVE_OK = 68, OTA_RESPONSE_UPDATE_END_OK = 69, + OTA_RESPONSE_SUPPORTS_COMPRESSION = 70, OTA_RESPONSE_ERROR_MAGIC = 128, OTA_RESPONSE_ERROR_UPDATE_PREPARE = 129, diff --git a/esphome/espota2.py b/esphome/espota2.py index f8a2fab94c..8f299395dd 100644 --- a/esphome/espota2.py +++ b/esphome/espota2.py @@ -4,6 +4,7 @@ import random import socket import sys import time +import gzip from esphome.core import EsphomeError from esphome.helpers import is_ip_address, resolve_ip_address @@ -17,6 +18,7 @@ RESPONSE_UPDATE_PREPARE_OK = 66 RESPONSE_BIN_MD5_OK = 67 RESPONSE_RECEIVE_OK = 68 RESPONSE_UPDATE_END_OK = 69 +RESPONSE_SUPPORTS_COMPRESSION = 70 RESPONSE_ERROR_MAGIC = 128 RESPONSE_ERROR_UPDATE_PREPARE = 129 @@ -34,6 +36,8 @@ OTA_VERSION_1_0 = 1 MAGIC_BYTES = [0x6C, 0x26, 0xF7, 0x5C, 0x45] +FEATURE_SUPPORTS_COMPRESSION = 0x01 + _LOGGER = logging.getLogger(__name__) @@ -170,11 +174,9 @@ def send_check(sock, data, msg): def perform_ota(sock, password, file_handle, filename): - file_md5 = hashlib.md5(file_handle.read()).hexdigest() - file_size = file_handle.tell() + file_contents = file_handle.read() + file_size = len(file_contents) _LOGGER.info("Uploading %s (%s bytes)", filename, file_size) - file_handle.seek(0) - _LOGGER.debug("MD5 of binary is %s", file_md5) # Enable nodelay, we need it for phase 1 sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) @@ -185,8 +187,16 @@ def perform_ota(sock, password, file_handle, filename): raise OTAError(f"Unsupported OTA version {version}") # Features - send_check(sock, 0x00, "features") - receive_exactly(sock, 1, "features", RESPONSE_HEADER_OK) + send_check(sock, FEATURE_SUPPORTS_COMPRESSION, "features") + features = receive_exactly( + sock, 1, "features", [RESPONSE_HEADER_OK, RESPONSE_SUPPORTS_COMPRESSION] + )[0] + + if features == RESPONSE_SUPPORTS_COMPRESSION: + upload_contents = gzip.compress(file_contents, compresslevel=9) + _LOGGER.info("Compressed to %s bytes", len(upload_contents)) + else: + upload_contents = file_contents (auth,) = receive_exactly( sock, 1, "auth", [RESPONSE_REQUEST_AUTH, RESPONSE_AUTH_OK] @@ -213,16 +223,20 @@ def perform_ota(sock, password, file_handle, filename): send_check(sock, result, "auth result") receive_exactly(sock, 1, "auth result", RESPONSE_AUTH_OK) - file_size_encoded = [ - (file_size >> 24) & 0xFF, - (file_size >> 16) & 0xFF, - (file_size >> 8) & 0xFF, - (file_size >> 0) & 0xFF, + upload_size = len(upload_contents) + upload_size_encoded = [ + (upload_size >> 24) & 0xFF, + (upload_size >> 16) & 0xFF, + (upload_size >> 8) & 0xFF, + (upload_size >> 0) & 0xFF, ] - send_check(sock, file_size_encoded, "binary size") + send_check(sock, upload_size_encoded, "binary size") receive_exactly(sock, 1, "binary size", RESPONSE_UPDATE_PREPARE_OK) - send_check(sock, file_md5, "file checksum") + upload_md5 = hashlib.md5(upload_contents).hexdigest() + _LOGGER.debug("MD5 of upload is %s", upload_md5) + + send_check(sock, upload_md5, "file checksum") receive_exactly(sock, 1, "file checksum", RESPONSE_BIN_MD5_OK) # Disable nodelay for transfer @@ -236,7 +250,7 @@ def perform_ota(sock, password, file_handle, filename): offset = 0 progress = ProgressBar() while True: - chunk = file_handle.read(1024) + chunk = upload_contents[offset : offset + 1024] if not chunk: break offset += len(chunk) @@ -247,7 +261,7 @@ def perform_ota(sock, password, file_handle, filename): sys.stderr.write("\n") raise OTAError(f"Error sending data: {err}") from err - progress.update(offset / float(file_size)) + progress.update(offset / upload_size) progress.done() # Enable nodelay for last checks From ff2c316b1830571c50b69e6bfc91962a0b0850c5 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Fri, 22 Oct 2021 14:14:07 +0200 Subject: [PATCH 0061/3388] Re-raise keyboardinterrupt (#2603) --- esphome/util.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/esphome/util.py b/esphome/util.py index 0f168cade3..937635fa43 100644 --- a/esphome/util.py +++ b/esphome/util.py @@ -192,8 +192,8 @@ def run_external_command( sys.argv = list(cmd) sys.exit = mock_exit return func() or 0 - except KeyboardInterrupt: - return 1 + except KeyboardInterrupt: # pylint: disable=try-except-raise + raise except SystemExit as err: return err.args[0] except Exception as err: # pylint: disable=broad-except @@ -227,6 +227,8 @@ def run_external_process(*cmd, **kwargs): try: return subprocess.call(cmd, stdout=sub_stdout, stderr=sub_stderr) + except KeyboardInterrupt: # pylint: disable=try-except-raise + raise except Exception as err: # pylint: disable=broad-except _LOGGER.error("Running command failed: %s", err) _LOGGER.error("Please try running %s locally.", full_cmd) From dfb96e4b7fb23ed00707d5401facf8970b930168 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Fri, 22 Oct 2021 14:14:14 +0200 Subject: [PATCH 0062/3388] Add owner to all libraries used (#2604) --- esphome/components/bme680_bsec/__init__.py | 2 +- esphome/components/mqtt/__init__.py | 2 +- esphome/components/web_server_base/__init__.py | 2 +- platformio.ini | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/esphome/components/bme680_bsec/__init__.py b/esphome/components/bme680_bsec/__init__.py index 38da18d702..2f844fa666 100644 --- a/esphome/components/bme680_bsec/__init__.py +++ b/esphome/components/bme680_bsec/__init__.py @@ -67,4 +67,4 @@ async def to_code(config): cg.add_library("SPI", None) cg.add_define("USE_BSEC") - cg.add_library("BSEC Software Library", "1.6.1480") + cg.add_library("boschsensortec/BSEC Software Library", "1.6.1480") diff --git a/esphome/components/mqtt/__init__.py b/esphome/components/mqtt/__init__.py index 8f02f8d437..3d52dab67f 100644 --- a/esphome/components/mqtt/__init__.py +++ b/esphome/components/mqtt/__init__.py @@ -215,7 +215,7 @@ async def to_code(config): await cg.register_component(var, config) # https://github.com/OttoWinter/async-mqtt-client/blob/master/library.json - cg.add_library("ottowinter/AsyncMqttClient-esphome", "0.8.4") + cg.add_library("ottowinter/AsyncMqttClient-esphome", "0.8.6") cg.add_define("USE_MQTT") cg.add_global(mqtt_ns.using) diff --git a/esphome/components/web_server_base/__init__.py b/esphome/components/web_server_base/__init__.py index 95d59a863e..019f31954f 100644 --- a/esphome/components/web_server_base/__init__.py +++ b/esphome/components/web_server_base/__init__.py @@ -28,4 +28,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", "1.3.0") + cg.add_library("esphome/ESPAsyncWebServer-esphome", "1.3.1") diff --git a/platformio.ini b/platformio.ini index ac5144fc37..3d720e24ac 100644 --- a/platformio.ini +++ b/platformio.ini @@ -39,9 +39,9 @@ src_filter = extends = common lib_deps = ${common.lib_deps} - ottowinter/AsyncMqttClient-esphome@0.8.4 ; mqtt + ottowinter/AsyncMqttClient-esphome@0.8.6 ; mqtt ottowinter/ArduinoJson-esphomelib@5.13.3 ; json - esphome/ESPAsyncWebServer-esphome@1.3.0 ; web_server_base + esphome/ESPAsyncWebServer-esphome@1.3.1 ; web_server_base fastled/FastLED@3.3.2 ; fastled_base mikalhart/TinyGPSPlus@1.0.2 ; gps freekode/TM1651@1.0.1 ; tm1651 From 6bdae55ee16f638cd80d43d8a1a79b466ca510bf Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Fri, 22 Oct 2021 16:52:43 +0200 Subject: [PATCH 0063/3388] Fix compiler warnings and update platformio line filter (#2607) --- esphome/components/climate/climate.cpp | 4 ++++ esphome/components/mqtt/mqtt_fan.cpp | 6 ++++++ esphome/components/web_server/web_server.cpp | 6 ++++++ esphome/components/web_server_base/__init__.py | 2 +- esphome/platformio_api.py | 17 ++++++++++++----- platformio.ini | 2 +- 6 files changed, 30 insertions(+), 7 deletions(-) diff --git a/esphome/components/climate/climate.cpp b/esphome/components/climate/climate.cpp index 34e6328d8a..ebea20ed1f 100644 --- a/esphome/components/climate/climate.cpp +++ b/esphome/components/climate/climate.cpp @@ -440,7 +440,11 @@ void Climate::set_visual_max_temperature_override(float visual_max_temperature_o void Climate::set_visual_temperature_step_override(float visual_temperature_step_override) { this->visual_temperature_step_override_ = visual_temperature_step_override; } +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" Climate::Climate(const std::string &name) : EntityBase(name) {} +#pragma GCC diagnostic pop + Climate::Climate() : Climate("") {} ClimateCall Climate::make_call() { return ClimateCall(this); } diff --git a/esphome/components/mqtt/mqtt_fan.cpp b/esphome/components/mqtt/mqtt_fan.cpp index 898183cc58..1703343a77 100644 --- a/esphome/components/mqtt/mqtt_fan.cpp +++ b/esphome/components/mqtt/mqtt_fan.cpp @@ -88,9 +88,12 @@ void MQTTFanComponent::setup() { if (this->state_->get_traits().supports_speed()) { this->subscribe(this->get_speed_command_topic(), [this](const std::string &topic, const std::string &payload) { +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" this->state_->make_call() .set_speed(payload.c_str()) // NOLINT(clang-diagnostic-deprecated-declarations) .perform(); +#pragma GCC diagnostic pop }); } @@ -145,6 +148,8 @@ bool MQTTFanComponent::publish_state() { } if (traits.supports_speed()) { const char *payload; +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" // NOLINTNEXTLINE(clang-diagnostic-deprecated-declarations) switch (fan::speed_level_to_enum(this->state_->speed, traits.supported_speed_count())) { case FAN_SPEED_LOW: { // NOLINT(clang-diagnostic-deprecated-declarations) @@ -161,6 +166,7 @@ bool MQTTFanComponent::publish_state() { break; } } +#pragma GCC diagnostic pop bool success = this->publish(this->get_speed_state_topic(), payload); failed = failed || !success; } diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index e99431be36..44ace38990 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -414,6 +414,8 @@ std::string WebServer::fan_json(fan::FanState *obj) { const auto traits = obj->get_traits(); if (traits.supports_speed()) { root["speed_level"] = obj->speed; +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" // NOLINTNEXTLINE(clang-diagnostic-deprecated-declarations) switch (fan::speed_level_to_enum(obj->speed, traits.supported_speed_count())) { case fan::FAN_SPEED_LOW: // NOLINT(clang-diagnostic-deprecated-declarations) @@ -426,6 +428,7 @@ std::string WebServer::fan_json(fan::FanState *obj) { root["speed"] = "high"; break; } +#pragma GCC diagnostic pop } if (obj->get_traits().supports_oscillation()) root["oscillation"] = obj->oscillating; @@ -448,7 +451,10 @@ void WebServer::handle_fan_request(AsyncWebServerRequest *request, const UrlMatc auto call = obj->turn_on(); if (request->hasParam("speed")) { String speed = request->getParam("speed")->value(); +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" call.set_speed(speed.c_str()); // NOLINT(clang-diagnostic-deprecated-declarations) +#pragma GCC diagnostic pop } if (request->hasParam("speed_level")) { String speed_level = request->getParam("speed_level")->value(); diff --git a/esphome/components/web_server_base/__init__.py b/esphome/components/web_server_base/__init__.py index 019f31954f..4da94d990a 100644 --- a/esphome/components/web_server_base/__init__.py +++ b/esphome/components/web_server_base/__init__.py @@ -28,4 +28,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", "1.3.1") + cg.add_library("esphome/ESPAsyncWebServer-esphome", "2.0.0") diff --git a/esphome/platformio_api.py b/esphome/platformio_api.py index 054c0cb1b0..70e4430e71 100644 --- a/esphome/platformio_api.py +++ b/esphome/platformio_api.py @@ -46,24 +46,31 @@ IGNORE_LIB_WARNINGS = f"(?:{'|'.join(['Hash', 'Update'])})" FILTER_PLATFORMIO_LINES = [ r"Verbose mode can be enabled via `-v, --verbose` option.*", r"CONFIGURATION: https://docs.platformio.org/.*", - r"PLATFORM: .*", r"DEBUG: Current.*", - r"PACKAGES: .*", + r"LDF Modes:.*", r"LDF: Library Dependency Finder -> http://bit.ly/configure-pio-ldf.*", - r"LDF Modes: Finder ~ chain, Compatibility ~ soft.*", f"Looking for {IGNORE_LIB_WARNINGS} library in registry", f"Warning! Library `.*'{IGNORE_LIB_WARNINGS}.*` has not been found in PlatformIO Registry.", f"You can ignore this message, if `.*{IGNORE_LIB_WARNINGS}.*` is a built-in library.*", r"Scanning dependencies...", r"Found \d+ compatible libraries", r"Memory Usage -> http://bit.ly/pio-memory-usage", - r"esptool.py v.*", r"Found: https://platformio.org/lib/show/.*", r"Using cache: .*", r"Installing dependencies", - r".* @ .* is already installed", + r"Library Manager: Already installed, built-in library", r"Building in .* mode", r"Advanced Memory Usage is available via .*", + r"Merged .* ELF section", + r"esptool.py v.*", + r"Checking size .*", + r"Retrieving maximum program size .*", + r"PLATFORM: .*", + r"PACKAGES:.*", + r" - framework-arduinoespressif.* \(.*\)", + r" - tool-esptool.* \(.*\)", + r" - toolchain-.* \(.*\)", + r"Creating BIN file .*", ] diff --git a/platformio.ini b/platformio.ini index 3d720e24ac..ee895ed882 100644 --- a/platformio.ini +++ b/platformio.ini @@ -41,7 +41,7 @@ lib_deps = ${common.lib_deps} ottowinter/AsyncMqttClient-esphome@0.8.6 ; mqtt ottowinter/ArduinoJson-esphomelib@5.13.3 ; json - esphome/ESPAsyncWebServer-esphome@1.3.1 ; web_server_base + esphome/ESPAsyncWebServer-esphome@2.0.0 ; web_server_base fastled/FastLED@3.3.2 ; fastled_base mikalhart/TinyGPSPlus@1.0.2 ; gps freekode/TM1651@1.0.1 ; tm1651 From 901ec918b1f0575bdfbced07aa7f275e9f75cef0 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Fri, 22 Oct 2021 17:23:31 +0200 Subject: [PATCH 0064/3388] Fix ESP8266 OTA compression only starting framework v2.7.0 (#2610) --- esphome/components/ota/ota_backend_arduino_esp8266.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/esphome/components/ota/ota_backend_arduino_esp8266.h b/esphome/components/ota/ota_backend_arduino_esp8266.h index cf29a90fc1..329f2cf0f2 100644 --- a/esphome/components/ota/ota_backend_arduino_esp8266.h +++ b/esphome/components/ota/ota_backend_arduino_esp8266.h @@ -5,6 +5,7 @@ #include "ota_component.h" #include "ota_backend.h" +#include "esphome/core/macros.h" namespace esphome { namespace ota { @@ -16,7 +17,11 @@ class ArduinoESP8266OTABackend : public OTABackend { OTAResponseTypes write(uint8_t *data, size_t len) override; OTAResponseTypes end() override; void abort() override; +#if ARDUINO_VERSION_CODE >= VERSION_CODE(2, 7, 0) bool supports_compression() override { return true; } +#else + bool supports_compression() override { return false; } +#endif }; } // namespace ota From c7ef18fbc4ace4dbf4ba2d0f7bf84ddec77d5356 Mon Sep 17 00:00:00 2001 From: Andreas Hergert <36455093+andreashergert1984@users.noreply.github.com> Date: Fri, 22 Oct 2021 18:20:57 +0200 Subject: [PATCH 0065/3388] Bugfix tca9548a and idf refactor anh (#2612) Co-authored-by: Andreas Hergert --- esphome/components/tca9548a/tca9548a.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/tca9548a/tca9548a.cpp b/esphome/components/tca9548a/tca9548a.cpp index 5117ad8969..e902eb5ed4 100644 --- a/esphome/components/tca9548a/tca9548a.cpp +++ b/esphome/components/tca9548a/tca9548a.cpp @@ -22,7 +22,7 @@ i2c::ErrorCode TCA9548AChannel::writev(uint8_t address, i2c::WriteBuffer *buffer void TCA9548AComponent::setup() { ESP_LOGCONFIG(TAG, "Setting up TCA9548A..."); uint8_t status = 0; - if (!this->read_register(0x00, &status, 1)) { + if (this->read_register(0x00, &status, 1) != i2c::ERROR_OK) { ESP_LOGI(TAG, "TCA9548A failed"); this->mark_failed(); return; From eda1c471ad247c1f94e7fd1b7d33e80ef430eeaf Mon Sep 17 00:00:00 2001 From: Otto winter Date: Fri, 22 Oct 2021 18:26:24 +0200 Subject: [PATCH 0066/3388] Bump version to 2021.10.2 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index ce401b9f73..37c325465b 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2021.10.1" +__version__ = "2021.10.2" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" From cbfbcf7f1b74969152cbcf69ab9446a3f7bf6dc9 Mon Sep 17 00:00:00 2001 From: Andreas Hergert <36455093+andreashergert1984@users.noreply.github.com> Date: Fri, 22 Oct 2021 18:52:47 +0200 Subject: [PATCH 0067/3388] fixed dependency for pca9685 component (#2614) Co-authored-by: Otto Winter Co-authored-by: Andreas --- esphome/components/pca9685/pca9685_output.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/esphome/components/pca9685/pca9685_output.cpp b/esphome/components/pca9685/pca9685_output.cpp index 1ad6f4a665..957f4062fc 100644 --- a/esphome/components/pca9685/pca9685_output.cpp +++ b/esphome/components/pca9685/pca9685_output.cpp @@ -1,6 +1,7 @@ #include "pca9685_output.h" #include "esphome/core/log.h" #include "esphome/core/helpers.h" +#include "esphome/core/hal.h" namespace esphome { namespace pca9685 { From 9aaaf4dd4be4b88e295d6ee8ffbcacb8eab231fe Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Sat, 23 Oct 2021 12:37:50 +0200 Subject: [PATCH 0068/3388] Bump platform-espressif8266 from 2.6.2 to 2.6.3 (#2620) --- esphome/components/esp8266/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/esp8266/__init__.py b/esphome/components/esp8266/__init__.py index ddaeee6ab7..b2706bd4fb 100644 --- a/esphome/components/esp8266/__init__.py +++ b/esphome/components/esp8266/__init__.py @@ -63,7 +63,7 @@ RECOMMENDED_ARDUINO_FRAMEWORK_VERSION = cv.Version(2, 7, 4) # The platformio/espressif8266 version to use for arduino 2 framework versions # - https://github.com/platformio/platform-espressif8266/releases # - https://api.registry.platformio.org/v3/packages/platformio/platform/espressif8266 -ARDUINO_2_PLATFORM_VERSION = cv.Version(2, 6, 2) +ARDUINO_2_PLATFORM_VERSION = cv.Version(2, 6, 3) # for arduino 3 framework versions ARDUINO_3_PLATFORM_VERSION = cv.Version(3, 2, 0) From 2abe09529a0a18fba35dfc05d4253a93f640552f Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Sat, 23 Oct 2021 13:25:46 +0200 Subject: [PATCH 0069/3388] Autodetect flash size (#2615) --- esphome/__main__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/esphome/__main__.py b/esphome/__main__.py index 97059154fd..c2a6dd343f 100644 --- a/esphome/__main__.py +++ b/esphome/__main__.py @@ -226,6 +226,8 @@ def upload_using_esptool(config, port): mcu, "write_flash", "-z", + "--flash_size", + "detect", ] for img in flash_images: cmd += [img.offset, img.path] From b34eed125dc604417c313891e37420624b3218b6 Mon Sep 17 00:00:00 2001 From: 0hax <43876620+0hax@users.noreply.github.com> Date: Sat, 23 Oct 2021 19:01:23 +0200 Subject: [PATCH 0070/3388] Teleinfo ptec (#2599) * teleinfo: handle historical mode correctly. In historical mode, tags like PTEC leads to an issue where we detect a timestamp wheras this is not possible in historical mode. PTEC teleinfo tag looks like: PTEC HP.. Instead of the usual format IINST1 001 I This make our data parsing fails. While at here, make sure we continue parsing other tags even if parsing one of the tag fails. Signed-off-by: 0hax <0hax@protonmail.com> * teleinfo: fix compilation with loglevel set to debug. Signed-off-by: 0hax <0hax@protonmail.com> --- .../teleinfo/sensor/teleinfo_sensor.cpp | 2 +- esphome/components/teleinfo/teleinfo.cpp | 17 +++++++++-------- .../text_sensor/teleinfo_text_sensor.cpp | 2 +- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/esphome/components/teleinfo/sensor/teleinfo_sensor.cpp b/esphome/components/teleinfo/sensor/teleinfo_sensor.cpp index 661c149c09..4e4cd9f9e6 100644 --- a/esphome/components/teleinfo/sensor/teleinfo_sensor.cpp +++ b/esphome/components/teleinfo/sensor/teleinfo_sensor.cpp @@ -9,6 +9,6 @@ void TeleInfoSensor::publish_val(const std::string &val) { auto newval = parse_float(val); publish_state(*newval); } -void TeleInfoSensor::dump_config() { LOG_SENSOR(" ", tag.c_str(), this); } +void TeleInfoSensor::dump_config() { LOG_SENSOR(" ", "Teleinfo Sensor", this); } } // namespace teleinfo } // namespace esphome diff --git a/esphome/components/teleinfo/teleinfo.cpp b/esphome/components/teleinfo/teleinfo.cpp index badd66ae83..5a1e44ac8b 100644 --- a/esphome/components/teleinfo/teleinfo.cpp +++ b/esphome/components/teleinfo/teleinfo.cpp @@ -141,21 +141,22 @@ void TeleInfo::loop() { field_len = get_field(tag_, buf_finger, grp_end, separator_, MAX_TAG_SIZE); if (!field_len || field_len >= MAX_TAG_SIZE) { ESP_LOGE(TAG, "Invalid tag."); - break; + continue; } /* Advance buf_finger to after the tag and the separator. */ buf_finger += field_len + 1; /* - * If there is two separators and the tag is not equal to "DATE", - * it means there is a timestamp to read first. + * If there is two separators and the tag is not equal to "DATE" or + * historical mode is not in use (separator_ != 0x20), it means there is a + * timestamp to read first. */ - if (std::count(buf_finger, grp_end, separator_) == 2 && strcmp(tag_, "DATE") != 0) { + if (std::count(buf_finger, grp_end, separator_) == 2 && strcmp(tag_, "DATE") != 0 && separator_ != 0x20) { field_len = get_field(timestamp_, buf_finger, grp_end, separator_, MAX_TIMESTAMP_SIZE); if (!field_len || field_len >= MAX_TIMESTAMP_SIZE) { - ESP_LOGE(TAG, "Invalid Timestamp"); - break; + ESP_LOGE(TAG, "Invalid timestamp for tag %s", timestamp_); + continue; } /* Advance buf_finger to after the first data and the separator. */ @@ -164,8 +165,8 @@ void TeleInfo::loop() { field_len = get_field(val_, buf_finger, grp_end, separator_, MAX_VAL_SIZE); if (!field_len || field_len >= MAX_VAL_SIZE) { - ESP_LOGE(TAG, "Invalid Value"); - break; + ESP_LOGE(TAG, "Invalid value for tag %s", tag_); + continue; } /* Advance buf_finger to end of group */ diff --git a/esphome/components/teleinfo/text_sensor/teleinfo_text_sensor.cpp b/esphome/components/teleinfo/text_sensor/teleinfo_text_sensor.cpp index 1adbd9ce13..87cf0dea17 100644 --- a/esphome/components/teleinfo/text_sensor/teleinfo_text_sensor.cpp +++ b/esphome/components/teleinfo/text_sensor/teleinfo_text_sensor.cpp @@ -6,6 +6,6 @@ namespace teleinfo { static const char *const TAG = "teleinfo_text_sensor"; TeleInfoTextSensor::TeleInfoTextSensor(const char *tag) { this->tag = std::string(tag); } void TeleInfoTextSensor::publish_val(const std::string &val) { publish_state(val); } -void TeleInfoTextSensor::dump_config() { LOG_TEXT_SENSOR(" ", tag.c_str(), this); } +void TeleInfoTextSensor::dump_config() { LOG_TEXT_SENSOR(" ", "Teleinfo Text Sensor", this); } } // namespace teleinfo } // namespace esphome From 91999a38ca1608a234d42d2079263a6d53064692 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Sat, 23 Oct 2021 19:25:53 +0200 Subject: [PATCH 0071/3388] Fix glue code missing micros() (#2623) --- esphome/core/config.py | 1 + 1 file changed, 1 insertion(+) diff --git a/esphome/core/config.py b/esphome/core/config.py index 235e0eeb84..262451df88 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -214,6 +214,7 @@ def include_file(path, basename): ARDUINO_GLUE_CODE = """\ #define yield() esphome::yield() #define millis() esphome::millis() +#define micros() esphome::micros() #define delay(x) esphome::delay(x) #define delayMicroseconds(x) esphome::delayMicroseconds(x) """ From c6adaaea9726fbdcbc8414144291048e556fb2ef Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 26 Oct 2021 21:55:20 +1300 Subject: [PATCH 0072/3388] Remove power and energy from sensors that are not true power (#2628) --- esphome/components/dsmr/sensor.py | 16 ++++++++-------- esphome/components/havells_solar/sensor.py | 4 +--- esphome/components/pipsolar/sensor/__init__.py | 4 ++-- esphome/components/sdm_meter/sensor.py | 4 ---- esphome/components/selec_meter/sensor.py | 8 -------- 5 files changed, 11 insertions(+), 25 deletions(-) diff --git a/esphome/components/dsmr/sensor.py b/esphome/components/dsmr/sensor.py index 761009c766..d809d0d105 100644 --- a/esphome/components/dsmr/sensor.py +++ b/esphome/components/dsmr/sensor.py @@ -75,14 +75,14 @@ CONFIG_SCHEMA = cv.Schema( UNIT_KILOVOLT_AMPS_REACTIVE_HOURS, ICON_EMPTY, 3, - DEVICE_CLASS_ENERGY, + DEVICE_CLASS_EMPTY, STATE_CLASS_NONE, ), cv.Optional("total_exported_energy"): sensor.sensor_schema( UNIT_KILOVOLT_AMPS_REACTIVE_HOURS, ICON_EMPTY, 3, - DEVICE_CLASS_ENERGY, + DEVICE_CLASS_EMPTY, STATE_CLASS_NONE, ), cv.Optional("power_delivered"): sensor.sensor_schema( @@ -166,42 +166,42 @@ CONFIG_SCHEMA = cv.Schema( UNIT_KILOVOLT_AMPS_REACTIVE, ICON_EMPTY, 3, - DEVICE_CLASS_POWER, + DEVICE_CLASS_EMPTY, STATE_CLASS_MEASUREMENT, ), cv.Optional("reactive_power_delivered_l2"): sensor.sensor_schema( UNIT_KILOVOLT_AMPS_REACTIVE, ICON_EMPTY, 3, - DEVICE_CLASS_POWER, + DEVICE_CLASS_EMPTY, STATE_CLASS_MEASUREMENT, ), cv.Optional("reactive_power_delivered_l3"): sensor.sensor_schema( UNIT_KILOVOLT_AMPS_REACTIVE, ICON_EMPTY, 3, - DEVICE_CLASS_POWER, + DEVICE_CLASS_EMPTY, STATE_CLASS_MEASUREMENT, ), cv.Optional("reactive_power_returned_l1"): sensor.sensor_schema( UNIT_KILOVOLT_AMPS_REACTIVE, ICON_EMPTY, 3, - DEVICE_CLASS_POWER, + DEVICE_CLASS_EMPTY, STATE_CLASS_MEASUREMENT, ), cv.Optional("reactive_power_returned_l2"): sensor.sensor_schema( UNIT_KILOVOLT_AMPS_REACTIVE, ICON_EMPTY, 3, - DEVICE_CLASS_POWER, + DEVICE_CLASS_EMPTY, STATE_CLASS_MEASUREMENT, ), cv.Optional("reactive_power_returned_l3"): sensor.sensor_schema( UNIT_KILOVOLT_AMPS_REACTIVE, ICON_EMPTY, 3, - DEVICE_CLASS_POWER, + DEVICE_CLASS_EMPTY, STATE_CLASS_MEASUREMENT, ), cv.Optional("voltage_l1"): sensor.sensor_schema( diff --git a/esphome/components/havells_solar/sensor.py b/esphome/components/havells_solar/sensor.py index 3ec12d5b83..d7c8d544f9 100644 --- a/esphome/components/havells_solar/sensor.py +++ b/esphome/components/havells_solar/sensor.py @@ -93,13 +93,12 @@ PV_SENSORS = { CONF_VOLTAGE_SAMPLED_BY_SECONDARY_CPU: sensor.sensor_schema( unit_of_measurement=UNIT_VOLT, accuracy_decimals=0, - device_class=DEVICE_CLASS_POWER, + device_class=DEVICE_CLASS_VOLTAGE, state_class=STATE_CLASS_MEASUREMENT, ), CONF_INSULATION_OF_P_TO_GROUND: sensor.sensor_schema( unit_of_measurement=UNIT_KOHM, accuracy_decimals=0, - device_class=DEVICE_CLASS_POWER, state_class=STATE_CLASS_MEASUREMENT, ), } @@ -135,7 +134,6 @@ CONFIG_SCHEMA = ( cv.Optional(CONF_REACTIVE_POWER): sensor.sensor_schema( unit_of_measurement=UNIT_VOLT_AMPS_REACTIVE, accuracy_decimals=2, - device_class=DEVICE_CLASS_POWER, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_ENERGY_PRODUCTION_DAY): sensor.sensor_schema( diff --git a/esphome/components/pipsolar/sensor/__init__.py b/esphome/components/pipsolar/sensor/__init__.py index 5e4dd6c40c..a206e41988 100644 --- a/esphome/components/pipsolar/sensor/__init__.py +++ b/esphome/components/pipsolar/sensor/__init__.py @@ -89,7 +89,7 @@ TYPES = { UNIT_AMPERE, ICON_EMPTY, 1, DEVICE_CLASS_CURRENT ), CONF_AC_OUTPUT_RATING_APPARENT_POWER: sensor.sensor_schema( - UNIT_VOLT_AMPS, ICON_EMPTY, 1, DEVICE_CLASS_POWER + UNIT_VOLT_AMPS, ICON_EMPTY, 1, DEVICE_CLASS_EMPTY ), CONF_AC_OUTPUT_RATING_ACTIVE_POWER: sensor.sensor_schema( UNIT_WATT, ICON_EMPTY, 1, DEVICE_CLASS_POWER @@ -159,7 +159,7 @@ TYPES = { UNIT_HERTZ, ICON_CURRENT_AC, 1, DEVICE_CLASS_EMPTY ), CONF_AC_OUTPUT_APPARENT_POWER: sensor.sensor_schema( - UNIT_VOLT_AMPS, ICON_EMPTY, 1, DEVICE_CLASS_POWER + UNIT_VOLT_AMPS, ICON_EMPTY, 1, DEVICE_CLASS_EMPTY ), CONF_AC_OUTPUT_ACTIVE_POWER: sensor.sensor_schema( UNIT_WATT, ICON_EMPTY, 1, DEVICE_CLASS_POWER diff --git a/esphome/components/sdm_meter/sensor.py b/esphome/components/sdm_meter/sensor.py index 8a0d9674a7..87c99c9152 100644 --- a/esphome/components/sdm_meter/sensor.py +++ b/esphome/components/sdm_meter/sensor.py @@ -64,13 +64,11 @@ PHASE_SENSORS = { CONF_APPARENT_POWER: sensor.sensor_schema( unit_of_measurement=UNIT_VOLT_AMPS, accuracy_decimals=2, - device_class=DEVICE_CLASS_POWER, state_class=STATE_CLASS_MEASUREMENT, ), CONF_REACTIVE_POWER: sensor.sensor_schema( unit_of_measurement=UNIT_VOLT_AMPS_REACTIVE, accuracy_decimals=2, - device_class=DEVICE_CLASS_POWER, state_class=STATE_CLASS_MEASUREMENT, ), CONF_POWER_FACTOR: sensor.sensor_schema( @@ -115,13 +113,11 @@ CONFIG_SCHEMA = ( cv.Optional(CONF_IMPORT_REACTIVE_ENERGY): sensor.sensor_schema( unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE_HOURS, accuracy_decimals=2, - device_class=DEVICE_CLASS_ENERGY, state_class=STATE_CLASS_TOTAL_INCREASING, ), cv.Optional(CONF_EXPORT_REACTIVE_ENERGY): sensor.sensor_schema( unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE_HOURS, accuracy_decimals=2, - device_class=DEVICE_CLASS_ENERGY, state_class=STATE_CLASS_TOTAL_INCREASING, ), } diff --git a/esphome/components/selec_meter/sensor.py b/esphome/components/selec_meter/sensor.py index 168d3a3db2..e698255c25 100644 --- a/esphome/components/selec_meter/sensor.py +++ b/esphome/components/selec_meter/sensor.py @@ -71,25 +71,21 @@ SENSORS = { CONF_TOTAL_REACTIVE_ENERGY: sensor.sensor_schema( unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE_HOURS, accuracy_decimals=2, - device_class=DEVICE_CLASS_ENERGY, state_class=STATE_CLASS_TOTAL_INCREASING, ), CONF_IMPORT_REACTIVE_ENERGY: sensor.sensor_schema( unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE_HOURS, accuracy_decimals=2, - device_class=DEVICE_CLASS_ENERGY, state_class=STATE_CLASS_TOTAL_INCREASING, ), CONF_EXPORT_REACTIVE_ENERGY: sensor.sensor_schema( unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE_HOURS, accuracy_decimals=2, - device_class=DEVICE_CLASS_ENERGY, state_class=STATE_CLASS_TOTAL_INCREASING, ), CONF_APPARENT_ENERGY: sensor.sensor_schema( unit_of_measurement=UNIT_KILOVOLT_AMPS_HOURS, accuracy_decimals=2, - device_class=DEVICE_CLASS_ENERGY, state_class=STATE_CLASS_TOTAL_INCREASING, ), CONF_ACTIVE_POWER: sensor.sensor_schema( @@ -101,13 +97,11 @@ SENSORS = { CONF_REACTIVE_POWER: sensor.sensor_schema( unit_of_measurement=UNIT_VOLT_AMPS_REACTIVE, accuracy_decimals=3, - device_class=DEVICE_CLASS_POWER, state_class=STATE_CLASS_MEASUREMENT, ), CONF_APPARENT_POWER: sensor.sensor_schema( unit_of_measurement=UNIT_VOLT_AMPS, accuracy_decimals=3, - device_class=DEVICE_CLASS_POWER, state_class=STATE_CLASS_MEASUREMENT, ), CONF_VOLTAGE: sensor.sensor_schema( @@ -142,13 +136,11 @@ SENSORS = { CONF_MAXIMUM_DEMAND_REACTIVE_POWER: sensor.sensor_schema( unit_of_measurement=UNIT_VOLT_AMPS_REACTIVE, accuracy_decimals=3, - device_class=DEVICE_CLASS_POWER, state_class=STATE_CLASS_MEASUREMENT, ), CONF_MAXIMUM_DEMAND_APPARENT_POWER: sensor.sensor_schema( unit_of_measurement=UNIT_VOLT_AMPS, accuracy_decimals=3, - device_class=DEVICE_CLASS_POWER, state_class=STATE_CLASS_MEASUREMENT, ), } From 72108684ea20bc6b8527726a27c5c298919f3e24 Mon Sep 17 00:00:00 2001 From: Martin <25747549+martgras@users.noreply.github.com> Date: Tue, 26 Oct 2021 11:30:25 +0200 Subject: [PATCH 0073/3388] fix modbus output (#2630) --- .../components/modbus_controller/__init__.py | 15 +++++++++++++++ .../modbus_controller/number/__init__.py | 17 +---------------- .../modbus_controller/output/__init__.py | 13 ++++++++++++- .../modbus_controller/output/modbus_output.h | 3 ++- .../modbus_controller/sensor/__init__.py | 18 +----------------- 5 files changed, 31 insertions(+), 35 deletions(-) diff --git a/esphome/components/modbus_controller/__init__.py b/esphome/components/modbus_controller/__init__.py index 6b452ea25c..8499cec561 100644 --- a/esphome/components/modbus_controller/__init__.py +++ b/esphome/components/modbus_controller/__init__.py @@ -61,6 +61,21 @@ SENSOR_VALUE_TYPE = { "FP32_R": SensorValueType.FP32_R, } +TYPE_REGISTER_MAP = { + "RAW": 1, + "U_WORD": 1, + "S_WORD": 1, + "U_DWORD": 2, + "U_DWORD_R": 2, + "S_DWORD": 2, + "S_DWORD_R": 2, + "U_QWORD": 4, + "U_QWORDU_R": 4, + "S_QWORD": 4, + "U_QWORD_R": 4, + "FP32": 2, + "FP32_R": 2, +} MULTI_CONF = True diff --git a/esphome/components/modbus_controller/number/__init__.py b/esphome/components/modbus_controller/number/__init__.py index c7919bb972..afb69f8798 100644 --- a/esphome/components/modbus_controller/number/__init__.py +++ b/esphome/components/modbus_controller/number/__init__.py @@ -17,6 +17,7 @@ from .. import ( ModbusController, SENSOR_VALUE_TYPE, SensorItem, + TYPE_REGISTER_MAP, ) @@ -39,22 +40,6 @@ ModbusNumber = modbus_controller_ns.class_( "ModbusNumber", cg.Component, number.Number, SensorItem ) -TYPE_REGISTER_MAP = { - "RAW": 1, - "U_WORD": 1, - "S_WORD": 1, - "U_DWORD": 2, - "U_DWORD_R": 2, - "S_DWORD": 2, - "S_DWORD_R": 2, - "U_QWORD": 4, - "U_QWORDU_R": 4, - "S_QWORD": 4, - "U_QWORD_R": 4, - "FP32": 2, - "FP32_R": 2, -} - def validate_min_max(config): if config[CONF_MAX_VALUE] <= config[CONF_MIN_VALUE]: diff --git a/esphome/components/modbus_controller/output/__init__.py b/esphome/components/modbus_controller/output/__init__.py index 9c41fc011c..4aca4db64f 100644 --- a/esphome/components/modbus_controller/output/__init__.py +++ b/esphome/components/modbus_controller/output/__init__.py @@ -13,11 +13,13 @@ from .. import ( SensorItem, modbus_controller_ns, ModbusController, + TYPE_REGISTER_MAP, ) from ..const import ( CONF_BYTE_OFFSET, CONF_MODBUS_CONTROLLER_ID, + CONF_REGISTER_COUNT, CONF_VALUE_TYPE, CONF_WRITE_LAMBDA, ) @@ -40,6 +42,7 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_OFFSET, default=0): cv.positive_int, cv.Optional(CONF_BYTE_OFFSET): cv.positive_int, cv.Optional(CONF_VALUE_TYPE, default="U_WORD"): cv.enum(SENSOR_VALUE_TYPE), + cv.Optional(CONF_REGISTER_COUNT, default=0): cv.positive_int, cv.Optional(CONF_WRITE_LAMBDA): cv.returning_lambda, cv.Optional(CONF_MULTIPLY, default=1.0): cv.float_, } @@ -54,8 +57,16 @@ async def to_code(config): # A CONF_BYTE_OFFSET setting overrides CONF_OFFSET if CONF_BYTE_OFFSET in config: byte_offset = config[CONF_BYTE_OFFSET] + value_type = config[CONF_VALUE_TYPE] + reg_count = config[CONF_REGISTER_COUNT] + if reg_count == 0: + reg_count = TYPE_REGISTER_MAP[value_type] var = cg.new_Pvariable( - config[CONF_ID], config[CONF_ADDRESS], byte_offset, config[CONF_VALUE_TYPE] + config[CONF_ID], + config[CONF_ADDRESS], + byte_offset, + value_type, + reg_count, ) await output.register_output(var, config) cg.add(var.set_write_multiply(config[CONF_MULTIPLY])) diff --git a/esphome/components/modbus_controller/output/modbus_output.h b/esphome/components/modbus_controller/output/modbus_output.h index 053186a321..6e8521854b 100644 --- a/esphome/components/modbus_controller/output/modbus_output.h +++ b/esphome/components/modbus_controller/output/modbus_output.h @@ -11,12 +11,13 @@ using value_to_data_t = std::function(float); class ModbusOutput : public output::FloatOutput, public Component, public SensorItem { public: - ModbusOutput(uint16_t start_address, uint8_t offset, SensorValueType value_type) + ModbusOutput(uint16_t start_address, uint8_t offset, SensorValueType value_type, int register_count) : output::FloatOutput(), Component() { this->register_type = ModbusRegisterType::HOLDING; this->start_address = start_address; this->offset = offset; this->bitmask = bitmask; + this->register_count = register_count; this->sensor_value_type = value_type; this->skip_updates = 0; this->start_address += offset; diff --git a/esphome/components/modbus_controller/sensor/__init__.py b/esphome/components/modbus_controller/sensor/__init__.py index 687f3d82fb..82acfe120b 100644 --- a/esphome/components/modbus_controller/sensor/__init__.py +++ b/esphome/components/modbus_controller/sensor/__init__.py @@ -9,6 +9,7 @@ from .. import ( ModbusController, MODBUS_REGISTER_TYPE, SENSOR_VALUE_TYPE, + TYPE_REGISTER_MAP, ) from ..const import ( CONF_BITMASK, @@ -29,23 +30,6 @@ ModbusSensor = modbus_controller_ns.class_( "ModbusSensor", cg.Component, sensor.Sensor, SensorItem ) -TYPE_REGISTER_MAP = { - "RAW": 1, - "U_WORD": 1, - "S_WORD": 1, - "U_DWORD": 2, - "U_DWORD_R": 2, - "S_DWORD": 2, - "S_DWORD_R": 2, - "U_QWORD": 4, - "U_QWORDU_R": 4, - "S_QWORD": 4, - "U_QWORD_R": 4, - "FP32": 2, - "FP32_R": 2, -} - - CONFIG_SCHEMA = cv.All( sensor.SENSOR_SCHEMA.extend( { From f1377b560e696330e1a6faa902c38ddf81032be1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C4=8Cerm=C3=A1k?= Date: Tue, 26 Oct 2021 18:10:45 +0200 Subject: [PATCH 0074/3388] Fix pin number validation for sn74hc595 (#2621) --- esphome/components/sn74hc595/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/sn74hc595/__init__.py b/esphome/components/sn74hc595/__init__.py index 0d1ff6ecba..630abc8bca 100644 --- a/esphome/components/sn74hc595/__init__.py +++ b/esphome/components/sn74hc595/__init__.py @@ -60,7 +60,7 @@ SN74HC595_PIN_SCHEMA = cv.All( { cv.GenerateID(): cv.declare_id(SN74HC595GPIOPin), cv.Required(CONF_SN74HC595): cv.use_id(SN74HC595Component), - cv.Required(CONF_NUMBER): cv.int_range(min=0, max=7), + cv.Required(CONF_NUMBER): cv.int_range(min=0, max=31), cv.Optional(CONF_MODE, default={}): cv.All( { cv.Optional(CONF_OUTPUT, default=True): cv.All( From 23560e608c5848b6029c76847f94144cb8fc9b04 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 27 Oct 2021 08:27:51 +1300 Subject: [PATCH 0075/3388] Fix select.set using lambda (#2633) --- esphome/components/select/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/select/__init__.py b/esphome/components/select/__init__.py index c156a63a86..7e4047d3c8 100644 --- a/esphome/components/select/__init__.py +++ b/esphome/components/select/__init__.py @@ -90,6 +90,6 @@ async def to_code(config): async def select_set_to_code(config, action_id, template_arg, args): paren = await cg.get_variable(config[CONF_ID]) var = cg.new_Pvariable(action_id, template_arg, paren) - template_ = await cg.templatable(config[CONF_OPTION], args, str) + template_ = await cg.templatable(config[CONF_OPTION], args, cg.std_string) cg.add(var.set_option(template_)) return var From bd782fc82809a7f77734db05b146caab3d568d93 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 27 Oct 2021 10:49:11 +1300 Subject: [PATCH 0076/3388] Bump version to 2021.10.3 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 37c325465b..d90ff04e7f 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2021.10.2" +__version__ = "2021.10.3" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" From b226215593449487dba12f2d27b0f02efa650c36 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 11 Nov 2021 10:10:05 +1300 Subject: [PATCH 0077/3388] Bump version to 2021.11.0b1 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index f0df8c562d..26fd514173 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2021.10.0b11" +__version__ = "2021.11.0b1" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" From 8cbb3798986013d4af3b1fcf1cf499243444a50f Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 11 Nov 2021 10:35:18 +1300 Subject: [PATCH 0078/3388] Remove import (not sure how it got there) --- esphome/components/bme680_bsec/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/esphome/components/bme680_bsec/__init__.py b/esphome/components/bme680_bsec/__init__.py index b27d477ea0..83e519f8aa 100644 --- a/esphome/components/bme680_bsec/__init__.py +++ b/esphome/components/bme680_bsec/__init__.py @@ -2,7 +2,6 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import i2c from esphome.const import CONF_ID -from esphome.core import CORE CODEOWNERS = ["@trvrnrth"] DEPENDENCIES = ["i2c"] From 4b7fe202ec2fc7501268da01c65e75544a9fb285 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 11 Nov 2021 11:24:48 +1300 Subject: [PATCH 0079/3388] Fix template number initial value being NaN (#2692) --- esphome/components/template/number/__init__.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/esphome/components/template/number/__init__.py b/esphome/components/template/number/__init__.py index 887f6b15ad..3dec7066d3 100644 --- a/esphome/components/template/number/__init__.py +++ b/esphome/components/template/number/__init__.py @@ -35,6 +35,9 @@ def validate(config): raise cv.Invalid("initial_value cannot be used with lambda") if CONF_RESTORE_VALUE in config: raise cv.Invalid("restore_value cannot be used with lambda") + elif CONF_INITIAL_VALUE not in config: + config[CONF_INITIAL_VALUE] = config[CONF_MIN_VALUE] + if not config[CONF_OPTIMISTIC] and CONF_SET_ACTION not in config: raise cv.Invalid( "Either optimistic mode must be enabled, or set_action must be set, to handle the number being set." @@ -80,8 +83,7 @@ async def to_code(config): else: cg.add(var.set_optimistic(config[CONF_OPTIMISTIC])) - if CONF_INITIAL_VALUE in config: - cg.add(var.set_initial_value(config[CONF_INITIAL_VALUE])) + cg.add(var.set_initial_value(config[CONF_INITIAL_VALUE])) if CONF_RESTORE_VALUE in config: cg.add(var.set_restore_value(config[CONF_RESTORE_VALUE])) From 21c896d8f83be31dc1a6ff8067f59a04d0326a54 Mon Sep 17 00:00:00 2001 From: Carlos Garcia Saura Date: Wed, 10 Nov 2021 23:28:45 +0100 Subject: [PATCH 0080/3388] [remote_transmitter] accurate pulse timing for ESP8266 (#2476) --- .../remote_transmitter/remote_transmitter.h | 3 + .../remote_transmitter_esp8266.cpp | 78 ++++++++++--------- 2 files changed, 46 insertions(+), 35 deletions(-) diff --git a/esphome/components/remote_transmitter/remote_transmitter.h b/esphome/components/remote_transmitter/remote_transmitter.h index 733ac5e50d..a4235e875f 100644 --- a/esphome/components/remote_transmitter/remote_transmitter.h +++ b/esphome/components/remote_transmitter/remote_transmitter.h @@ -32,6 +32,9 @@ class RemoteTransmitterComponent : public remote_base::RemoteTransmitterBase, void mark_(uint32_t on_time, uint32_t off_time, uint32_t usec); void space_(uint32_t usec); + + void await_target_time_(); + uint32_t target_time_; #endif #ifdef USE_ESP32 diff --git a/esphome/components/remote_transmitter/remote_transmitter_esp8266.cpp b/esphome/components/remote_transmitter/remote_transmitter_esp8266.cpp index 74e62d4e3b..39752cac5b 100644 --- a/esphome/components/remote_transmitter/remote_transmitter_esp8266.cpp +++ b/esphome/components/remote_transmitter/remote_transmitter_esp8266.cpp @@ -33,56 +33,64 @@ void RemoteTransmitterComponent::calculate_on_off_time_(uint32_t carrier_frequen *off_time_period = period - *on_time_period; } +void RemoteTransmitterComponent::await_target_time_() { + const uint32_t current_time = micros(); + if (this->target_time_ == 0) + this->target_time_ = current_time; + else if (this->target_time_ > current_time) + delayMicroseconds(this->target_time_ - current_time); +} + void RemoteTransmitterComponent::mark_(uint32_t on_time, uint32_t off_time, uint32_t usec) { - if (this->carrier_duty_percent_ == 100 || (on_time == 0 && off_time == 0)) { - this->pin_->digital_write(true); - delayMicroseconds(usec); - this->pin_->digital_write(false); - return; - } - - const uint32_t start_time = micros(); - uint32_t current_time = start_time; - - while (current_time - start_time < usec) { - const uint32_t elapsed = current_time - start_time; - this->pin_->digital_write(true); - - delayMicroseconds(std::min(on_time, usec - elapsed)); - this->pin_->digital_write(false); - if (elapsed + on_time >= usec) - return; - - delayMicroseconds(std::min(usec - elapsed - on_time, off_time)); - - current_time = micros(); + this->await_target_time_(); + this->pin_->digital_write(true); + + const uint32_t target = this->target_time_ + usec; + if (this->carrier_duty_percent_ < 100 && (on_time > 0 || off_time > 0)) { + while (true) { // Modulate with carrier frequency + this->target_time_ += on_time; + if (this->target_time_ >= target) + break; + this->await_target_time_(); + this->pin_->digital_write(false); + + this->target_time_ += off_time; + if (this->target_time_ >= target) + break; + this->await_target_time_(); + this->pin_->digital_write(true); + } } + this->target_time_ = target; } + void RemoteTransmitterComponent::space_(uint32_t usec) { + this->await_target_time_(); this->pin_->digital_write(false); - delayMicroseconds(usec); + this->target_time_ += usec; } + void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t send_wait) { ESP_LOGD(TAG, "Sending remote code..."); uint32_t on_time, off_time; this->calculate_on_off_time_(this->temp_.get_carrier_frequency(), &on_time, &off_time); + this->target_time_ = 0; for (uint32_t i = 0; i < send_times; i++) { - { - InterruptLock lock; - for (int32_t item : this->temp_.get_data()) { - if (item > 0) { - const auto length = uint32_t(item); - this->mark_(on_time, off_time, length); - } else { - const auto length = uint32_t(-item); - this->space_(length); - } - App.feed_wdt(); + for (int32_t item : this->temp_.get_data()) { + if (item > 0) { + const auto length = uint32_t(item); + this->mark_(on_time, off_time, length); + } else { + const auto length = uint32_t(-item); + this->space_(length); } + App.feed_wdt(); } + this->await_target_time_(); // wait for duration of last pulse + this->pin_->digital_write(false); if (i + 1 < send_times) - delayMicroseconds(send_wait); + this->target_time_ += send_wait; } } From 755289331146f9cd53dc002a69e693513cd4c323 Mon Sep 17 00:00:00 2001 From: Maurice Makaay Date: Wed, 10 Nov 2021 23:34:17 +0100 Subject: [PATCH 0081/3388] Uart debugging support (#2478) Co-authored-by: Maurice Makaay Co-authored-by: Maurice Makaay Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/uart/__init__.py | 69 +++++++ esphome/components/uart/uart.h | 6 +- esphome/components/uart/uart_component.h | 21 ++ .../uart/uart_component_esp32_arduino.cpp | 12 +- .../uart/uart_component_esp8266.cpp | 9 +- .../uart/uart_component_esp_idf.cpp | 12 +- esphome/components/uart/uart_debugger.cpp | 193 ++++++++++++++++++ esphome/components/uart/uart_debugger.h | 101 +++++++++ esphome/const.py | 7 + esphome/core/defines.h | 1 + tests/test1.yaml | 12 ++ 11 files changed, 430 insertions(+), 13 deletions(-) create mode 100644 esphome/components/uart/uart_debugger.cpp create mode 100644 esphome/components/uart/uart_debugger.h diff --git a/esphome/components/uart/__init__.py b/esphome/components/uart/__init__.py index 35af3eedf7..53209dfc7b 100644 --- a/esphome/components/uart/__init__.py +++ b/esphome/components/uart/__init__.py @@ -14,6 +14,16 @@ from esphome.const import ( CONF_DATA, CONF_RX_BUFFER_SIZE, CONF_INVERT, + CONF_TRIGGER_ID, + CONF_SEQUENCE, + CONF_TIMEOUT, + CONF_DEBUG, + CONF_DIRECTION, + CONF_AFTER, + CONF_BYTES, + CONF_DELIMITER, + CONF_DUMMY_RECEIVER, + CONF_DUMMY_RECEIVER_ID, ) from esphome.core import CORE @@ -31,6 +41,8 @@ ESP8266UartComponent = uart_ns.class_( UARTDevice = uart_ns.class_("UARTDevice") UARTWriteAction = uart_ns.class_("UARTWriteAction", automation.Action) +UARTDebugger = uart_ns.class_("UARTDebugger", cg.Component, automation.Action) +UARTDummyReceiver = uart_ns.class_("UARTDummyReceiver", cg.Component) MULTI_CONF = True @@ -75,6 +87,34 @@ CONF_STOP_BITS = "stop_bits" CONF_DATA_BITS = "data_bits" CONF_PARITY = "parity" +UARTDirection = uart_ns.enum("UARTDirection") +UART_DIRECTIONS = { + "RX": UARTDirection.UART_DIRECTION_RX, + "TX": UARTDirection.UART_DIRECTION_TX, + "BOTH": UARTDirection.UART_DIRECTION_BOTH, +} + +DEBUG_SCHEMA = cv.Schema( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(UARTDebugger), + cv.Optional(CONF_DIRECTION, default="BOTH"): cv.enum( + UART_DIRECTIONS, upper=True + ), + cv.Optional(CONF_AFTER): cv.Schema( + { + cv.Optional(CONF_BYTES, default=256): cv.validate_bytes, + cv.Optional( + CONF_TIMEOUT, default="100ms" + ): cv.positive_time_period_milliseconds, + cv.Optional(CONF_DELIMITER): cv.templatable(validate_raw_data), + } + ), + cv.Required(CONF_SEQUENCE): automation.validate_automation(), + cv.Optional(CONF_DUMMY_RECEIVER, default=False): cv.boolean, + cv.GenerateID(CONF_DUMMY_RECEIVER_ID): cv.declare_id(UARTDummyReceiver), + } +) + CONFIG_SCHEMA = cv.All( cv.Schema( { @@ -91,12 +131,38 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_INVERT): cv.invalid( "This option has been removed. Please instead use invert in the tx/rx pin schemas." ), + cv.Optional(CONF_DEBUG): DEBUG_SCHEMA, } ).extend(cv.COMPONENT_SCHEMA), cv.has_at_least_one_key(CONF_TX_PIN, CONF_RX_PIN), ) +async def debug_to_code(config, parent): + trigger = cg.new_Pvariable(config[CONF_TRIGGER_ID], parent) + await cg.register_component(trigger, config) + for action in config[CONF_SEQUENCE]: + await automation.build_automation( + trigger, + [(UARTDirection, "direction"), (cg.std_vector.template(cg.uint8), "bytes")], + action, + ) + cg.add(trigger.set_direction(config[CONF_DIRECTION])) + after = config[CONF_AFTER] + cg.add(trigger.set_after_bytes(after[CONF_BYTES])) + cg.add(trigger.set_after_timeout(after[CONF_TIMEOUT])) + if CONF_DELIMITER in after: + data = after[CONF_DELIMITER] + if isinstance(data, bytes): + data = list(data) + for byte in after[CONF_DELIMITER]: + cg.add(trigger.add_delimiter_byte(byte)) + if config[CONF_DUMMY_RECEIVER]: + dummy = cg.new_Pvariable(config[CONF_DUMMY_RECEIVER_ID], parent) + await cg.register_component(dummy, {}) + cg.add_define("USE_UART_DEBUGGER") + + async def to_code(config): cg.add_global(uart_ns.using) var = cg.new_Pvariable(config[CONF_ID]) @@ -115,6 +181,9 @@ async def to_code(config): cg.add(var.set_data_bits(config[CONF_DATA_BITS])) cg.add(var.set_parity(config[CONF_PARITY])) + if CONF_DEBUG in config: + await debug_to_code(config[CONF_DEBUG], var) + # A schema to use for all UART devices, all UART integrations must extend this! UART_DEVICE_SCHEMA = cv.Schema( diff --git a/esphome/components/uart/uart.h b/esphome/components/uart/uart.h index c368f9ed6b..d41dbe26e6 100644 --- a/esphome/components/uart/uart.h +++ b/esphome/components/uart/uart.h @@ -45,17 +45,17 @@ class UARTDevice { // Compat APIs int read() { uint8_t data; - if (!read_byte(&data)) + if (!this->read_byte(&data)) return -1; return data; } size_t write(uint8_t data) { - write_byte(data); + this->write_byte(data); return 1; } int peek() { uint8_t data; - if (!peek_byte(&data)) + if (!this->peek_byte(&data)) return -1; return data; } diff --git a/esphome/components/uart/uart_component.h b/esphome/components/uart/uart_component.h index de85cd2ca3..73694d3db7 100644 --- a/esphome/components/uart/uart_component.h +++ b/esphome/components/uart/uart_component.h @@ -2,9 +2,13 @@ #include #include +#include "esphome/core/defines.h" #include "esphome/core/component.h" #include "esphome/core/hal.h" #include "esphome/core/log.h" +#ifdef USE_UART_DEBUGGER +#include "esphome/core/automation.h" +#endif namespace esphome { namespace uart { @@ -15,6 +19,14 @@ enum UARTParityOptions { UART_CONFIG_PARITY_ODD, }; +#ifdef USE_UART_DEBUGGER +enum UARTDirection { + UART_DIRECTION_RX, + UART_DIRECTION_TX, + UART_DIRECTION_BOTH, +}; +#endif + const LogString *parity_to_str(UARTParityOptions parity); class UARTComponent { @@ -50,6 +62,12 @@ class UARTComponent { void set_baud_rate(uint32_t baud_rate) { baud_rate_ = baud_rate; } uint32_t get_baud_rate() const { return baud_rate_; } +#ifdef USE_UART_DEBUGGER + void add_debug_callback(std::function &&callback) { + this->debug_callback_.add(std::move(callback)); + } +#endif + protected: virtual void check_logger_conflict() = 0; bool check_read_timeout_(size_t len = 1); @@ -61,6 +79,9 @@ class UARTComponent { uint8_t stop_bits_; uint8_t data_bits_; UARTParityOptions parity_; +#ifdef USE_UART_DEBUGGER + CallbackManager debug_callback_{}; +#endif }; } // namespace uart diff --git a/esphome/components/uart/uart_component_esp32_arduino.cpp b/esphome/components/uart/uart_component_esp32_arduino.cpp index 1b1ce382f2..95cdde4a43 100644 --- a/esphome/components/uart/uart_component_esp32_arduino.cpp +++ b/esphome/components/uart/uart_component_esp32_arduino.cpp @@ -117,26 +117,32 @@ void ESP32ArduinoUARTComponent::dump_config() { void ESP32ArduinoUARTComponent::write_array(const uint8_t *data, size_t len) { this->hw_serial_->write(data, len); +#ifdef USE_UART_DEBUGGER for (size_t i = 0; i < len; i++) { - ESP_LOGVV(TAG, " Wrote 0b" BYTE_TO_BINARY_PATTERN " (0x%02X)", BYTE_TO_BINARY(data[i]), data[i]); + this->debug_callback_.call(UART_DIRECTION_TX, data[i]); } +#endif } + bool ESP32ArduinoUARTComponent::peek_byte(uint8_t *data) { if (!this->check_read_timeout_()) return false; *data = this->hw_serial_->peek(); return true; } + bool ESP32ArduinoUARTComponent::read_array(uint8_t *data, size_t len) { if (!this->check_read_timeout_(len)) return false; this->hw_serial_->readBytes(data, len); +#ifdef USE_UART_DEBUGGER for (size_t i = 0; i < len; i++) { - ESP_LOGVV(TAG, " Read 0b" BYTE_TO_BINARY_PATTERN " (0x%02X)", BYTE_TO_BINARY(data[i]), data[i]); + this->debug_callback_.call(UART_DIRECTION_RX, data[i]); } - +#endif return true; } + int ESP32ArduinoUARTComponent::available() { return this->hw_serial_->available(); } void ESP32ArduinoUARTComponent::flush() { ESP_LOGVV(TAG, " Flushing..."); diff --git a/esphome/components/uart/uart_component_esp8266.cpp b/esphome/components/uart/uart_component_esp8266.cpp index 973306cde2..c367de05bb 100644 --- a/esphome/components/uart/uart_component_esp8266.cpp +++ b/esphome/components/uart/uart_component_esp8266.cpp @@ -130,9 +130,11 @@ void ESP8266UartComponent::write_array(const uint8_t *data, size_t len) { for (size_t i = 0; i < len; i++) this->sw_serial_->write_byte(data[i]); } +#ifdef USE_UART_DEBUGGER for (size_t i = 0; i < len; i++) { - ESP_LOGVV(TAG, " Wrote 0b" BYTE_TO_BINARY_PATTERN " (0x%02X)", BYTE_TO_BINARY(data[i]), data[i]); + this->debug_callback_.call(UART_DIRECTION_TX, data[i]); } +#endif } bool ESP8266UartComponent::peek_byte(uint8_t *data) { if (!this->check_read_timeout_()) @@ -153,10 +155,11 @@ bool ESP8266UartComponent::read_array(uint8_t *data, size_t len) { for (size_t i = 0; i < len; i++) data[i] = this->sw_serial_->read_byte(); } +#ifdef USE_UART_DEBUGGER for (size_t i = 0; i < len; i++) { - ESP_LOGVV(TAG, " Read 0b" BYTE_TO_BINARY_PATTERN " (0x%02X)", BYTE_TO_BINARY(data[i]), data[i]); + this->debug_callback_.call(UART_DIRECTION_RX, data[i]); } - +#endif return true; } int ESP8266UartComponent::available() { diff --git a/esphome/components/uart/uart_component_esp_idf.cpp b/esphome/components/uart/uart_component_esp_idf.cpp index 1cccd5821e..4d6a6af0fc 100644 --- a/esphome/components/uart/uart_component_esp_idf.cpp +++ b/esphome/components/uart/uart_component_esp_idf.cpp @@ -130,10 +130,13 @@ void IDFUARTComponent::write_array(const uint8_t *data, size_t len) { xSemaphoreTake(this->lock_, portMAX_DELAY); uart_write_bytes(this->uart_num_, data, len); xSemaphoreGive(this->lock_); +#ifdef USE_UART_DEBUGGER for (size_t i = 0; i < len; i++) { - ESP_LOGVV(TAG, " Wrote 0b" BYTE_TO_BINARY_PATTERN " (0x%02X)", BYTE_TO_BINARY(data[i]), data[i]); + this->debug_callback_.call(UART_DIRECTION_TX, data[i]); } +#endif } + bool IDFUARTComponent::peek_byte(uint8_t *data) { if (!this->check_read_timeout_()) return false; @@ -152,6 +155,7 @@ bool IDFUARTComponent::peek_byte(uint8_t *data) { xSemaphoreGive(this->lock_); return true; } + bool IDFUARTComponent::read_array(uint8_t *data, size_t len) { size_t length_to_read = len; if (!this->check_read_timeout_(len)) @@ -165,12 +169,12 @@ bool IDFUARTComponent::read_array(uint8_t *data, size_t len) { } if (length_to_read > 0) uart_read_bytes(this->uart_num_, data, length_to_read, 20 / portTICK_RATE_MS); - xSemaphoreGive(this->lock_); +#ifdef USE_UART_DEBUGGER for (size_t i = 0; i < len; i++) { - ESP_LOGVV(TAG, " Read 0b" BYTE_TO_BINARY_PATTERN " (0x%02X)", BYTE_TO_BINARY(data[i]), data[i]); + this->debug_callback_.call(UART_DIRECTION_RX, data[i]); } - +#endif return true; } diff --git a/esphome/components/uart/uart_debugger.cpp b/esphome/components/uart/uart_debugger.cpp new file mode 100644 index 0000000000..9a535656a2 --- /dev/null +++ b/esphome/components/uart/uart_debugger.cpp @@ -0,0 +1,193 @@ +#include "esphome/core/defines.h" +#ifdef USE_UART_DEBUGGER + +#include +#include "uart_debugger.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace uart { + +static const char *const TAG = "uart_debug"; + +UARTDebugger::UARTDebugger(UARTComponent *parent) { + parent->add_debug_callback([this](UARTDirection direction, uint8_t byte) { + if (!this->is_my_direction_(direction) || this->is_recursive_()) { + return; + } + this->trigger_after_direction_change_(direction); + this->store_byte_(direction, byte); + this->trigger_after_delimiter_(byte); + this->trigger_after_bytes_(); + }); +} + +void UARTDebugger::loop() { this->trigger_after_timeout_(); } + +bool UARTDebugger::is_my_direction_(UARTDirection direction) { + return this->for_direction_ == UART_DIRECTION_BOTH || this->for_direction_ == direction; +} + +bool UARTDebugger::is_recursive_() { return this->is_triggering_; } + +void UARTDebugger::trigger_after_direction_change_(UARTDirection direction) { + if (this->has_buffered_bytes_() && this->for_direction_ == UART_DIRECTION_BOTH && + this->last_direction_ != direction) { + this->fire_trigger_(); + } +} + +void UARTDebugger::store_byte_(UARTDirection direction, uint8_t byte) { + this->bytes_.push_back(byte); + this->last_direction_ = direction; + this->last_time_ = millis(); +} + +void UARTDebugger::trigger_after_delimiter_(uint8_t byte) { + if (this->after_delimiter_.empty() || !this->has_buffered_bytes_()) { + return; + } + if (this->after_delimiter_[this->after_delimiter_pos_] != byte) { + this->after_delimiter_pos_ = 0; + return; + } + this->after_delimiter_pos_++; + if (this->after_delimiter_pos_ == this->after_delimiter_.size()) { + this->fire_trigger_(); + this->after_delimiter_pos_ = 0; + } +} + +void UARTDebugger::trigger_after_bytes_() { + if (this->has_buffered_bytes_() && this->after_bytes_ > 0 && this->bytes_.size() >= this->after_bytes_) { + this->fire_trigger_(); + } +} + +void UARTDebugger::trigger_after_timeout_() { + if (this->has_buffered_bytes_() && this->after_timeout_ > 0 && millis() - this->last_time_ >= this->after_timeout_) { + this->fire_trigger_(); + } +} + +bool UARTDebugger::has_buffered_bytes_() { return !this->bytes_.empty(); } + +void UARTDebugger::fire_trigger_() { + this->is_triggering_ = true; + trigger(this->last_direction_, this->bytes_); + this->bytes_.clear(); + this->is_triggering_ = false; +} + +void UARTDummyReceiver::loop() { + // Reading up to a limited number of bytes, to make sure that this loop() + // won't lock up the system on a continuous incoming stream of bytes. + uint8_t data; + int count = 50; + while (this->available() && count--) { + this->read_byte(&data); + } +} + +void UARTDebug::log_hex(UARTDirection direction, std::vector bytes, uint8_t separator) { + std::string res; + if (direction == UART_DIRECTION_RX) { + res += "<<< "; + } else { + res += ">>> "; + } + size_t len = bytes.size(); + char buf[5]; + for (size_t i = 0; i < len; i++) { + if (i > 0) { + res += separator; + } + sprintf(buf, "%02X", bytes[i]); + res += buf; + } + ESP_LOGD(TAG, "%s", res.c_str()); +} + +void UARTDebug::log_string(UARTDirection direction, std::vector bytes) { + std::string res; + if (direction == UART_DIRECTION_RX) { + res += "<<< \""; + } else { + res += ">>> \""; + } + size_t len = bytes.size(); + char buf[5]; + for (size_t i = 0; i < len; i++) { + if (bytes[i] == 7) { + res += "\\a"; + } else if (bytes[i] == 8) { + res += "\\b"; + } else if (bytes[i] == 9) { + res += "\\t"; + } else if (bytes[i] == 10) { + res += "\\n"; + } else if (bytes[i] == 11) { + res += "\\v"; + } else if (bytes[i] == 12) { + res += "\\f"; + } else if (bytes[i] == 13) { + res += "\\r"; + } else if (bytes[i] == 27) { + res += "\\e"; + } else if (bytes[i] == 34) { + res += "\\\""; + } else if (bytes[i] == 39) { + res += "\\'"; + } else if (bytes[i] == 92) { + res += "\\\\"; + } else if (bytes[i] < 32 || bytes[i] > 127) { + sprintf(buf, "\\x%02X", bytes[i]); + res += buf; + } else { + res += bytes[i]; + } + } + res += '"'; + ESP_LOGD(TAG, "%s", res.c_str()); +} + +void UARTDebug::log_int(UARTDirection direction, std::vector bytes, uint8_t separator) { + std::string res; + size_t len = bytes.size(); + if (direction == UART_DIRECTION_RX) { + res += "<<< "; + } else { + res += ">>> "; + } + for (size_t i = 0; i < len; i++) { + if (i > 0) { + res += separator; + } + res += to_string(bytes[i]); + } + ESP_LOGD(TAG, "%s", res.c_str()); +} + +void UARTDebug::log_binary(UARTDirection direction, std::vector bytes, uint8_t separator) { + std::string res; + size_t len = bytes.size(); + if (direction == UART_DIRECTION_RX) { + res += "<<< "; + } else { + res += ">>> "; + } + char buf[20]; + for (size_t i = 0; i < len; i++) { + if (i > 0) { + res += separator; + } + sprintf(buf, "0b" BYTE_TO_BINARY_PATTERN " (0x%02X)", BYTE_TO_BINARY(bytes[i]), bytes[i]); + res += buf; + } + ESP_LOGD(TAG, "%s", res.c_str()); +} + +} // namespace uart +} // namespace esphome +#endif diff --git a/esphome/components/uart/uart_debugger.h b/esphome/components/uart/uart_debugger.h new file mode 100644 index 0000000000..6e84bbe450 --- /dev/null +++ b/esphome/components/uart/uart_debugger.h @@ -0,0 +1,101 @@ +#pragma once +#include "esphome/core/defines.h" +#ifdef USE_UART_DEBUGGER + +#include +#include "esphome/core/component.h" +#include "esphome/core/automation.h" +#include "uart.h" +#include "uart_component.h" + +namespace esphome { +namespace uart { + +/// The UARTDebugger class adds debugging support to a UART bus. +/// +/// It accumulates bytes that travel over the UART bus and triggers one or +/// more actions that can log the data at an appropriate time. What +/// 'appropriate time' means exactly, is determined by a number of +/// configurable constraints. E.g. when a given number of bytes is gathered +/// and/or when no more data has been seen for a given time interval. +class UARTDebugger : public Component, public Trigger> { + public: + explicit UARTDebugger(UARTComponent *parent); + void loop() override; + + /// Set the direction in which to inspect the bytes: incoming, outgoing + /// or both. When debugging in both directions, logging will be triggered + /// when the direction of the data stream changes. + void set_direction(UARTDirection direction) { this->for_direction_ = direction; } + + /// Set the maximum number of bytes to accumulate. When the number of bytes + /// is reached, logging will be triggered. + void set_after_bytes(size_t size) { this->after_bytes_ = size; } + + /// Set a timeout for the data stream. When no new bytes are seen during + /// this timeout, logging will be triggered. + void set_after_timeout(uint32_t timeout) { this->after_timeout_ = timeout; } + + /// Add a delimiter byte. This can be called multiple times to setup a + /// multi-byte delimiter (a typical example would be '\r\n'). + /// When the constructued byte sequence is found in the data stream, + /// logging will be triggered. + void add_delimiter_byte(uint8_t byte) { this->after_delimiter_.push_back(byte); } + + protected: + UARTDirection for_direction_; + UARTDirection last_direction_{}; + std::vector bytes_{}; + size_t after_bytes_; + uint32_t after_timeout_; + uint32_t last_time_{}; + std::vector after_delimiter_{}; + size_t after_delimiter_pos_{}; + bool is_triggering_{false}; + + bool is_my_direction_(UARTDirection direction); + bool is_recursive_(); + void store_byte_(UARTDirection direction, uint8_t byte); + void trigger_after_direction_change_(UARTDirection direction); + void trigger_after_delimiter_(uint8_t byte); + void trigger_after_bytes_(); + void trigger_after_timeout_(); + bool has_buffered_bytes_(); + void fire_trigger_(); +}; + +/// This UARTDevice is used by the serial debugger to read data from a +/// serial interface when the 'dummy_receiver' option is enabled. +/// The data are not stored, nor processed. This is most useful when the +/// debugger is used to reverse engineer a serial protocol, for which no +/// specific UARTDevice implementation exists (yet), but for which the +/// incoming bytes must be read to drive the debugger. +class UARTDummyReceiver : public Component, public UARTDevice { + public: + UARTDummyReceiver(UARTComponent *parent) : UARTDevice(parent) {} + void loop() override; +}; + +/// This class contains some static methods, that can be used to easily +/// create a logging action for the debugger. +class UARTDebug { + public: + /// Log the bytes as hex values, separated by the provided separator + /// character. + static void log_hex(UARTDirection direction, std::vector bytes, uint8_t separator); + + /// Log the bytes as string values, escaping unprintable characters. + static void log_string(UARTDirection direction, std::vector bytes); + + /// Log the bytes as integer values, separated by the provided separator + /// character. + static void log_int(UARTDirection direction, std::vector bytes, uint8_t separator); + + /// Log the bytes as ' ()' values, separated by the provided + /// separator. + static void log_binary(UARTDirection direction, std::vector bytes, uint8_t separator); +}; + +} // namespace uart +} // namespace esphome +#endif diff --git a/esphome/const.py b/esphome/const.py index 26fd514173..af079ac69d 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -34,6 +34,7 @@ ARDUINO_VERSION_ESP8266 = { SOURCE_FILE_EXTENSIONS = {".cpp", ".hpp", ".h", ".c", ".tcc", ".ino"} HEADER_FILE_EXTENSIONS = {".h", ".hpp", ".tcc"} + CONF_ABOVE = "above" CONF_ACCELERATION = "acceleration" CONF_ACCELERATION_X = "acceleration_x" @@ -47,6 +48,7 @@ CONF_ACTIVE_POWER = "active_power" CONF_ADDRESS = "address" CONF_ADDRESSABLE_LIGHT_ID = "addressable_light_id" CONF_ADVANCED = "advanced" +CONF_AFTER = "after" CONF_ALPHA = "alpha" CONF_ALTITUDE = "altitude" CONF_AND = "and" @@ -93,6 +95,7 @@ CONF_BUFFER_SIZE = "buffer_size" CONF_BUILD_PATH = "build_path" CONF_BUS_VOLTAGE = "bus_voltage" CONF_BUSY_PIN = "busy_pin" +CONF_BYTES = "bytes" CONF_CALCULATED_LUX = "calculated_lux" CONF_CALIBRATE_LINEAR = "calibrate_linear" CONF_CALIBRATION = "calibration" @@ -164,6 +167,7 @@ CONF_DAYS_OF_WEEK = "days_of_week" CONF_DC_PIN = "dc_pin" CONF_DEASSERT_RTS_DTR = "deassert_rts_dtr" CONF_DEBOUNCE = "debounce" +CONF_DEBUG = "debug" CONF_DECAY_MODE = "decay_mode" CONF_DECELERATION = "deceleration" CONF_DEFAULT_MODE = "default_mode" @@ -171,6 +175,7 @@ CONF_DEFAULT_TARGET_TEMPERATURE_HIGH = "default_target_temperature_high" CONF_DEFAULT_TARGET_TEMPERATURE_LOW = "default_target_temperature_low" CONF_DEFAULT_TRANSITION_LENGTH = "default_transition_length" CONF_DELAY = "delay" +CONF_DELIMITER = "delimiter" CONF_DELTA = "delta" CONF_DEVICE = "device" CONF_DEVICE_CLASS = "device_class" @@ -192,6 +197,8 @@ CONF_DNS2 = "dns2" CONF_DOMAIN = "domain" CONF_DRY_ACTION = "dry_action" CONF_DRY_MODE = "dry_mode" +CONF_DUMMY_RECEIVER = "dummy_receiver" +CONF_DUMMY_RECEIVER_ID = "dummy_receiver_id" CONF_DUMP = "dump" CONF_DURATION = "duration" CONF_EAP = "eap" diff --git a/esphome/core/defines.h b/esphome/core/defines.h index 94fac73906..e679fe1cef 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -37,6 +37,7 @@ #define USE_SWITCH #define USE_TEXT_SENSOR #define USE_TIME +#define USE_UART_DEBUGGER #define USE_WEBSERVER #define USE_WIFI diff --git a/tests/test1.yaml b/tests/test1.yaml index dee7493bf2..04d928e1b8 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -193,6 +193,18 @@ uart: data_bits: 8 stop_bits: 1 rx_buffer_size: 512 + debug: + dummy_receiver: true + direction: both + after: + bytes: 50 + timeout: 500ms + delimiter: "\r\n" + sequence: + - lambda: UARTDebug::log_hex(direction, bytes, ':'); + - lambda: UARTDebug::log_string(direction, bytes); + - lambda: UARTDebug::log_int(direction, bytes, ','); + - lambda: UARTDebug::log_binary(direction, bytes, ';'); - id: adalight_uart tx_pin: GPIO25 From b4cd8d21a517c19dfdb7e432899db4fcaec10428 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Wed, 10 Nov 2021 23:53:25 +0100 Subject: [PATCH 0082/3388] Enable addressable light power supply based on raw values (#2690) --- esphome/components/light/addressable_light.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/light/addressable_light.h b/esphome/components/light/addressable_light.h index 2b2c0ca7e4..8302239d6a 100644 --- a/esphome/components/light/addressable_light.h +++ b/esphome/components/light/addressable_light.h @@ -87,7 +87,7 @@ class AddressableLight : public LightOutput, public Component { void mark_shown_() { #ifdef USE_POWER_SUPPLY for (const auto &c : *this) { - if (c.get().is_on()) { + if (c.get_red_raw() > 0 || c.get_green_raw() > 0 || c.get_blue_raw() > 0 || c.get_white_raw() > 0) { this->power_.request(); return; } From 78026e766f9bb1b8a567ec7fdfdb3f44393fb515 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 11 Nov 2021 12:25:41 +1300 Subject: [PATCH 0083/3388] Bump version to 2021.11.0b2 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index af079ac69d..065145761b 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2021.11.0b1" +__version__ = "2021.11.0b2" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" From 38cb9888094ce71ccba8c29b97483146fcd432dd Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 11 Nov 2021 15:15:37 +1300 Subject: [PATCH 0084/3388] Remove my.ha links from improv (#2695) --- .../components/esp32_improv/esp32_improv_component.cpp | 10 ++++++++-- .../improv_serial/improv_serial_component.cpp | 3 +-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/esphome/components/esp32_improv/esp32_improv_component.cpp b/esphome/components/esp32_improv/esp32_improv_component.cpp index faa9ab7df6..22bebdfe98 100644 --- a/esphome/components/esp32_improv/esp32_improv_component.cpp +++ b/esphome/components/esp32_improv/esp32_improv_component.cpp @@ -11,6 +11,7 @@ namespace esphome { namespace esp32_improv { static const char *const TAG = "esp32_improv.component"; +static const char *const ESPHOME_MY_LINK = "https://my.home-assistant.io/redirect/config_flow_start?domain=esphome"; ESP32ImprovComponent::ESP32ImprovComponent() { global_improv_component = this; } @@ -124,8 +125,13 @@ void ESP32ImprovComponent::loop() { this->cancel_timeout("wifi-connect-timeout"); this->set_state_(improv::STATE_PROVISIONED); - std::string url = "https://my.home-assistant.io/redirect/config_flow_start?domain=esphome"; - std::vector data = improv::build_rpc_response(improv::WIFI_SETTINGS, {url}); + std::vector urls = {ESPHOME_MY_LINK}; +#ifdef USE_WEBSERVER + auto ip = wifi::global_wifi_component->wifi_sta_ip(); + std::string webserver_url = "http://" + ip.str() + ":" + to_string(WEBSERVER_PORT); + urls.push_back(webserver_url); +#endif + std::vector data = improv::build_rpc_response(improv::WIFI_SETTINGS, urls); this->send_response_(data); this->set_timeout("end-service", 1000, [this] { this->service_->stop(); diff --git a/esphome/components/improv_serial/improv_serial_component.cpp b/esphome/components/improv_serial/improv_serial_component.cpp index a12f1bd83b..abbb76ab11 100644 --- a/esphome/components/improv_serial/improv_serial_component.cpp +++ b/esphome/components/improv_serial/improv_serial_component.cpp @@ -92,8 +92,7 @@ void ImprovSerialComponent::loop() { } std::vector ImprovSerialComponent::build_rpc_settings_response_(improv::Command command) { - std::string url = "https://my.home-assistant.io/redirect/config_flow_start?domain=esphome"; - std::vector urls = {url}; + std::vector urls; #ifdef USE_WEBSERVER auto ip = wifi::global_wifi_component->wifi_sta_ip(); std::string webserver_url = "http://" + ip.str() + ":" + to_string(WEBSERVER_PORT); From 62c3f301e7459c57db7195416cab93ba9f0a93a6 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 11 Nov 2021 22:56:35 +1300 Subject: [PATCH 0085/3388] Only allow prometheus when using arduino (#2697) --- esphome/components/prometheus/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/esphome/components/prometheus/__init__.py b/esphome/components/prometheus/__init__.py index f5f166d085..45345f06e8 100644 --- a/esphome/components/prometheus/__init__.py +++ b/esphome/components/prometheus/__init__.py @@ -15,7 +15,8 @@ CONFIG_SCHEMA = cv.Schema( cv.GenerateID(CONF_WEB_SERVER_BASE_ID): cv.use_id( web_server_base.WebServerBase ), - } + }, + cv.only_with_arduino, ).extend(cv.COMPONENT_SCHEMA) From b526155cce6611c908e1da8ff562c92c42458313 Mon Sep 17 00:00:00 2001 From: lcavalli Date: Fri, 12 Nov 2021 01:17:10 +0100 Subject: [PATCH 0086/3388] Update device classes for binary sensors (#2703) --- esphome/components/binary_sensor/__init__.py | 6 ++++-- esphome/const.py | 3 ++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/esphome/components/binary_sensor/__init__.py b/esphome/components/binary_sensor/__init__.py index faafcddd06..3f11e18e45 100644 --- a/esphome/components/binary_sensor/__init__.py +++ b/esphome/components/binary_sensor/__init__.py @@ -44,10 +44,11 @@ from esphome.const import ( DEVICE_CLASS_POWER, DEVICE_CLASS_PRESENCE, DEVICE_CLASS_PROBLEM, + DEVICE_CLASS_RUNNING, DEVICE_CLASS_SAFETY, DEVICE_CLASS_SMOKE, DEVICE_CLASS_SOUND, - DEVICE_CLASS_UPDATE, + DEVICE_CLASS_TAMPER, DEVICE_CLASS_VIBRATION, DEVICE_CLASS_WINDOW, ) @@ -76,10 +77,11 @@ DEVICE_CLASSES = [ DEVICE_CLASS_POWER, DEVICE_CLASS_PRESENCE, DEVICE_CLASS_PROBLEM, + DEVICE_CLASS_RUNNING, DEVICE_CLASS_SAFETY, DEVICE_CLASS_SMOKE, DEVICE_CLASS_SOUND, - DEVICE_CLASS_UPDATE, + DEVICE_CLASS_TAMPER, DEVICE_CLASS_VIBRATION, DEVICE_CLASS_WINDOW, ] diff --git a/esphome/const.py b/esphome/const.py index 065145761b..32773f06bd 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -878,10 +878,11 @@ DEVICE_CLASS_OPENING = "opening" DEVICE_CLASS_PLUG = "plug" DEVICE_CLASS_PRESENCE = "presence" DEVICE_CLASS_PROBLEM = "problem" +DEVICE_CLASS_RUNNING = "running" DEVICE_CLASS_SAFETY = "safety" DEVICE_CLASS_SMOKE = "smoke" DEVICE_CLASS_SOUND = "sound" -DEVICE_CLASS_UPDATE = "update" +DEVICE_CLASS_TAMPER = "tamper" DEVICE_CLASS_VIBRATION = "vibration" DEVICE_CLASS_WINDOW = "window" # device classes of both binary_sensor and sensor component From f1c5e2ef8130fb2b737724bc0033247f3e7fac4f Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Fri, 12 Nov 2021 16:12:31 +1300 Subject: [PATCH 0087/3388] Bump version to 2021.11.0b3 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 32773f06bd..f45c6022dd 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2021.11.0b2" +__version__ = "2021.11.0b3" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" From 81a36146efb4b686d2eb49d2ae73d16be7e5b6a6 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Sat, 13 Nov 2021 21:22:32 +1300 Subject: [PATCH 0088/3388] Bump ESPAsyncWebServer to 2.1.0 (#2686) --- 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 dc1a2bc2f0..14fb033a56 100644 --- a/esphome/components/web_server_base/__init__.py +++ b/esphome/components/web_server_base/__init__.py @@ -28,4 +28,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", "2.0.1") + cg.add_library("esphome/ESPAsyncWebServer-esphome", "2.1.0") diff --git a/platformio.ini b/platformio.ini index 0dd32268e0..5e89afe8e6 100644 --- a/platformio.ini +++ b/platformio.ini @@ -41,7 +41,7 @@ lib_deps = ${common.lib_deps} ottowinter/AsyncMqttClient-esphome@0.8.6 ; mqtt ottowinter/ArduinoJson-esphomelib@5.13.3 ; json - esphome/ESPAsyncWebServer-esphome@2.0.1 ; web_server_base + esphome/ESPAsyncWebServer-esphome@2.1.0 ; web_server_base fastled/FastLED@3.3.2 ; fastled_base mikalhart/TinyGPSPlus@1.0.2 ; gps freekode/TM1651@1.0.1 ; tm1651 From 87e1cdeedb39a01353d683c31082fbd13e24d70a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Bia=C5=82ek?= Date: Sun, 14 Nov 2021 14:59:34 +0100 Subject: [PATCH 0089/3388] Allow setting custom command_topic for Select and Number components (#2714) --- esphome/components/number/__init__.py | 2 +- esphome/components/select/__init__.py | 2 +- tests/test1.yaml | 20 ++++++++++++++++++++ 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/esphome/components/number/__init__.py b/esphome/components/number/__init__.py index bb3427e4bd..1da25caafe 100644 --- a/esphome/components/number/__init__.py +++ b/esphome/components/number/__init__.py @@ -41,7 +41,7 @@ NumberInRangeCondition = number_ns.class_( icon = cv.icon -NUMBER_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend( +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.GenerateID(): cv.declare_id(Number), diff --git a/esphome/components/select/__init__.py b/esphome/components/select/__init__.py index 8ea159d657..c15036e9f9 100644 --- a/esphome/components/select/__init__.py +++ b/esphome/components/select/__init__.py @@ -30,7 +30,7 @@ SelectSetAction = select_ns.class_("SelectSetAction", automation.Action) icon = cv.icon -SELECT_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend( +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), diff --git a/tests/test1.yaml b/tests/test1.yaml index 04d928e1b8..a7f2e24465 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -2512,3 +2512,23 @@ teleinfo: uart_id: uart0 update_interval: 60s historical_mode: true + +number: + - platform: template + id: test_number + state_topic: livingroom/custom_state_topic + command_topic: livingroom/custom_command_topic + min_value: 0 + step: 1 + max_value: 10 + optimistic: true + +select: + - platform: template + id: test_select + state_topic: livingroom/custom_state_topic + command_topic: livingroom/custom_command_topic + options: + - one + - two + optimistic: true From ab506b09fe66c62123aa624e7e31791752a11b75 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Sun, 14 Nov 2021 20:05:11 +0100 Subject: [PATCH 0090/3388] Restore InterruptLock on wifi-less ESP8266 (#2712) --- esphome/core/helpers.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index daca3ffd32..27608a84c1 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -9,6 +9,7 @@ #ifdef USE_WIFI #include #endif +#include #include #elif defined(USE_ESP32_FRAMEWORK_ARDUINO) #include @@ -430,13 +431,8 @@ void hsv_to_rgb(int hue, float saturation, float value, float &red, float &green } #ifdef USE_ESP8266 -#ifdef USE_WIFI IRAM_ATTR InterruptLock::InterruptLock() { xt_state_ = xt_rsil(15); } IRAM_ATTR InterruptLock::~InterruptLock() { xt_wsr_ps(xt_state_); } -#else -IRAM_ATTR InterruptLock::InterruptLock() {} -IRAM_ATTR InterruptLock::~InterruptLock() {} -#endif #endif #ifdef USE_ESP32 IRAM_ATTR InterruptLock::InterruptLock() { portDISABLE_INTERRUPTS(); } From f4a140e126fcbd3649d5a9569131dda66687a4ce Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Sun, 14 Nov 2021 21:45:25 +0100 Subject: [PATCH 0091/3388] Feed WDT between doing ESP32 touchpad measurements (#2720) --- esphome/components/esp32_touch/esp32_touch.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/esphome/components/esp32_touch/esp32_touch.cpp b/esphome/components/esp32_touch/esp32_touch.cpp index cb72820900..85f4058eee 100644 --- a/esphome/components/esp32_touch/esp32_touch.cpp +++ b/esphome/components/esp32_touch/esp32_touch.cpp @@ -1,6 +1,7 @@ #ifdef USE_ESP32 #include "esp32_touch.h" +#include "esphome/core/application.h" #include "esphome/core/log.h" #include "esphome/core/hal.h" @@ -125,6 +126,8 @@ void ESP32TouchComponent::loop() { if (should_print) { ESP_LOGD(TAG, "Touch Pad '%s' (T%u): %u", child->get_name().c_str(), child->get_touch_pad(), value); } + + App.feed_wdt(); } if (should_print) { From c2f57baec2cef35e1fde9b4f1781e639dab37208 Mon Sep 17 00:00:00 2001 From: "Sergey V. DUDANOV" Date: Mon, 15 Nov 2021 01:40:35 +0400 Subject: [PATCH 0092/3388] RemoteTransmitter fix. Bug from version 2021.10. Some changes. (#2706) --- .../midea/{adapter.cpp => ac_adapter.cpp} | 4 +- .../midea/{adapter.h => ac_adapter.h} | 6 +- .../midea/{automations.h => ac_automations.h} | 2 + esphome/components/midea/air_conditioner.cpp | 13 +-- esphome/components/midea/air_conditioner.h | 21 +++- esphome/components/midea/appliance_base.h | 109 ++++++++++-------- esphome/components/midea/climate.py | 20 ++-- .../midea/{midea_ir.h => ir_transmitter.h} | 15 +++ 8 files changed, 122 insertions(+), 68 deletions(-) rename esphome/components/midea/{adapter.cpp => ac_adapter.cpp} (98%) rename esphome/components/midea/{adapter.h => ac_adapter.h} (95%) rename esphome/components/midea/{automations.h => ac_automations.h} (97%) rename esphome/components/midea/{midea_ir.h => ir_transmitter.h} (73%) diff --git a/esphome/components/midea/adapter.cpp b/esphome/components/midea/ac_adapter.cpp similarity index 98% rename from esphome/components/midea/adapter.cpp rename to esphome/components/midea/ac_adapter.cpp index a3f19dbda8..2837713c35 100644 --- a/esphome/components/midea/adapter.cpp +++ b/esphome/components/midea/ac_adapter.cpp @@ -1,10 +1,11 @@ #ifdef USE_ARDUINO #include "esphome/core/log.h" -#include "adapter.h" +#include "ac_adapter.h" namespace esphome { namespace midea { +namespace ac { const char *const Constants::TAG = "midea"; const std::string Constants::FREEZE_PROTECTION = "freeze protection"; @@ -171,6 +172,7 @@ void Converters::to_climate_traits(ClimateTraits &traits, const dudanov::midea:: traits.add_supported_custom_preset(Constants::FREEZE_PROTECTION); } +} // namespace ac } // namespace midea } // namespace esphome diff --git a/esphome/components/midea/adapter.h b/esphome/components/midea/ac_adapter.h similarity index 95% rename from esphome/components/midea/adapter.h rename to esphome/components/midea/ac_adapter.h index 2497cbbe5b..c17894ae31 100644 --- a/esphome/components/midea/adapter.h +++ b/esphome/components/midea/ac_adapter.h @@ -2,12 +2,15 @@ #ifdef USE_ARDUINO +// MideaUART #include + #include "esphome/components/climate/climate_traits.h" -#include "appliance_base.h" +#include "air_conditioner.h" namespace esphome { namespace midea { +namespace ac { using MideaMode = dudanov::midea::ac::Mode; using MideaSwingMode = dudanov::midea::ac::SwingMode; @@ -41,6 +44,7 @@ class Converters { static void to_climate_traits(ClimateTraits &traits, const dudanov::midea::ac::Capabilities &capabilities); }; +} // namespace ac } // namespace midea } // namespace esphome diff --git a/esphome/components/midea/automations.h b/esphome/components/midea/ac_automations.h similarity index 97% rename from esphome/components/midea/automations.h rename to esphome/components/midea/ac_automations.h index 5b638286ac..d4ed2e7168 100644 --- a/esphome/components/midea/automations.h +++ b/esphome/components/midea/ac_automations.h @@ -7,6 +7,7 @@ namespace esphome { namespace midea { +namespace ac { template class MideaActionBase : public Action { public: @@ -55,6 +56,7 @@ template class PowerOffAction : public MideaActionBase { void play(Ts... x) override { this->parent_->do_power_off(); } }; +} // namespace ac } // namespace midea } // namespace esphome diff --git a/esphome/components/midea/air_conditioner.cpp b/esphome/components/midea/air_conditioner.cpp index 103b852936..dd48f640a2 100644 --- a/esphome/components/midea/air_conditioner.cpp +++ b/esphome/components/midea/air_conditioner.cpp @@ -2,13 +2,11 @@ #include "esphome/core/log.h" #include "air_conditioner.h" -#include "adapter.h" -#ifdef USE_REMOTE_TRANSMITTER -#include "midea_ir.h" -#endif +#include "ac_adapter.h" namespace esphome { namespace midea { +namespace ac { static void set_sensor(Sensor *sensor, float value) { if (sensor != nullptr && (!sensor->has_state() || sensor->get_raw_state() != value)) @@ -122,7 +120,7 @@ void AirConditioner::dump_config() { void AirConditioner::do_follow_me(float temperature, bool beeper) { #ifdef USE_REMOTE_TRANSMITTER IrFollowMeData data(static_cast(lroundf(temperature)), beeper); - this->transmit_ir(data); + this->transmitter_.transmit(data); #else ESP_LOGW(Constants::TAG, "Action needs remote_transmitter component"); #endif @@ -131,7 +129,7 @@ void AirConditioner::do_follow_me(float temperature, bool beeper) { void AirConditioner::do_swing_step() { #ifdef USE_REMOTE_TRANSMITTER IrSpecialData data(0x01); - this->transmit_ir(data); + this->transmitter_.transmit(data); #else ESP_LOGW(Constants::TAG, "Action needs remote_transmitter component"); #endif @@ -143,13 +141,14 @@ void AirConditioner::do_display_toggle() { } else { #ifdef USE_REMOTE_TRANSMITTER IrSpecialData data(0x08); - this->transmit_ir(data); + this->transmitter_.transmit(data); #else ESP_LOGW(Constants::TAG, "Action needs remote_transmitter component"); #endif } } +} // namespace ac } // namespace midea } // namespace esphome diff --git a/esphome/components/midea/air_conditioner.h b/esphome/components/midea/air_conditioner.h index 8dfb9dcb3d..a6023b78bb 100644 --- a/esphome/components/midea/air_conditioner.h +++ b/esphome/components/midea/air_conditioner.h @@ -2,17 +2,25 @@ #ifdef USE_ARDUINO +// MideaUART #include + #include "appliance_base.h" #include "esphome/components/sensor/sensor.h" namespace esphome { namespace midea { +namespace ac { using sensor::Sensor; using climate::ClimateCall; +using climate::ClimatePreset; +using climate::ClimateTraits; +using climate::ClimateMode; +using climate::ClimateSwingMode; +using climate::ClimateFanMode; -class AirConditioner : public ApplianceBase { +class AirConditioner : public ApplianceBase, public climate::Climate { public: void dump_config() override; void set_outdoor_temperature_sensor(Sensor *sensor) { this->outdoor_sensor_ = sensor; } @@ -31,15 +39,26 @@ class AirConditioner : public ApplianceBase void do_beeper_off() { this->set_beeper_feedback(false); } void do_power_on() { this->base_.setPowerState(true); } void do_power_off() { this->base_.setPowerState(false); } + void set_supported_modes(const std::set &modes) { this->supported_modes_ = modes; } + void set_supported_swing_modes(const std::set &modes) { this->supported_swing_modes_ = modes; } + void set_supported_presets(const std::set &presets) { this->supported_presets_ = presets; } + void set_custom_presets(const std::set &presets) { this->supported_custom_presets_ = presets; } + void set_custom_fan_modes(const std::set &modes) { this->supported_custom_fan_modes_ = modes; } protected: void control(const ClimateCall &call) override; ClimateTraits traits() override; + std::set supported_modes_{}; + std::set supported_swing_modes_{}; + std::set supported_presets_{}; + std::set supported_custom_presets_{}; + std::set supported_custom_fan_modes_{}; Sensor *outdoor_sensor_{nullptr}; Sensor *humidity_sensor_{nullptr}; Sensor *power_sensor_{nullptr}; }; +} // namespace ac } // namespace midea } // namespace esphome diff --git a/esphome/components/midea/appliance_base.h b/esphome/components/midea/appliance_base.h index 88a722e389..060cbd996b 100644 --- a/esphome/components/midea/appliance_base.h +++ b/esphome/components/midea/appliance_base.h @@ -2,84 +2,97 @@ #ifdef USE_ARDUINO +// MideaUART +#include +#include + +// Include global defines +#include "esphome/core/defines.h" + #include "esphome/core/component.h" #include "esphome/core/log.h" #include "esphome/components/uart/uart.h" #include "esphome/components/climate/climate.h" -#ifdef USE_REMOTE_TRANSMITTER -#include "esphome/components/remote_base/midea_protocol.h" -#include "esphome/components/remote_transmitter/remote_transmitter.h" -#endif -#include -#include +#include "ir_transmitter.h" namespace esphome { namespace midea { -using climate::ClimatePreset; -using climate::ClimateTraits; -using climate::ClimateMode; -using climate::ClimateSwingMode; -using climate::ClimateFanMode; +/* Stream from UART component */ +class UARTStream : public Stream { + public: + void set_uart(uart::UARTComponent *uart) { this->uart_ = uart; } -template -class ApplianceBase : public Component, public uart::UARTDevice, public climate::Climate, public Stream { + /* Stream interface implementation */ + + int available() override { return this->uart_->available(); } + int read() override { + uint8_t data; + this->uart_->read_byte(&data); + return data; + } + int peek() override { + uint8_t data; + this->uart_->peek_byte(&data); + return data; + } + size_t write(uint8_t data) override { + this->uart_->write_byte(data); + return 1; + } + size_t write(const uint8_t *data, size_t size) override { + this->uart_->write_array(data, size); + return size; + } + void flush() override { this->uart_->flush(); } + + protected: + uart::UARTComponent *uart_; +}; + +template class ApplianceBase : public Component { static_assert(std::is_base_of::value, "T must derive from dudanov::midea::ApplianceBase class"); public: ApplianceBase() { - this->base_.setStream(this); + this->base_.setStream(&this->stream_); this->base_.addOnStateCallback(std::bind(&ApplianceBase::on_status_change, this)); dudanov::midea::ApplianceBase::setLogger( [](int level, const char *tag, int line, const String &format, va_list args) { esp_log_vprintf_(level, tag, line, format.c_str(), args); }); } - bool can_proceed() override { - return this->base_.getAutoconfStatus() != dudanov::midea::AutoconfStatus::AUTOCONF_PROGRESS; - } - float get_setup_priority() const override { return setup_priority::BEFORE_CONNECTION; } - void setup() override { this->base_.setup(); } - void loop() override { this->base_.loop(); } + +#ifdef USE_REMOTE_TRANSMITTER + void set_transmitter(RemoteTransmitterBase *transmitter) { this->transmitter_.set_transmitter(transmitter); } +#endif + + /* UART communication */ + + void set_uart_parent(uart::UARTComponent *parent) { this->stream_.set_uart(parent); } void set_period(uint32_t ms) { this->base_.setPeriod(ms); } void set_response_timeout(uint32_t ms) { this->base_.setTimeout(ms); } void set_request_attempts(uint32_t attempts) { this->base_.setNumAttempts(attempts); } + + /* Component methods */ + + void setup() override { this->base_.setup(); } + void loop() override { this->base_.loop(); } + float get_setup_priority() const override { return setup_priority::BEFORE_CONNECTION; } + bool can_proceed() override { + return this->base_.getAutoconfStatus() != dudanov::midea::AutoconfStatus::AUTOCONF_PROGRESS; + } + void set_beeper_feedback(bool state) { this->base_.setBeeper(state); } void set_autoconf(bool value) { this->base_.setAutoconf(value); } - void set_supported_modes(const std::set &modes) { this->supported_modes_ = modes; } - void set_supported_swing_modes(const std::set &modes) { this->supported_swing_modes_ = modes; } - void set_supported_presets(const std::set &presets) { this->supported_presets_ = presets; } - void set_custom_presets(const std::set &presets) { this->supported_custom_presets_ = presets; } - void set_custom_fan_modes(const std::set &modes) { this->supported_custom_fan_modes_ = modes; } virtual void on_status_change() = 0; -#ifdef USE_REMOTE_TRANSMITTER - void set_transmitter(remote_transmitter::RemoteTransmitterComponent *transmitter) { - this->transmitter_ = transmitter; - } - void transmit_ir(remote_base::MideaData &data) { - data.finalize(); - auto transmit = this->transmitter_->transmit(); - remote_base::MideaProtocol().encode(transmit.get_data(), data); - transmit.perform(); - } -#endif - - int available() override { return uart::UARTDevice::available(); } - int read() override { return uart::UARTDevice::read(); } - int peek() override { return uart::UARTDevice::peek(); } - void flush() override { uart::UARTDevice::flush(); } - size_t write(uint8_t data) override { return uart::UARTDevice::write(data); } protected: T base_; - std::set supported_modes_{}; - std::set supported_swing_modes_{}; - std::set supported_presets_{}; - std::set supported_custom_presets_{}; - std::set supported_custom_fan_modes_{}; + UARTStream stream_; #ifdef USE_REMOTE_TRANSMITTER - remote_transmitter::RemoteTransmitterComponent *transmitter_{nullptr}; + IrTransmitter transmitter_; #endif }; diff --git a/esphome/components/midea/climate.py b/esphome/components/midea/climate.py index 08e82025b6..46c0019efa 100644 --- a/esphome/components/midea/climate.py +++ b/esphome/components/midea/climate.py @@ -40,9 +40,9 @@ AUTO_LOAD = ["sensor"] CONF_OUTDOOR_TEMPERATURE = "outdoor_temperature" CONF_POWER_USAGE = "power_usage" CONF_HUMIDITY_SETPOINT = "humidity_setpoint" -midea_ns = cg.esphome_ns.namespace("midea") -AirConditioner = midea_ns.class_("AirConditioner", climate.Climate, cg.Component) -Capabilities = midea_ns.namespace("Constants") +midea_ac_ns = cg.esphome_ns.namespace("midea").namespace("ac") +AirConditioner = midea_ac_ns.class_("AirConditioner", climate.Climate, cg.Component) +Capabilities = midea_ac_ns.namespace("Constants") def templatize(value): @@ -156,13 +156,13 @@ CONFIG_SCHEMA = cv.All( ) # Actions -FollowMeAction = midea_ns.class_("FollowMeAction", automation.Action) -DisplayToggleAction = midea_ns.class_("DisplayToggleAction", automation.Action) -SwingStepAction = midea_ns.class_("SwingStepAction", automation.Action) -BeeperOnAction = midea_ns.class_("BeeperOnAction", automation.Action) -BeeperOffAction = midea_ns.class_("BeeperOffAction", automation.Action) -PowerOnAction = midea_ns.class_("PowerOnAction", automation.Action) -PowerOffAction = midea_ns.class_("PowerOffAction", automation.Action) +FollowMeAction = midea_ac_ns.class_("FollowMeAction", automation.Action) +DisplayToggleAction = midea_ac_ns.class_("DisplayToggleAction", automation.Action) +SwingStepAction = midea_ac_ns.class_("SwingStepAction", automation.Action) +BeeperOnAction = midea_ac_ns.class_("BeeperOnAction", automation.Action) +BeeperOffAction = midea_ac_ns.class_("BeeperOffAction", automation.Action) +PowerOnAction = midea_ac_ns.class_("PowerOnAction", automation.Action) +PowerOffAction = midea_ac_ns.class_("PowerOffAction", automation.Action) MIDEA_ACTION_BASE_SCHEMA = cv.Schema( { diff --git a/esphome/components/midea/midea_ir.h b/esphome/components/midea/ir_transmitter.h similarity index 73% rename from esphome/components/midea/midea_ir.h rename to esphome/components/midea/ir_transmitter.h index abd4324bcc..34a9f8498e 100644 --- a/esphome/components/midea/midea_ir.h +++ b/esphome/components/midea/ir_transmitter.h @@ -7,6 +7,7 @@ namespace esphome { namespace midea { +using remote_base::RemoteTransmitterBase; using IrData = remote_base::MideaData; class IrFollowMeData : public IrData { @@ -38,6 +39,20 @@ class IrSpecialData : public IrData { IrSpecialData(uint8_t code) : IrData({MIDEA_TYPE_SPECIAL, code, 0xFF, 0xFF, 0xFF}) {} }; +class IrTransmitter { + public: + void set_transmitter(RemoteTransmitterBase *transmitter) { this->transmitter_ = transmitter; } + void transmit(IrData &data) { + data.finalize(); + auto transmit = this->transmitter_->transmit(); + remote_base::MideaProtocol().encode(transmit.get_data(), data); + transmit.perform(); + } + + protected: + RemoteTransmitterBase *transmitter_{nullptr}; +}; + } // namespace midea } // namespace esphome From fea3c4809893a6b3a94ebac7a32e7dc7214d2996 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 15 Nov 2021 10:59:48 +1300 Subject: [PATCH 0093/3388] Fix indentation of write_lambda for modbus_controller number (#2722) --- .../modbus_controller/number/__init__.py | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/esphome/components/modbus_controller/number/__init__.py b/esphome/components/modbus_controller/number/__init__.py index afb69f8798..4de0ffbcea 100644 --- a/esphome/components/modbus_controller/number/__init__.py +++ b/esphome/components/modbus_controller/number/__init__.py @@ -129,14 +129,14 @@ async def to_code(config): return_type=cg.optional.template(float), ) cg.add(var.set_template(template_)) - if CONF_WRITE_LAMBDA in config: - template_ = await cg.process_lambda( - config[CONF_WRITE_LAMBDA], - [ - (ModbusNumber.operator("ptr"), "item"), - (cg.float_, "x"), - (cg.std_vector.template(cg.uint16).operator("ref"), "payload"), - ], - return_type=cg.optional.template(float), - ) - cg.add(var.set_write_template(template_)) + if CONF_WRITE_LAMBDA in config: + template_ = await cg.process_lambda( + config[CONF_WRITE_LAMBDA], + [ + (ModbusNumber.operator("ptr"), "item"), + (cg.float_, "x"), + (cg.std_vector.template(cg.uint16).operator("ref"), "payload"), + ], + return_type=cg.optional.template(float), + ) + cg.add(var.set_write_template(template_)) From 194f922312119556cef465d4b9b89ce7477a9a33 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 15 Nov 2021 11:03:40 +1300 Subject: [PATCH 0094/3388] Bump version to 2021.11.0b4 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index f45c6022dd..324356ee0b 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2021.11.0b3" +__version__ = "2021.11.0b4" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" From 09e87823180bd37366391f5cd89338ae63d64f2a Mon Sep 17 00:00:00 2001 From: Alexandre-Jacques St-Jacques Date: Sun, 14 Nov 2021 17:58:22 -0500 Subject: [PATCH 0095/3388] Remove unnecessary duplicate touch_pad_filter_start (#2724) --- esphome/components/esp32_touch/esp32_touch.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/esphome/components/esp32_touch/esp32_touch.cpp b/esphome/components/esp32_touch/esp32_touch.cpp index 85f4058eee..b225ae1a8a 100644 --- a/esphome/components/esp32_touch/esp32_touch.cpp +++ b/esphome/components/esp32_touch/esp32_touch.cpp @@ -94,7 +94,6 @@ void ESP32TouchComponent::dump_config() { if (this->iir_filter_enabled_()) { ESP_LOGCONFIG(TAG, " IIR Filter: %ums", this->iir_filter_); - touch_pad_filter_start(this->iir_filter_); } else { ESP_LOGCONFIG(TAG, " IIR Filter DISABLED"); } From 687a7e9b2f3a8813e195f7b5e5cba3a27ad92241 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 15 Nov 2021 12:02:18 +1300 Subject: [PATCH 0096/3388] Bump version to 2021.11.0b5 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 324356ee0b..a47673b085 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2021.11.0b4" +__version__ = "2021.11.0b5" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" From 0f2df599988c5421972bf33a6b297b15d7391b49 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 16 Nov 2021 09:53:52 +1300 Subject: [PATCH 0097/3388] Add zeroconf as a direct dependency and lock the version (#2729) --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index 6e1fe56057..c4b211283d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,6 +11,7 @@ esptool==3.2 click==8.0.3 esphome-dashboard==20211021.1 aioesphomeapi==10.2.0 +zeroconf==0.36.13 # esp-idf requires this, but doesn't bundle it by default # https://github.com/espressif/esp-idf/blob/220590d599e134d7a5e7f1e683cc4550349ffbf8/requirements.txt#L24 From 0a545a28b9d7e6844695d005cc21515fc8436d58 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 16 Nov 2021 09:59:00 +1300 Subject: [PATCH 0098/3388] Bump version to 2021.11.0b6 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index a47673b085..e2bfc81208 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2021.11.0b5" +__version__ = "2021.11.0b6" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" From b0a0a153f3bf5182ce0b7d5f61e29021184d5e5f Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 16 Nov 2021 11:02:45 +1300 Subject: [PATCH 0099/3388] Improv serial/checksum changes (#2731) Co-authored-by: Paulus Schoutsen --- esphome/components/improv/improv.cpp | 54 ++++++++++--------- esphome/components/improv/improv.h | 9 ++-- .../improv_serial/improv_serial_component.cpp | 35 ++++++++---- 3 files changed, 61 insertions(+), 37 deletions(-) diff --git a/esphome/components/improv/improv.cpp b/esphome/components/improv/improv.cpp index 94068bc626..759962b51a 100644 --- a/esphome/components/improv/improv.cpp +++ b/esphome/components/improv/improv.cpp @@ -2,30 +2,32 @@ namespace improv { -ImprovCommand parse_improv_data(const std::vector &data) { - return parse_improv_data(data.data(), data.size()); +ImprovCommand parse_improv_data(const std::vector &data, bool check_checksum) { + return parse_improv_data(data.data(), data.size(), check_checksum); } -ImprovCommand parse_improv_data(const uint8_t *data, size_t length) { +ImprovCommand parse_improv_data(const uint8_t *data, size_t length, bool check_checksum) { ImprovCommand improv_command; Command command = (Command) data[0]; uint8_t data_length = data[1]; - if (data_length != length - 3) { + if (data_length != length - 2 - check_checksum) { improv_command.command = UNKNOWN; return improv_command; } - uint8_t checksum = data[length - 1]; + if (check_checksum) { + uint8_t checksum = data[length - 1]; - uint32_t calculated_checksum = 0; - for (uint8_t i = 0; i < length - 1; i++) { - calculated_checksum += data[i]; - } + uint32_t calculated_checksum = 0; + for (uint8_t i = 0; i < length - 1; i++) { + calculated_checksum += data[i]; + } - if ((uint8_t) calculated_checksum != checksum) { - improv_command.command = BAD_CHECKSUM; - return improv_command; + if ((uint8_t) calculated_checksum != checksum) { + improv_command.command = BAD_CHECKSUM; + return improv_command; + } } if (command == WIFI_SETTINGS) { @@ -46,7 +48,7 @@ ImprovCommand parse_improv_data(const uint8_t *data, size_t length) { return improv_command; } -std::vector build_rpc_response(Command command, const std::vector &datum) { +std::vector build_rpc_response(Command command, const std::vector &datum, bool add_checksum) { std::vector out; uint32_t length = 0; out.push_back(command); @@ -58,17 +60,19 @@ std::vector build_rpc_response(Command command, const std::vector build_rpc_response(Command command, const std::vector &datum) { +#ifdef ARDUINO +std::vector build_rpc_response(Command command, const std::vector &datum, bool add_checksum) { std::vector out; uint32_t length = 0; out.push_back(command); @@ -80,14 +84,16 @@ std::vector build_rpc_response(Command command, const std::vector &data); -ImprovCommand parse_improv_data(const uint8_t *data, size_t length); +ImprovCommand parse_improv_data(const std::vector &data, bool check_checksum = true); +ImprovCommand parse_improv_data(const uint8_t *data, size_t length, bool check_checksum = true); -std::vector build_rpc_response(Command command, const std::vector &datum); +std::vector build_rpc_response(Command command, const std::vector &datum, + bool add_checksum = true); #ifdef ARDUINO -std::vector build_rpc_response(Command command, const std::vector &datum); +std::vector build_rpc_response(Command command, const std::vector &datum, bool add_checksum = true); #endif // ARDUINO } // namespace improv diff --git a/esphome/components/improv_serial/improv_serial_component.cpp b/esphome/components/improv_serial/improv_serial_component.cpp index abbb76ab11..a9a7467125 100644 --- a/esphome/components/improv_serial/improv_serial_component.cpp +++ b/esphome/components/improv_serial/improv_serial_component.cpp @@ -98,13 +98,13 @@ std::vector ImprovSerialComponent::build_rpc_settings_response_(improv: std::string webserver_url = "http://" + ip.str() + ":" + to_string(WEBSERVER_PORT); urls.push_back(webserver_url); #endif - std::vector data = improv::build_rpc_response(command, urls); + std::vector data = improv::build_rpc_response(command, urls, false); return data; } std::vector ImprovSerialComponent::build_version_info_() { std::vector infos = {"ESPHome", ESPHOME_VERSION, ESPHOME_VARIANT, App.get_name()}; - std::vector data = improv::build_rpc_response(improv::GET_DEVICE_INFO, infos); + std::vector data = improv::build_rpc_response(improv::GET_DEVICE_INFO, infos, false); return data; }; @@ -140,22 +140,33 @@ bool ImprovSerialComponent::parse_improv_serial_byte_(uint8_t byte) { if (at < 8 + data_len) return true; - if (at == 8 + data_len) { + if (at == 8 + data_len) + return true; + + if (at == 8 + data_len + 1) { + uint8_t checksum = 0x00; + for (uint8_t i = 0; i < at; i++) + checksum += raw[i]; + + if (checksum != byte) { + ESP_LOGW(TAG, "Error decoding Improv payload"); + this->set_error_(improv::ERROR_INVALID_RPC); + return false; + } + if (type == TYPE_RPC) { this->set_error_(improv::ERROR_NONE); - auto command = improv::parse_improv_data(&raw[9], data_len); + auto command = improv::parse_improv_data(&raw[9], data_len, false); return this->parse_improv_payload_(command); } } - return true; + + // If we got here then the command coming is is improv, but not an RPC command + return false; } bool ImprovSerialComponent::parse_improv_payload_(improv::ImprovCommand &command) { switch (command.command) { - case improv::BAD_CHECKSUM: - ESP_LOGW(TAG, "Error decoding Improv payload"); - this->set_error_(improv::ERROR_INVALID_RPC); - return false; case improv::WIFI_SETTINGS: { wifi::WiFiAP sta{}; sta.set_ssid(command.ssid); @@ -232,6 +243,12 @@ void ImprovSerialComponent::send_response_(std::vector &response) { data[7] = TYPE_RPC_RESPONSE; data[8] = response.size(); data.insert(data.end(), response.begin(), response.end()); + + uint8_t checksum = 0x00; + for (uint8_t d : data) + checksum += d; + data.push_back(checksum); + this->write_data_(data); } From d7432f7c10b7be33a5b7d974cb91eb61a378515a Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 16 Nov 2021 11:05:51 +1300 Subject: [PATCH 0100/3388] Bump version to 2021.11.0b7 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index e2bfc81208..48b1e8aa96 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2021.11.0b6" +__version__ = "2021.11.0b7" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" From c75566b374bb7dbf38f05e331f6b3a3f2f09885c Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 16 Nov 2021 12:47:06 +1300 Subject: [PATCH 0101/3388] Fix zeroconf time comparisons (#2733) Co-authored-by: J. Nick Koston --- esphome/zeroconf.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/esphome/zeroconf.py b/esphome/zeroconf.py index a19fc143ec..1fbdf7e93f 100644 --- a/esphome/zeroconf.py +++ b/esphome/zeroconf.py @@ -13,8 +13,9 @@ from zeroconf import ( RecordUpdateListener, Zeroconf, ServiceBrowser, + ServiceStateChange, + current_time_millis, ) -from zeroconf._services import ServiceStateChange _CLASS_IN = 1 _FLAGS_QR_QUERY = 0x0000 # query @@ -88,7 +89,7 @@ class DashboardStatus(threading.Thread): entries = self.zc.cache.entries_with_name(key) if not entries: return False - now = time.time() * 1000 + now = current_time_millis() return any( (entry.created + DashboardStatus.OFFLINE_AFTER) >= now for entry in entries @@ -99,7 +100,7 @@ class DashboardStatus(threading.Thread): self.on_update( {key: self.host_status(host) for key, host in self.key_to_host.items()} ) - now = time.time() * 1000 + now = current_time_millis() for host in self.query_hosts: entries = self.zc.cache.entries_with_name(host) if not entries or all( From cbbafbcca2a340aa6701c63fe719e71c25a53cbc Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 16 Nov 2021 12:53:56 +1300 Subject: [PATCH 0102/3388] Bump version to 2021.11.0b8 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 48b1e8aa96..38ef0e60ee 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2021.11.0b7" +__version__ = "2021.11.0b8" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" From 0d47d41c85fce14c36cb144a7446260496032b30 Mon Sep 17 00:00:00 2001 From: Ryan Hoffman Date: Tue, 16 Nov 2021 12:53:36 -0500 Subject: [PATCH 0103/3388] Use as_reversed_hex_array in ble_sensor to fix UUID parsing (#2737) #1627 renamed as_hex_array to as_reversed_hex_array but forgot to rename these users. --- esphome/components/ble_client/sensor/__init__.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/esphome/components/ble_client/sensor/__init__.py b/esphome/components/ble_client/sensor/__init__.py index efe4bf0e9a..4aa6a92ba5 100644 --- a/esphome/components/ble_client/sensor/__init__.py +++ b/esphome/components/ble_client/sensor/__init__.py @@ -67,7 +67,7 @@ async def to_code(config): var.set_service_uuid32(esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID])) ) elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid128_format): - uuid128 = esp32_ble_tracker.as_hex_array(config[CONF_SERVICE_UUID]) + uuid128 = esp32_ble_tracker.as_reversed_hex_array(config[CONF_SERVICE_UUID]) cg.add(var.set_service_uuid128(uuid128)) if len(config[CONF_CHARACTERISTIC_UUID]) == len(esp32_ble_tracker.bt_uuid16_format): @@ -87,7 +87,9 @@ async def to_code(config): elif len(config[CONF_CHARACTERISTIC_UUID]) == len( esp32_ble_tracker.bt_uuid128_format ): - uuid128 = esp32_ble_tracker.as_hex_array(config[CONF_CHARACTERISTIC_UUID]) + uuid128 = esp32_ble_tracker.as_reversed_hex_array( + config[CONF_CHARACTERISTIC_UUID] + ) cg.add(var.set_char_uuid128(uuid128)) if CONF_DESCRIPTOR_UUID in config: @@ -108,7 +110,9 @@ async def to_code(config): elif len(config[CONF_DESCRIPTOR_UUID]) == len( esp32_ble_tracker.bt_uuid128_format ): - uuid128 = esp32_ble_tracker.as_hex_array(config[CONF_DESCRIPTOR_UUID]) + uuid128 = esp32_ble_tracker.as_reversed_hex_array( + config[CONF_DESCRIPTOR_UUID] + ) cg.add(var.set_descr_uuid128(uuid128)) if CONF_LAMBDA in config: From c41547fd4a631eae1018acdd93dbd58020476553 Mon Sep 17 00:00:00 2001 From: rotarykite Date: Wed, 17 Nov 2021 02:57:03 +0800 Subject: [PATCH 0104/3388] Fix senseair component uart read timeout (#2658) Co-authored-by: DAVe3283 Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Co-authored-by: Chua Jun Chieh --- esphome/components/senseair/senseair.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/esphome/components/senseair/senseair.cpp b/esphome/components/senseair/senseair.cpp index 610892dd9e..50b9e01f17 100644 --- a/esphome/components/senseair/senseair.cpp +++ b/esphome/components/senseair/senseair.cpp @@ -141,12 +141,16 @@ void SenseAirComponent::abc_get_period() { } bool SenseAirComponent::senseair_write_command_(const uint8_t *command, uint8_t *response, uint8_t response_length) { + // Verify we have somewhere to store the response + if (response == nullptr) { + return false; + } + // Write wake up byte required by some S8 sensor models + this->write_byte(0); this->flush(); + delay(5); this->write_array(command, SENSEAIR_REQUEST_LENGTH); - if (response == nullptr) - return true; - bool ret = this->read_array(response, response_length); this->flush(); return ret; From 7e495a5e278345237135d3fb77a867ec7e655f39 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 17 Nov 2021 08:00:26 +1300 Subject: [PATCH 0105/3388] Bump version to 2021.11.0b9 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 38ef0e60ee..5d25ba1688 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2021.11.0b8" +__version__ = "2021.11.0b9" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" From 58a0b28a39a3b78ef41e530bc796499541115277 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 17 Nov 2021 21:58:30 +1300 Subject: [PATCH 0106/3388] Bump version to 2021.11.0 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 5d25ba1688..128d12aad5 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2021.11.0b9" +__version__ = "2021.11.0" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" From 67558bec472237495410a610f9457d04971c5e66 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Wed, 17 Nov 2021 09:52:40 +0100 Subject: [PATCH 0107/3388] Fix HM3301 AQI index calculator (#2739) --- esphome/components/hm3301/aqi_calculator.h | 2 +- esphome/components/hm3301/caqi_calculator.h | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/esphome/components/hm3301/aqi_calculator.h b/esphome/components/hm3301/aqi_calculator.h index 1410eac72b..a3839b643c 100644 --- a/esphome/components/hm3301/aqi_calculator.h +++ b/esphome/components/hm3301/aqi_calculator.h @@ -33,7 +33,7 @@ class AQICalculator : public AbstractAQICalculator { int conc_lo = array[grid_index][0]; int conc_hi = array[grid_index][1]; - return ((aqi_hi - aqi_lo) / (conc_hi - conc_lo)) * (value - conc_lo) + aqi_lo; + return (value - conc_lo) * (aqi_hi - aqi_lo) / (conc_hi - conc_lo) + aqi_lo; } int get_grid_index_(uint16_t value, int array[AMOUNT_OF_LEVELS][2]) { diff --git a/esphome/components/hm3301/caqi_calculator.h b/esphome/components/hm3301/caqi_calculator.h index 51158454d0..a7f5460e0a 100644 --- a/esphome/components/hm3301/caqi_calculator.h +++ b/esphome/components/hm3301/caqi_calculator.h @@ -37,9 +37,7 @@ class CAQICalculator : public AbstractAQICalculator { int conc_lo = array[grid_index][0]; int conc_hi = array[grid_index][1]; - int aqi = ((aqi_hi - aqi_lo) / (conc_hi - conc_lo)) * (value - conc_lo) + aqi_lo; - - return aqi; + return (value - conc_lo) * (aqi_hi - aqi_lo) / (conc_hi - conc_lo) + aqi_lo; } int get_grid_index_(uint16_t value, int array[AMOUNT_OF_LEVELS][2]) { From 8294d10d5bb55c6590117e303ad92a9d071670a2 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Wed, 17 Nov 2021 11:28:31 +0100 Subject: [PATCH 0108/3388] Re-instate device class update for binary sensors (#2743) --- esphome/components/binary_sensor/__init__.py | 2 ++ esphome/const.py | 1 + 2 files changed, 3 insertions(+) diff --git a/esphome/components/binary_sensor/__init__.py b/esphome/components/binary_sensor/__init__.py index 3f11e18e45..1eab76d54e 100644 --- a/esphome/components/binary_sensor/__init__.py +++ b/esphome/components/binary_sensor/__init__.py @@ -49,6 +49,7 @@ from esphome.const import ( DEVICE_CLASS_SMOKE, DEVICE_CLASS_SOUND, DEVICE_CLASS_TAMPER, + DEVICE_CLASS_UPDATE, DEVICE_CLASS_VIBRATION, DEVICE_CLASS_WINDOW, ) @@ -82,6 +83,7 @@ DEVICE_CLASSES = [ DEVICE_CLASS_SMOKE, DEVICE_CLASS_SOUND, DEVICE_CLASS_TAMPER, + DEVICE_CLASS_UPDATE, DEVICE_CLASS_VIBRATION, DEVICE_CLASS_WINDOW, ] diff --git a/esphome/const.py b/esphome/const.py index 128d12aad5..4c540ccb11 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -883,6 +883,7 @@ DEVICE_CLASS_SAFETY = "safety" DEVICE_CLASS_SMOKE = "smoke" DEVICE_CLASS_SOUND = "sound" DEVICE_CLASS_TAMPER = "tamper" +DEVICE_CLASS_UPDATE = "update" DEVICE_CLASS_VIBRATION = "vibration" DEVICE_CLASS_WINDOW = "window" # device classes of both binary_sensor and sensor component From 9c6a475a6e29d47f0f52f079bcb57d79cfa8d5e7 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 17 Nov 2021 23:31:38 +1300 Subject: [PATCH 0109/3388] Bump version to 2021.11.1 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 4c540ccb11..4bd02022d1 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2021.11.0" +__version__ = "2021.11.1" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" From d30e2f2a4f0313bbe1bc1b4d30279be0cb70d86a Mon Sep 17 00:00:00 2001 From: Maurice Makaay Date: Thu, 18 Nov 2021 22:41:26 +0100 Subject: [PATCH 0110/3388] Allow UART debug configuration with no after: definition (#2753) --- esphome/components/uart/__init__.py | 10 +++++++--- tests/test2.yaml | 6 ++++++ 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/esphome/components/uart/__init__.py b/esphome/components/uart/__init__.py index 53209dfc7b..159b08d2d9 100644 --- a/esphome/components/uart/__init__.py +++ b/esphome/components/uart/__init__.py @@ -94,17 +94,21 @@ UART_DIRECTIONS = { "BOTH": UARTDirection.UART_DIRECTION_BOTH, } +AFTER_DEFAULTS = {CONF_BYTES: 256, CONF_TIMEOUT: "100ms"} + DEBUG_SCHEMA = cv.Schema( { cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(UARTDebugger), cv.Optional(CONF_DIRECTION, default="BOTH"): cv.enum( UART_DIRECTIONS, upper=True ), - cv.Optional(CONF_AFTER): cv.Schema( + cv.Optional(CONF_AFTER, default=AFTER_DEFAULTS): cv.Schema( { - cv.Optional(CONF_BYTES, default=256): cv.validate_bytes, cv.Optional( - CONF_TIMEOUT, default="100ms" + CONF_BYTES, default=AFTER_DEFAULTS[CONF_BYTES] + ): cv.validate_bytes, + cv.Optional( + CONF_TIMEOUT, default=AFTER_DEFAULTS[CONF_TIMEOUT] ): cv.positive_time_period_milliseconds, cv.Optional(CONF_DELIMITER): cv.templatable(validate_raw_data), } diff --git a/tests/test2.yaml b/tests/test2.yaml index f90e522b1e..3afef9501d 100644 --- a/tests/test2.yaml +++ b/tests/test2.yaml @@ -39,6 +39,12 @@ uart: tx_pin: GPIO22 rx_pin: GPIO23 baud_rate: 115200 + # Specifically added for testing debug with no after: definition. + debug: + dummy_receiver: false + direction: rx + sequence: + - lambda: UARTDebug::log_hex(direction, bytes, ':'); ota: safe_mode: True From 3178243811627b1068f31ac74adec062e79c2fd6 Mon Sep 17 00:00:00 2001 From: Dave T <17680170+davet2001@users.noreply.github.com> Date: Thu, 18 Nov 2021 22:20:32 +0000 Subject: [PATCH 0111/3388] Fix frame scaling for animated gifs (#2750) --- esphome/components/animation/__init__.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/esphome/components/animation/__init__.py b/esphome/components/animation/__init__.py index 3f03e5c185..1780bdf72e 100644 --- a/esphome/components/animation/__init__.py +++ b/esphome/components/animation/__init__.py @@ -60,6 +60,10 @@ async def to_code(config): image.seek(frameIndex) frame = image.convert("L", dither=Image.NONE) pixels = list(frame.getdata()) + if len(pixels) != height * width: + raise core.EsphomeError( + f"Unexpected number of pixels in frame {frameIndex}: {len(pixels)} != {height*width}" + ) for pix in pixels: data[pos] = pix pos += 1 @@ -69,8 +73,14 @@ async def to_code(config): pos = 0 for frameIndex in range(frames): image.seek(frameIndex) + if CONF_RESIZE in config: + image.thumbnail(config[CONF_RESIZE]) frame = image.convert("RGB") pixels = list(frame.getdata()) + if len(pixels) != height * width: + raise core.EsphomeError( + f"Unexpected number of pixels in frame {frameIndex}: {len(pixels)} != {height*width}" + ) for pix in pixels: data[pos] = pix[0] pos += 1 From 3a72dd5cb6f9595615c6dca4d778b6adac925b94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Trzci=C5=84ski?= Date: Mon, 22 Nov 2021 00:09:11 +0100 Subject: [PATCH 0112/3388] esp32_camera_web_server: Improve support for MotionEye (#2777) --- .../camera_web_server.cpp | 26 ++++++++++++------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/esphome/components/esp32_camera_web_server/camera_web_server.cpp b/esphome/components/esp32_camera_web_server/camera_web_server.cpp index c9a684c7e5..653a274bf4 100644 --- a/esphome/components/esp32_camera_web_server/camera_web_server.cpp +++ b/esphome/components/esp32_camera_web_server/camera_web_server.cpp @@ -21,12 +21,19 @@ static const char *const TAG = "esp32_camera_web_server"; #define CONTENT_TYPE "image/jpeg" #define CONTENT_LENGTH "Content-Length" -static const char *const STREAM_HEADER = - "HTTP/1.1 200\r\nAccess-Control-Allow-Origin: *\r\nContent-Type: multipart/x-mixed-replace;boundary=" PART_BOUNDARY - "\r\n"; -static const char *const STREAM_500 = "HTTP/1.1 500\r\nContent-Type: text/plain\r\n\r\nNo frames send.\r\n"; -static const char *const STREAM_BOUNDARY = "\r\n--" PART_BOUNDARY "\r\n"; +static const char *const STREAM_HEADER = "HTTP/1.0 200 OK\r\n" + "Access-Control-Allow-Origin: *\r\n" + "Connection: close\r\n" + "Content-Type: multipart/x-mixed-replace;boundary=" PART_BOUNDARY "\r\n" + "\r\n" + "--" PART_BOUNDARY "\r\n"; +static const char *const STREAM_ERROR = "Content-Type: text/plain\r\n" + "\r\n" + "No frames send.\r\n" + "--" PART_BOUNDARY "\r\n"; static const char *const STREAM_PART = "Content-Type: " CONTENT_TYPE "\r\n" CONTENT_LENGTH ": %u\r\n\r\n"; +static const char *const STREAM_BOUNDARY = "\r\n" + "--" PART_BOUNDARY "\r\n"; CameraWebServer::CameraWebServer() {} @@ -45,6 +52,7 @@ void CameraWebServer::setup() { config.ctrl_port = this->port_; config.max_open_sockets = 1; config.backlog_conn = 2; + config.lru_purge_enable = true; if (httpd_start(&this->httpd_, &config) != ESP_OK) { mark_failed(); @@ -172,9 +180,6 @@ esp_err_t CameraWebServer::streaming_handler_(struct httpd_req *req) { ESP_LOGW(TAG, "STREAM: failed to acquire frame"); res = ESP_FAIL; } - if (res == ESP_OK) { - res = httpd_send_all(req, STREAM_BOUNDARY, strlen(STREAM_BOUNDARY)); - } if (res == ESP_OK) { size_t hlen = snprintf(part_buf, 64, STREAM_PART, image->get_data_length()); res = httpd_send_all(req, part_buf, hlen); @@ -182,6 +187,9 @@ esp_err_t CameraWebServer::streaming_handler_(struct httpd_req *req) { if (res == ESP_OK) { res = httpd_send_all(req, (const char *) image->get_data_buffer(), image->get_data_length()); } + if (res == ESP_OK) { + res = httpd_send_all(req, STREAM_BOUNDARY, strlen(STREAM_BOUNDARY)); + } if (res == ESP_OK) { frames++; int64_t frame_time = millis() - last_frame; @@ -193,7 +201,7 @@ esp_err_t CameraWebServer::streaming_handler_(struct httpd_req *req) { } if (!frames) { - res = httpd_send_all(req, STREAM_500, strlen(STREAM_500)); + res = httpd_send_all(req, STREAM_ERROR, strlen(STREAM_ERROR)); } ESP_LOGI(TAG, "STREAM: closed. Frames: %u", frames); From 980b7cda8f519ffbe6aeddafae32eb37e6e2c008 Mon Sep 17 00:00:00 2001 From: Samuel Sieb Date: Sun, 21 Nov 2021 15:11:36 -0800 Subject: [PATCH 0113/3388] Remove floating point ops from the ISR (#2751) Co-authored-by: Samuel Sieb --- esphome/components/zyaura/zyaura.cpp | 40 +++++++++++++++++----------- esphome/components/zyaura/zyaura.h | 8 +++--- 2 files changed, 28 insertions(+), 20 deletions(-) diff --git a/esphome/components/zyaura/zyaura.cpp b/esphome/components/zyaura/zyaura.cpp index 11643a5c23..621439aa0c 100644 --- a/esphome/components/zyaura/zyaura.cpp +++ b/esphome/components/zyaura/zyaura.cpp @@ -57,38 +57,46 @@ void IRAM_ATTR ZaSensorStore::interrupt(ZaSensorStore *arg) { void IRAM_ATTR ZaSensorStore::set_data_(ZaMessage *message) { switch (message->type) { case HUMIDITY: - this->humidity = (message->value > 10000) ? NAN : (message->value / 100.0f); + this->humidity = message->value; break; - case TEMPERATURE: - this->temperature = (message->value > 5970) ? NAN : (message->value / 16.0f - 273.15f); + this->temperature = message->value; break; - case CO2: - this->co2 = (message->value > 10000) ? NAN : message->value; - break; - - default: + this->co2 = message->value; break; } } -bool ZyAuraSensor::publish_state_(sensor::Sensor *sensor, float *value) { - // Sensor doesn't added to configuration +bool ZyAuraSensor::publish_state_(ZaDataType data_type, sensor::Sensor *sensor, uint16_t *data_value) { + // Sensor wasn't added to configuration if (sensor == nullptr) { return true; } - sensor->publish_state(*value); + float value = NAN; + switch (data_type) { + case HUMIDITY: + value = (*data_value > 10000) ? NAN : (*data_value / 100.0f); + break; + case TEMPERATURE: + value = (*data_value > 5970) ? NAN : (*data_value / 16.0f - 273.15f); + break; + case CO2: + value = (*data_value > 10000) ? NAN : *data_value; + break; + } + + sensor->publish_state(value); // Sensor reported wrong value - if (std::isnan(*value)) { + if (std::isnan(value)) { ESP_LOGW(TAG, "Sensor reported invalid data. Is the update interval too small?"); this->status_set_warning(); return false; } - *value = NAN; + *data_value = -1; return true; } @@ -104,9 +112,9 @@ void ZyAuraSensor::dump_config() { } void ZyAuraSensor::update() { - bool co2_result = this->publish_state_(this->co2_sensor_, &this->store_.co2); - bool temperature_result = this->publish_state_(this->temperature_sensor_, &this->store_.temperature); - bool humidity_result = this->publish_state_(this->humidity_sensor_, &this->store_.humidity); + bool co2_result = this->publish_state_(CO2, this->co2_sensor_, &this->store_.co2); + bool temperature_result = this->publish_state_(TEMPERATURE, this->temperature_sensor_, &this->store_.temperature); + bool humidity_result = this->publish_state_(HUMIDITY, this->humidity_sensor_, &this->store_.humidity); if (co2_result && temperature_result && humidity_result) { this->status_clear_warning(); diff --git a/esphome/components/zyaura/zyaura.h b/esphome/components/zyaura/zyaura.h index 2b9e3fbb35..85c31ec75a 100644 --- a/esphome/components/zyaura/zyaura.h +++ b/esphome/components/zyaura/zyaura.h @@ -42,9 +42,9 @@ class ZaDataProcessor { class ZaSensorStore { public: - float co2 = NAN; - float temperature = NAN; - float humidity = NAN; + uint16_t co2 = -1; + uint16_t temperature = -1; + uint16_t humidity = -1; void setup(InternalGPIOPin *pin_clock, InternalGPIOPin *pin_data); static void interrupt(ZaSensorStore *arg); @@ -79,7 +79,7 @@ class ZyAuraSensor : public PollingComponent { sensor::Sensor *temperature_sensor_{nullptr}; sensor::Sensor *humidity_sensor_{nullptr}; - bool publish_state_(sensor::Sensor *sensor, float *value); + bool publish_state_(ZaDataType data_type, sensor::Sensor *sensor, uint16_t *data_value); }; } // namespace zyaura From 8e1c9f50427890beb7d62da8319910ff9f56f409 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Thu, 25 Nov 2021 21:00:49 +0100 Subject: [PATCH 0114/3388] Fix parsing numbers from null-terminated buffers (#2755) --- esphome/components/anova/anova_base.cpp | 23 ++++++++++++----------- esphome/components/anova/anova_base.h | 1 - esphome/components/ezo/ezo.cpp | 4 ++-- esphome/core/helpers.h | 22 ++++++++++++---------- 4 files changed, 26 insertions(+), 24 deletions(-) diff --git a/esphome/components/anova/anova_base.cpp b/esphome/components/anova/anova_base.cpp index d55404089e..dcef75e483 100644 --- a/esphome/components/anova/anova_base.cpp +++ b/esphome/components/anova/anova_base.cpp @@ -73,51 +73,52 @@ AnovaPacket *AnovaCodec::get_stop_request() { } void AnovaCodec::decode(const uint8_t *data, uint16_t length) { - memset(this->buf_, 0, 32); - strncpy(this->buf_, (char *) data, length); + char buf[32]; + memset(buf, 0, sizeof(buf)); + strncpy(buf, (char *) data, std::min(length, sizeof(buf) - 1)); this->has_target_temp_ = this->has_current_temp_ = this->has_unit_ = this->has_running_ = false; switch (this->current_query_) { case READ_DEVICE_STATUS: { - if (!strncmp(this->buf_, "stopped", 7)) { + if (!strncmp(buf, "stopped", 7)) { this->has_running_ = true; this->running_ = false; } - if (!strncmp(this->buf_, "running", 7)) { + if (!strncmp(buf, "running", 7)) { this->has_running_ = true; this->running_ = true; } break; } case START: { - if (!strncmp(this->buf_, "start", 5)) { + if (!strncmp(buf, "start", 5)) { this->has_running_ = true; this->running_ = true; } break; } case STOP: { - if (!strncmp(this->buf_, "stop", 4)) { + if (!strncmp(buf, "stop", 4)) { this->has_running_ = true; this->running_ = false; } break; } case READ_TARGET_TEMPERATURE: { - this->target_temp_ = parse_number(this->buf_, sizeof(this->buf_)).value_or(0.0f); + this->target_temp_ = parse_number(buf, sizeof(buf)).value_or(0.0f); if (this->fahrenheit_) this->target_temp_ = ftoc(this->target_temp_); this->has_target_temp_ = true; break; } case SET_TARGET_TEMPERATURE: { - this->target_temp_ = parse_number(this->buf_, sizeof(this->buf_)).value_or(0.0f); + this->target_temp_ = parse_number(buf, sizeof(buf)).value_or(0.0f); if (this->fahrenheit_) this->target_temp_ = ftoc(this->target_temp_); this->has_target_temp_ = true; break; } case READ_CURRENT_TEMPERATURE: { - this->current_temp_ = parse_number(this->buf_, sizeof(this->buf_)).value_or(0.0f); + this->current_temp_ = parse_number(buf, sizeof(buf)).value_or(0.0f); if (this->fahrenheit_) this->current_temp_ = ftoc(this->current_temp_); this->has_current_temp_ = true; @@ -125,8 +126,8 @@ void AnovaCodec::decode(const uint8_t *data, uint16_t length) { } case SET_UNIT: case READ_UNIT: { - this->unit_ = this->buf_[0]; - this->fahrenheit_ = this->buf_[0] == 'f'; + this->unit_ = buf[0]; + this->fahrenheit_ = buf[0] == 'f'; this->has_unit_ = true; break; } diff --git a/esphome/components/anova/anova_base.h b/esphome/components/anova/anova_base.h index 7c1383512d..b831157849 100644 --- a/esphome/components/anova/anova_base.h +++ b/esphome/components/anova/anova_base.h @@ -70,7 +70,6 @@ class AnovaCodec { bool has_current_temp_; bool has_unit_; bool has_running_; - char buf_[32]; bool fahrenheit_; CurrentQuery current_query_; diff --git a/esphome/components/ezo/ezo.cpp b/esphome/components/ezo/ezo.cpp index 7f7a41fb41..426f2807c1 100644 --- a/esphome/components/ezo/ezo.cpp +++ b/esphome/components/ezo/ezo.cpp @@ -32,7 +32,7 @@ void EZOSensor::update() { } void EZOSensor::loop() { - uint8_t buf[20]; + uint8_t buf[21]; if (!(this->state_ & EZO_STATE_WAIT)) { if (this->state_ & EZO_STATE_SEND_TEMP) { int len = sprintf((char *) buf, "T,%0.3f", this->tempcomp_); @@ -74,7 +74,7 @@ void EZOSensor::loop() { if (buf[0] != 1) return; - float val = parse_number((char *) &buf[1], sizeof(buf) - 1).value_or(0); + float val = parse_number((char *) &buf[1], sizeof(buf) - 2).value_or(0); this->publish_state(val); } diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index c67ad8eea3..9cdbf7ca16 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -362,45 +362,47 @@ std::string str_sanitize(const std::string &str); /// @name Parsing & formatting ///@{ -/// Parse a unsigned decimal number. +/// Parse an unsigned decimal number (requires null-terminated string). template::value && std::is_unsigned::value), int> = 0> optional parse_number(const char *str, size_t len) { char *end = nullptr; unsigned long value = ::strtoul(str, &end, 10); // NOLINT(google-runtime-int) - if (end == nullptr || end != str + len || value > std::numeric_limits::max()) + if (end == str || *end != '\0' || value > std::numeric_limits::max()) return {}; return value; } +/// Parse an unsigned decimal number. template::value && std::is_unsigned::value), int> = 0> optional parse_number(const std::string &str) { - return parse_number(str.c_str(), str.length()); + return parse_number(str.c_str(), str.length() + 1); } -/// Parse a signed decimal number. +/// Parse a signed decimal number (requires null-terminated string). template::value && std::is_signed::value), int> = 0> optional parse_number(const char *str, size_t len) { char *end = nullptr; signed long value = ::strtol(str, &end, 10); // NOLINT(google-runtime-int) - if (end == nullptr || end != str + len || value < std::numeric_limits::min() || - value > std::numeric_limits::max()) + if (end == str || *end != '\0' || value < std::numeric_limits::min() || value > std::numeric_limits::max()) return {}; return value; } +/// Parse a signed decimal number. template::value && std::is_signed::value), int> = 0> optional parse_number(const std::string &str) { - return parse_number(str.c_str(), str.length()); + return parse_number(str.c_str(), str.length() + 1); } -/// Parse a decimal floating-point number. +/// Parse a decimal floating-point number (requires null-terminated string). template::value), int> = 0> optional parse_number(const char *str, size_t len) { char *end = nullptr; float value = ::strtof(str, &end); - if (end == nullptr || end != str + len || value == HUGE_VALF) + if (end == str || *end != '\0' || value == HUGE_VALF) return {}; return value; } +/// Parse a decimal floating-point number. template::value), int> = 0> optional parse_number(const std::string &str) { - return parse_number(str.c_str(), str.length()); + return parse_number(str.c_str(), str.length() + 1); } ///@} From 7d03823afd9d24092c27b9b44ee99af56c7aa495 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Fri, 26 Nov 2021 09:02:54 +1300 Subject: [PATCH 0115/3388] Bump version to 2021.11.2 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 4bd02022d1..6af44197ff 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2021.11.1" +__version__ = "2021.11.2" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" From 57a029189ca5309d7ddc3fddfbd30a43683d07d5 Mon Sep 17 00:00:00 2001 From: Maurice Makaay Date: Fri, 26 Nov 2021 21:25:58 +0100 Subject: [PATCH 0116/3388] Add missing nvs_flash_init() to ESP32 preferences code (#2805) Co-authored-by: Maurice Makaay --- esphome/components/esp32/preferences.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/esphome/components/esp32/preferences.cpp b/esphome/components/esp32/preferences.cpp index 96b7e7809e..8c2b67a942 100644 --- a/esphome/components/esp32/preferences.cpp +++ b/esphome/components/esp32/preferences.cpp @@ -76,6 +76,7 @@ class ESP32Preferences : public ESPPreferences { uint32_t current_offset = 0; void open() { + nvs_flash_init(); esp_err_t err = nvs_open("esphome", NVS_READWRITE, &nvs_handle); if (err == 0) return; From 5009b3029ff569bd7df35cce6fb265e51a515cad Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Sat, 27 Nov 2021 21:13:01 +1300 Subject: [PATCH 0117/3388] Bump version to 2021.11.3 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 6af44197ff..cb6ea8addd 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2021.11.2" +__version__ = "2021.11.3" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" From db2128a3447daaf19ad26f4cb792f07b4075627c Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Sun, 28 Nov 2021 20:00:29 +0100 Subject: [PATCH 0118/3388] Fix parsing numbers in Anova (#2816) --- esphome/components/anova/anova_base.cpp | 6 +++--- esphome/core/helpers.cpp | 5 +++++ esphome/core/helpers.h | 6 ++++++ 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/esphome/components/anova/anova_base.cpp b/esphome/components/anova/anova_base.cpp index dcef75e483..cb877bef35 100644 --- a/esphome/components/anova/anova_base.cpp +++ b/esphome/components/anova/anova_base.cpp @@ -104,21 +104,21 @@ void AnovaCodec::decode(const uint8_t *data, uint16_t length) { break; } case READ_TARGET_TEMPERATURE: { - this->target_temp_ = parse_number(buf, sizeof(buf)).value_or(0.0f); + this->target_temp_ = parse_number(str_until(buf, '\r')).value_or(0.0f); if (this->fahrenheit_) this->target_temp_ = ftoc(this->target_temp_); this->has_target_temp_ = true; break; } case SET_TARGET_TEMPERATURE: { - this->target_temp_ = parse_number(buf, sizeof(buf)).value_or(0.0f); + this->target_temp_ = parse_number(str_until(buf, '\r')).value_or(0.0f); if (this->fahrenheit_) this->target_temp_ = ftoc(this->target_temp_); this->has_target_temp_ = true; break; } case READ_CURRENT_TEMPERATURE: { - this->current_temp_ = parse_number(buf, sizeof(buf)).value_or(0.0f); + this->current_temp_ = parse_number(str_until(buf, '\r')).value_or(0.0f); if (this->fahrenheit_) this->current_temp_ = ftoc(this->current_temp_); this->has_current_temp_ = true; diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index 27608a84c1..cfc1c74145 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -444,6 +444,11 @@ IRAM_ATTR InterruptLock::~InterruptLock() { portENABLE_INTERRUPTS(); } std::string str_truncate(const std::string &str, size_t length) { return str.length() > length ? str.substr(0, length) : str; } +std::string str_until(const char *str, char ch) { + char *pos = strchr(str, ch); + return pos == nullptr ? std::string(str) : std::string(str, pos - str); +} +std::string str_until(const std::string &str, char ch) { return str.substr(0, str.find(ch)); } std::string str_snake_case(const std::string &str) { std::string result; result.resize(str.length()); diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index 9cdbf7ca16..7718c5f1b2 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -351,6 +351,12 @@ template::value, int> = 0> constexpr /// Truncate a string to a specific length. std::string str_truncate(const std::string &str, size_t length); +/// Extract the part of the string until either the first occurence of the specified character, or the end (requires str +/// to be null-terminated). +std::string str_until(const char *str, char ch); +/// Extract the part of the string until either the first occurence of the specified character, or the end. +std::string str_until(const std::string &str, char ch); + /// Convert the string to snake case (lowercase with underscores). std::string str_snake_case(const std::string &str); From 3d5e1d8d91a117d4a26e5fb0205c7eef280bb26c Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Sun, 28 Nov 2021 20:02:10 +0100 Subject: [PATCH 0119/3388] Fix parsing of multiple values in EZO sensor (#2814) Co-authored-by: Lydia Sevelt --- esphome/components/ezo/ezo.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/esphome/components/ezo/ezo.cpp b/esphome/components/ezo/ezo.cpp index 426f2807c1..ca6f121dbb 100644 --- a/esphome/components/ezo/ezo.cpp +++ b/esphome/components/ezo/ezo.cpp @@ -74,6 +74,11 @@ void EZOSensor::loop() { if (buf[0] != 1) return; + // some sensors return multiple comma-separated values, terminate string after first one + for (int i = 1; i < sizeof(buf) - 1; i++) + if (buf[i] == ',') + buf[i] = '\0'; + float val = parse_number((char *) &buf[1], sizeof(buf) - 2).value_or(0); this->publish_state(val); } From 50ec1d0445d4a67d08b3ab0b903733b7aa0c9d9c Mon Sep 17 00:00:00 2001 From: Carlos Garcia Saura Date: Sun, 28 Nov 2021 20:06:53 +0100 Subject: [PATCH 0120/3388] Fix compilation error for WPA enterprise in ESP-IDF (#2815) --- esphome/components/wifi/wifi_component_esp_idf.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/esphome/components/wifi/wifi_component_esp_idf.cpp b/esphome/components/wifi/wifi_component_esp_idf.cpp index 7f71b7078c..1d346c0a8e 100644 --- a/esphome/components/wifi/wifi_component_esp_idf.cpp +++ b/esphome/components/wifi/wifi_component_esp_idf.cpp @@ -375,8 +375,7 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) { ESP_LOGV(TAG, "esp_wifi_sta_wpa2_ent_set_password failed! %d", err); } } - esp_wpa2_config_t wpa2_config = WPA2_CONFIG_INIT_DEFAULT(); - err = esp_wifi_sta_wpa2_ent_enable(&wpa2_config); + err = esp_wifi_sta_wpa2_ent_enable(); if (err != ESP_OK) { ESP_LOGV(TAG, "esp_wifi_sta_wpa2_ent_enable failed! %d", err); } From e55506f9dbbb4cceec2345c8e37bf6b0bc95b15c Mon Sep 17 00:00:00 2001 From: Dave T <17680170+davet2001@users.noreply.github.com> Date: Sun, 28 Nov 2021 19:12:40 +0000 Subject: [PATCH 0121/3388] Correct bitmask for third color (blue) scaling. (#2817) --- esphome/components/display/display_color_utils.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/display/display_color_utils.h b/esphome/components/display/display_color_utils.h index 8fc3b0adb9..202de912de 100644 --- a/esphome/components/display/display_color_utils.h +++ b/esphome/components/display/display_color_utils.h @@ -42,7 +42,7 @@ class ColorUtil { ? esp_scale(((colorcode >> third_bits) & ((1 << second_bits) - 1)), ((1 << second_bits) - 1)) : esp_scale(((colorcode >> 8) & 0xFF), ((1 << second_bits) - 1)); - third_color = (right_bit_aligned ? esp_scale(((colorcode >> 0) & 0xFF), ((1 << third_bits) - 1)) + third_color = (right_bit_aligned ? esp_scale(((colorcode >> 0) & ((1 << third_bits) - 1)), ((1 << third_bits) - 1)) : esp_scale(((colorcode >> 0) & 0xFF), (1 << third_bits) - 1)); Color color_return; From a5fb0360118d163f31273732c7e1ae27ed3ed74b Mon Sep 17 00:00:00 2001 From: Conclusio Date: Sun, 28 Nov 2021 20:13:42 +0100 Subject: [PATCH 0122/3388] Add delay to improve stability (#2793) --- esphome/components/scd30/scd30.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/esphome/components/scd30/scd30.cpp b/esphome/components/scd30/scd30.cpp index e6d6ec1c1a..272ee75e30 100644 --- a/esphome/components/scd30/scd30.cpp +++ b/esphome/components/scd30/scd30.cpp @@ -200,6 +200,7 @@ bool SCD30Component::is_data_ready_() { if (!this->write_command_(SCD30_CMD_GET_DATA_READY_STATUS)) { return false; } + delay(4); uint16_t is_data_ready; if (!this->read_data_(&is_data_ready, 1)) { return false; From ea9e75039baa83636e498a87f019ee67bd3512ff Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 29 Nov 2021 10:18:49 +1300 Subject: [PATCH 0123/3388] Bump version to 2021.11.4 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index cb6ea8addd..c2df6ea922 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2021.11.3" +__version__ = "2021.11.4" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" From c6414138c723ff18492f1ce75a8ae7b2a2b3281a Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 2 Dec 2021 19:38:49 +1300 Subject: [PATCH 0124/3388] Bump version to 2021.12.0b1 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index f3189a6918..27feacb296 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2021.12.0-dev" +__version__ = "2021.12.0b1" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" From 86c205fe43089b7a7f3d1e23eb31978385150f15 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 2 Dec 2021 21:08:11 +1300 Subject: [PATCH 0125/3388] Remove blank line --- esphome/const.py | 1 - 1 file changed, 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 27feacb296..925e198fd7 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -14,7 +14,6 @@ HEADER_FILE_EXTENSIONS = {".h", ".hpp", ".tcc"} SECRETS_FILES = {"secrets.yaml", "secrets.yml"} - CONF_ABOVE = "above" CONF_ACCELERATION = "acceleration" CONF_ACCELERATION_X = "acceleration_x" From 9dcd3d18a04d9d50805ec67743457bdcccb0e023 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Thu, 2 Dec 2021 19:52:56 +0100 Subject: [PATCH 0126/3388] Update ota_component.cpp (#2852) --- esphome/components/ota/ota_component.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/esphome/components/ota/ota_component.cpp b/esphome/components/ota/ota_component.cpp index 79edd91173..0cf5ea242c 100644 --- a/esphome/components/ota/ota_component.cpp +++ b/esphome/components/ota/ota_component.cpp @@ -277,6 +277,7 @@ void OTAComponent::handle_() { ssize_t read = this->client_->read(buf, requested); if (read == -1) { if (errno == EAGAIN || errno == EWOULDBLOCK) { + App.feed_wdt(); delay(1); continue; } @@ -305,8 +306,9 @@ void OTAComponent::handle_() { #ifdef USE_OTA_STATE_CALLBACK this->state_callback_.call(OTA_IN_PROGRESS, percentage, 0); #endif - // slow down OTA update to avoid getting killed by task watchdog (task_wdt) - delay(10); + // feed watchdog and give other tasks a chance to run + App.feed_wdt(); + yield(); } } From 329bf861d674c9b1d23c85afa5e98738a52ba050 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Fri, 3 Dec 2021 07:54:34 +1300 Subject: [PATCH 0127/3388] Bump version to 2021.12.0b2 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 925e198fd7..40ed60cd6f 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2021.12.0b1" +__version__ = "2021.12.0b2" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" From 5ac88de985c23c9fb2d195ad3dbe4457ffca8db8 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 6 Dec 2021 19:42:49 +1300 Subject: [PATCH 0128/3388] Bump esphome-dashboard to 20211206.0 (#2870) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 6061476802..22cfeecd5d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,7 +9,7 @@ pyserial==3.5 platformio==5.2.2 # When updating platformio, also update Dockerfile esptool==3.2 click==8.0.3 -esphome-dashboard==20211201.0 +esphome-dashboard==20211206.0 aioesphomeapi==10.6.0 zeroconf==0.36.13 From f72abc6f3d3d7e09a2c95c8c3f631ea92b292c92 Mon Sep 17 00:00:00 2001 From: Martin <25747549+martgras@users.noreply.github.com> Date: Mon, 6 Dec 2021 07:54:46 +0100 Subject: [PATCH 0129/3388] tlc59208f : fix compilation error (#2867) --- esphome/components/tlc59208f/tlc59208f_output.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/esphome/components/tlc59208f/tlc59208f_output.cpp b/esphome/components/tlc59208f/tlc59208f_output.cpp index 59fb9f98ed..bd62f8de6d 100644 --- a/esphome/components/tlc59208f/tlc59208f_output.cpp +++ b/esphome/components/tlc59208f/tlc59208f_output.cpp @@ -1,6 +1,7 @@ #include "tlc59208f_output.h" #include "esphome/core/log.h" #include "esphome/core/helpers.h" +#include "esphome/core/hal.h" namespace esphome { namespace tlc59208f { From 1bc757ad0677cac4eb3cb445dd8990b86fc091f9 Mon Sep 17 00:00:00 2001 From: Carlos Garcia Saura Date: Mon, 6 Dec 2021 07:56:53 +0100 Subject: [PATCH 0130/3388] ADC: Turn verbose the debugging "got voltage" (#2863) --- esphome/components/adc/adc_sensor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/adc/adc_sensor.cpp b/esphome/components/adc/adc_sensor.cpp index c8242ce008..0a439f8b8d 100644 --- a/esphome/components/adc/adc_sensor.cpp +++ b/esphome/components/adc/adc_sensor.cpp @@ -91,7 +91,7 @@ void ADCSensor::dump_config() { float ADCSensor::get_setup_priority() const { return setup_priority::DATA; } void ADCSensor::update() { float value_v = this->sample(); - ESP_LOGD(TAG, "'%s': Got voltage=%.4fV", this->get_name().c_str(), value_v); + ESP_LOGV(TAG, "'%s': Got voltage=%.4fV", this->get_name().c_str(), value_v); this->publish_state(value_v); } From 3ac720df476fef4316fcdefb4209329e2476921f Mon Sep 17 00:00:00 2001 From: Martin <25747549+martgras@users.noreply.github.com> Date: Mon, 6 Dec 2021 07:58:26 +0100 Subject: [PATCH 0131/3388] SPS30 : fix i2c read size (#2866) --- esphome/components/sps30/sps30.cpp | 26 ++++++++++++++++---------- esphome/components/sps30/sps30.h | 1 + 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/esphome/components/sps30/sps30.cpp b/esphome/components/sps30/sps30.cpp index 472b7606ed..6160120564 100644 --- a/esphome/components/sps30/sps30.cpp +++ b/esphome/components/sps30/sps30.cpp @@ -32,14 +32,11 @@ void SPS30Component::setup() { return; } - uint16_t raw_firmware_version[4]; - if (!this->read_data_(raw_firmware_version, 4)) { + if (!this->read_data_(&raw_firmware_version_, 1)) { this->error_code_ = FIRMWARE_VERSION_READ_FAILED; this->mark_failed(); return; } - ESP_LOGD(TAG, " Firmware version v%0d.%02d", (raw_firmware_version[0] >> 8), - uint16_t(raw_firmware_version[0] & 0xFF)); /// Serial number identification if (!this->write_command_(SPS30_CMD_GET_SERIAL_NUMBER)) { this->error_code_ = SERIAL_NUMBER_REQUEST_FAILED; @@ -59,6 +56,8 @@ void SPS30Component::setup() { this->serial_number_[i * 2 + 1] = uint16_t(uint16_t(raw_serial_number[i] & 0xFF)); } ESP_LOGD(TAG, " Serial Number: '%s'", this->serial_number_); + this->status_clear_warning(); + this->skipped_data_read_cycles_ = 0; this->start_continuous_measurement_(); }); } @@ -93,10 +92,17 @@ void SPS30Component::dump_config() { } LOG_UPDATE_INTERVAL(this); ESP_LOGCONFIG(TAG, " Serial Number: '%s'", this->serial_number_); - LOG_SENSOR(" ", "PM1.0", this->pm_1_0_sensor_); - LOG_SENSOR(" ", "PM2.5", this->pm_2_5_sensor_); - LOG_SENSOR(" ", "PM4", this->pm_4_0_sensor_); - LOG_SENSOR(" ", "PM10", this->pm_10_0_sensor_); + ESP_LOGCONFIG(TAG, " Firmware version v%0d.%0d", (raw_firmware_version_ >> 8), + uint16_t(raw_firmware_version_ & 0xFF)); + LOG_SENSOR(" ", "PM1.0 Weight Concentration", this->pm_1_0_sensor_); + LOG_SENSOR(" ", "PM2.5 Weight Concentration", this->pm_2_5_sensor_); + LOG_SENSOR(" ", "PM4 Weight Concentration", this->pm_4_0_sensor_); + LOG_SENSOR(" ", "PM10 Weight Concentration", this->pm_10_0_sensor_); + LOG_SENSOR(" ", "PM1.0 Number Concentration", this->pmc_1_0_sensor_); + LOG_SENSOR(" ", "PM2.5 Number Concentration", this->pmc_2_5_sensor_); + LOG_SENSOR(" ", "PM4 Number Concentration", this->pmc_4_0_sensor_); + LOG_SENSOR(" ", "PM10 Number Concentration", this->pmc_10_0_sensor_); + LOG_SENSOR(" ", "PM typical size", this->pm_size_sensor_); } void SPS30Component::update() { @@ -123,8 +129,8 @@ void SPS30Component::update() { return; } - uint16_t raw_read_status[1]; - if (!this->read_data_(raw_read_status, 1) || raw_read_status[0] == 0x00) { + uint16_t raw_read_status; + if (!this->read_data_(&raw_read_status, 1) || raw_read_status == 0x00) { ESP_LOGD(TAG, "Sensor measurement not ready yet."); this->skipped_data_read_cycles_++; /// The following logic is required to address the cases when a sensor is quickly replaced before it's marked diff --git a/esphome/components/sps30/sps30.h b/esphome/components/sps30/sps30.h index 2f977252a5..bae33a46e1 100644 --- a/esphome/components/sps30/sps30.h +++ b/esphome/components/sps30/sps30.h @@ -33,6 +33,7 @@ class SPS30Component : public PollingComponent, public i2c::I2CDevice { bool read_data_(uint16_t *data, uint8_t len); uint8_t sht_crc_(uint8_t data1, uint8_t data2); char serial_number_[17] = {0}; /// Terminating NULL character + uint16_t raw_firmware_version_; bool start_continuous_measurement_(); uint8_t skipped_data_read_cycles_ = 0; From 56870ed4a8cbe82db4c51acc8ec7d9fd9ed741a2 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Mon, 6 Dec 2021 07:59:50 +0100 Subject: [PATCH 0132/3388] Fix MCP23x17 not disabling pullup after config change (#2855) --- esphome/components/mcp23x17_base/mcp23x17_base.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/esphome/components/mcp23x17_base/mcp23x17_base.cpp b/esphome/components/mcp23x17_base/mcp23x17_base.cpp index e975670faa..744f2fbe9c 100644 --- a/esphome/components/mcp23x17_base/mcp23x17_base.cpp +++ b/esphome/components/mcp23x17_base/mcp23x17_base.cpp @@ -24,6 +24,7 @@ void MCP23X17Base::pin_mode(uint8_t pin, gpio::Flags flags) { uint8_t gppu = pin < 8 ? mcp23x17_base::MCP23X17_GPPUA : mcp23x17_base::MCP23X17_GPPUB; if (flags == gpio::FLAG_INPUT) { this->update_reg(pin, true, iodir); + this->update_reg(pin, false, gppu); } else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLUP)) { this->update_reg(pin, true, iodir); this->update_reg(pin, true, gppu); From a66e94a0b00110bc011b6eda718adebc60af65c8 Mon Sep 17 00:00:00 2001 From: Massimiliano Ravelli Date: Mon, 6 Dec 2021 08:01:50 +0100 Subject: [PATCH 0133/3388] Ignore already stopped dhcp for ethernet (#2862) Co-authored-by: Oxan van Leeuwen Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/ethernet/ethernet_component.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/esphome/components/ethernet/ethernet_component.cpp b/esphome/components/ethernet/ethernet_component.cpp index 00f68df2b4..384a31ed2f 100644 --- a/esphome/components/ethernet/ethernet_component.cpp +++ b/esphome/components/ethernet/ethernet_component.cpp @@ -184,7 +184,9 @@ void EthernetComponent::start_connect_() { } err = tcpip_adapter_dhcpc_stop(TCPIP_ADAPTER_IF_ETH); - ESPHL_ERROR_CHECK(err, "DHCPC stop error"); + if (err != ESP_ERR_TCPIP_ADAPTER_DHCP_ALREADY_STOPPED) { + ESPHL_ERROR_CHECK(err, "DHCPC stop error"); + } err = tcpip_adapter_set_ip_info(TCPIP_ADAPTER_IF_ETH, &info); ESPHL_ERROR_CHECK(err, "DHCPC set IP info error"); From c128880033c05012b47db901366db85433219486 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 6 Dec 2021 20:15:34 +1300 Subject: [PATCH 0134/3388] Add endpoint to fetch secrets keys (#2873) --- esphome/dashboard/dashboard.py | 25 ++++++++++++++++++++++++- esphome/yaml_util.py | 7 ++++--- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/esphome/dashboard/dashboard.py b/esphome/dashboard/dashboard.py index c98047d9e5..5e5cc4ecd2 100644 --- a/esphome/dashboard/dashboard.py +++ b/esphome/dashboard/dashboard.py @@ -27,7 +27,7 @@ import tornado.process import tornado.web import tornado.websocket -from esphome import const, platformio_api, util +from esphome import const, platformio_api, util, yaml_util from esphome.helpers import mkdir_p, get_bool_env, run_system_command from esphome.storage_json import ( EsphomeStorageJSON, @@ -836,6 +836,28 @@ class LogoutHandler(BaseHandler): self.redirect("./login") +class SecretKeysRequestHandler(BaseHandler): + @authenticated + def get(self): + + filename = None + + for secret_filename in const.SECRETS_FILES: + relative_filename = settings.rel_path(secret_filename) + if os.path.isfile(relative_filename): + filename = relative_filename + break + + if filename is None: + self.send_error(404) + return + + secret_keys = list(yaml_util.load_yaml(filename, clear_secrets=False)) + + self.set_header("content-type", "application/json") + self.write(json.dumps(secret_keys)) + + def get_base_frontend_path(): if ENV_DEV not in os.environ: import esphome_dashboard @@ -939,6 +961,7 @@ def make_app(debug=get_bool_env(ENV_DEV)): (f"{rel}static/(.*)", StaticFileHandler, {"path": get_static_path()}), (f"{rel}devices", ListDevicesHandler), (f"{rel}import", ImportRequestHandler), + (f"{rel}secret_keys", SecretKeysRequestHandler), ], **app_settings, ) diff --git a/esphome/yaml_util.py b/esphome/yaml_util.py index bdadbbd43a..57009be57e 100644 --- a/esphome/yaml_util.py +++ b/esphome/yaml_util.py @@ -329,9 +329,10 @@ ESPHomeLoader.add_constructor("!lambda", ESPHomeLoader.construct_lambda) ESPHomeLoader.add_constructor("!force", ESPHomeLoader.construct_force) -def load_yaml(fname): - _SECRET_VALUES.clear() - _SECRET_CACHE.clear() +def load_yaml(fname, clear_secrets=True): + if clear_secrets: + _SECRET_VALUES.clear() + _SECRET_CACHE.clear() return _load_yaml_internal(fname) From 24874f4c3cb9e9efa3d70758ccd1d4b31e03a7c3 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 6 Dec 2021 20:57:56 +1300 Subject: [PATCH 0135/3388] Adopt using wifi secrets that should exist at this point (#2874) --- esphome/components/dashboard_import/__init__.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/esphome/components/dashboard_import/__init__.py b/esphome/components/dashboard_import/__init__.py index d483c77c61..4c47c32ccc 100644 --- a/esphome/components/dashboard_import/__init__.py +++ b/esphome/components/dashboard_import/__init__.py @@ -29,12 +29,11 @@ CONFIG_SCHEMA = cv.Schema( } ) -WIFI_MESSAGE = """ +WIFI_CONFIG = """ -# Do not forget to add your own wifi configuration before installing this configuration -# wifi: -# ssid: !secret wifi_ssid -# password: !secret wifi_password +wifi: + ssid: !secret wifi_ssid + password: !secret wifi_password """ @@ -55,6 +54,6 @@ def import_config(path: str, name: str, project_name: str, import_url: str) -> N "esphome": {"name_add_mac_suffix": False}, } p.write_text( - dump(config) + WIFI_MESSAGE, + dump(config) + WIFI_CONFIG, encoding="utf8", ) From 7ee4bb621c37b58ef037a565d06d0642c1623e2c Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 6 Dec 2021 20:58:51 +1300 Subject: [PATCH 0136/3388] Allow wizard to specify secrets (#2875) --- esphome/wizard.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/esphome/wizard.py b/esphome/wizard.py index 6c87b66453..5f4f347ba7 100644 --- a/esphome/wizard.py +++ b/esphome/wizard.py @@ -86,12 +86,11 @@ def wizard_file(**kwargs): config += "\n\nwifi:\n" if "ssid" in kwargs: - # pylint: disable=consider-using-f-string - config += """ ssid: "{ssid}" - password: "{psk}" -""".format( - **kwargs - ) + if kwargs["ssid"].startswith("!secret"): + template = " ssid: {ssid}\n password: {psk}\n" + else: + template = """ ssid: "{ssid}"\n password: "{psk}"\n""" + config += template.format(**kwargs) else: config += """ # ssid: "My SSID" # password: "mypassword" From df315a1f5182a4ab18fc4c147dc370209c9f3704 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Mon, 6 Dec 2021 19:24:20 +0100 Subject: [PATCH 0137/3388] Feed watchdog when no component loops (#2857) --- esphome/core/application.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/esphome/core/application.cpp b/esphome/core/application.cpp index 1bef99e868..a423397453 100644 --- a/esphome/core/application.cpp +++ b/esphome/core/application.cpp @@ -37,6 +37,7 @@ void Application::setup() { component->call(); this->scheduler.process_to_add(); + this->feed_wdt(); if (component->can_proceed()) continue; @@ -46,14 +47,15 @@ void Application::setup() { do { uint32_t new_app_state = STATUS_LED_WARNING; this->scheduler.call(); + this->feed_wdt(); for (uint32_t j = 0; j <= i; j++) { this->components_[j]->call(); new_app_state |= this->components_[j]->get_component_state(); this->app_state_ |= new_app_state; + this->feed_wdt(); } this->app_state_ = new_app_state; yield(); - this->feed_wdt(); } while (!component->can_proceed()); } @@ -65,6 +67,7 @@ void Application::loop() { uint32_t new_app_state = 0; this->scheduler.call(); + this->feed_wdt(); for (Component *component : this->looping_components_) { { WarnIfComponentBlockingGuard guard{component}; From 09b7c6f55092501be2283b451be00132908deced Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 7 Dec 2021 07:41:40 +1300 Subject: [PATCH 0138/3388] Bump esphome-dashboard to 20211207.0 (#2877) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 22cfeecd5d..e27ae0f625 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,7 +9,7 @@ pyserial==3.5 platformio==5.2.2 # When updating platformio, also update Dockerfile esptool==3.2 click==8.0.3 -esphome-dashboard==20211206.0 +esphome-dashboard==20211207.0 aioesphomeapi==10.6.0 zeroconf==0.36.13 From ed5e2dd332643b201b1c9854556c6260bd91561e Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 7 Dec 2021 07:47:48 +1300 Subject: [PATCH 0139/3388] Bump version to 2021.12.0b3 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 40ed60cd6f..a77629ad35 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2021.12.0b2" +__version__ = "2021.12.0b3" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" From e763469af89fbd22d293424eed89dd393c78df9c Mon Sep 17 00:00:00 2001 From: Carlos Garcia Saura Date: Mon, 6 Dec 2021 23:26:06 +0100 Subject: [PATCH 0140/3388] Feed watchdog while setting up OTA (#2876) --- esphome/components/ota/ota_component.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/esphome/components/ota/ota_component.cpp b/esphome/components/ota/ota_component.cpp index 0cf5ea242c..92256eb1b6 100644 --- a/esphome/components/ota/ota_component.cpp +++ b/esphome/components/ota/ota_component.cpp @@ -372,6 +372,7 @@ bool OTAComponent::readall_(uint8_t *buf, size_t len) { ssize_t read = this->client_->read(buf + at, len - at); if (read == -1) { if (errno == EAGAIN || errno == EWOULDBLOCK) { + App.feed_wdt(); delay(1); continue; } @@ -383,6 +384,7 @@ bool OTAComponent::readall_(uint8_t *buf, size_t len) { } else { at += read; } + App.feed_wdt(); delay(1); } @@ -401,6 +403,7 @@ bool OTAComponent::writeall_(const uint8_t *buf, size_t len) { ssize_t written = this->client_->write(buf + at, len - at); if (written == -1) { if (errno == EAGAIN || errno == EWOULDBLOCK) { + App.feed_wdt(); delay(1); continue; } @@ -409,6 +412,7 @@ bool OTAComponent::writeall_(const uint8_t *buf, size_t len) { } else { at += written; } + App.feed_wdt(); delay(1); } return true; From fbc84861c7fba05e40603b513c65d73a37f27136 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 8 Dec 2021 09:22:03 +1300 Subject: [PATCH 0141/3388] Use new platform component config blocks for wizard (#2885) --- esphome/wizard.py | 24 ++++++++++++++++++++++-- tests/unit_tests/test_wizard.py | 9 +++++---- 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/esphome/wizard.py b/esphome/wizard.py index 5f4f347ba7..f2632caf71 100644 --- a/esphome/wizard.py +++ b/esphome/wizard.py @@ -45,9 +45,9 @@ OTA_BIG = r""" ____ _______ BASE_CONFIG = """esphome: name: {name} - platform: {platform} - board: {board} +""" +LOGGER_API_CONFIG = """ # Enable logging logger: @@ -55,6 +55,18 @@ logger: api: """ +ESP8266_CONFIG = """ +esp8266: + board: {board} +""" + +ESP32_CONFIG = """ +esp32: + board: {board} + framework: + type: arduino +""" + def sanitize_double_quotes(value): return value.replace("\\", "\\\\").replace('"', '\\"') @@ -71,6 +83,14 @@ def wizard_file(**kwargs): config = BASE_CONFIG.format(**kwargs) + config += ( + ESP8266_CONFIG.format(**kwargs) + if kwargs["platform"] == "ESP8266" + else ESP32_CONFIG.format(**kwargs) + ) + + config += LOGGER_API_CONFIG + # Configure API if "password" in kwargs: config += f" password: \"{kwargs['password']}\"\n" diff --git a/tests/unit_tests/test_wizard.py b/tests/unit_tests/test_wizard.py index 18e040b0a6..59fcfbff60 100644 --- a/tests/unit_tests/test_wizard.py +++ b/tests/unit_tests/test_wizard.py @@ -11,7 +11,7 @@ def default_config(): return { "name": "test-name", "platform": "test_platform", - "board": "test_board", + "board": "esp01_1m", "ssid": "test_ssid", "psk": "test_psk", "password": "", @@ -105,6 +105,7 @@ def test_wizard_write_sets_platform(default_config, tmp_path, monkeypatch): If the platform is not explicitly set, use "ESP8266" if the board is one of the ESP8266 boards """ # Given + del default_config["platform"] monkeypatch.setattr(wz, "write_file", MagicMock()) # When @@ -112,7 +113,7 @@ def test_wizard_write_sets_platform(default_config, tmp_path, monkeypatch): # Then generated_config = wz.write_file.call_args.args[1] - assert f"platform: {default_config['platform']}" in generated_config + assert "esp8266:" in generated_config def test_wizard_write_defaults_platform_from_board_esp8266( @@ -132,7 +133,7 @@ def test_wizard_write_defaults_platform_from_board_esp8266( # Then generated_config = wz.write_file.call_args.args[1] - assert "platform: ESP8266" in generated_config + assert "esp8266:" in generated_config def test_wizard_write_defaults_platform_from_board_esp32( @@ -152,7 +153,7 @@ def test_wizard_write_defaults_platform_from_board_esp32( # Then generated_config = wz.write_file.call_args.args[1] - assert "platform: ESP32" in generated_config + assert "esp32:" in generated_config def test_safe_print_step_prints_step_number_and_description(monkeypatch): From 090e10730cfcfd22df5a3252def5c09df89d1fb9 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 8 Dec 2021 12:42:50 +1300 Subject: [PATCH 0142/3388] Bump esphome-dashboard to 20211208.0 (#2887) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index e27ae0f625..f7b1a6a1fe 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,7 +9,7 @@ pyserial==3.5 platformio==5.2.2 # When updating platformio, also update Dockerfile esptool==3.2 click==8.0.3 -esphome-dashboard==20211207.0 +esphome-dashboard==20211208.0 aioesphomeapi==10.6.0 zeroconf==0.36.13 From f3d9d707b6dca605382f74bf855e2ec12139bc47 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 8 Dec 2021 12:58:14 +1300 Subject: [PATCH 0143/3388] Bump version to 2021.12.0b4 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index a77629ad35..67bbcba853 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2021.12.0b3" +__version__ = "2021.12.0b4" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" From 649366ff446f1812681e76c3de38b2834fac1a69 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Fri, 10 Dec 2021 09:32:34 +1300 Subject: [PATCH 0144/3388] Fix published state for modbus number (#2894) --- .../modbus_controller/number/modbus_number.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/esphome/components/modbus_controller/number/modbus_number.cpp b/esphome/components/modbus_controller/number/modbus_number.cpp index ba2ffdd09f..70be9eaa1f 100644 --- a/esphome/components/modbus_controller/number/modbus_number.cpp +++ b/esphome/components/modbus_controller/number/modbus_number.cpp @@ -27,6 +27,7 @@ void ModbusNumber::parse_and_publish(const std::vector &data) { void ModbusNumber::control(float value) { std::vector data; + float write_value = value; // Is there are lambda configured? if (this->write_transform_func_.has_value()) { // data is passed by reference @@ -35,23 +36,23 @@ void ModbusNumber::control(float value) { auto val = (*this->write_transform_func_)(this, value, data); if (val.has_value()) { ESP_LOGV(TAG, "Value overwritten by lambda"); - value = val.value(); + write_value = val.value(); } else { ESP_LOGV(TAG, "Communication handled by lambda - exiting control"); return; } } else { - value = multiply_by_ * value; + write_value = multiply_by_ * write_value; } // lambda didn't set payload if (data.empty()) { - data = float_to_payload(value, this->sensor_value_type); + data = float_to_payload(write_value, this->sensor_value_type); } ESP_LOGD(TAG, "Updating register: connected Sensor=%s start address=0x%X register count=%d new value=%.02f (val=%.02f)", - this->get_name().c_str(), this->start_address, this->register_count, value, value); + this->get_name().c_str(), this->start_address, this->register_count, write_value, write_value); // Create and send the write command auto write_cmd = ModbusCommandItem::create_write_multiple_command(parent_, this->start_address + this->offset, From 708b928c73e7a1a5eb9173a7b74c1decf6d6ed9d Mon Sep 17 00:00:00 2001 From: Guillermo Ruffino Date: Thu, 9 Dec 2021 17:44:43 -0300 Subject: [PATCH 0145/3388] Modbus number/output use write single (#2896) Co-authored-by: Martin <25747549+martgras@users.noreply.github.com> --- esphome/components/modbus_controller/const.py | 1 + .../components/modbus_controller/number/__init__.py | 3 +++ .../modbus_controller/number/modbus_number.cpp | 10 +++++++--- .../modbus_controller/number/modbus_number.h | 2 ++ .../components/modbus_controller/output/__init__.py | 3 +++ .../modbus_controller/output/modbus_output.cpp | 10 ++++++++-- .../modbus_controller/output/modbus_output.h | 2 ++ .../components/modbus_controller/switch/__init__.py | 2 +- 8 files changed, 27 insertions(+), 6 deletions(-) diff --git a/esphome/components/modbus_controller/const.py b/esphome/components/modbus_controller/const.py index 8d1676dd38..baf72efb94 100644 --- a/esphome/components/modbus_controller/const.py +++ b/esphome/components/modbus_controller/const.py @@ -10,5 +10,6 @@ CONF_REGISTER_COUNT = "register_count" CONF_REGISTER_TYPE = "register_type" CONF_RESPONSE_SIZE = "response_size" CONF_SKIP_UPDATES = "skip_updates" +CONF_USE_WRITE_MULTIPLE = "use_write_multiple" CONF_VALUE_TYPE = "value_type" CONF_WRITE_LAMBDA = "write_lambda" diff --git a/esphome/components/modbus_controller/number/__init__.py b/esphome/components/modbus_controller/number/__init__.py index 3c5db9b9c8..4ad6601fee 100644 --- a/esphome/components/modbus_controller/number/__init__.py +++ b/esphome/components/modbus_controller/number/__init__.py @@ -25,6 +25,7 @@ from ..const import ( CONF_FORCE_NEW_RANGE, CONF_MODBUS_CONTROLLER_ID, CONF_SKIP_UPDATES, + CONF_USE_WRITE_MULTIPLE, CONF_VALUE_TYPE, CONF_WRITE_LAMBDA, ) @@ -69,6 +70,7 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_MIN_VALUE, default=-16777215.0): cv.float_, cv.Optional(CONF_STEP, default=1): cv.positive_float, cv.Optional(CONF_MULTIPLY, default=1.0): cv.float_, + cv.Optional(CONF_USE_WRITE_MULTIPLE, default=False): cv.boolean, } ) .extend(cv.polling_component_schema("60s")), @@ -105,6 +107,7 @@ async def to_code(config): cg.add(var.set_parent(parent)) cg.add(parent.add_sensor_item(var)) await add_modbus_base_properties(var, config, ModbusNumber) + cg.add(var.set_use_write_mutiple(config[CONF_USE_WRITE_MULTIPLE])) if CONF_WRITE_LAMBDA in config: template_ = await cg.process_lambda( config[CONF_WRITE_LAMBDA], diff --git a/esphome/components/modbus_controller/number/modbus_number.cpp b/esphome/components/modbus_controller/number/modbus_number.cpp index 70be9eaa1f..e5afd0c611 100644 --- a/esphome/components/modbus_controller/number/modbus_number.cpp +++ b/esphome/components/modbus_controller/number/modbus_number.cpp @@ -55,9 +55,13 @@ void ModbusNumber::control(float value) { this->get_name().c_str(), this->start_address, this->register_count, write_value, write_value); // Create and send the write command - auto write_cmd = ModbusCommandItem::create_write_multiple_command(parent_, this->start_address + this->offset, - this->register_count, data); - + ModbusCommandItem write_cmd; + if (this->register_count == 1 && !this->use_write_multiple_) { + write_cmd = ModbusCommandItem::create_write_single_command(parent_, this->start_address + this->offset, data[0]); + } else { + write_cmd = ModbusCommandItem::create_write_multiple_command(parent_, this->start_address + this->offset, + this->register_count, data); + } // publish new value write_cmd.on_data_func = [this, write_cmd, value](ModbusRegisterType register_type, uint16_t start_address, const std::vector &data) { diff --git a/esphome/components/modbus_controller/number/modbus_number.h b/esphome/components/modbus_controller/number/modbus_number.h index 271bbfac50..c678cd00cc 100644 --- a/esphome/components/modbus_controller/number/modbus_number.h +++ b/esphome/components/modbus_controller/number/modbus_number.h @@ -35,6 +35,7 @@ class ModbusNumber : public number::Number, public Component, public SensorItem using write_transform_func_t = std::function(ModbusNumber *, float, std::vector &)>; void set_template(transform_func_t &&f) { this->transform_func_ = f; } void set_write_template(write_transform_func_t &&f) { this->write_transform_func_ = f; } + void set_use_write_mutiple(bool use_write_multiple) { this->use_write_multiple_ = use_write_multiple; } protected: void control(float value) override; @@ -42,6 +43,7 @@ class ModbusNumber : public number::Number, public Component, public SensorItem optional write_transform_func_; ModbusController *parent_; float multiply_by_{1.0}; + bool use_write_multiple_{false}; }; } // namespace modbus_controller diff --git a/esphome/components/modbus_controller/output/__init__.py b/esphome/components/modbus_controller/output/__init__.py index eacd96579f..a26d05a18b 100644 --- a/esphome/components/modbus_controller/output/__init__.py +++ b/esphome/components/modbus_controller/output/__init__.py @@ -18,6 +18,7 @@ from .. import ( from ..const import ( CONF_MODBUS_CONTROLLER_ID, + CONF_USE_WRITE_MULTIPLE, CONF_VALUE_TYPE, CONF_WRITE_LAMBDA, ) @@ -36,6 +37,7 @@ CONFIG_SCHEMA = cv.All( cv.GenerateID(): cv.declare_id(ModbusOutput), cv.Optional(CONF_WRITE_LAMBDA): cv.returning_lambda, cv.Optional(CONF_MULTIPLY, default=1.0): cv.float_, + cv.Optional(CONF_USE_WRITE_MULTIPLE, default=False): cv.boolean, } ), validate_modbus_register, @@ -54,6 +56,7 @@ async def to_code(config): await output.register_output(var, config) cg.add(var.set_write_multiply(config[CONF_MULTIPLY])) parent = await cg.get_variable(config[CONF_MODBUS_CONTROLLER_ID]) + cg.add(var.set_use_write_mutiple(config[CONF_USE_WRITE_MULTIPLE])) cg.add(var.set_parent(parent)) if CONF_WRITE_LAMBDA in config: template_ = await cg.process_lambda( diff --git a/esphome/components/modbus_controller/output/modbus_output.cpp b/esphome/components/modbus_controller/output/modbus_output.cpp index d2b5d02bda..4c2e5775b9 100644 --- a/esphome/components/modbus_controller/output/modbus_output.cpp +++ b/esphome/components/modbus_controller/output/modbus_output.cpp @@ -40,8 +40,14 @@ void ModbusOutput::write_state(float value) { this->start_address, this->register_count, value, original_value); // Create and send the write command - auto write_cmd = - ModbusCommandItem::create_write_multiple_command(parent_, this->start_address, this->register_count, data); + // Create and send the write command + ModbusCommandItem write_cmd; + if (this->register_count == 1 && !this->use_write_multiple_) { + write_cmd = ModbusCommandItem::create_write_single_command(parent_, this->start_address + this->offset, data[0]); + } else { + write_cmd = ModbusCommandItem::create_write_multiple_command(parent_, this->start_address + this->offset, + this->register_count, data); + } parent_->queue_command(write_cmd); } diff --git a/esphome/components/modbus_controller/output/modbus_output.h b/esphome/components/modbus_controller/output/modbus_output.h index 6e8521854b..78d3474ad6 100644 --- a/esphome/components/modbus_controller/output/modbus_output.h +++ b/esphome/components/modbus_controller/output/modbus_output.h @@ -33,6 +33,7 @@ class ModbusOutput : public output::FloatOutput, public Component, public Sensor using write_transform_func_t = std::function(ModbusOutput *, float, std::vector &)>; void set_write_template(write_transform_func_t &&f) { this->write_transform_func_ = f; } + void set_use_write_mutiple(bool use_write_multiple) { this->use_write_multiple_ = use_write_multiple; } protected: void write_state(float value) override; @@ -40,6 +41,7 @@ class ModbusOutput : public output::FloatOutput, public Component, public Sensor ModbusController *parent_; float multiply_by_{1.0}; + bool use_write_multiple_; }; } // namespace modbus_controller diff --git a/esphome/components/modbus_controller/switch/__init__.py b/esphome/components/modbus_controller/switch/__init__.py index df11b268ac..9858d45617 100644 --- a/esphome/components/modbus_controller/switch/__init__.py +++ b/esphome/components/modbus_controller/switch/__init__.py @@ -18,10 +18,10 @@ from ..const import ( CONF_FORCE_NEW_RANGE, CONF_MODBUS_CONTROLLER_ID, CONF_REGISTER_TYPE, + CONF_USE_WRITE_MULTIPLE, CONF_WRITE_LAMBDA, ) -CONF_USE_WRITE_MULTIPLE = "use_write_multiple" DEPENDENCIES = ["modbus_controller"] CODEOWNERS = ["@martgras"] From 3bf632003056dd93c0fbc22cbcbeb199340a9dbd Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Fri, 10 Dec 2021 09:55:48 +1300 Subject: [PATCH 0146/3388] Bump version to 2021.12.0b5 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 67bbcba853..e284ee6414 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2021.12.0b4" +__version__ = "2021.12.0b5" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" From d504daef9102b0eccfa1a3d2d9f5c7dd16c09160 Mon Sep 17 00:00:00 2001 From: Keith Burzinski Date: Sat, 11 Dec 2021 01:03:22 -0600 Subject: [PATCH 0147/3388] Fix for two points setting when fan_only_cooling is disabled (#2903) Co-authored-by: Paulus Schoutsen Co-authored-by: Keith Burzinski --- esphome/components/thermostat/climate.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/esphome/components/thermostat/climate.py b/esphome/components/thermostat/climate.py index 7b5ee7c624..20565e811c 100644 --- a/esphome/components/thermostat/climate.py +++ b/esphome/components/thermostat/climate.py @@ -431,7 +431,8 @@ async def to_code(config): heat_cool_mode_available = CONF_HEAT_ACTION in config and CONF_COOL_ACTION in config two_points_available = CONF_HEAT_ACTION in config and ( - CONF_COOL_ACTION in config or CONF_FAN_ONLY_ACTION in config + CONF_COOL_ACTION in config + or (config[CONF_FAN_ONLY_COOLING] and CONF_FAN_ONLY_ACTION in config) ) sens = await cg.get_variable(config[CONF_SENSOR]) From bfaa64883794d1167d80e4d12ba8c42862861d0e Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Sat, 11 Dec 2021 21:03:41 +1300 Subject: [PATCH 0148/3388] Bump esphome-dashboard to 20211211.0 (#2904) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index f7b1a6a1fe..c45797a71f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,7 +9,7 @@ pyserial==3.5 platformio==5.2.2 # When updating platformio, also update Dockerfile esptool==3.2 click==8.0.3 -esphome-dashboard==20211208.0 +esphome-dashboard==20211211.0 aioesphomeapi==10.6.0 zeroconf==0.36.13 From 08057720b81b894e74e1afea9278afb9631755dd Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Sat, 11 Dec 2021 21:07:07 +1300 Subject: [PATCH 0149/3388] Bump version to 2021.12.0b6 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index e284ee6414..a078ca56a6 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2021.12.0b5" +__version__ = "2021.12.0b6" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" From 8c9e0e552dab8ac7c04fc8b1142fe650671b8bfb Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Sun, 12 Dec 2021 07:10:51 +1300 Subject: [PATCH 0150/3388] Bump version to 2021.12.0 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index a078ca56a6..1508f9b78a 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2021.12.0b6" +__version__ = "2021.12.0" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" From e32a999cd0bb8a4dec5c8dc266407bfad93e7197 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Mon, 13 Dec 2021 03:21:09 +0100 Subject: [PATCH 0151/3388] Set text sensor state property to filter output (#2893) --- esphome/components/text_sensor/text_sensor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/text_sensor/text_sensor.cpp b/esphome/components/text_sensor/text_sensor.cpp index 0bcab90843..5d47e7465a 100644 --- a/esphome/components/text_sensor/text_sensor.cpp +++ b/esphome/components/text_sensor/text_sensor.cpp @@ -62,7 +62,7 @@ void TextSensor::add_on_raw_state_callback(std::function call std::string TextSensor::get_state() const { return this->state; } std::string TextSensor::get_raw_state() const { return this->raw_state; } void TextSensor::internal_send_state_to_frontend(const std::string &state) { - this->state = this->raw_state; + this->state = state; this->has_state_ = true; ESP_LOGD(TAG, "'%s': Sending state '%s'", this->name_.c_str(), state.c_str()); this->callback_.call(state); From 386a5b63629775e74fc8ddd85598c2eed8fb1f57 Mon Sep 17 00:00:00 2001 From: wilberforce Date: Tue, 14 Dec 2021 15:08:01 +1300 Subject: [PATCH 0152/3388] Allow button POST on press from web server (#2913) --- esphome/components/web_server/web_server.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index 29cb4827bd..1e7696edfb 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -390,8 +390,6 @@ void WebServer::handle_switch_request(AsyncWebServerRequest *request, const UrlM #ifdef USE_BUTTON void WebServer::handle_button_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (button::Button *obj : App.get_buttons()) { - if (obj->is_internal()) - continue; if (obj->get_object_id() != match.id) continue; From 4bb779d9a565caf562e16a0169cf35c099282b51 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 15 Dec 2021 14:57:32 +1300 Subject: [PATCH 0153/3388] Bump version to 2021.12.1 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 1508f9b78a..a47d90f401 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2021.12.0" +__version__ = "2021.12.1" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" From 6d39f64be714ca4b94b6e2a6219fdd7270339d93 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Mon, 6 Dec 2021 08:01:14 +0100 Subject: [PATCH 0154/3388] Don't disable idle task WDT when it's not enabled (#2856) --- esphome/components/esp32/core.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/esphome/components/esp32/core.cpp b/esphome/components/esp32/core.cpp index a9756b41cd..6123d83a34 100644 --- a/esphome/components/esp32/core.cpp +++ b/esphome/components/esp32/core.cpp @@ -42,11 +42,11 @@ void arch_init() { // Idle task watchdog is disabled on ESP-IDF #elif defined(USE_ARDUINO) enableLoopWDT(); - // Disable idle task watchdog on the core we're using (Arduino pins the process to a core) -#if CONFIG_ARDUINO_RUNNING_CORE == 0 + // Disable idle task watchdog on the core we're using (Arduino pins the task to a core) +#if defined(CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0) && CONFIG_ARDUINO_RUNNING_CORE == 0 disableCore0WDT(); #endif -#if CONFIG_ARDUINO_RUNNING_CORE == 1 +#if defined(CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU1) && CONFIG_ARDUINO_RUNNING_CORE == 1 disableCore1WDT(); #endif #endif From 9471df0a1b7b4933610bed6c6b79838f38b9370b Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Mon, 20 Dec 2021 20:19:20 +0100 Subject: [PATCH 0155/3388] Fix MQTT button press action (#2917) --- esphome/components/mqtt/mqtt_button.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/esphome/components/mqtt/mqtt_button.cpp b/esphome/components/mqtt/mqtt_button.cpp index 25ff327cf9..5f3aaa1dd9 100644 --- a/esphome/components/mqtt/mqtt_button.cpp +++ b/esphome/components/mqtt/mqtt_button.cpp @@ -17,7 +17,7 @@ MQTTButtonComponent::MQTTButtonComponent(button::Button *button) : MQTTComponent void MQTTButtonComponent::setup() { this->subscribe(this->get_command_topic_(), [this](const std::string &topic, const std::string &payload) { - if (payload == "press") { + if (payload == "PRESS") { this->button_->press(); } else { ESP_LOGW(TAG, "'%s': Received unknown status payload: %s", this->friendly_name().c_str(), payload.c_str()); @@ -31,6 +31,7 @@ void MQTTButtonComponent::dump_config() { } void MQTTButtonComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) { + config.state_topic = false; if (!this->button_->get_device_class().empty()) root[MQTT_DEVICE_CLASS] = this->button_->get_device_class(); } From 5516f65971da8a0a3c6e073bb31392b6f5d8766e Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 21 Dec 2021 08:24:08 +1300 Subject: [PATCH 0156/3388] Bump version to 2021.12.2 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index a47d90f401..614288ba51 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2021.12.1" +__version__ = "2021.12.2" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" From ffd4280d6c7af4ab7e0ee8f5fe8373531419d413 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 22 Dec 2021 20:08:54 +1300 Subject: [PATCH 0157/3388] Require arduino in webserver for better validation (#2941) --- esphome/components/web_server/__init__.py | 57 +++++++++++++---------- 1 file changed, 32 insertions(+), 25 deletions(-) diff --git a/esphome/components/web_server/__init__.py b/esphome/components/web_server/__init__.py index d9ff84d501..62d5ec6f14 100644 --- a/esphome/components/web_server/__init__.py +++ b/esphome/components/web_server/__init__.py @@ -22,31 +22,38 @@ AUTO_LOAD = ["json", "web_server_base"] web_server_ns = cg.esphome_ns.namespace("web_server") WebServer = web_server_ns.class_("WebServer", cg.Component, cg.Controller) -CONFIG_SCHEMA = cv.Schema( - { - cv.GenerateID(): cv.declare_id(WebServer), - cv.Optional(CONF_PORT, default=80): cv.port, - cv.Optional( - CONF_CSS_URL, default="https://esphome.io/_static/webserver-v1.min.css" - ): cv.string, - cv.Optional(CONF_CSS_INCLUDE): cv.file_, - cv.Optional( - CONF_JS_URL, default="https://esphome.io/_static/webserver-v1.min.js" - ): cv.string, - cv.Optional(CONF_JS_INCLUDE): cv.file_, - cv.Optional(CONF_AUTH): cv.Schema( - { - cv.Required(CONF_USERNAME): cv.All(cv.string_strict, cv.Length(min=1)), - cv.Required(CONF_PASSWORD): cv.All(cv.string_strict, cv.Length(min=1)), - } - ), - cv.GenerateID(CONF_WEB_SERVER_BASE_ID): cv.use_id( - web_server_base.WebServerBase - ), - cv.Optional(CONF_INCLUDE_INTERNAL, default=False): cv.boolean, - cv.Optional(CONF_OTA, default=True): cv.boolean, - } -).extend(cv.COMPONENT_SCHEMA) +CONFIG_SCHEMA = cv.All( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(WebServer), + cv.Optional(CONF_PORT, default=80): cv.port, + cv.Optional( + CONF_CSS_URL, default="https://esphome.io/_static/webserver-v1.min.css" + ): cv.string, + cv.Optional(CONF_CSS_INCLUDE): cv.file_, + cv.Optional( + CONF_JS_URL, default="https://esphome.io/_static/webserver-v1.min.js" + ): cv.string, + cv.Optional(CONF_JS_INCLUDE): cv.file_, + cv.Optional(CONF_AUTH): cv.Schema( + { + cv.Required(CONF_USERNAME): cv.All( + cv.string_strict, cv.Length(min=1) + ), + cv.Required(CONF_PASSWORD): cv.All( + cv.string_strict, cv.Length(min=1) + ), + } + ), + cv.GenerateID(CONF_WEB_SERVER_BASE_ID): cv.use_id( + web_server_base.WebServerBase + ), + cv.Optional(CONF_INCLUDE_INTERNAL, default=False): cv.boolean, + cv.Optional(CONF_OTA, default=True): cv.boolean, + }, + ).extend(cv.COMPONENT_SCHEMA), + cv.only_with_arduino, +) @coroutine_with_priority(40.0) From fc0a6546a221984b6a7e124d497aa8a669955c94 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 23 Dec 2021 08:31:56 +1300 Subject: [PATCH 0158/3388] Only allow internal pins for dht sensor (#2940) --- esphome/components/dht/sensor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/dht/sensor.py b/esphome/components/dht/sensor.py index 1334f0270c..cd1886728e 100644 --- a/esphome/components/dht/sensor.py +++ b/esphome/components/dht/sensor.py @@ -33,7 +33,7 @@ DHT = dht_ns.class_("DHT", cg.PollingComponent) CONFIG_SCHEMA = cv.Schema( { cv.GenerateID(): cv.declare_id(DHT), - cv.Required(CONF_PIN): pins.gpio_input_pin_schema, + cv.Required(CONF_PIN): pins.internal_gpio_input_pin_schema, cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( unit_of_measurement=UNIT_CELSIUS, accuracy_decimals=1, From 41879e41e6227dc2273ca083831da80a4938241f Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 23 Dec 2021 08:43:17 +1300 Subject: [PATCH 0159/3388] Workaround installing as editable package not working (#2936) --- docker/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 62a64c851d..7df2fbf3d8 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -64,7 +64,7 @@ RUN \ # Copy esphome and install COPY . /esphome -RUN pip3 install --no-cache-dir -e /esphome +RUN pip3 install --no-cache-dir /esphome # Settings for dashboard ENV USERNAME="" PASSWORD="" From 28f87dc8045e0f1af7794c121f20ca943f2f2ad0 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 30 Dec 2021 10:42:22 +1300 Subject: [PATCH 0160/3388] Remove -e for hassio images (#2964) --- docker/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 7df2fbf3d8..eece2108f7 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -112,7 +112,7 @@ RUN \ # Copy esphome and install COPY . /esphome -RUN pip3 install --no-cache-dir -e /esphome +RUN pip3 install --no-cache-dir /esphome # Labels LABEL \ From b37739eec2fb39bb97602411a71a949da82902ee Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 30 Dec 2021 13:58:47 +1300 Subject: [PATCH 0161/3388] Bump version to 2021.12.3 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 614288ba51..9ed0975cd4 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2021.12.2" +__version__ = "2021.12.3" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" From d32633b3c7604d5a3415db6c4d14a52fba5c96e5 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 22 Dec 2021 15:27:34 +1300 Subject: [PATCH 0162/3388] Update curl package version in docker (#2939) --- docker/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index eece2108f7..25f2cf85d2 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -27,7 +27,7 @@ RUN \ python3-cryptography=3.3.2-1 \ iputils-ping=3:20210202-1 \ git=1:2.30.2-1 \ - curl=7.74.0-1.3+b1 \ + curl=7.74.0-1.3+deb11u1 \ && rm -rf \ /tmp/* \ /var/{cache,log}/* \ From 961c27f1c27ff8e9496d669254f2fb522e3361f6 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 13 Jan 2022 11:02:07 +1300 Subject: [PATCH 0163/3388] Bump version to 2022.1.0b1 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index fdc880caf3..a26e3f050f 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2022.1.0-dev" +__version__ = "2022.1.0b1" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" From d6009453df6d79c693ae8cb1c5cc554aa570ed28 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 12 Jan 2022 22:35:30 -0800 Subject: [PATCH 0164/3388] Add factory to download name (#3040) --- esphome/dashboard/dashboard.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/dashboard/dashboard.py b/esphome/dashboard/dashboard.py index ca257d93b4..f9ae3a4fc8 100644 --- a/esphome/dashboard/dashboard.py +++ b/esphome/dashboard/dashboard.py @@ -416,7 +416,7 @@ class DownloadBinaryRequestHandler(BaseHandler): if storage_json is None: self.send_error(404) return - filename = f"{storage_json.name}.bin" + filename = f"{storage_json.name}-factory.bin" path = storage_json.firmware_bin_path.replace( "firmware.bin", "firmware-factory.bin" ) From 6dfe3039d03e585dc22c23c35c597580b60f62b3 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 12 Jan 2022 23:22:19 -0800 Subject: [PATCH 0165/3388] Bump dashboard to 20220113.2 (#3041) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index b21218f511..05636da805 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,7 +9,7 @@ pyserial==3.5 platformio==5.2.4 # When updating platformio, also update Dockerfile esptool==3.2 click==8.0.3 -esphome-dashboard==20220113.1 +esphome-dashboard==20220113.2 aioesphomeapi==10.6.0 zeroconf==0.37.0 From cbe30924049be5caa16a44fbf959ef8e0ae72afe Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 13 Jan 2022 21:28:45 +1300 Subject: [PATCH 0166/3388] Bump version to 2022.1.0b2 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index a26e3f050f..daa61ebd9b 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2022.1.0b1" +__version__ = "2022.1.0b2" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" From d274545e770c11784a20ec14ad697d03ec907583 Mon Sep 17 00:00:00 2001 From: Ohad Lutzky Date: Sun, 16 Jan 2022 22:14:45 +0000 Subject: [PATCH 0167/3388] Disable caching for binary download (#3054) --- esphome/dashboard/dashboard.py | 1 + 1 file changed, 1 insertion(+) diff --git a/esphome/dashboard/dashboard.py b/esphome/dashboard/dashboard.py index f9ae3a4fc8..c68d037fe6 100644 --- a/esphome/dashboard/dashboard.py +++ b/esphome/dashboard/dashboard.py @@ -445,6 +445,7 @@ class DownloadBinaryRequestHandler(BaseHandler): self.set_header("Content-Type", "application/octet-stream") self.set_header("Content-Disposition", f'attachment; filename="{filename}"') + self.set_header("Cache-Control", "no-cache") if not Path(path).is_file(): self.send_error(404) return From 282313ab52f4cf6433767e95eddcfa486e1ed5c6 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Sun, 16 Jan 2022 23:15:11 +0100 Subject: [PATCH 0168/3388] Rename post_build scripts to fix codeowners script (#3057) --- esphome/components/esp32/__init__.py | 2 +- .../components/esp32/{post_build.py => post_build.py.script} | 0 esphome/components/esp8266/__init__.py | 2 +- .../components/esp8266/{post_build.py => post_build.py.script} | 0 4 files changed, 2 insertions(+), 2 deletions(-) rename esphome/components/esp32/{post_build.py => post_build.py.script} (100%) rename esphome/components/esp8266/{post_build.py => post_build.py.script} (100%) diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index 161803eaf4..8214886f8c 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -417,7 +417,7 @@ def copy_files(): ) dir = os.path.dirname(__file__) - post_build_file = os.path.join(dir, "post_build.py") + post_build_file = os.path.join(dir, "post_build.py.script") copy_file_if_changed( post_build_file, CORE.relative_build_path("post_build.py"), diff --git a/esphome/components/esp32/post_build.py b/esphome/components/esp32/post_build.py.script similarity index 100% rename from esphome/components/esp32/post_build.py rename to esphome/components/esp32/post_build.py.script diff --git a/esphome/components/esp8266/__init__.py b/esphome/components/esp8266/__init__.py index 34a4a2fadb..7182042770 100644 --- a/esphome/components/esp8266/__init__.py +++ b/esphome/components/esp8266/__init__.py @@ -220,7 +220,7 @@ async def to_code(config): def copy_files(): dir = os.path.dirname(__file__) - post_build_file = os.path.join(dir, "post_build.py") + post_build_file = os.path.join(dir, "post_build.py.script") copy_file_if_changed( post_build_file, CORE.relative_build_path("post_build.py"), diff --git a/esphome/components/esp8266/post_build.py b/esphome/components/esp8266/post_build.py.script similarity index 100% rename from esphome/components/esp8266/post_build.py rename to esphome/components/esp8266/post_build.py.script From c5eba0451771620ee75d6a0f5cc757b205867ff7 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Sun, 16 Jan 2022 23:40:15 +0100 Subject: [PATCH 0169/3388] Remove deprecated attribute from virtual entity methods (#3056) --- .../components/binary_sensor/binary_sensor.h | 6 +++-- esphome/components/cover/cover.h | 6 ++++- esphome/components/sensor/sensor.h | 24 ++++++++++++------- 3 files changed, 25 insertions(+), 11 deletions(-) diff --git a/esphome/components/binary_sensor/binary_sensor.h b/esphome/components/binary_sensor/binary_sensor.h index ecf68de74c..591f444387 100644 --- a/esphome/components/binary_sensor/binary_sensor.h +++ b/esphome/components/binary_sensor/binary_sensor.h @@ -74,8 +74,10 @@ class BinarySensor : public EntityBase { // ========== OVERRIDE METHODS ========== // (You'll only need this when creating your own custom binary sensor) - /// Get the default device class for this sensor, or empty string for no default. - ESPDEPRECATED("device_class() is deprecated, set property during config validation instead.", "2022.01") + /** Override this to set the default device class. + * + * @deprecated This method is deprecated, set the property during config validation instead. (2022.1) + */ virtual std::string device_class(); protected: diff --git a/esphome/components/cover/cover.h b/esphome/components/cover/cover.h index 779e4a2a46..1b5d3a8fa1 100644 --- a/esphome/components/cover/cover.h +++ b/esphome/components/cover/cover.h @@ -169,7 +169,11 @@ class Cover : public EntityBase { friend CoverCall; virtual void control(const CoverCall &call) = 0; - ESPDEPRECATED("device_class() is deprecated, set property during config validation instead.", "2022.01") + + /** Override this to set the default device class. + * + * @deprecated This method is deprecated, set the property during config validation instead. (2022.1) + */ virtual std::string device_class(); optional restore_state_(); diff --git a/esphome/components/sensor/sensor.h b/esphome/components/sensor/sensor.h index 794aecca95..d31fe9d834 100644 --- a/esphome/components/sensor/sensor.h +++ b/esphome/components/sensor/sensor.h @@ -150,20 +150,28 @@ class Sensor : public EntityBase { void internal_send_state_to_frontend(float state); protected: - /// Override this to set the default unit of measurement. - ESPDEPRECATED("unit_of_measurement() is deprecated, set property during config validation instead.", "2022.01") + /** Override this to set the default unit of measurement. + * + * @deprecated This method is deprecated, set the property during config validation instead. (2022.1) + */ virtual std::string unit_of_measurement(); // NOLINT - /// Override this to set the default accuracy in decimals. - ESPDEPRECATED("accuracy_decimals() is deprecated, set property during config validation instead.", "2022.01") + /** Override this to set the default accuracy in decimals. + * + * @deprecated This method is deprecated, set the property during config validation instead. (2022.1) + */ virtual int8_t accuracy_decimals(); // NOLINT - /// Override this to set the default device class. - ESPDEPRECATED("device_class() is deprecated, set property during config validation instead.", "2022.01") + /** Override this to set the default device class. + * + * @deprecated This method is deprecated, set the property during config validation instead. (2022.1) + */ virtual std::string device_class(); // NOLINT - /// Override this to set the default state class. - ESPDEPRECATED("state_class() is deprecated, set property during config validation instead.", "2022.01") + /** Override this to set the default state class. + * + * @deprecated This method is deprecated, set the property during config validation instead. (2022.1) + */ virtual StateClass state_class(); // NOLINT uint32_t hash_base() override; From 01b62a16c3eb081edf34a99196e0c7f17de0525f Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 17 Jan 2022 12:31:44 +1300 Subject: [PATCH 0170/3388] Add number setting to web_server/rest_api (#3055) --- esphome/components/web_server/web_server.cpp | 45 +++++++++++++++++--- 1 file changed, 38 insertions(+), 7 deletions(-) diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index 4cc77da256..7413af67c4 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -236,7 +236,18 @@ void WebServer::handle_index_request(AsyncWebServerRequest *request) { #ifdef USE_NUMBER for (auto *obj : App.get_numbers()) if (this->include_internal_ || !obj->is_internal()) - write_row(stream, obj, "number", ""); + write_row(stream, obj, "number", "", [](AsyncResponseStream &stream, EntityBase *obj) { + number::Number *number = (number::Number *) obj; + stream.print(R"(traits.get_min_value()); + stream.print(R"(" max=")"); + stream.print(number->traits.get_max_value()); + stream.print(R"(" step=")"); + stream.print(number->traits.get_step()); + stream.print(R"(" value=")"); + stream.print(number->state); + stream.print(R"("/>)"); + }); #endif #ifdef USE_SELECT @@ -652,8 +663,29 @@ void WebServer::handle_number_request(AsyncWebServerRequest *request, const UrlM for (auto *obj : App.get_numbers()) { if (obj->get_object_id() != match.id) continue; - std::string data = this->number_json(obj, obj->state); - request->send(200, "text/json", data.c_str()); + + if (request->method() == HTTP_GET) { + std::string data = this->number_json(obj, obj->state); + request->send(200, "text/json", data.c_str()); + return; + } + + if (match.method != "set") { + request->send(404); + return; + } + + auto call = obj->make_call(); + + if (request->hasParam("value")) { + String value = request->getParam("value")->value(); + optional value_f = parse_number(value.c_str()); + if (value_f.has_value()) + call.set_value(*value_f); + } + + this->defer([call]() mutable { call.perform(); }); + request->send(200); return; } request->send(404); @@ -661,9 +693,8 @@ void WebServer::handle_number_request(AsyncWebServerRequest *request, const UrlM std::string WebServer::number_json(number::Number *obj, float value) { return json::build_json([obj, value](JsonObject root) { root["id"] = "number-" + obj->get_object_id(); - char buffer[64]; - snprintf(buffer, sizeof(buffer), "%f", value); - root["state"] = buffer; + std::string state = str_sprintf("%f", value); + root["state"] = state; root["value"] = value; }); } @@ -769,7 +800,7 @@ bool WebServer::canHandle(AsyncWebServerRequest *request) { #endif #ifdef USE_NUMBER - if (request->method() == HTTP_GET && match.domain == "number") + if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain == "number") return true; #endif From afbf9897154776c77444a788679847060da908e6 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 17 Jan 2022 12:40:07 +1300 Subject: [PATCH 0171/3388] Bump version to 2022.1.0b3 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index daa61ebd9b..9005cc927a 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2022.1.0b2" +__version__ = "2022.1.0b3" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" From 348f880e150d76aca5ff0c2a0dfd68fee32010b4 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 17 Jan 2022 11:44:18 -0800 Subject: [PATCH 0172/3388] bump dashboard to 20220116.0 (#3061) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 05636da805..9add417bdf 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,7 +9,7 @@ pyserial==3.5 platformio==5.2.4 # When updating platformio, also update Dockerfile esptool==3.2 click==8.0.3 -esphome-dashboard==20220113.2 +esphome-dashboard==20220116.0 aioesphomeapi==10.6.0 zeroconf==0.37.0 From 7b03e07908497c98069ca864e13eaf81403a1101 Mon Sep 17 00:00:00 2001 From: Martin <25747549+martgras@users.noreply.github.com> Date: Mon, 17 Jan 2022 21:05:13 +0100 Subject: [PATCH 0173/3388] [modbus_controller] add missing skip_updates (#3063) --- esphome/components/modbus_controller/switch/__init__.py | 2 ++ esphome/components/modbus_controller/switch/modbus_switch.h | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/esphome/components/modbus_controller/switch/__init__.py b/esphome/components/modbus_controller/switch/__init__.py index 9858d45617..0dfbd83cb8 100644 --- a/esphome/components/modbus_controller/switch/__init__.py +++ b/esphome/components/modbus_controller/switch/__init__.py @@ -18,6 +18,7 @@ from ..const import ( CONF_FORCE_NEW_RANGE, CONF_MODBUS_CONTROLLER_ID, CONF_REGISTER_TYPE, + CONF_SKIP_UPDATES, CONF_USE_WRITE_MULTIPLE, CONF_WRITE_LAMBDA, ) @@ -53,6 +54,7 @@ async def to_code(config): config[CONF_ADDRESS], byte_offset, config[CONF_BITMASK], + config[CONF_SKIP_UPDATES], config[CONF_FORCE_NEW_RANGE], ) await cg.register_component(var, config) diff --git a/esphome/components/modbus_controller/switch/modbus_switch.h b/esphome/components/modbus_controller/switch/modbus_switch.h index 5ac2af01a1..6732c01eef 100644 --- a/esphome/components/modbus_controller/switch/modbus_switch.h +++ b/esphome/components/modbus_controller/switch/modbus_switch.h @@ -10,14 +10,14 @@ namespace modbus_controller { class ModbusSwitch : public Component, public switch_::Switch, public SensorItem { public: ModbusSwitch(ModbusRegisterType register_type, uint16_t start_address, uint8_t offset, uint32_t bitmask, - bool force_new_range) + uint8_t skip_updates, bool force_new_range) : Component(), switch_::Switch() { this->register_type = register_type; this->start_address = start_address; this->offset = offset; this->bitmask = bitmask; this->sensor_value_type = SensorValueType::BIT; - this->skip_updates = 0; + this->skip_updates = skip_updates; this->register_count = 1; if (register_type == ModbusRegisterType::HOLDING || register_type == ModbusRegisterType::COIL) { this->start_address += offset; From 869743a7427f33e8e61fa9c4429b040ab0bf2561 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Tue, 18 Jan 2022 02:29:57 +0100 Subject: [PATCH 0174/3388] Fail hard if no random bytes available for encryption (#3067) --- esphome/components/api/api_frame_helper.cpp | 8 +++++++- esphome/core/helpers.cpp | 7 +++---- esphome/core/helpers.h | 2 +- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/esphome/components/api/api_frame_helper.cpp b/esphome/components/api/api_frame_helper.cpp index 094dd67e33..d9eadb2aaa 100644 --- a/esphome/components/api/api_frame_helper.cpp +++ b/esphome/components/api/api_frame_helper.cpp @@ -1,6 +1,7 @@ #include "api_frame_helper.h" #include "esphome/core/log.h" +#include "esphome/core/hal.h" #include "esphome/core/helpers.h" #include "proto.h" #include @@ -721,7 +722,12 @@ APIError APINoiseFrameHelper::shutdown(int how) { } extern "C" { // declare how noise generates random bytes (here with a good HWRNG based on the RF system) -void noise_rand_bytes(void *output, size_t len) { esphome::random_bytes(reinterpret_cast(output), len); } +void noise_rand_bytes(void *output, size_t len) { + if (!esphome::random_bytes(reinterpret_cast(output), len)) { + ESP_LOGE(TAG, "Failed to acquire random bytes, rebooting!"); + arch_restart(); + } +} } #endif // USE_API_NOISE diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index e15e3a8ea3..5f29abe579 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -287,13 +287,12 @@ uint32_t random_uint32() { #endif } float random_float() { return static_cast(random_uint32()) / static_cast(UINT32_MAX); } -void random_bytes(uint8_t *data, size_t len) { +bool random_bytes(uint8_t *data, size_t len) { #ifdef USE_ESP32 esp_fill_random(data, len); + return true; #elif defined(USE_ESP8266) - if (os_get_random(data, len) != 0) { - ESP_LOGE(TAG, "Failed to generate random bytes!"); - } + return os_get_random(data, len) == 0; #else #error "No random source available for this configuration." #endif diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index f071b4a814..c9a27a2fab 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -311,7 +311,7 @@ uint32_t random_uint32(); /// Return a random float between 0 and 1. float random_float(); /// Generate \p len number of random bytes. -void random_bytes(uint8_t *data, size_t len); +bool random_bytes(uint8_t *data, size_t len); ///@} From 72d60f30f79a89c1b2506a32a22832d6a91c3e19 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 18 Jan 2022 15:49:31 +1300 Subject: [PATCH 0175/3388] Bump version to 2022.1.0b4 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 9005cc927a..a94902d946 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2022.1.0b3" +__version__ = "2022.1.0b4" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" From 9296a078a7fea5c837b8c4c6b0d75096b7ed0e63 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 19 Jan 2022 16:08:27 +1300 Subject: [PATCH 0176/3388] Bump version to 2022.1.0 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index a94902d946..637cf43928 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2022.1.0b4" +__version__ = "2022.1.0" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" From c19458696e78327516ae1559e2336f07aeb30114 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 20 Jan 2022 08:33:13 +1300 Subject: [PATCH 0177/3388] Add *.py.script files to distributions (#3074) --- MANIFEST.in | 1 + 1 file changed, 1 insertion(+) diff --git a/MANIFEST.in b/MANIFEST.in index 0fe80762b3..a3126404f2 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -4,4 +4,5 @@ include requirements.txt include esphome/dashboard/templates/*.html recursive-include esphome/dashboard/static *.ico *.js *.css *.woff* LICENSE recursive-include esphome *.cpp *.h *.tcc +recursive-include esphome *.py.script recursive-include esphome LICENSE.txt From cd6f4fb93fbad3df8dc2539611931da33d7d323a Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 20 Jan 2022 08:34:18 +1300 Subject: [PATCH 0178/3388] Bump version to 2022.1.1 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 637cf43928..a9f4f0900a 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2022.1.0" +__version__ = "2022.1.1" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" From 76a238912b2b474c928e5ce0bceba1e05b15ebdd Mon Sep 17 00:00:00 2001 From: Martin <25747549+martgras@users.noreply.github.com> Date: Wed, 19 Jan 2022 21:19:24 +0100 Subject: [PATCH 0179/3388] [modbus_controller] fix incorrect start address for number write (#3073) --- .../components/modbus_controller/number/modbus_number.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/esphome/components/modbus_controller/number/modbus_number.cpp b/esphome/components/modbus_controller/number/modbus_number.cpp index 5e977f5df4..a0e990d272 100644 --- a/esphome/components/modbus_controller/number/modbus_number.cpp +++ b/esphome/components/modbus_controller/number/modbus_number.cpp @@ -57,9 +57,11 @@ void ModbusNumber::control(float value) { // Create and send the write command ModbusCommandItem write_cmd; if (this->register_count == 1 && !this->use_write_multiple_) { - write_cmd = ModbusCommandItem::create_write_single_command(parent_, this->start_address + this->offset, data[0]); + // since offset is in bytes and a register is 16 bits we get the start by adding offset/2 + write_cmd = + ModbusCommandItem::create_write_single_command(parent_, this->start_address + this->offset / 2, data[0]); } else { - write_cmd = ModbusCommandItem::create_write_multiple_command(parent_, this->start_address + this->offset, + write_cmd = ModbusCommandItem::create_write_multiple_command(parent_, this->start_address + this->offset / 2, this->register_count, data); } // publish new value From 46af4cad6ef8cceddd6787a7ddc41e1c39dc63a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pl=C3=A1cido=20Revilla?= Date: Sat, 22 Jan 2022 12:04:36 -0800 Subject: [PATCH 0180/3388] Set the wrapped single light in light partition to internal (#3092) --- esphome/components/partition/light.py | 1 - 1 file changed, 1 deletion(-) diff --git a/esphome/components/partition/light.py b/esphome/components/partition/light.py index 822b7ac306..73cda2c926 100644 --- a/esphome/components/partition/light.py +++ b/esphome/components/partition/light.py @@ -104,7 +104,6 @@ async def to_code(config): ) light_state = cg.new_Pvariable(conf[CONF_LIGHT_ID], "", wrapper) await cg.register_component(light_state, conf) - cg.add(cg.App.register_light(light_state)) segments.append(AddressableSegment(light_state, 0, 1, False)) else: From bdb9546ca3d9743cd382ce1031b9ddff4dee14a1 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 25 Jan 2022 09:11:20 +1300 Subject: [PATCH 0181/3388] Bump version to 2022.1.2 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index a9f4f0900a..e97e0ac41a 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2022.1.1" +__version__ = "2022.1.2" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" From 7479e0aadaeb4bd62294f9d82af1cfb48b077139 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 31 Jan 2022 10:58:27 +1300 Subject: [PATCH 0182/3388] Fix backwards string case helpers (#3126) --- esphome/components/dallas/dallas_component.cpp | 2 +- esphome/core/helpers.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/esphome/components/dallas/dallas_component.cpp b/esphome/components/dallas/dallas_component.cpp index 3610e79447..17412b08c9 100644 --- a/esphome/components/dallas/dallas_component.cpp +++ b/esphome/components/dallas/dallas_component.cpp @@ -235,7 +235,7 @@ float DallasTemperatureSensor::get_temp_c() { return temp / 128.0f; } -std::string DallasTemperatureSensor::unique_id() { return "dallas-" + str_upper_case(format_hex(this->address_)); } +std::string DallasTemperatureSensor::unique_id() { return "dallas-" + str_lower_case(format_hex(this->address_)); } } // namespace dallas } // namespace esphome diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index 5f29abe579..11a2b1acb0 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -316,8 +316,8 @@ template std::string str_ctype_transform(const std::string &str) std::transform(str.begin(), str.end(), result.begin(), [](unsigned char ch) { return fn(ch); }); return result; } -std::string str_lower_case(const std::string &str) { return str_ctype_transform(str); } -std::string str_upper_case(const std::string &str) { return str_ctype_transform(str); } +std::string str_lower_case(const std::string &str) { return str_ctype_transform(str); } +std::string str_upper_case(const std::string &str) { return str_ctype_transform(str); } std::string str_snake_case(const std::string &str) { std::string result; result.resize(str.length()); From c7f091ab10effc0e386163e2832777109b7b89d0 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 2 Feb 2022 22:16:24 +1300 Subject: [PATCH 0183/3388] Bump version to 2022.1.3 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index e97e0ac41a..243b3d84b1 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2022.1.2" +__version__ = "2022.1.3" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" From 9ad84150aa35673fbc63c0168d0601d4967476ff Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Tue, 8 Feb 2022 09:21:52 +0100 Subject: [PATCH 0184/3388] Enable mDNS during OTA safe mode (#3146) --- esphome/components/mdns/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/esphome/components/mdns/__init__.py b/esphome/components/mdns/__init__.py index b95469d9da..b5be153d5a 100644 --- a/esphome/components/mdns/__init__.py +++ b/esphome/components/mdns/__init__.py @@ -1,7 +1,7 @@ from esphome.const import CONF_ID import esphome.codegen as cg import esphome.config_validation as cv -from esphome.core import CORE +from esphome.core import CORE, coroutine_with_priority CODEOWNERS = ["@esphome/core"] DEPENDENCIES = ["network"] @@ -29,6 +29,7 @@ CONFIG_SCHEMA = cv.All( ) +@coroutine_with_priority(55.0) async def to_code(config): if CORE.using_arduino: if CORE.is_esp32: From 2b5dce52327094cda8d209e7b0cc0ec0ce799df3 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 9 Feb 2022 11:42:00 +1300 Subject: [PATCH 0185/3388] Try fix canbus config validation (#3173) --- esphome/components/canbus/__init__.py | 32 +++++++++++++-------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/esphome/components/canbus/__init__.py b/esphome/components/canbus/__init__.py index 3a3cece579..808b31d1d2 100644 --- a/esphome/components/canbus/__init__.py +++ b/esphome/components/canbus/__init__.py @@ -14,10 +14,14 @@ CONF_BIT_RATE = "bit_rate" CONF_ON_FRAME = "on_frame" -def validate_id(id_value, id_ext): - if not id_ext: - if id_value > 0x7FF: - raise cv.Invalid("Standard IDs must be 11 Bit (0x000-0x7ff / 0-2047)") +def validate_id(config): + if CONF_CAN_ID in config: + id_value = config[CONF_CAN_ID] + id_ext = config[CONF_USE_EXTENDED_ID] + if not id_ext: + if id_value > 0x7FF: + raise cv.Invalid("Standard IDs must be 11 Bit (0x000-0x7ff / 0-2047)") + return config def validate_raw_data(value): @@ -67,23 +71,18 @@ CANBUS_SCHEMA = cv.Schema( cv.Optional(CONF_ON_FRAME): automation.validate_automation( { cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(CanbusTrigger), - cv.GenerateID(CONF_CAN_ID): cv.int_range(min=0, max=0x1FFFFFFF), + cv.Required(CONF_CAN_ID): cv.int_range(min=0, max=0x1FFFFFFF), cv.Optional(CONF_USE_EXTENDED_ID, default=False): cv.boolean, - cv.Optional(CONF_ON_FRAME): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(CanbusTrigger), - cv.GenerateID(CONF_CAN_ID): cv.int_range(min=0, max=0x1FFFFFFF), - cv.Optional(CONF_USE_EXTENDED_ID, default=False): cv.boolean, - } - ), - } + }, + validate_id, ), - } + }, ).extend(cv.COMPONENT_SCHEMA) +CANBUS_SCHEMA.add_extra(validate_id) + async def setup_canbus_core_(var, config): - validate_id(config[CONF_CAN_ID], config[CONF_USE_EXTENDED_ID]) await cg.register_component(var, config) cg.add(var.set_can_id([config[CONF_CAN_ID]])) cg.add(var.set_use_extended_id([config[CONF_USE_EXTENDED_ID]])) @@ -92,7 +91,6 @@ async def setup_canbus_core_(var, config): for conf in config.get(CONF_ON_FRAME, []): can_id = conf[CONF_CAN_ID] ext_id = conf[CONF_USE_EXTENDED_ID] - validate_id(can_id, ext_id) trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var, can_id, ext_id) await cg.register_component(trigger, conf) await automation.build_automation( @@ -117,11 +115,11 @@ async def register_canbus(var, config): cv.Optional(CONF_USE_EXTENDED_ID, default=False): cv.boolean, cv.Required(CONF_DATA): cv.templatable(validate_raw_data), }, + validate_id, key=CONF_DATA, ), ) async def canbus_action_to_code(config, action_id, template_arg, args): - validate_id(config[CONF_CAN_ID], config[CONF_USE_EXTENDED_ID]) var = cg.new_Pvariable(action_id, template_arg) await cg.register_parented(var, config[CONF_CANBUS_ID]) From 53d3718028c9f3257f12a95d8a154221bda3b183 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 9 Feb 2022 11:54:41 +1300 Subject: [PATCH 0186/3388] Bump version to 2022.1.4 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 243b3d84b1..566f79a390 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2022.1.3" +__version__ = "2022.1.4" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" From 71a438e2cb00e2f6b9fac735800c29b2012010b2 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 9 Feb 2022 23:47:36 +1300 Subject: [PATCH 0187/3388] Bump version to 2022.2.0b1 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index dcd6fea31f..d3b78ca44a 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2022.2.0-dev" +__version__ = "2022.2.0b1" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" From 8dcc9d6b66ac90ec9945dd083d5eab8d5ba63148 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 9 Feb 2022 20:13:02 +0100 Subject: [PATCH 0188/3388] Bump aioesphomeapi from 10.8.1 to 10.8.2 (#3182) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index a33461b79b..0c408f90b5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,7 +10,7 @@ platformio==5.2.4 # When updating platformio, also update Dockerfile esptool==3.2 click==8.0.3 esphome-dashboard==20220209.0 -aioesphomeapi==10.8.1 +aioesphomeapi==10.8.2 zeroconf==0.37.0 # esp-idf requires this, but doesn't bundle it by default From f376a39e55c724c5dbda7eb2cc0b8380249678cf Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 10 Feb 2022 11:12:05 +1300 Subject: [PATCH 0189/3388] Clamp rotary_encoder restored value to min and max (#3184) --- esphome/components/rotary_encoder/rotary_encoder.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/esphome/components/rotary_encoder/rotary_encoder.cpp b/esphome/components/rotary_encoder/rotary_encoder.cpp index c5227b41a7..c5e9cec596 100644 --- a/esphome/components/rotary_encoder/rotary_encoder.cpp +++ b/esphome/components/rotary_encoder/rotary_encoder.cpp @@ -138,6 +138,8 @@ void RotaryEncoderSensor::setup() { initial_value = 0; break; } + initial_value = clamp(initial_value, this->store_.min_value, this->store_.max_value); + this->store_.counter = initial_value; this->store_.last_read = initial_value; From dd554bcdf4fe0041ea144d44ce4e2313c229f6a2 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Fri, 11 Feb 2022 09:06:00 +0100 Subject: [PATCH 0190/3388] Make generating combined binary output verbose (#3127) --- esphome/components/esp32/post_build.py.script | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/esphome/components/esp32/post_build.py.script b/esphome/components/esp32/post_build.py.script index 7feaf9e8e5..2bb1a6c3d6 100644 --- a/esphome/components/esp32/post_build.py.script +++ b/esphome/components/esp32/post_build.py.script @@ -1,13 +1,16 @@ # Source https://github.com/letscontrolit/ESPEasy/pull/3845#issuecomment-1005864664 import esptool +from SCons.Script import ARGUMENTS # pylint: disable=E0602 Import("env") # noqa def esp32_create_combined_bin(source, target, env): - print("Generating combined binary for serial flashing") + verbose = bool(int(ARGUMENTS.get("PIOVERBOSE", "0"))) + if verbose: + print("Generating combined binary for serial flashing") app_offset = 0x10000 new_file_name = env.subst("$BUILD_DIR/${PROGNAME}-factory.bin") @@ -24,18 +27,21 @@ def esp32_create_combined_bin(source, target, env): "--flash_size", flash_size, ] - print(" Offset | File") + if verbose: + print(" Offset | File") for section in sections: sect_adr, sect_file = section.split(" ", 1) - print(f" - {sect_adr} | {sect_file}") + if verbose: + print(f" - {sect_adr} | {sect_file}") cmd += [sect_adr, sect_file] - print(f" - {hex(app_offset)} | {firmware_name}") cmd += [hex(app_offset), firmware_name] - print() - print(f"Using esptool.py arguments: {' '.join(cmd)}") - print() + if verbose: + print(f" - {hex(app_offset)} | {firmware_name}") + print() + print(f"Using esptool.py arguments: {' '.join(cmd)}") + print() esptool.main(cmd) From dcc80f9032c6d30bdf6b22dfbd76ee7b27b33d7f Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 15 Feb 2022 11:57:47 +1300 Subject: [PATCH 0191/3388] Allow framework version validator to be maximum version (#3197) --- esphome/components/fastled_clockless/light.py | 7 +++- esphome/components/fastled_spi/light.py | 7 +++- esphome/config_validation.py | 35 ++++++++++++++----- 3 files changed, 39 insertions(+), 10 deletions(-) diff --git a/esphome/components/fastled_clockless/light.py b/esphome/components/fastled_clockless/light.py index acf9488ae3..dc456d4959 100644 --- a/esphome/components/fastled_clockless/light.py +++ b/esphome/components/fastled_clockless/light.py @@ -49,7 +49,12 @@ CONFIG_SCHEMA = cv.All( } ), _validate, - cv.only_with_arduino, + cv.require_framework_version( + esp8266_arduino=cv.Version(2, 7, 4), + esp32_arduino=cv.Version(99, 0, 0), + max_version=True, + extra_message="Please see note on documentation for FastLED", + ), ) diff --git a/esphome/components/fastled_spi/light.py b/esphome/components/fastled_spi/light.py index a729fc015a..b3ce1722ee 100644 --- a/esphome/components/fastled_spi/light.py +++ b/esphome/components/fastled_spi/light.py @@ -33,7 +33,12 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_DATA_RATE): cv.frequency, } ), - cv.only_with_arduino, + cv.require_framework_version( + esp8266_arduino=cv.Version(2, 7, 4), + esp32_arduino=cv.Version(99, 0, 0), + max_version=True, + extra_message="Please see note on documentation for FastLED", + ), ) diff --git a/esphome/config_validation.py b/esphome/config_validation.py index 2e7a4e5677..8e1c63a54e 100644 --- a/esphome/config_validation.py +++ b/esphome/config_validation.py @@ -1713,30 +1713,49 @@ def require_framework_version( esp_idf=None, esp32_arduino=None, esp8266_arduino=None, + max_version=False, + extra_message=None, ): def validator(value): core_data = CORE.data[KEY_CORE] framework = core_data[KEY_TARGET_FRAMEWORK] if framework == "esp-idf": if esp_idf is None: - raise Invalid("This feature is incompatible with esp-idf") + msg = "This feature is incompatible with esp-idf" + if extra_message: + msg += f". {extra_message}" + raise Invalid(msg) required = esp_idf elif CORE.is_esp32 and framework == "arduino": if esp32_arduino is None: - raise Invalid( - "This feature is incompatible with ESP32 using arduino framework" - ) + msg = "This feature is incompatible with ESP32 using arduino framework" + if extra_message: + msg += f". {extra_message}" + raise Invalid(msg) required = esp32_arduino elif CORE.is_esp8266 and framework == "arduino": if esp8266_arduino is None: - raise Invalid("This feature is incompatible with ESP8266") + msg = "This feature is incompatible with ESP8266" + if extra_message: + msg += f". {extra_message}" + raise Invalid(msg) required = esp8266_arduino else: raise NotImplementedError + + if max_version: + if core_data[KEY_FRAMEWORK_VERSION] > required: + msg = f"This feature requires framework version {required} or lower" + if extra_message: + msg += f". {extra_message}" + raise Invalid(msg) + return value + if core_data[KEY_FRAMEWORK_VERSION] < required: - raise Invalid( - f"This feature requires at least framework version {required}" - ) + msg = f"This feature requires at least framework version {required}" + if extra_message: + msg += f". {extra_message}" + raise Invalid(msg) return value return validator From bb6b77bd98a71e983166dcceae042873f5e51456 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 15 Feb 2022 12:00:12 +1300 Subject: [PATCH 0192/3388] Bump version to 2022.2.0b2 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index d3b78ca44a..580826c954 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2022.2.0b1" +__version__ = "2022.2.0b2" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" From 16dc7762f9935a4714925de39a3323c2dc880b82 Mon Sep 17 00:00:00 2001 From: Maurice Makaay Date: Tue, 15 Feb 2022 20:54:21 +0100 Subject: [PATCH 0193/3388] Fix strlcpy() uses to make long SSIDs and passwords work (#3199) Co-authored-by: Maurice Makaay --- esphome/components/wifi/wifi_component_esp32_arduino.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/esphome/components/wifi/wifi_component_esp32_arduino.cpp b/esphome/components/wifi/wifi_component_esp32_arduino.cpp index e1332e3181..83381f3424 100644 --- a/esphome/components/wifi/wifi_component_esp32_arduino.cpp +++ b/esphome/components/wifi/wifi_component_esp32_arduino.cpp @@ -161,8 +161,8 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) { // https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/network/esp_wifi.html#_CPPv417wifi_sta_config_t wifi_config_t conf; memset(&conf, 0, sizeof(conf)); - strlcpy(reinterpret_cast(conf.sta.ssid), ap.get_ssid().c_str(), sizeof(conf.sta.ssid)); - strlcpy(reinterpret_cast(conf.sta.password), ap.get_password().c_str(), sizeof(conf.sta.password)); + strncpy(reinterpret_cast(conf.sta.ssid), ap.get_ssid().c_str(), sizeof(conf.sta.ssid)); + strncpy(reinterpret_cast(conf.sta.password), ap.get_password().c_str(), sizeof(conf.sta.password)); // The weakest authmode to accept in the fast scan mode if (ap.get_password().empty()) { @@ -709,7 +709,7 @@ bool WiFiComponent::wifi_start_ap_(const WiFiAP &ap) { wifi_config_t conf; memset(&conf, 0, sizeof(conf)); - strlcpy(reinterpret_cast(conf.ap.ssid), ap.get_ssid().c_str(), sizeof(conf.ap.ssid)); + strncpy(reinterpret_cast(conf.ap.ssid), ap.get_ssid().c_str(), sizeof(conf.ap.ssid)); conf.ap.channel = ap.get_channel().value_or(1); conf.ap.ssid_hidden = ap.get_ssid().size(); conf.ap.max_connection = 5; @@ -720,7 +720,7 @@ bool WiFiComponent::wifi_start_ap_(const WiFiAP &ap) { *conf.ap.password = 0; } else { conf.ap.authmode = WIFI_AUTH_WPA2_PSK; - strlcpy(reinterpret_cast(conf.ap.password), ap.get_password().c_str(), sizeof(conf.ap.ssid)); + strncpy(reinterpret_cast(conf.ap.password), ap.get_password().c_str(), sizeof(conf.ap.ssid)); } #if ESP_IDF_VERSION_MAJOR >= 4 From 6ddad6b299db67d937f315f1cfcf2b4d9c40f570 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 16 Feb 2022 09:11:46 +1300 Subject: [PATCH 0194/3388] Update HA addon token (#3200) --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 02a55494e9..f5281c2fb7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -143,7 +143,7 @@ jobs: needs: [deploy-docker] steps: - env: - TOKEN: ${{ secrets.DEPLOY_HASSIO_TOKEN }} + TOKEN: ${{ secrets.DEPLOY_HA_ADDON_REPO_TOKEN }} run: | TAG="${GITHUB_REF#refs/tags/}" curl \ From ec7a79049a37af82b0c01f20e0cac0ec2c94f930 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 16 Feb 2022 09:45:05 +1300 Subject: [PATCH 0195/3388] Bump version to 2022.2.0b3 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 580826c954..8bb99752aa 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2022.2.0b2" +__version__ = "2022.2.0b3" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" From 69002fb1e6cdd3a6d79f2be389d0ec6fe78bfe55 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 16 Feb 2022 20:13:14 +1300 Subject: [PATCH 0196/3388] Bump version to 2022.2.0 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 8bb99752aa..70e27374ae 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2022.2.0b3" +__version__ = "2022.2.0" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" From 1fb214165bb8297b1661d693e5ac2841fd8f8eb5 Mon Sep 17 00:00:00 2001 From: Stewart Date: Wed, 16 Feb 2022 15:50:10 +0000 Subject: [PATCH 0197/3388] Fix missed ARDUINO_VERSION_CODE to USE_ARDUINO_VERSION_CODE changes (#3206) Co-authored-by: Stewart Morgan --- esphome/components/debug/debug_component.cpp | 6 +++--- esphome/components/debug/debug_component.h | 4 ++-- esphome/components/ota/ota_backend_arduino_esp8266.h | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/esphome/components/debug/debug_component.cpp b/esphome/components/debug/debug_component.cpp index a2697084bd..97d5aeb8a8 100644 --- a/esphome/components/debug/debug_component.cpp +++ b/esphome/components/debug/debug_component.cpp @@ -53,9 +53,9 @@ void DebugComponent::dump_config() { #ifdef USE_SENSOR LOG_SENSOR(" ", "Free space on heap", this->free_sensor_); LOG_SENSOR(" ", "Largest free heap block", this->block_sensor_); -#if defined(USE_ESP8266) && ARDUINO_VERSION_CODE >= VERSION_CODE(2, 5, 2) +#if defined(USE_ESP8266) && USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 5, 2) LOG_SENSOR(" ", "Heap fragmentation", this->fragmentation_sensor_); -#endif // defined(USE_ESP8266) && ARDUINO_VERSION_CODE >= VERSION_CODE(2, 5, 2) +#endif // defined(USE_ESP8266) && USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 5, 2) #endif // USE_SENSOR ESP_LOGD(TAG, "ESPHome version %s", ESPHOME_VERSION); @@ -316,7 +316,7 @@ void DebugComponent::update() { #endif } -#if defined(USE_ESP8266) && ARDUINO_VERSION_CODE >= VERSION_CODE(2, 5, 2) +#if defined(USE_ESP8266) && USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 5, 2) if (this->fragmentation_sensor_ != nullptr) { // NOLINTNEXTLINE(readability-static-accessed-through-instance) this->fragmentation_sensor_->publish_state(ESP.getHeapFragmentation()); diff --git a/esphome/components/debug/debug_component.h b/esphome/components/debug/debug_component.h index f966b4fafc..4dc1659616 100644 --- a/esphome/components/debug/debug_component.h +++ b/esphome/components/debug/debug_component.h @@ -28,7 +28,7 @@ class DebugComponent : public PollingComponent { #ifdef USE_SENSOR void set_free_sensor(sensor::Sensor *free_sensor) { free_sensor_ = free_sensor; } void set_block_sensor(sensor::Sensor *block_sensor) { block_sensor_ = block_sensor; } -#if defined(USE_ESP8266) && ARDUINO_VERSION_CODE >= VERSION_CODE(2, 5, 2) +#if defined(USE_ESP8266) && USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 5, 2) void set_fragmentation_sensor(sensor::Sensor *fragmentation_sensor) { fragmentation_sensor_ = fragmentation_sensor; } #endif void set_loop_time_sensor(sensor::Sensor *loop_time_sensor) { loop_time_sensor_ = loop_time_sensor; } @@ -42,7 +42,7 @@ class DebugComponent : public PollingComponent { sensor::Sensor *free_sensor_{nullptr}; sensor::Sensor *block_sensor_{nullptr}; -#if defined(USE_ESP8266) && ARDUINO_VERSION_CODE >= VERSION_CODE(2, 5, 2) +#if defined(USE_ESP8266) && USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 5, 2) sensor::Sensor *fragmentation_sensor_{nullptr}; #endif sensor::Sensor *loop_time_sensor_{nullptr}; diff --git a/esphome/components/ota/ota_backend_arduino_esp8266.h b/esphome/components/ota/ota_backend_arduino_esp8266.h index 329f2cf0f2..7937c665b0 100644 --- a/esphome/components/ota/ota_backend_arduino_esp8266.h +++ b/esphome/components/ota/ota_backend_arduino_esp8266.h @@ -17,7 +17,7 @@ class ArduinoESP8266OTABackend : public OTABackend { OTAResponseTypes write(uint8_t *data, size_t len) override; OTAResponseTypes end() override; void abort() override; -#if ARDUINO_VERSION_CODE >= VERSION_CODE(2, 7, 0) +#if USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 7, 0) bool supports_compression() override { return true; } #else bool supports_compression() override { return false; } From dc54b177785f2b49f852e4cbd9a0b13ee128fcfc Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 17 Feb 2022 07:36:14 +1300 Subject: [PATCH 0198/3388] Bump version to 2022.2.1 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 70e27374ae..7d4fc234db 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2022.2.0" +__version__ = "2022.2.1" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" From b9398897c1f11eb7ddef665a8cf841099b990399 Mon Sep 17 00:00:00 2001 From: Stewart Date: Thu, 17 Feb 2022 00:53:26 +0000 Subject: [PATCH 0199/3388] Set entity-category to diagnostic for debug component (#3209) Co-authored-by: Stewart Morgan Co-authored-by: root --- esphome/components/debug/sensor.py | 29 +++++++++++++++++++++---- esphome/components/debug/text_sensor.py | 6 +++-- 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/esphome/components/debug/sensor.py b/esphome/components/debug/sensor.py index deea6fd5ed..f7ea07d138 100644 --- a/esphome/components/debug/sensor.py +++ b/esphome/components/debug/sensor.py @@ -6,6 +6,7 @@ from esphome.const import ( CONF_FRAGMENTATION, CONF_BLOCK, CONF_LOOP_TIME, + ENTITY_CATEGORY_DIAGNOSTIC, UNIT_MILLISECOND, UNIT_PERCENT, UNIT_BYTES, @@ -18,14 +19,34 @@ DEPENDENCIES = ["debug"] CONFIG_SCHEMA = { cv.GenerateID(CONF_DEBUG_ID): cv.use_id(DebugComponent), - cv.Optional(CONF_FREE): sensor.sensor_schema(UNIT_BYTES, ICON_COUNTER, 0), - cv.Optional(CONF_BLOCK): sensor.sensor_schema(UNIT_BYTES, ICON_COUNTER, 0), + cv.Optional(CONF_FREE): sensor.sensor_schema( + unit_of_measurement=UNIT_BYTES, + icon=ICON_COUNTER, + accuracy_decimals=0, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ), + cv.Optional(CONF_BLOCK): sensor.sensor_schema( + unit_of_measurement=UNIT_BYTES, + icon=ICON_COUNTER, + accuracy_decimals=0, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ), cv.Optional(CONF_FRAGMENTATION): cv.All( cv.only_on_esp8266, cv.require_framework_version(esp8266_arduino=cv.Version(2, 5, 2)), - sensor.sensor_schema(UNIT_PERCENT, ICON_COUNTER, 1), + sensor.sensor_schema( + unit_of_measurement=UNIT_PERCENT, + icon=ICON_COUNTER, + accuracy_decimals=1, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ), + ), + cv.Optional(CONF_LOOP_TIME): sensor.sensor_schema( + unit_of_measurement=UNIT_MILLISECOND, + icon=ICON_TIMER, + accuracy_decimals=0, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), - cv.Optional(CONF_LOOP_TIME): sensor.sensor_schema(UNIT_MILLISECOND, ICON_TIMER, 0), } diff --git a/esphome/components/debug/text_sensor.py b/esphome/components/debug/text_sensor.py index f8d1016fbf..11e6354f57 100644 --- a/esphome/components/debug/text_sensor.py +++ b/esphome/components/debug/text_sensor.py @@ -1,7 +1,7 @@ from esphome.components import text_sensor import esphome.config_validation as cv import esphome.codegen as cg -from esphome.const import CONF_DEVICE +from esphome.const import CONF_DEVICE, ENTITY_CATEGORY_DIAGNOSTIC from . import CONF_DEBUG_ID, DebugComponent @@ -11,7 +11,9 @@ DEPENDENCIES = ["debug"] CONFIG_SCHEMA = cv.Schema( { cv.GenerateID(CONF_DEBUG_ID): cv.use_id(DebugComponent), - cv.Optional(CONF_DEVICE): text_sensor.text_sensor_schema(), + cv.Optional(CONF_DEVICE): text_sensor.text_sensor_schema( + entity_category=ENTITY_CATEGORY_DIAGNOSTIC + ), } ) From 7dcc4d030bb4eb2997eef44453981e0d1515c45c Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Thu, 17 Feb 2022 11:56:14 +0100 Subject: [PATCH 0200/3388] Fix platformio docker version mismstch (#3215) --- docker/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 5d9decbf1b..6735037c1e 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -43,7 +43,7 @@ RUN \ # Ubuntu python3-pip is missing wheel pip3 install --no-cache-dir \ wheel==0.37.1 \ - platformio==5.2.4 \ + platformio==5.2.5 \ # Change some platformio settings && platformio settings set enable_telemetry No \ && platformio settings set check_libraries_interval 1000000 \ From 8886b7e1417823bd12e1f9e37d07ee0c6a28aa1a Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Fri, 18 Feb 2022 10:11:22 +1300 Subject: [PATCH 0201/3388] Add LONG LONG flag for arduinojson (#3212) --- esphome/components/json/json_util.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/esphome/components/json/json_util.h b/esphome/components/json/json_util.h index 57fe6107d8..2299a4cfed 100644 --- a/esphome/components/json/json_util.h +++ b/esphome/components/json/json_util.h @@ -4,9 +4,10 @@ #include "esphome/core/helpers.h" -#undef ARDUINOJSON_ENABLE_STD_STRING #define ARDUINOJSON_ENABLE_STD_STRING 1 // NOLINT +#define ARDUINOJSON_USE_LONG_LONG 1 // NOLINT + #include namespace esphome { From 83b7181bcb22262dee690a591099aa036cd06b20 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Fri, 18 Feb 2022 10:16:11 +1300 Subject: [PATCH 0202/3388] Bump version to 2022.2.2 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 7d4fc234db..b501428f9e 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2022.2.1" +__version__ = "2022.2.2" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" From ccc2fbfd67aebf4179c56e8e0e4e84166aaa15d7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 11 Feb 2022 13:57:46 +0100 Subject: [PATCH 0203/3388] Bump platformio from 5.2.4 to 5.2.5 (#3188) * Bump platformio from 5.2.4 to 5.2.5 Bumps [platformio](https://github.com/platformio/platformio) from 5.2.4 to 5.2.5. - [Release notes](https://github.com/platformio/platformio/releases) - [Changelog](https://github.com/platformio/platformio-core/blob/develop/HISTORY.rst) - [Commits](https://github.com/platformio/platformio/commits) --- updated-dependencies: - dependency-name: platformio dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * Update requirements.txt Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Otto Winter --- requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 0c408f90b5..acbf1d9984 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,12 +6,12 @@ tornado==6.1 tzlocal==4.1 # from time tzdata>=2021.1 # from time pyserial==3.5 -platformio==5.2.4 # When updating platformio, also update Dockerfile +platformio==5.2.5 # When updating platformio, also update Dockerfile esptool==3.2 click==8.0.3 esphome-dashboard==20220209.0 aioesphomeapi==10.8.2 -zeroconf==0.37.0 +zeroconf==0.38.3 # esp-idf requires this, but doesn't bundle it by default # https://github.com/espressif/esp-idf/blob/220590d599e134d7a5e7f1e683cc4550349ffbf8/requirements.txt#L24 From 5764c988afab26b6b9c6249017f0b6575c8672e4 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Fri, 18 Feb 2022 11:51:56 +1300 Subject: [PATCH 0204/3388] Bump version to 2022.2.3 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index b501428f9e..0e8695adef 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2022.2.2" +__version__ = "2022.2.3" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" From 0c4de2bc97ebc75130aa04f391b202793ebba9bb Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Sat, 19 Feb 2022 14:11:01 +0100 Subject: [PATCH 0205/3388] Publish NAN when dallas conversion failed (#3227) --- esphome/components/dallas/dallas_component.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/esphome/components/dallas/dallas_component.cpp b/esphome/components/dallas/dallas_component.cpp index 1eed2ebf78..6240983798 100644 --- a/esphome/components/dallas/dallas_component.cpp +++ b/esphome/components/dallas/dallas_component.cpp @@ -110,6 +110,9 @@ void DallasComponent::update() { if (!result) { ESP_LOGE(TAG, "Requesting conversion failed"); this->status_set_warning(); + for (auto *sensor : this->sensors_) { + sensor->publish_state(NAN); + } return; } From 616c787e377f12c83bcaf7f252d329ba4715581a Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Sat, 19 Feb 2022 14:11:45 +0100 Subject: [PATCH 0206/3388] Fix ESP8266 climate memaccess warning (#3226) --- esphome/components/climate/climate.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/esphome/components/climate/climate.cpp b/esphome/components/climate/climate.cpp index ebea20ed1f..65b5ef4eb4 100644 --- a/esphome/components/climate/climate.cpp +++ b/esphome/components/climate/climate.cpp @@ -1,4 +1,5 @@ #include "climate.h" +#include "esphome/core/macros.h" namespace esphome { namespace climate { @@ -326,14 +327,17 @@ optional Climate::restore_state_() { return recovered; } void Climate::save_state_() { -#if defined(USE_ESP_IDF) && !defined(CLANG_TIDY) +#if (defined(USE_ESP_IDF) || (defined(USE_ESP8266) && USE_ARDUINO_VERSION_CODE >= VERSION_CODE(3, 0, 0))) && \ + !defined(CLANG_TIDY) #pragma GCC diagnostic ignored "-Wclass-memaccess" +#define TEMP_IGNORE_MEMACCESS #endif ClimateDeviceRestoreState state{}; // initialize as zero to prevent random data on stack triggering erase memset(&state, 0, sizeof(ClimateDeviceRestoreState)); -#if USE_ESP_IDF && !defined(CLANG_TIDY) +#ifdef TEMP_IGNORE_MEMACCESS #pragma GCC diagnostic pop +#undef TEMP_IGNORE_MEMACCESS #endif state.mode = this->mode; From 1d0395d1c7cfc546f41b4b9116df4b2b7b482f1d Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Sat, 19 Feb 2022 15:09:17 +0100 Subject: [PATCH 0207/3388] Improve ESP8266 iram usage (#3223) --- esphome/core/scheduler.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/core/scheduler.cpp b/esphome/core/scheduler.cpp index 7f0ed0b17c..56f823556b 100644 --- a/esphome/core/scheduler.cpp +++ b/esphome/core/scheduler.cpp @@ -116,7 +116,7 @@ optional HOT Scheduler::next_schedule_in() { return 0; return next_time - now; } -void IRAM_ATTR HOT Scheduler::call() { +void HOT Scheduler::call() { const uint32_t now = this->millis_(); this->process_to_add(); From b881bc071ecd0e1493441ba981b00e7101ed3c6d Mon Sep 17 00:00:00 2001 From: Tyler Bules Date: Sat, 19 Feb 2022 09:13:48 -0500 Subject: [PATCH 0208/3388] ESP32-C3 deep sleep fix (#3066) --- esphome/components/deep_sleep/__init__.py | 40 ++++++++++++++++++- .../deep_sleep/deep_sleep_component.cpp | 14 ++++++- .../deep_sleep/deep_sleep_component.h | 5 ++- tests/test1.yaml | 2 +- tests/test2.yaml | 2 +- 5 files changed, 58 insertions(+), 5 deletions(-) diff --git a/esphome/components/deep_sleep/__init__.py b/esphome/components/deep_sleep/__init__.py index ba4c2c0d7e..2a74d0c1bb 100644 --- a/esphome/components/deep_sleep/__init__.py +++ b/esphome/components/deep_sleep/__init__.py @@ -11,9 +11,39 @@ from esphome.const import ( CONF_WAKEUP_PIN, ) +from esphome.components.esp32 import get_esp32_variant +from esphome.components.esp32.const import ( + VARIANT_ESP32, + VARIANT_ESP32C3, +) + +WAKEUP_PINS = { + VARIANT_ESP32: [ + 0, + 2, + 4, + 12, + 13, + 14, + 15, + 25, + 26, + 27, + 32, + 33, + 34, + 35, + 36, + 37, + 38, + 39, + ], + VARIANT_ESP32C3: [0, 1, 2, 3, 4, 5], +} + def validate_pin_number(value): - valid_pins = [0, 2, 4, 12, 13, 14, 15, 25, 26, 27, 32, 33, 34, 35, 36, 37, 38, 39] + valid_pins = WAKEUP_PINS.get(get_esp32_variant(), WAKEUP_PINS[VARIANT_ESP32]) if value[CONF_NUMBER] not in valid_pins: raise cv.Invalid( f"Only pins {', '.join(str(x) for x in valid_pins)} support wakeup" @@ -21,6 +51,14 @@ def validate_pin_number(value): return value +def validate_config(config): + if get_esp32_variant() == VARIANT_ESP32C3 and CONF_ESP32_EXT1_WAKEUP in config: + raise cv.Invalid("ESP32-C3 does not support wakeup from touch.") + if get_esp32_variant() == VARIANT_ESP32C3 and CONF_TOUCH_WAKEUP in config: + raise cv.Invalid("ESP32-C3 does not support wakeup from ext1") + return config + + deep_sleep_ns = cg.esphome_ns.namespace("deep_sleep") DeepSleepComponent = deep_sleep_ns.class_("DeepSleepComponent", cg.Component) EnterDeepSleepAction = deep_sleep_ns.class_("EnterDeepSleepAction", automation.Action) diff --git a/esphome/components/deep_sleep/deep_sleep_component.cpp b/esphome/components/deep_sleep/deep_sleep_component.cpp index 7774014d3d..82751b538b 100644 --- a/esphome/components/deep_sleep/deep_sleep_component.cpp +++ b/esphome/components/deep_sleep/deep_sleep_component.cpp @@ -104,7 +104,7 @@ void DeepSleepComponent::begin_sleep(bool manual) { App.run_safe_shutdown_hooks(); -#ifdef USE_ESP32 +#if defined(USE_ESP32) && !defined(USE_ESP32_VARIANT_ESP32C3) if (this->sleep_duration_.has_value()) esp_sleep_enable_timer_wakeup(*this->sleep_duration_); if (this->wakeup_pin_ != nullptr) { @@ -126,6 +126,18 @@ void DeepSleepComponent::begin_sleep(bool manual) { esp_deep_sleep_start(); #endif +#ifdef USE_ESP32_VARIANT_ESP32C3 + if (this->sleep_duration_.has_value()) + esp_sleep_enable_timer_wakeup(*this->sleep_duration_); + if (this->wakeup_pin_ != nullptr) { + bool level = !this->wakeup_pin_->is_inverted(); + if (this->wakeup_pin_mode_ == WAKEUP_PIN_MODE_INVERT_WAKEUP && this->wakeup_pin_->digital_read()) { + level = !level; + } + esp_deep_sleep_enable_gpio_wakeup(gpio_num_t(this->wakeup_pin_->get_pin()), level); + } +#endif + #ifdef USE_ESP8266 ESP.deepSleep(*this->sleep_duration_); // NOLINT(readability-static-accessed-through-instance) #endif diff --git a/esphome/components/deep_sleep/deep_sleep_component.h b/esphome/components/deep_sleep/deep_sleep_component.h index 59df199a9f..057d992427 100644 --- a/esphome/components/deep_sleep/deep_sleep_component.h +++ b/esphome/components/deep_sleep/deep_sleep_component.h @@ -57,13 +57,16 @@ class DeepSleepComponent : public Component { public: /// Set the duration in ms the component should sleep once it's in deep sleep mode. void set_sleep_duration(uint32_t time_ms); -#ifdef USE_ESP32 +#if defined(USE_ESP32) /** Set the pin to wake up to on the ESP32 once it's in deep sleep mode. * Use the inverted property to set the wakeup level. */ void set_wakeup_pin(InternalGPIOPin *pin) { this->wakeup_pin_ = pin; } void set_wakeup_pin_mode(WakeupPinMode wakeup_pin_mode); +#endif + +#if defined(USE_ESP32) && !defined(USE_ESP32_VARIANT_ESP32C3) void set_ext1_wakeup(Ext1Wakeup ext1_wakeup); diff --git a/tests/test1.yaml b/tests/test1.yaml index d8fea223dc..b9799bdb36 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -262,7 +262,7 @@ power_supply: deep_sleep: run_duration: 20s sleep_duration: 50s - wakeup_pin: GPIO39 + wakeup_pin: GPIO2 wakeup_pin_mode: INVERT_WAKEUP ads1115: diff --git a/tests/test2.yaml b/tests/test2.yaml index 2a122b971f..76b9775c54 100644 --- a/tests/test2.yaml +++ b/tests/test2.yaml @@ -60,7 +60,7 @@ deep_sleep: gpio_wakeup_reason: 10s touch_wakeup_reason: 15s sleep_duration: 50s - wakeup_pin: GPIO39 + wakeup_pin: GPIO2 wakeup_pin_mode: INVERT_WAKEUP as3935_i2c: From e73d47918f9b3c070af75919a0418d598d77273e Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 21 Feb 2022 09:58:53 +1300 Subject: [PATCH 0209/3388] Fix lilygo touchscreen rotation (#3221) --- .../touchscreen/lilygo_t5_47_touchscreen.cpp | 47 +++++++++++++++++-- 1 file changed, 43 insertions(+), 4 deletions(-) diff --git a/esphome/components/lilygo_t5_47/touchscreen/lilygo_t5_47_touchscreen.cpp b/esphome/components/lilygo_t5_47/touchscreen/lilygo_t5_47_touchscreen.cpp index b5cf63980b..b92d7d6f10 100644 --- a/esphome/components/lilygo_t5_47/touchscreen/lilygo_t5_47_touchscreen.cpp +++ b/esphome/components/lilygo_t5_47/touchscreen/lilygo_t5_47_touchscreen.cpp @@ -113,8 +113,27 @@ void LilygoT547Touchscreen::loop() { if (tp.state == 0x06) tp.state = 0x07; - tp.y = (uint16_t)((buffer[i * 5 + 1 + offset] << 4) | ((buffer[i * 5 + 3 + offset] >> 4) & 0x0F)); - tp.x = (uint16_t)((buffer[i * 5 + 2 + offset] << 4) | (buffer[i * 5 + 3 + offset] & 0x0F)); + uint16_t y = (uint16_t)((buffer[i * 5 + 1 + offset] << 4) | ((buffer[i * 5 + 3 + offset] >> 4) & 0x0F)); + uint16_t x = (uint16_t)((buffer[i * 5 + 2 + offset] << 4) | (buffer[i * 5 + 3 + offset] & 0x0F)); + + switch (this->rotation_) { + case ROTATE_0_DEGREES: + tp.y = this->display_height_ - y; + tp.x = x; + break; + case ROTATE_90_DEGREES: + tp.x = this->display_height_ - y; + tp.y = this->display_width_ - x; + break; + case ROTATE_180_DEGREES: + tp.y = y; + tp.x = this->display_width_ - x; + break; + case ROTATE_270_DEGREES: + tp.x = y; + tp.y = x; + break; + } this->defer([this, tp]() { this->send_touch_(tp); }); } @@ -122,8 +141,28 @@ void LilygoT547Touchscreen::loop() { TouchPoint tp; tp.id = (buffer[0] >> 4) & 0x0F; tp.state = 0x06; - tp.y = (uint16_t)((buffer[0 * 5 + 1] << 4) | ((buffer[0 * 5 + 3] >> 4) & 0x0F)); - tp.x = (uint16_t)((buffer[0 * 5 + 2] << 4) | (buffer[0 * 5 + 3] & 0x0F)); + + uint16_t y = (uint16_t)((buffer[0 * 5 + 1] << 4) | ((buffer[0 * 5 + 3] >> 4) & 0x0F)); + uint16_t x = (uint16_t)((buffer[0 * 5 + 2] << 4) | (buffer[0 * 5 + 3] & 0x0F)); + + switch (this->rotation_) { + case ROTATE_0_DEGREES: + tp.y = this->display_height_ - y; + tp.x = x; + break; + case ROTATE_90_DEGREES: + tp.x = this->display_height_ - y; + tp.y = this->display_width_ - x; + break; + case ROTATE_180_DEGREES: + tp.y = y; + tp.x = this->display_width_ - x; + break; + case ROTATE_270_DEGREES: + tp.x = y; + tp.y = x; + break; + } this->defer([this, tp]() { this->send_touch_(tp); }); } From dbd4e927d8987c319cc580df913e1d1f1c5da44d Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 21 Feb 2022 10:01:00 +1300 Subject: [PATCH 0210/3388] Fix fatal erroring in addon startup script (#3244) --- docker/ha-addon-rootfs/etc/cont-init.d/10-requirements.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker/ha-addon-rootfs/etc/cont-init.d/10-requirements.sh b/docker/ha-addon-rootfs/etc/cont-init.d/10-requirements.sh index 9d49c2b4dd..544787d568 100755 --- a/docker/ha-addon-rootfs/etc/cont-init.d/10-requirements.sh +++ b/docker/ha-addon-rootfs/etc/cont-init.d/10-requirements.sh @@ -7,12 +7,12 @@ # Check SSL requirements, if enabled if bashio::config.true 'ssl'; then if ! bashio::config.has_value 'certfile'; then - bashio::fatal 'SSL is enabled, but no certfile was specified.' + bashio::log.fatal 'SSL is enabled, but no certfile was specified.' bashio::exit.nok fi if ! bashio::config.has_value 'keyfile'; then - bashio::fatal 'SSL is enabled, but no keyfile was specified' + bashio::log.fatal 'SSL is enabled, but no keyfile was specified' bashio::exit.nok fi From 2748e6ba2910d5653b45e95fbadcec64591fdbaf Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 21 Feb 2022 10:17:25 +1300 Subject: [PATCH 0211/3388] Bump version to 2022.2.4 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 0e8695adef..015a598ded 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2022.2.3" +__version__ = "2022.2.4" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" From 3d0899aa58859e91b37507be391c0d4845dac005 Mon Sep 17 00:00:00 2001 From: Martin Weinelt Date: Mon, 21 Feb 2022 02:05:13 +0100 Subject: [PATCH 0212/3388] Respect ESPHOME_USE_SUBPROCESS in esp32 post_build script (#3246) --- esphome/components/esp32/post_build.py.script | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/esphome/components/esp32/post_build.py.script b/esphome/components/esp32/post_build.py.script index 2bb1a6c3d6..406516a102 100644 --- a/esphome/components/esp32/post_build.py.script +++ b/esphome/components/esp32/post_build.py.script @@ -1,6 +1,10 @@ # Source https://github.com/letscontrolit/ESPEasy/pull/3845#issuecomment-1005864664 -import esptool +import os +if os.environ.get("ESPHOME_USE_SUBPROCESS") is None: + import esptool +else: + import subprocess from SCons.Script import ARGUMENTS # pylint: disable=E0602 @@ -42,8 +46,11 @@ def esp32_create_combined_bin(source, target, env): print() print(f"Using esptool.py arguments: {' '.join(cmd)}") print() - esptool.main(cmd) + if os.environ.get("ESPHOME_USE_SUBPROCESS") is None: + esptool.main(cmd) + else: + subprocess.run(["esptool.py", *cmd]) # pylint: disable=E0602 env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", esp32_create_combined_bin) # noqa From 95acf1906703d5658dd9cbae8ef61afe74ac737a Mon Sep 17 00:00:00 2001 From: Nicholas Peters Date: Mon, 21 Feb 2022 19:53:24 -0500 Subject: [PATCH 0213/3388] Fix regression caused by TSL2591 auto gain (#3249) --- esphome/components/tsl2591/tsl2591.cpp | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/esphome/components/tsl2591/tsl2591.cpp b/esphome/components/tsl2591/tsl2591.cpp index 8a540c5f13..f8c59a53c6 100644 --- a/esphome/components/tsl2591/tsl2591.cpp +++ b/esphome/components/tsl2591/tsl2591.cpp @@ -43,16 +43,34 @@ void TSL2591Component::disable_if_power_saving_() { } void TSL2591Component::setup() { - if (this->component_gain_ == TSL2591_CGAIN_AUTO) - this->gain_ = TSL2591_GAIN_MED; + switch (this->component_gain_) { + case TSL2591_CGAIN_LOW: + this->gain_ = TSL2591_GAIN_LOW; + break; + case TSL2591_CGAIN_MED: + this->gain_ = TSL2591_GAIN_MED; + break; + case TSL2591_CGAIN_HIGH: + this->gain_ = TSL2591_GAIN_HIGH; + break; + case TSL2591_CGAIN_MAX: + this->gain_ = TSL2591_GAIN_MAX; + break; + case TSL2591_CGAIN_AUTO: + this->gain_ = TSL2591_GAIN_MED; + break; + } + uint8_t address = this->address_; ESP_LOGI(TAG, "Setting up TSL2591 sensor at I2C address 0x%02X", address); + uint8_t id; if (!this->read_byte(TSL2591_COMMAND_BIT | TSL2591_REGISTER_DEVICE_ID, &id)) { ESP_LOGE(TAG, "Failed I2C read during setup()"); this->mark_failed(); return; } + if (id != 0x50) { ESP_LOGE(TAG, "Could not find the TSL2591 sensor. The ID register of the device at address 0x%02X reported 0x%02X " @@ -61,6 +79,7 @@ void TSL2591Component::setup() { this->mark_failed(); return; } + this->set_integration_time_and_gain(this->integration_time_, this->gain_); this->disable_if_power_saving_(); } From 97aca8e54c62e514c7cd042c1f3c2f7c8f789a06 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 23 Feb 2022 11:20:48 +1300 Subject: [PATCH 0214/3388] Bump version to 2022.2.5 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 015a598ded..7cd88a8101 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2022.2.4" +__version__ = "2022.2.5" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" From 859cca49d19a95ce3329180710cbaaf650ef7ac0 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 2 Mar 2022 16:44:35 +1300 Subject: [PATCH 0215/3388] Only get free memory size from internal (#3259) --- esphome/components/json/json_util.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/json/json_util.cpp b/esphome/components/json/json_util.cpp index 7e88fb6e59..9acba76597 100644 --- a/esphome/components/json/json_util.cpp +++ b/esphome/components/json/json_util.cpp @@ -22,7 +22,7 @@ std::string build_json(const json_build_t &f) { #ifdef USE_ESP8266 const size_t free_heap = ESP.getMaxFreeBlockSize() - 2048; // NOLINT(readability-static-accessed-through-instance) #elif defined(USE_ESP32) - const size_t free_heap = heap_caps_get_largest_free_block(MALLOC_CAP_DEFAULT) - 2048; + const size_t free_heap = heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL) - 2048; #endif DynamicJsonDocument json_document(free_heap); @@ -42,7 +42,7 @@ void parse_json(const std::string &data, const json_parse_t &f) { #ifdef USE_ESP8266 const size_t free_heap = ESP.getMaxFreeBlockSize() - 2048; // NOLINT(readability-static-accessed-through-instance) #elif defined(USE_ESP32) - const size_t free_heap = heap_caps_get_largest_free_block(MALLOC_CAP_DEFAULT) - 2048; + const size_t free_heap = heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL) - 2048; #endif DynamicJsonDocument json_document(free_heap); From 942b0de7fd7ad31d4e79bbe749ac8b928ba597cb Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 2 Mar 2022 17:07:08 +1300 Subject: [PATCH 0216/3388] Bump version to 2022.2.6 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 7cd88a8101..e49f468c30 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2022.2.5" +__version__ = "2022.2.6" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" From 023d26f5218c8bcafee022873e7737d5cf5838f2 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 9 Mar 2022 20:07:50 +1300 Subject: [PATCH 0217/3388] Bump version to 2022.3.0b1 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 2ec00edf7b..26444baffd 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2022.3.0-dev" +__version__ = "2022.3.0b1" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" From 06a35056989ad441b94c35d0240a5fbf97ad6f67 Mon Sep 17 00:00:00 2001 From: stegm Date: Wed, 9 Mar 2022 20:40:43 +0100 Subject: [PATCH 0218/3388] Add optimistic config flag to modbus select. (#3267) --- esphome/components/modbus_controller/select/__init__.py | 4 +++- esphome/components/modbus_controller/select/modbus_select.cpp | 3 +++ esphome/components/modbus_controller/select/modbus_select.h | 2 ++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/esphome/components/modbus_controller/select/__init__.py b/esphome/components/modbus_controller/select/__init__.py index 7d03064fa5..6f194ef2a3 100644 --- a/esphome/components/modbus_controller/select/__init__.py +++ b/esphome/components/modbus_controller/select/__init__.py @@ -1,7 +1,7 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import select -from esphome.const import CONF_ADDRESS, CONF_ID, CONF_LAMBDA +from esphome.const import CONF_ADDRESS, CONF_ID, CONF_LAMBDA, CONF_OPTIMISTIC from esphome.jsonschema import jschema_composite from .. import ( @@ -79,6 +79,7 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_FORCE_NEW_RANGE, default=False): cv.boolean, cv.Required(CONF_OPTIONSMAP): ensure_option_map(), cv.Optional(CONF_USE_WRITE_MULTIPLE, default=False): cv.boolean, + cv.Optional(CONF_OPTIMISTIC, default=False): cv.boolean, cv.Optional(CONF_LAMBDA): cv.returning_lambda, cv.Optional(CONF_WRITE_LAMBDA): cv.returning_lambda, }, @@ -112,6 +113,7 @@ async def to_code(config): cg.add(parent.add_sensor_item(var)) cg.add(var.set_parent(parent)) cg.add(var.set_use_write_mutiple(config[CONF_USE_WRITE_MULTIPLE])) + cg.add(var.set_optimistic(config[CONF_OPTIMISTIC])) if CONF_LAMBDA in config: template_ = await cg.process_lambda( diff --git a/esphome/components/modbus_controller/select/modbus_select.cpp b/esphome/components/modbus_controller/select/modbus_select.cpp index 2c6b32f545..33cef39a18 100644 --- a/esphome/components/modbus_controller/select/modbus_select.cpp +++ b/esphome/components/modbus_controller/select/modbus_select.cpp @@ -80,6 +80,9 @@ void ModbusSelect::control(const std::string &value) { } parent_->queue_command(write_cmd); + + if (this->optimistic_) + this->publish_state(value); } } // namespace modbus_controller diff --git a/esphome/components/modbus_controller/select/modbus_select.h b/esphome/components/modbus_controller/select/modbus_select.h index 0875194768..2a31dfd7cc 100644 --- a/esphome/components/modbus_controller/select/modbus_select.h +++ b/esphome/components/modbus_controller/select/modbus_select.h @@ -32,6 +32,7 @@ class ModbusSelect : public Component, public select::Select, public SensorItem void set_parent(ModbusController *const parent) { this->parent_ = parent; } void set_use_write_mutiple(bool use_write_multiple) { this->use_write_multiple_ = use_write_multiple; } + void set_optimistic(bool optimistic) { this->optimistic_ = optimistic; } void set_template(transform_func_t &&f) { this->transform_func_ = f; } void set_write_template(write_transform_func_t &&f) { this->write_transform_func_ = f; } @@ -43,6 +44,7 @@ class ModbusSelect : public Component, public select::Select, public SensorItem std::vector mapping_; ModbusController *parent_; bool use_write_multiple_{false}; + bool optimistic_{false}; optional transform_func_; optional write_transform_func_; }; From b7535693fa4628f8f831e2c87fdbccc3c050f05f Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 16 Mar 2022 13:35:37 +1300 Subject: [PATCH 0219/3388] Add helper overloads for hex print 16-bit (#3297) --- esphome/core/helpers.cpp | 19 +++++++++++++++++++ esphome/core/helpers.h | 4 ++++ 2 files changed, 23 insertions(+) diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index a346cd7e0b..b03d890ad8 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -213,6 +213,25 @@ std::string format_hex_pretty(const uint8_t *data, size_t length) { } std::string format_hex_pretty(const std::vector &data) { return format_hex_pretty(data.data(), data.size()); } +std::string format_hex_pretty(const uint16_t *data, size_t length) { + if (length == 0) + return ""; + std::string ret; + ret.resize(5 * length - 1); + for (size_t i = 0; i < length; i++) { + ret[5 * i] = format_hex_pretty_char((data[i] & 0xF000) >> 12); + ret[5 * i + 1] = format_hex_pretty_char((data[i] & 0x0F00) >> 8); + ret[5 * i + 2] = format_hex_pretty_char((data[i] & 0x00F0) >> 4); + ret[5 * i + 3] = format_hex_pretty_char(data[i] & 0x000F); + if (i != length - 1) + ret[5 * i + 2] = '.'; + } + if (length > 4) + return ret + " (" + to_string(length) + ")"; + return ret; +} +std::string format_hex_pretty(const std::vector &data) { return format_hex_pretty(data.data(), data.size()); } + ParseOnOffState parse_on_off(const char *str, const char *on, const char *off) { if (on == nullptr && strcasecmp(str, "on") == 0) return PARSE_ON; diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index e0763d2c71..074bea6fd1 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -386,8 +386,12 @@ template::value, int> = 0> std::stri /// Format the byte array \p data of length \p len in pretty-printed, human-readable hex. std::string format_hex_pretty(const uint8_t *data, size_t length); +/// Format the word array \p data of length \p len in pretty-printed, human-readable hex. +std::string format_hex_pretty(const uint16_t *data, size_t length); /// Format the vector \p data in pretty-printed, human-readable hex. std::string format_hex_pretty(const std::vector &data); +/// Format the vector \p data in pretty-printed, human-readable hex. +std::string format_hex_pretty(const std::vector &data); /// Format an unsigned integer in pretty-printed, human-readable hex, starting with the most significant byte. template::value, int> = 0> std::string format_hex_pretty(T val) { val = convert_big_endian(val); From 756f71c3822a2f457b42c5b55f8af1151c69c444 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 16 Mar 2022 13:43:05 +1300 Subject: [PATCH 0220/3388] Allow custom register type for modbus number (#3202) --- esphome/components/modbus_controller/number/__init__.py | 6 ++++++ esphome/components/modbus_controller/number/modbus_number.h | 6 +++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/esphome/components/modbus_controller/number/__init__.py b/esphome/components/modbus_controller/number/__init__.py index 56ec734315..37a39ff334 100644 --- a/esphome/components/modbus_controller/number/__init__.py +++ b/esphome/components/modbus_controller/number/__init__.py @@ -11,6 +11,7 @@ from esphome.const import ( ) from .. import ( + MODBUS_WRITE_REGISTER_TYPE, add_modbus_base_properties, modbus_controller_ns, modbus_calc_properties, @@ -24,6 +25,7 @@ from ..const import ( CONF_CUSTOM_COMMAND, CONF_FORCE_NEW_RANGE, CONF_MODBUS_CONTROLLER_ID, + CONF_REGISTER_TYPE, CONF_SKIP_UPDATES, CONF_USE_WRITE_MULTIPLE, CONF_VALUE_TYPE, @@ -61,6 +63,9 @@ CONFIG_SCHEMA = cv.All( number.NUMBER_SCHEMA.extend(ModbusItemBaseSchema).extend( { cv.GenerateID(): cv.declare_id(ModbusNumber), + cv.Optional(CONF_REGISTER_TYPE, default="holding"): cv.enum( + MODBUS_WRITE_REGISTER_TYPE + ), cv.Optional(CONF_VALUE_TYPE, default="U_WORD"): cv.enum(SENSOR_VALUE_TYPE), cv.Optional(CONF_WRITE_LAMBDA): cv.returning_lambda, # 24 bits are the maximum value for fp32 before precison is lost @@ -81,6 +86,7 @@ async def to_code(config): byte_offset, reg_count = modbus_calc_properties(config) var = cg.new_Pvariable( config[CONF_ID], + config[CONF_REGISTER_TYPE], config[CONF_ADDRESS], byte_offset, config[CONF_BITMASK], diff --git a/esphome/components/modbus_controller/number/modbus_number.h b/esphome/components/modbus_controller/number/modbus_number.h index 0c525d9c89..aa5c8d1500 100644 --- a/esphome/components/modbus_controller/number/modbus_number.h +++ b/esphome/components/modbus_controller/number/modbus_number.h @@ -11,9 +11,9 @@ using value_to_data_t = std::function(float); class ModbusNumber : public number::Number, public Component, public SensorItem { public: - ModbusNumber(uint16_t start_address, uint8_t offset, uint32_t bitmask, SensorValueType value_type, int register_count, - uint8_t skip_updates, bool force_new_range) { - this->register_type = ModbusRegisterType::HOLDING; + ModbusNumber(ModbusRegisterType register_type, uint16_t start_address, uint8_t offset, uint32_t bitmask, + SensorValueType value_type, int register_count, uint8_t skip_updates, bool force_new_range) { + this->register_type = register_type; this->start_address = start_address; this->offset = offset; this->bitmask = bitmask; From e5c2dbc7ec1f7e06c043a095957a6945f4f7448d Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 16 Mar 2022 14:06:00 +1300 Subject: [PATCH 0221/3388] Bump version to 2022.3.0b2 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 26444baffd..579cefd507 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2022.3.0b1" +__version__ = "2022.3.0b2" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" From 792a24f38d05f87a106096f90be7cd9cbe959b6f Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 16 Mar 2022 22:32:24 +1300 Subject: [PATCH 0222/3388] Bump version to 2022.3.0 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 579cefd507..e9e3809b2c 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2022.3.0b2" +__version__ = "2022.3.0" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" From 24b75b7ed64ee9e239e27511c91e33a66519e9a2 Mon Sep 17 00:00:00 2001 From: wysiwyng <4764286+wysiwyng@users.noreply.github.com> Date: Wed, 16 Mar 2022 20:33:05 +0100 Subject: [PATCH 0223/3388] Fix WDT reset during dallas search algorithm (#3293) --- esphome/components/dallas/esp_one_wire.cpp | 4 ---- esphome/components/dallas/esp_one_wire.h | 1 - 2 files changed, 5 deletions(-) diff --git a/esphome/components/dallas/esp_one_wire.cpp b/esphome/components/dallas/esp_one_wire.cpp index 885846e5e5..5bd0f42855 100644 --- a/esphome/components/dallas/esp_one_wire.cpp +++ b/esphome/components/dallas/esp_one_wire.cpp @@ -142,7 +142,6 @@ void IRAM_ATTR ESPOneWire::select(uint64_t address) { void IRAM_ATTR ESPOneWire::reset_search() { this->last_discrepancy_ = 0; this->last_device_flag_ = false; - this->last_family_discrepancy_ = 0; this->rom_number_ = 0; } uint64_t IRAM_ATTR ESPOneWire::search() { @@ -195,9 +194,6 @@ uint64_t IRAM_ATTR ESPOneWire::search() { if (!branch) { last_zero = id_bit_number; - if (last_zero < 9) { - this->last_discrepancy_ = last_zero; - } } } diff --git a/esphome/components/dallas/esp_one_wire.h b/esphome/components/dallas/esp_one_wire.h index ef6f079f02..7544a6fe98 100644 --- a/esphome/components/dallas/esp_one_wire.h +++ b/esphome/components/dallas/esp_one_wire.h @@ -60,7 +60,6 @@ class ESPOneWire { ISRInternalGPIOPin pin_; uint8_t last_discrepancy_{0}; - uint8_t last_family_discrepancy_{0}; bool last_device_flag_{false}; uint64_t rom_number_{0}; }; From 0729ed538e6b13b5ad5d4f6d3051e79012430cf5 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 23 Mar 2022 09:45:05 +1300 Subject: [PATCH 0224/3388] Webserver utilize Component Iterator to not overload eventstream (#3310) --- esphome/components/api/api_connection.cpp | 2 +- esphome/components/api/api_server.h | 1 - esphome/components/api/list_entities.cpp | 3 +- esphome/components/api/list_entities.h | 6 +- esphome/components/api/proto.cpp | 1 - esphome/components/api/subscribe_state.cpp | 3 +- esphome/components/api/subscribe_state.h | 6 +- .../components/web_server/list_entities.cpp | 97 +++++++++++++++++++ esphome/components/web_server/list_entities.h | 60 ++++++++++++ esphome/components/web_server/web_server.cpp | 86 +--------------- esphome/components/web_server/web_server.h | 7 +- .../util.cpp => core/component_iterator.cpp} | 56 ++++++----- .../api/util.h => core/component_iterator.h} | 23 +++-- 13 files changed, 217 insertions(+), 134 deletions(-) create mode 100644 esphome/components/web_server/list_entities.cpp create mode 100644 esphome/components/web_server/list_entities.h rename esphome/{components/api/util.cpp => core/component_iterator.cpp} (80%) rename esphome/{components/api/util.h => core/component_iterator.h} (91%) diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index b998ef5929..81f2465b74 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -23,7 +23,7 @@ static const char *const TAG = "api.connection"; static const int ESP32_CAMERA_STOP_STREAM = 5000; APIConnection::APIConnection(std::unique_ptr sock, APIServer *parent) - : parent_(parent), initial_state_iterator_(parent, this), list_entities_iterator_(parent, this) { + : parent_(parent), initial_state_iterator_(this), list_entities_iterator_(this) { this->proto_write_buffer_.reserve(64); #if defined(USE_API_PLAINTEXT) diff --git a/esphome/components/api/api_server.h b/esphome/components/api/api_server.h index 3214da5b3d..fdc46922ad 100644 --- a/esphome/components/api/api_server.h +++ b/esphome/components/api/api_server.h @@ -7,7 +7,6 @@ #include "esphome/components/socket/socket.h" #include "api_pb2.h" #include "api_pb2_service.h" -#include "util.h" #include "list_entities.h" #include "subscribe_state.h" #include "user_services.h" diff --git a/esphome/components/api/list_entities.cpp b/esphome/components/api/list_entities.cpp index fb0dfa3d05..9f55fda617 100644 --- a/esphome/components/api/list_entities.cpp +++ b/esphome/components/api/list_entities.cpp @@ -40,8 +40,7 @@ bool ListEntitiesIterator::on_lock(lock::Lock *a_lock) { return this->client_->s #endif bool ListEntitiesIterator::on_end() { return this->client_->send_list_info_done(); } -ListEntitiesIterator::ListEntitiesIterator(APIServer *server, APIConnection *client) - : ComponentIterator(server), client_(client) {} +ListEntitiesIterator::ListEntitiesIterator(APIConnection *client) : client_(client) {} bool ListEntitiesIterator::on_service(UserServiceDescriptor *service) { auto resp = service->encode_list_service_response(); return this->client_->send_list_entities_services_response(resp); diff --git a/esphome/components/api/list_entities.h b/esphome/components/api/list_entities.h index bfceb39ebf..51c343eb03 100644 --- a/esphome/components/api/list_entities.h +++ b/esphome/components/api/list_entities.h @@ -1,8 +1,8 @@ #pragma once #include "esphome/core/component.h" +#include "esphome/core/component_iterator.h" #include "esphome/core/defines.h" -#include "util.h" namespace esphome { namespace api { @@ -11,7 +11,7 @@ class APIConnection; class ListEntitiesIterator : public ComponentIterator { public: - ListEntitiesIterator(APIServer *server, APIConnection *client); + ListEntitiesIterator(APIConnection *client); #ifdef USE_BINARY_SENSOR bool on_binary_sensor(binary_sensor::BinarySensor *binary_sensor) override; #endif @@ -60,5 +60,3 @@ class ListEntitiesIterator : public ComponentIterator { } // namespace api } // namespace esphome - -#include "api_server.h" diff --git a/esphome/components/api/proto.cpp b/esphome/components/api/proto.cpp index 0ba277d90a..ca7a4c0887 100644 --- a/esphome/components/api/proto.cpp +++ b/esphome/components/api/proto.cpp @@ -1,5 +1,4 @@ #include "proto.h" -#include "util.h" #include "esphome/core/log.h" namespace esphome { diff --git a/esphome/components/api/subscribe_state.cpp b/esphome/components/api/subscribe_state.cpp index 10416ecc5c..ba277502c8 100644 --- a/esphome/components/api/subscribe_state.cpp +++ b/esphome/components/api/subscribe_state.cpp @@ -50,8 +50,7 @@ bool InitialStateIterator::on_select(select::Select *select) { #ifdef USE_LOCK bool InitialStateIterator::on_lock(lock::Lock *a_lock) { return this->client_->send_lock_state(a_lock, a_lock->state); } #endif -InitialStateIterator::InitialStateIterator(APIServer *server, APIConnection *client) - : ComponentIterator(server), client_(client) {} +InitialStateIterator::InitialStateIterator(APIConnection *client) : client_(client) {} } // namespace api } // namespace esphome diff --git a/esphome/components/api/subscribe_state.h b/esphome/components/api/subscribe_state.h index caea013f84..515e1a2d07 100644 --- a/esphome/components/api/subscribe_state.h +++ b/esphome/components/api/subscribe_state.h @@ -1,9 +1,9 @@ #pragma once #include "esphome/core/component.h" +#include "esphome/core/component_iterator.h" #include "esphome/core/controller.h" #include "esphome/core/defines.h" -#include "util.h" namespace esphome { namespace api { @@ -12,7 +12,7 @@ class APIConnection; class InitialStateIterator : public ComponentIterator { public: - InitialStateIterator(APIServer *server, APIConnection *client); + InitialStateIterator(APIConnection *client); #ifdef USE_BINARY_SENSOR bool on_binary_sensor(binary_sensor::BinarySensor *binary_sensor) override; #endif @@ -55,5 +55,3 @@ class InitialStateIterator : public ComponentIterator { } // namespace api } // namespace esphome - -#include "api_server.h" diff --git a/esphome/components/web_server/list_entities.cpp b/esphome/components/web_server/list_entities.cpp new file mode 100644 index 0000000000..6f833a5c83 --- /dev/null +++ b/esphome/components/web_server/list_entities.cpp @@ -0,0 +1,97 @@ +#ifdef USE_ARDUINO + +#include "list_entities.h" +#include "esphome/core/application.h" +#include "esphome/core/log.h" +#include "esphome/core/util.h" + +#include "web_server.h" + +namespace esphome { +namespace web_server { + +ListEntitiesIterator::ListEntitiesIterator(WebServer *web_server) : web_server_(web_server) {} + +#ifdef USE_BINARY_SENSOR +bool ListEntitiesIterator::on_binary_sensor(binary_sensor::BinarySensor *binary_sensor) { + this->web_server_->events_.send( + this->web_server_->binary_sensor_json(binary_sensor, binary_sensor->state, DETAIL_ALL).c_str(), "state"); + return true; +} +#endif +#ifdef USE_COVER +bool ListEntitiesIterator::on_cover(cover::Cover *cover) { + this->web_server_->events_.send(this->web_server_->cover_json(cover, DETAIL_ALL).c_str(), "state"); + return true; +} +#endif +#ifdef USE_FAN +bool ListEntitiesIterator::on_fan(fan::Fan *fan) { + this->web_server_->events_.send(this->web_server_->fan_json(fan, DETAIL_ALL).c_str(), "state"); + return true; +} +#endif +#ifdef USE_LIGHT +bool ListEntitiesIterator::on_light(light::LightState *light) { + this->web_server_->events_.send(this->web_server_->light_json(light, DETAIL_ALL).c_str(), "state"); + return true; +} +#endif +#ifdef USE_SENSOR +bool ListEntitiesIterator::on_sensor(sensor::Sensor *sensor) { + this->web_server_->events_.send(this->web_server_->sensor_json(sensor, sensor->state, DETAIL_ALL).c_str(), "state"); + return true; +} +#endif +#ifdef USE_SWITCH +bool ListEntitiesIterator::on_switch(switch_::Switch *a_switch) { + this->web_server_->events_.send(this->web_server_->switch_json(a_switch, a_switch->state, DETAIL_ALL).c_str(), + "state"); + return true; +} +#endif +#ifdef USE_BUTTON +bool ListEntitiesIterator::on_button(button::Button *button) { + this->web_server_->events_.send(this->web_server_->button_json(button, DETAIL_ALL).c_str(), "state"); + return true; +} +#endif +#ifdef USE_TEXT_SENSOR +bool ListEntitiesIterator::on_text_sensor(text_sensor::TextSensor *text_sensor) { + this->web_server_->events_.send( + this->web_server_->text_sensor_json(text_sensor, text_sensor->state, DETAIL_ALL).c_str(), "state"); + return true; +} +#endif +#ifdef USE_LOCK +bool ListEntitiesIterator::on_lock(lock::Lock *a_lock) { + this->web_server_->events_.send(this->web_server_->lock_json(a_lock, a_lock->state, DETAIL_ALL).c_str(), "state"); + return true; +} +#endif + +#ifdef USE_CLIMATE +bool ListEntitiesIterator::on_climate(climate::Climate *climate) { + this->web_server_->events_.send(this->web_server_->climate_json(climate, DETAIL_ALL).c_str(), "state"); + return true; +} +#endif + +#ifdef USE_NUMBER +bool ListEntitiesIterator::on_number(number::Number *number) { + this->web_server_->events_.send(this->web_server_->number_json(number, number->state, DETAIL_ALL).c_str(), "state"); + return true; +} +#endif + +#ifdef USE_SELECT +bool ListEntitiesIterator::on_select(select::Select *select) { + this->web_server_->events_.send(this->web_server_->select_json(select, select->state, DETAIL_ALL).c_str(), "state"); + return true; +} +#endif + +} // namespace web_server +} // namespace esphome + +#endif // USE_ARDUINO diff --git a/esphome/components/web_server/list_entities.h b/esphome/components/web_server/list_entities.h new file mode 100644 index 0000000000..85868caff8 --- /dev/null +++ b/esphome/components/web_server/list_entities.h @@ -0,0 +1,60 @@ +#pragma once + +#ifdef USE_ARDUINO + +#include "esphome/core/component.h" +#include "esphome/core/component_iterator.h" +#include "esphome/core/defines.h" +namespace esphome { +namespace web_server { + +class WebServer; + +class ListEntitiesIterator : public ComponentIterator { + public: + ListEntitiesIterator(WebServer *web_server); +#ifdef USE_BINARY_SENSOR + bool on_binary_sensor(binary_sensor::BinarySensor *binary_sensor) override; +#endif +#ifdef USE_COVER + bool on_cover(cover::Cover *cover) override; +#endif +#ifdef USE_FAN + bool on_fan(fan::Fan *fan) override; +#endif +#ifdef USE_LIGHT + bool on_light(light::LightState *light) override; +#endif +#ifdef USE_SENSOR + bool on_sensor(sensor::Sensor *sensor) override; +#endif +#ifdef USE_SWITCH + bool on_switch(switch_::Switch *a_switch) override; +#endif +#ifdef USE_BUTTON + bool on_button(button::Button *button) override; +#endif +#ifdef USE_TEXT_SENSOR + bool on_text_sensor(text_sensor::TextSensor *text_sensor) override; +#endif +#ifdef USE_CLIMATE + bool on_climate(climate::Climate *climate) override; +#endif +#ifdef USE_NUMBER + bool on_number(number::Number *number) override; +#endif +#ifdef USE_SELECT + bool on_select(select::Select *select) override; +#endif +#ifdef USE_LOCK + bool on_lock(lock::Lock *a_lock) override; +#endif + + protected: + WebServer *web_server_; +}; + +} // namespace web_server +} // namespace esphome + +#endif // USE_ARDUINO diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index 278aeab937..0dfd608661 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -1,6 +1,7 @@ #ifdef USE_ARDUINO #include "web_server.h" + #include "esphome/core/log.h" #include "esphome/core/application.h" #include "esphome/core/entity_base.h" @@ -17,7 +18,7 @@ #endif #ifdef USE_LOGGER -#include +#include "esphome/components/logger/logger.h" #endif #ifdef USE_FAN @@ -106,87 +107,7 @@ void WebServer::setup() { }).c_str(), "ping", millis(), 30000); -#ifdef USE_SENSOR - for (auto *obj : App.get_sensors()) { - if (this->include_internal_ || !obj->is_internal()) - client->send(this->sensor_json(obj, obj->state, DETAIL_ALL).c_str(), "state"); - } -#endif - -#ifdef USE_SWITCH - for (auto *obj : App.get_switches()) { - if (this->include_internal_ || !obj->is_internal()) - client->send(this->switch_json(obj, obj->state, DETAIL_ALL).c_str(), "state"); - } -#endif - -#ifdef USE_BUTTON - for (auto *obj : App.get_buttons()) - client->send(this->button_json(obj, DETAIL_ALL).c_str(), "state"); -#endif - -#ifdef USE_BINARY_SENSOR - for (auto *obj : App.get_binary_sensors()) { - if (this->include_internal_ || !obj->is_internal()) - client->send(this->binary_sensor_json(obj, obj->state, DETAIL_ALL).c_str(), "state"); - } -#endif - -#ifdef USE_FAN - for (auto *obj : App.get_fans()) { - if (this->include_internal_ || !obj->is_internal()) - client->send(this->fan_json(obj, DETAIL_ALL).c_str(), "state"); - } -#endif - -#ifdef USE_LIGHT - for (auto *obj : App.get_lights()) { - if (this->include_internal_ || !obj->is_internal()) - client->send(this->light_json(obj, DETAIL_ALL).c_str(), "state"); - } -#endif - -#ifdef USE_TEXT_SENSOR - for (auto *obj : App.get_text_sensors()) { - if (this->include_internal_ || !obj->is_internal()) - client->send(this->text_sensor_json(obj, obj->state, DETAIL_ALL).c_str(), "state"); - } -#endif - -#ifdef USE_COVER - for (auto *obj : App.get_covers()) { - if (this->include_internal_ || !obj->is_internal()) - client->send(this->cover_json(obj, DETAIL_ALL).c_str(), "state"); - } -#endif - -#ifdef USE_NUMBER - for (auto *obj : App.get_numbers()) { - if (this->include_internal_ || !obj->is_internal()) - client->send(this->number_json(obj, obj->state, DETAIL_ALL).c_str(), "state"); - } -#endif - -#ifdef USE_SELECT - for (auto *obj : App.get_selects()) { - if (this->include_internal_ || !obj->is_internal()) - client->send(this->select_json(obj, obj->state, DETAIL_ALL).c_str(), "state"); - } -#endif - -#ifdef USE_CLIMATE - for (auto *obj : App.get_climates()) { - if (this->include_internal_ || !obj->is_internal()) - client->send(this->climate_json(obj, DETAIL_ALL).c_str(), "state"); - } -#endif - -#ifdef USE_LOCK - for (auto *obj : App.get_locks()) { - if (this->include_internal_ || !obj->is_internal()) - client->send(this->lock_json(obj, obj->state, DETAIL_ALL).c_str(), "state"); - } -#endif + this->entities_iterator_.begin(this->include_internal_); }); #ifdef USE_LOGGER @@ -203,6 +124,7 @@ void WebServer::setup() { this->set_interval(10000, [this]() { this->events_.send("", "ping", millis(), 30000); }); } +void WebServer::loop() { this->entities_iterator_.advance(); } void WebServer::dump_config() { ESP_LOGCONFIG(TAG, "Web Server:"); ESP_LOGCONFIG(TAG, " Address: %s:%u", network::get_use_address().c_str(), this->base_->get_port()); diff --git a/esphome/components/web_server/web_server.h b/esphome/components/web_server/web_server.h index 2717997f60..73813ecfa1 100644 --- a/esphome/components/web_server/web_server.h +++ b/esphome/components/web_server/web_server.h @@ -2,6 +2,8 @@ #ifdef USE_ARDUINO +#include "list_entities.h" + #include "esphome/components/web_server_base/web_server_base.h" #include "esphome/core/component.h" #include "esphome/core/controller.h" @@ -32,7 +34,7 @@ enum JsonDetail { DETAIL_ALL, DETAIL_STATE }; */ class WebServer : public Controller, public Component, public AsyncWebHandler { public: - WebServer(web_server_base::WebServerBase *base) : base_(base) {} + WebServer(web_server_base::WebServerBase *base) : base_(base), entities_iterator_(ListEntitiesIterator(this)) {} /** Set the URL to the CSS that's sent to each client. Defaults to * https://esphome.io/_static/webserver-v1.min.css @@ -76,6 +78,7 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { // (In most use cases you won't need these) /// Setup the internal web server and register handlers. void setup() override; + void loop() override; void dump_config() override; @@ -217,8 +220,10 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { bool isRequestHandlerTrivial() override; protected: + friend ListEntitiesIterator; web_server_base::WebServerBase *base_; AsyncEventSource events_{"/events"}; + ListEntitiesIterator entities_iterator_; const char *css_url_{nullptr}; const char *css_include_{nullptr}; const char *js_url_{nullptr}; diff --git a/esphome/components/api/util.cpp b/esphome/core/component_iterator.cpp similarity index 80% rename from esphome/components/api/util.cpp rename to esphome/core/component_iterator.cpp index fd55f89f9b..4781607a2d 100644 --- a/esphome/components/api/util.cpp +++ b/esphome/core/component_iterator.cpp @@ -1,16 +1,18 @@ -#include "util.h" -#include "api_server.h" -#include "user_services.h" -#include "esphome/core/log.h" +#include "component_iterator.h" + #include "esphome/core/application.h" -namespace esphome { -namespace api { +#ifdef USE_API +#include "esphome/components/api/api_server.h" +#include "esphome/components/api/user_services.h" +#endif -ComponentIterator::ComponentIterator(APIServer *server) : server_(server) {} -void ComponentIterator::begin() { +namespace esphome { + +void ComponentIterator::begin(bool include_internal) { this->state_ = IteratorState::BEGIN; this->at_ = 0; + this->include_internal_ = include_internal; } void ComponentIterator::advance() { bool advance_platform = false; @@ -32,7 +34,7 @@ void ComponentIterator::advance() { advance_platform = true; } else { auto *binary_sensor = App.get_binary_sensors()[this->at_]; - if (binary_sensor->is_internal()) { + if (binary_sensor->is_internal() && !this->include_internal_) { success = true; break; } else { @@ -47,7 +49,7 @@ void ComponentIterator::advance() { advance_platform = true; } else { auto *cover = App.get_covers()[this->at_]; - if (cover->is_internal()) { + if (cover->is_internal() && !this->include_internal_) { success = true; break; } else { @@ -62,7 +64,7 @@ void ComponentIterator::advance() { advance_platform = true; } else { auto *fan = App.get_fans()[this->at_]; - if (fan->is_internal()) { + if (fan->is_internal() && !this->include_internal_) { success = true; break; } else { @@ -77,7 +79,7 @@ void ComponentIterator::advance() { advance_platform = true; } else { auto *light = App.get_lights()[this->at_]; - if (light->is_internal()) { + if (light->is_internal() && !this->include_internal_) { success = true; break; } else { @@ -92,7 +94,7 @@ void ComponentIterator::advance() { advance_platform = true; } else { auto *sensor = App.get_sensors()[this->at_]; - if (sensor->is_internal()) { + if (sensor->is_internal() && !this->include_internal_) { success = true; break; } else { @@ -107,7 +109,7 @@ void ComponentIterator::advance() { advance_platform = true; } else { auto *a_switch = App.get_switches()[this->at_]; - if (a_switch->is_internal()) { + if (a_switch->is_internal() && !this->include_internal_) { success = true; break; } else { @@ -122,7 +124,7 @@ void ComponentIterator::advance() { advance_platform = true; } else { auto *button = App.get_buttons()[this->at_]; - if (button->is_internal()) { + if (button->is_internal() && !this->include_internal_) { success = true; break; } else { @@ -137,7 +139,7 @@ void ComponentIterator::advance() { advance_platform = true; } else { auto *text_sensor = App.get_text_sensors()[this->at_]; - if (text_sensor->is_internal()) { + if (text_sensor->is_internal() && !this->include_internal_) { success = true; break; } else { @@ -146,20 +148,22 @@ void ComponentIterator::advance() { } break; #endif +#ifdef USE_API case IteratorState ::SERVICE: - if (this->at_ >= this->server_->get_user_services().size()) { + if (this->at_ >= api::global_api_server->get_user_services().size()) { advance_platform = true; } else { - auto *service = this->server_->get_user_services()[this->at_]; + auto *service = api::global_api_server->get_user_services()[this->at_]; success = this->on_service(service); } break; +#endif #ifdef USE_ESP32_CAMERA case IteratorState::CAMERA: if (esp32_camera::global_esp32_camera == nullptr) { advance_platform = true; } else { - if (esp32_camera::global_esp32_camera->is_internal()) { + if (esp32_camera::global_esp32_camera->is_internal() && !this->include_internal_) { advance_platform = success = true; break; } else { @@ -174,7 +178,7 @@ void ComponentIterator::advance() { advance_platform = true; } else { auto *climate = App.get_climates()[this->at_]; - if (climate->is_internal()) { + if (climate->is_internal() && !this->include_internal_) { success = true; break; } else { @@ -189,7 +193,7 @@ void ComponentIterator::advance() { advance_platform = true; } else { auto *number = App.get_numbers()[this->at_]; - if (number->is_internal()) { + if (number->is_internal() && !this->include_internal_) { success = true; break; } else { @@ -204,7 +208,7 @@ void ComponentIterator::advance() { advance_platform = true; } else { auto *select = App.get_selects()[this->at_]; - if (select->is_internal()) { + if (select->is_internal() && !this->include_internal_) { success = true; break; } else { @@ -219,7 +223,7 @@ void ComponentIterator::advance() { advance_platform = true; } else { auto *a_lock = App.get_locks()[this->at_]; - if (a_lock->is_internal()) { + if (a_lock->is_internal() && !this->include_internal_) { success = true; break; } else { @@ -244,10 +248,10 @@ void ComponentIterator::advance() { } bool ComponentIterator::on_end() { return true; } bool ComponentIterator::on_begin() { return true; } -bool ComponentIterator::on_service(UserServiceDescriptor *service) { return true; } +#ifdef USE_API +bool ComponentIterator::on_service(api::UserServiceDescriptor *service) { return true; } +#endif #ifdef USE_ESP32_CAMERA bool ComponentIterator::on_camera(esp32_camera::ESP32Camera *camera) { return true; } #endif - -} // namespace api } // namespace esphome diff --git a/esphome/components/api/util.h b/esphome/core/component_iterator.h similarity index 91% rename from esphome/components/api/util.h rename to esphome/core/component_iterator.h index 9204b0829e..bd95fe95e1 100644 --- a/esphome/components/api/util.h +++ b/esphome/core/component_iterator.h @@ -1,23 +1,24 @@ #pragma once -#include "esphome/core/helpers.h" #include "esphome/core/component.h" #include "esphome/core/controller.h" +#include "esphome/core/helpers.h" + #ifdef USE_ESP32_CAMERA #include "esphome/components/esp32_camera/esp32_camera.h" #endif namespace esphome { -namespace api { -class APIServer; +#ifdef USE_API +namespace api { class UserServiceDescriptor; +} // namespace api +#endif class ComponentIterator { public: - ComponentIterator(APIServer *server); - - void begin(); + void begin(bool include_internal = false); void advance(); virtual bool on_begin(); #ifdef USE_BINARY_SENSOR @@ -44,7 +45,9 @@ class ComponentIterator { #ifdef USE_TEXT_SENSOR virtual bool on_text_sensor(text_sensor::TextSensor *text_sensor) = 0; #endif - virtual bool on_service(UserServiceDescriptor *service); +#ifdef USE_API + virtual bool on_service(api::UserServiceDescriptor *service); +#endif #ifdef USE_ESP32_CAMERA virtual bool on_camera(esp32_camera::ESP32Camera *camera); #endif @@ -90,7 +93,9 @@ class ComponentIterator { #ifdef USE_TEXT_SENSOR TEXT_SENSOR, #endif +#ifdef USE_API SERVICE, +#endif #ifdef USE_ESP32_CAMERA CAMERA, #endif @@ -109,9 +114,7 @@ class ComponentIterator { MAX, } state_{IteratorState::NONE}; size_t at_{0}; - - APIServer *server_; + bool include_internal_{false}; }; -} // namespace api } // namespace esphome From 4e4a512107f714bac29fab43a2fd6ddb7dcea308 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 23 Mar 2022 09:46:25 +1300 Subject: [PATCH 0225/3388] Reserve less memory for json (#3289) --- esphome/components/json/json_util.cpp | 60 +++++++++++++++++++-------- 1 file changed, 43 insertions(+), 17 deletions(-) diff --git a/esphome/components/json/json_util.cpp b/esphome/components/json/json_util.cpp index 9acba76597..2070b312e8 100644 --- a/esphome/components/json/json_util.cpp +++ b/esphome/components/json/json_util.cpp @@ -16,16 +16,24 @@ static const char *const TAG = "json"; static std::vector global_json_build_buffer; // NOLINT std::string build_json(const json_build_t &f) { - // Here we are allocating as much heap memory as available minus 2kb to be safe + // Here we are allocating up to 5kb of memory, + // with the heap size minus 2kb to be safe if less than 5kb // as we can not have a true dynamic sized document. // The excess memory is freed below with `shrinkToFit()` #ifdef USE_ESP8266 - const size_t free_heap = ESP.getMaxFreeBlockSize() - 2048; // NOLINT(readability-static-accessed-through-instance) + const size_t free_heap = ESP.getMaxFreeBlockSize(); // NOLINT(readability-static-accessed-through-instance) #elif defined(USE_ESP32) - const size_t free_heap = heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL) - 2048; + const size_t free_heap = heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL); #endif - DynamicJsonDocument json_document(free_heap); + const size_t request_size = std::min(free_heap - 2048, (size_t) 5120); + + DynamicJsonDocument json_document(request_size); + if (json_document.memoryPool().buffer() == nullptr) { + ESP_LOGE(TAG, "Could not allocate memory for JSON document! Requested %u bytes, largest free heap block: %u bytes", + request_size, free_heap); + return "{}"; + } JsonObject root = json_document.to(); f(root); json_document.shrinkToFit(); @@ -36,27 +44,45 @@ std::string build_json(const json_build_t &f) { } void parse_json(const std::string &data, const json_parse_t &f) { - // Here we are allocating as much heap memory as available minus 2kb to be safe + // Here we are allocating 1.5 times the data size, + // with the heap size minus 2kb to be safe if less than that // as we can not have a true dynamic sized document. // The excess memory is freed below with `shrinkToFit()` #ifdef USE_ESP8266 - const size_t free_heap = ESP.getMaxFreeBlockSize() - 2048; // NOLINT(readability-static-accessed-through-instance) + const size_t free_heap = ESP.getMaxFreeBlockSize(); // NOLINT(readability-static-accessed-through-instance) #elif defined(USE_ESP32) - const size_t free_heap = heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL) - 2048; + const size_t free_heap = heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL); #endif + bool pass = false; + do { + const size_t request_size = std::min(free_heap - 2048, (size_t)(data.size() * 1.5)); - DynamicJsonDocument json_document(free_heap); - DeserializationError err = deserializeJson(json_document, data); - json_document.shrinkToFit(); + DynamicJsonDocument json_document(request_size); + if (json_document.memoryPool().buffer() == nullptr) { + ESP_LOGE(TAG, "Could not allocate memory for JSON document! Requested %u bytes, free heap: %u", request_size, + free_heap); + return; + } + DeserializationError err = deserializeJson(json_document, data); + json_document.shrinkToFit(); - JsonObject root = json_document.as(); + JsonObject root = json_document.as(); - if (err) { - ESP_LOGW(TAG, "Parsing JSON failed."); - return; - } - - f(root); + if (err == DeserializationError::Ok) { + pass = true; + f(root); + } else if (err == DeserializationError::NoMemory) { + if (request_size * 2 >= free_heap) { + ESP_LOGE(TAG, "Can not allocate more memory for deserialization. Consider making source string smaller"); + return; + } + ESP_LOGW(TAG, "Increasing memory allocation."); + continue; + } else { + ESP_LOGE(TAG, "JSON parse error: %s", err.c_str()); + return; + } + } while (!pass); } } // namespace json From 9a9d5964eeda7fda76900ec50109f4465f092871 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 23 Mar 2022 11:12:22 +1300 Subject: [PATCH 0226/3388] Add small delay before setting up app in safe mode (#3323) --- esphome/components/ota/ota_component.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/esphome/components/ota/ota_component.cpp b/esphome/components/ota/ota_component.cpp index 37da3bdc44..3138c495da 100644 --- a/esphome/components/ota/ota_component.cpp +++ b/esphome/components/ota/ota_component.cpp @@ -473,6 +473,8 @@ bool OTAComponent::should_enter_safe_mode(uint8_t num_attempts, uint32_t enable_ App.reboot(); }); + // Delay here to allow power to stabilise before Wi-Fi/Ethernet is initialised. + delay(100); // NOLINT App.setup(); ESP_LOGI(TAG, "Waiting for OTA attempt."); From 24029cc918ea6f9bfbdcfb2f0f4a932dd5c460c8 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 23 Mar 2022 12:26:02 +1300 Subject: [PATCH 0227/3388] Bump version to 2022.3.1 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index e9e3809b2c..8bf303db09 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2022.3.0" +__version__ = "2022.3.1" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" From bb5f7249a6575751b95edf91eafc0b572c215dea Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 28 Mar 2022 17:04:25 +1300 Subject: [PATCH 0228/3388] Actually increase request memory for json parsing (#3331) --- esphome/components/json/json_util.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/esphome/components/json/json_util.cpp b/esphome/components/json/json_util.cpp index 2070b312e8..10179c9954 100644 --- a/esphome/components/json/json_util.cpp +++ b/esphome/components/json/json_util.cpp @@ -54,9 +54,8 @@ void parse_json(const std::string &data, const json_parse_t &f) { const size_t free_heap = heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL); #endif bool pass = false; + size_t request_size = std::min(free_heap - 2048, (size_t)(data.size() * 1.5)); do { - const size_t request_size = std::min(free_heap - 2048, (size_t)(data.size() * 1.5)); - DynamicJsonDocument json_document(request_size); if (json_document.memoryPool().buffer() == nullptr) { ESP_LOGE(TAG, "Could not allocate memory for JSON document! Requested %u bytes, free heap: %u", request_size, @@ -76,7 +75,8 @@ void parse_json(const std::string &data, const json_parse_t &f) { ESP_LOGE(TAG, "Can not allocate more memory for deserialization. Consider making source string smaller"); return; } - ESP_LOGW(TAG, "Increasing memory allocation."); + ESP_LOGV(TAG, "Increasing memory allocation."); + request_size *= 2; continue; } else { ESP_LOGE(TAG, "JSON parse error: %s", err.c_str()); From 8be2456c7e8facb0bab7eaaed6a0e8a4f8472350 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 30 Mar 2022 08:15:50 +1300 Subject: [PATCH 0229/3388] Bump version to 2022.3.2 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 8bf303db09..7eeed11271 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2022.3.1" +__version__ = "2022.3.2" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" From ad57faa9a9808545da35a61a6c3b9d2a016d1c49 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 13 Apr 2022 13:42:28 +1200 Subject: [PATCH 0230/3388] Bump version to 2022.4.0b1 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 9096e66f4e..01d2d59c3d 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2022.4.0-dev" +__version__ = "2022.4.0b1" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" From c59adf612f8ccdc3f415e99ce538ba1c8fc6c928 Mon Sep 17 00:00:00 2001 From: matthias882 <30553262+matthias882@users.noreply.github.com> Date: Wed, 13 Apr 2022 23:36:16 +0200 Subject: [PATCH 0231/3388] Changes accuracy of single cell voltage (#3387) --- esphome/components/daly_bms/sensor.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/esphome/components/daly_bms/sensor.py b/esphome/components/daly_bms/sensor.py index e2e8528317..2274a2153a 100644 --- a/esphome/components/daly_bms/sensor.py +++ b/esphome/components/daly_bms/sensor.py @@ -98,6 +98,8 @@ CELL_VOLTAGE_SCHEMA = sensor.sensor_schema( unit_of_measurement=UNIT_VOLT, device_class=DEVICE_CLASS_VOLTAGE, state_class=STATE_CLASS_MEASUREMENT, + icon=ICON_FLASH, + accuracy_decimals=3, ) CONFIG_SCHEMA = cv.All( From d5134e88b16edaeb9e39339cdd1f3b7ddd645d52 Mon Sep 17 00:00:00 2001 From: rnauber <7414650+rnauber@users.noreply.github.com> Date: Thu, 14 Apr 2022 03:13:51 +0200 Subject: [PATCH 0232/3388] Add support for Shelly Dimmer 2 (#2954) Co-authored-by: Niclas Larsson Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Co-authored-by: Jernej Kos Co-authored-by: Richard Nauber --- CODEOWNERS | 1 + esphome/components/shelly_dimmer/LICENSE.txt | 2 + esphome/components/shelly_dimmer/__init__.py | 1 + esphome/components/shelly_dimmer/dev_table.h | 158 +++ esphome/components/shelly_dimmer/light.py | 219 ++++ .../shelly_dimmer/shelly_dimmer.cpp | 526 ++++++++ .../components/shelly_dimmer/shelly_dimmer.h | 117 ++ .../components/shelly_dimmer/stm32flash.cpp | 1061 +++++++++++++++++ esphome/components/shelly_dimmer/stm32flash.h | 129 ++ esphome/core/defines.h | 6 + tests/test1.yaml | 12 + 11 files changed, 2232 insertions(+) create mode 100644 esphome/components/shelly_dimmer/LICENSE.txt create mode 100644 esphome/components/shelly_dimmer/__init__.py create mode 100644 esphome/components/shelly_dimmer/dev_table.h create mode 100644 esphome/components/shelly_dimmer/light.py create mode 100644 esphome/components/shelly_dimmer/shelly_dimmer.cpp create mode 100644 esphome/components/shelly_dimmer/shelly_dimmer.h create mode 100644 esphome/components/shelly_dimmer/stm32flash.cpp create mode 100644 esphome/components/shelly_dimmer/stm32flash.h diff --git a/CODEOWNERS b/CODEOWNERS index 7595fc52e2..02945ec0a4 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -173,6 +173,7 @@ esphome/components/select/* @esphome/core esphome/components/sensirion_common/* @martgras esphome/components/sensor/* @esphome/core esphome/components/sgp40/* @SenexCrenshaw +esphome/components/shelly_dimmer/* @edge90 @rnauber esphome/components/sht4x/* @sjtrny esphome/components/shutdown/* @esphome/core @jsuanet esphome/components/sim800l/* @glmnet diff --git a/esphome/components/shelly_dimmer/LICENSE.txt b/esphome/components/shelly_dimmer/LICENSE.txt new file mode 100644 index 0000000000..524fe0d514 --- /dev/null +++ b/esphome/components/shelly_dimmer/LICENSE.txt @@ -0,0 +1,2 @@ +The firmware files for the STM microcontroller (shelly-dimmer-stm32_*.bin) are taken from +https://github.com/jamesturton/shelly-dimmer-stm32 and GPLv3 licensed. diff --git a/esphome/components/shelly_dimmer/__init__.py b/esphome/components/shelly_dimmer/__init__.py new file mode 100644 index 0000000000..accefbbc34 --- /dev/null +++ b/esphome/components/shelly_dimmer/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@rnauber", "@edge90"] diff --git a/esphome/components/shelly_dimmer/dev_table.h b/esphome/components/shelly_dimmer/dev_table.h new file mode 100644 index 0000000000..f4bf7778f2 --- /dev/null +++ b/esphome/components/shelly_dimmer/dev_table.h @@ -0,0 +1,158 @@ +/* + stm32flash - Open Source ST STM32 flash program for Arduino + Copyright (C) 2010 Geoffrey McRae + Copyright (C) 2014-2015 Antonio Borneo + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ +#pragma once + +#include "esphome/core/defines.h" +#ifdef USE_SHD_FIRMWARE_DATA +#include "stm32flash.h" + +namespace esphome { +namespace shelly_dimmer { + +constexpr uint32_t SZ_128 = 0x00000080; +constexpr uint32_t SZ_256 = 0x00000100; +constexpr uint32_t SZ_1K = 0x00000400; +constexpr uint32_t SZ_2K = 0x00000800; +constexpr uint32_t SZ_16K = 0x00004000; +constexpr uint32_t SZ_32K = 0x00008000; +constexpr uint32_t SZ_64K = 0x00010000; +constexpr uint32_t SZ_128K = 0x00020000; +constexpr uint32_t SZ_256K = 0x00040000; + +/* + * Page-size for page-by-page flash erase. + * Arrays are zero terminated; last non-zero value is automatically repeated + */ + +/* fixed size pages */ +constexpr uint32_t p_128[] = {SZ_128, 0}; // NOLINT +constexpr uint32_t p_256[] = {SZ_256, 0}; // NOLINT +constexpr uint32_t p_1k[] = {SZ_1K, 0}; // NOLINT +constexpr uint32_t p_2k[] = {SZ_2K, 0}; // NOLINT +/* F2 and F4 page size */ +constexpr uint32_t f2f4[] = {SZ_16K, SZ_16K, SZ_16K, SZ_16K, SZ_64K, SZ_128K, 0}; // NOLINT +/* F4 dual bank page size */ +constexpr uint32_t f4db[] = {SZ_16K, SZ_16K, SZ_16K, SZ_16K, SZ_64K, SZ_128K, SZ_128K, // NOLINT + SZ_128K, SZ_16K, SZ_16K, SZ_16K, SZ_16K, SZ_64K, SZ_128K, 0}; +/* F7 page size */ +constexpr uint32_t f7[] = {SZ_32K, SZ_32K, SZ_32K, SZ_32K, SZ_128K, SZ_256K, 0}; // NOLINT + +/* + * Device table, corresponds to the "Bootloader device-dependant parameters" + * table in ST document AN2606. + * Note that the option bytes upper range is inclusive! + */ +constexpr stm32_dev_t DEVICES[] = { + /* ID "name" SRAM-address-range FLASH-address-range PPS PSize + Option-byte-addr-range System-mem-addr-range Flags */ + /* F0 */ + {0x440, "STM32F030x8/F05xxx", 0x20000800, 0x20002000, 0x08000000, 0x08010000, 4, p_1k, 0x1FFFF800, 0x1FFFF80F, + 0x1FFFEC00, 0x1FFFF800, 0}, + {0x442, "STM32F030xC/F09xxx", 0x20001800, 0x20008000, 0x08000000, 0x08040000, 2, p_2k, 0x1FFFF800, 0x1FFFF80F, + 0x1FFFC800, 0x1FFFF800, F_OBLL}, + {0x444, "STM32F03xx4/6", 0x20000800, 0x20001000, 0x08000000, 0x08008000, 4, p_1k, 0x1FFFF800, 0x1FFFF80F, + 0x1FFFEC00, 0x1FFFF800, 0}, + {0x445, "STM32F04xxx/F070x6", 0x20001800, 0x20001800, 0x08000000, 0x08008000, 4, p_1k, 0x1FFFF800, 0x1FFFF80F, + 0x1FFFC400, 0x1FFFF800, 0}, + {0x448, "STM32F070xB/F071xx/F72xx", 0x20001800, 0x20004000, 0x08000000, 0x08020000, 2, p_2k, 0x1FFFF800, 0x1FFFF80F, + 0x1FFFC800, 0x1FFFF800, 0}, + /* F1 */ + {0x412, "STM32F10xxx Low-density", 0x20000200, 0x20002800, 0x08000000, 0x08008000, 4, p_1k, 0x1FFFF800, 0x1FFFF80F, + 0x1FFFF000, 0x1FFFF800, 0}, + {0x410, "STM32F10xxx Medium-density", 0x20000200, 0x20005000, 0x08000000, 0x08020000, 4, p_1k, 0x1FFFF800, + 0x1FFFF80F, 0x1FFFF000, 0x1FFFF800, 0}, + {0x414, "STM32F10xxx High-density", 0x20000200, 0x20010000, 0x08000000, 0x08080000, 2, p_2k, 0x1FFFF800, 0x1FFFF80F, + 0x1FFFF000, 0x1FFFF800, 0}, + {0x420, "STM32F10xxx Medium-density VL", 0x20000200, 0x20002000, 0x08000000, 0x08020000, 4, p_1k, 0x1FFFF800, + 0x1FFFF80F, 0x1FFFF000, 0x1FFFF800, 0}, + {0x428, "STM32F10xxx High-density VL", 0x20000200, 0x20008000, 0x08000000, 0x08080000, 2, p_2k, 0x1FFFF800, + 0x1FFFF80F, 0x1FFFF000, 0x1FFFF800, 0}, + {0x418, "STM32F105xx/F107xx", 0x20001000, 0x20010000, 0x08000000, 0x08040000, 2, p_2k, 0x1FFFF800, 0x1FFFF80F, + 0x1FFFB000, 0x1FFFF800, 0}, + {0x430, "STM32F10xxx XL-density", 0x20000800, 0x20018000, 0x08000000, 0x08100000, 2, p_2k, 0x1FFFF800, 0x1FFFF80F, + 0x1FFFE000, 0x1FFFF800, 0}, + /* F2 */ + {0x411, "STM32F2xxxx", 0x20002000, 0x20020000, 0x08000000, 0x08100000, 1, f2f4, 0x1FFFC000, 0x1FFFC00F, 0x1FFF0000, + 0x1FFF7800, 0}, + /* F3 */ + {0x432, "STM32F373xx/F378xx", 0x20001400, 0x20008000, 0x08000000, 0x08040000, 2, p_2k, 0x1FFFF800, 0x1FFFF80F, + 0x1FFFD800, 0x1FFFF800, 0}, + {0x422, "STM32F302xB(C)/F303xB(C)/F358xx", 0x20001400, 0x2000A000, 0x08000000, 0x08040000, 2, p_2k, 0x1FFFF800, + 0x1FFFF80F, 0x1FFFD800, 0x1FFFF800, 0}, + {0x439, "STM32F301xx/F302x4(6/8)/F318xx", 0x20001800, 0x20004000, 0x08000000, 0x08010000, 2, p_2k, 0x1FFFF800, + 0x1FFFF80F, 0x1FFFD800, 0x1FFFF800, 0}, + {0x438, "STM32F303x4(6/8)/F334xx/F328xx", 0x20001800, 0x20003000, 0x08000000, 0x08010000, 2, p_2k, 0x1FFFF800, + 0x1FFFF80F, 0x1FFFD800, 0x1FFFF800, 0}, + {0x446, "STM32F302xD(E)/F303xD(E)/F398xx", 0x20001800, 0x20010000, 0x08000000, 0x08080000, 2, p_2k, 0x1FFFF800, + 0x1FFFF80F, 0x1FFFD800, 0x1FFFF800, 0}, + /* F4 */ + {0x413, "STM32F40xxx/41xxx", 0x20003000, 0x20020000, 0x08000000, 0x08100000, 1, f2f4, 0x1FFFC000, 0x1FFFC00F, + 0x1FFF0000, 0x1FFF7800, 0}, + {0x419, "STM32F42xxx/43xxx", 0x20003000, 0x20030000, 0x08000000, 0x08200000, 1, f4db, 0x1FFEC000, 0x1FFFC00F, + 0x1FFF0000, 0x1FFF7800, 0}, + {0x423, "STM32F401xB(C)", 0x20003000, 0x20010000, 0x08000000, 0x08040000, 1, f2f4, 0x1FFFC000, 0x1FFFC00F, + 0x1FFF0000, 0x1FFF7800, 0}, + {0x433, "STM32F401xD(E)", 0x20003000, 0x20018000, 0x08000000, 0x08080000, 1, f2f4, 0x1FFFC000, 0x1FFFC00F, + 0x1FFF0000, 0x1FFF7800, 0}, + {0x458, "STM32F410xx", 0x20003000, 0x20008000, 0x08000000, 0x08020000, 1, f2f4, 0x1FFFC000, 0x1FFFC00F, 0x1FFF0000, + 0x1FFF7800, 0}, + {0x431, "STM32F411xx", 0x20003000, 0x20020000, 0x08000000, 0x08080000, 1, f2f4, 0x1FFFC000, 0x1FFFC00F, 0x1FFF0000, + 0x1FFF7800, 0}, + {0x421, "STM32F446xx", 0x20003000, 0x20020000, 0x08000000, 0x08080000, 1, f2f4, 0x1FFFC000, 0x1FFFC00F, 0x1FFF0000, + 0x1FFF7800, 0}, + {0x434, "STM32F469xx", 0x20003000, 0x20060000, 0x08000000, 0x08200000, 1, f4db, 0x1FFEC000, 0x1FFFC00F, 0x1FFF0000, + 0x1FFF7800, 0}, + /* F7 */ + {0x449, "STM32F74xxx/75xxx", 0x20004000, 0x20050000, 0x08000000, 0x08100000, 1, f7, 0x1FFF0000, 0x1FFF001F, + 0x1FF00000, 0x1FF0EDC0, 0}, + /* L0 */ + {0x425, "STM32L031xx/041xx", 0x20001000, 0x20002000, 0x08000000, 0x08008000, 32, p_128, 0x1FF80000, 0x1FF8001F, + 0x1FF00000, 0x1FF01000, 0}, + {0x417, "STM32L05xxx/06xxx", 0x20001000, 0x20002000, 0x08000000, 0x08010000, 32, p_128, 0x1FF80000, 0x1FF8001F, + 0x1FF00000, 0x1FF01000, 0}, + {0x447, "STM32L07xxx/08xxx", 0x20002000, 0x20005000, 0x08000000, 0x08030000, 32, p_128, 0x1FF80000, 0x1FF8001F, + 0x1FF00000, 0x1FF02000, 0}, + /* L1 */ + {0x416, "STM32L1xxx6(8/B)", 0x20000800, 0x20004000, 0x08000000, 0x08020000, 16, p_256, 0x1FF80000, 0x1FF8001F, + 0x1FF00000, 0x1FF01000, F_NO_ME}, + {0x429, "STM32L1xxx6(8/B)A", 0x20001000, 0x20008000, 0x08000000, 0x08020000, 16, p_256, 0x1FF80000, 0x1FF8001F, + 0x1FF00000, 0x1FF01000, 0}, + {0x427, "STM32L1xxxC", 0x20001000, 0x20008000, 0x08000000, 0x08040000, 16, p_256, 0x1FF80000, 0x1FF8001F, + 0x1FF00000, 0x1FF02000, 0}, + {0x436, "STM32L1xxxD", 0x20001000, 0x2000C000, 0x08000000, 0x08060000, 16, p_256, 0x1FF80000, 0x1FF8009F, + 0x1FF00000, 0x1FF02000, 0}, + {0x437, "STM32L1xxxE", 0x20001000, 0x20014000, 0x08000000, 0x08080000, 16, p_256, 0x1FF80000, 0x1FF8009F, + 0x1FF00000, 0x1FF02000, F_NO_ME}, + /* L4 */ + {0x415, "STM32L476xx/486xx", 0x20003100, 0x20018000, 0x08000000, 0x08100000, 1, p_2k, 0x1FFF7800, 0x1FFFF80F, + 0x1FFF0000, 0x1FFF7000, 0}, + /* These are not (yet) in AN2606: */ + {0x641, "Medium_Density PL", 0x20000200, 0x20005000, 0x08000000, 0x08020000, 4, p_1k, 0x1FFFF800, 0x1FFFF80F, + 0x1FFFF000, 0x1FFFF800, 0}, + {0x9a8, "STM32W-128K", 0x20000200, 0x20002000, 0x08000000, 0x08020000, 4, p_1k, 0x08040800, 0x0804080F, 0x08040000, + 0x08040800, 0}, + {0x9b0, "STM32W-256K", 0x20000200, 0x20004000, 0x08000000, 0x08040000, 4, p_2k, 0x08040800, 0x0804080F, 0x08040000, + 0x08040800, 0}, + {0x0, "", 0x0, 0x0, 0x0, 0x0, 0x0, nullptr, 0x0, 0x0, 0x0, 0x0, 0x0}, +}; + +} // namespace shelly_dimmer +} // namespace esphome +#endif diff --git a/esphome/components/shelly_dimmer/light.py b/esphome/components/shelly_dimmer/light.py new file mode 100644 index 0000000000..003498c090 --- /dev/null +++ b/esphome/components/shelly_dimmer/light.py @@ -0,0 +1,219 @@ +from pathlib import Path +import hashlib +import re +import requests + + +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome import pins +from esphome.components import light, sensor, uart +from esphome.const import ( + CONF_OUTPUT_ID, + CONF_GAMMA_CORRECT, + CONF_POWER, + CONF_VOLTAGE, + CONF_CURRENT, + CONF_VERSION, + CONF_URL, + CONF_UPDATE_INTERVAL, + UNIT_VOLT, + UNIT_AMPERE, + UNIT_WATT, + DEVICE_CLASS_POWER, + DEVICE_CLASS_VOLTAGE, +) +from esphome.core import HexInt, CORE + +DOMAIN = "shelly_dimmer" +DEPENDENCIES = ["sensor", "uart"] + +shelly_dimmer_ns = cg.esphome_ns.namespace("shelly_dimmer") +ShellyDimmer = shelly_dimmer_ns.class_( + "ShellyDimmer", light.LightOutput, cg.PollingComponent, uart.UARTDevice +) + +CONF_FIRMWARE = "firmware" +CONF_SHA256 = "sha256" +CONF_UPDATE = "update" + +CONF_LEADING_EDGE = "leading_edge" +CONF_WARMUP_BRIGHTNESS = "warmup_brightness" +# CONF_WARMUP_TIME = "warmup_time" +CONF_MIN_BRIGHTNESS = "min_brightness" +CONF_MAX_BRIGHTNESS = "max_brightness" + +CONF_NRST_PIN = "nrst_pin" +CONF_BOOT0_PIN = "boot0_pin" + +KNOWN_FIRMWARE = { + "51.5": ( + "https://github.com/jamesturton/shelly-dimmer-stm32/releases/download/v51.5/shelly-dimmer-stm32_v51.5.bin", + "553fc1d78ed113227af7683eaa9c26189a961c4ea9a48000fb5aa8f8ac5d7b60", + ), + "51.6": ( + "https://github.com/jamesturton/shelly-dimmer-stm32/releases/download/v51.6/shelly-dimmer-stm32_v51.6.bin", + "eda483e111c914723a33f5088f1397d5c0b19333db4a88dc965636b976c16c36", + ), +} + + +def parse_firmware_version(value): + match = re.match(r"(\d+).(\d+)", value) + if match is None: + raise ValueError(f"Not a valid version number {value}") + major = int(match[1]) + minor = int(match[2]) + return major, minor + + +def get_firmware(value): + if not value[CONF_UPDATE]: + return None + + def dl(url): + try: + req = requests.get(url) + req.raise_for_status() + except requests.exceptions.RequestException as e: + raise cv.Invalid(f"Could not download firmware file ({url}): {e}") + + h = hashlib.new("sha256") + h.update(req.content) + return req.content, h.hexdigest() + + url = value[CONF_URL] + + if CONF_SHA256 in value: # we have a hash, enable caching + path = ( + Path(CORE.config_dir) + / ".esphome" + / DOMAIN + / (value[CONF_SHA256] + "_fw_stm.bin") + ) + + if not path.is_file(): + firmware_data, dl_hash = dl(url) + + if dl_hash != value[CONF_SHA256]: + raise cv.Invalid( + f"Hash mismatch for {url}: {dl_hash} != {value[CONF_SHA256]}" + ) + + path.parent.mkdir(exist_ok=True, parents=True) + path.write_bytes(firmware_data) + + else: + firmware_data = path.read_bytes() + else: # no caching, download every time + firmware_data, dl_hash = dl(url) + + return [HexInt(x) for x in firmware_data] + + +def validate_firmware(value): + config = value.copy() + if CONF_URL not in config: + try: + config[CONF_URL], config[CONF_SHA256] = KNOWN_FIRMWARE[config[CONF_VERSION]] + except KeyError as e: + raise cv.Invalid( + f"Firmware {config[CONF_VERSION]} is unknown, please specify an '{CONF_URL}' ..." + ) from e + get_firmware(config) + return config + + +def validate_sha256(value): + value = cv.string(value) + if not value.isalnum() or not len(value) == 64: + raise ValueError(f"Not a valid SHA256 hex string: {value}") + return value + + +def validate_version(value): + parse_firmware_version(value) + return value + + +CONFIG_SCHEMA = ( + light.BRIGHTNESS_ONLY_LIGHT_SCHEMA.extend( + { + cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(ShellyDimmer), + cv.Optional(CONF_FIRMWARE, default="51.6"): cv.maybe_simple_value( + { + cv.Optional(CONF_URL): cv.url, + cv.Optional(CONF_SHA256): validate_sha256, + cv.Required(CONF_VERSION): validate_version, + cv.Optional(CONF_UPDATE, default=False): cv.boolean, + }, + validate_firmware, # converts a simple version key to generate the full url + key=CONF_VERSION, + ), + cv.Optional(CONF_NRST_PIN, default="GPIO5"): pins.gpio_output_pin_schema, + cv.Optional(CONF_BOOT0_PIN, default="GPIO4"): pins.gpio_output_pin_schema, + cv.Optional(CONF_LEADING_EDGE, default=False): cv.boolean, + cv.Optional(CONF_WARMUP_BRIGHTNESS, default=100): cv.uint16_t, + # cv.Optional(CONF_WARMUP_TIME, default=20): cv.uint16_t, + cv.Optional(CONF_MIN_BRIGHTNESS, default=0): cv.uint16_t, + cv.Optional(CONF_MAX_BRIGHTNESS, default=1000): cv.uint16_t, + cv.Optional(CONF_POWER): sensor.sensor_schema( + unit_of_measurement=UNIT_WATT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_POWER, + ), + cv.Optional(CONF_VOLTAGE): sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_VOLTAGE, + ), + cv.Optional(CONF_CURRENT): sensor.sensor_schema( + unit_of_measurement=UNIT_AMPERE, + device_class=DEVICE_CLASS_POWER, + accuracy_decimals=2, + ), + # Change the default gamma_correct setting. + cv.Optional(CONF_GAMMA_CORRECT, default=1.0): cv.positive_float, + } + ) + .extend(cv.polling_component_schema("10s")) + .extend(uart.UART_DEVICE_SCHEMA) +) + + +def to_code(config): + fw_hex = get_firmware(config[CONF_FIRMWARE]) + fw_major, fw_minor = parse_firmware_version(config[CONF_FIRMWARE][CONF_VERSION]) + + if fw_hex is not None: + cg.add_define("USE_SHD_FIRMWARE_DATA", fw_hex) + cg.add_define("USE_SHD_FIRMWARE_MAJOR_VERSION", fw_major) + cg.add_define("USE_SHD_FIRMWARE_MINOR_VERSION", fw_minor) + + var = cg.new_Pvariable(config[CONF_OUTPUT_ID]) + yield cg.register_component(var, config) + config.pop( + CONF_UPDATE_INTERVAL + ) # drop UPDATE_INTERVAL as it does not apply to the light component + + yield light.register_light(var, config) + yield uart.register_uart_device(var, config) + + nrst_pin = yield cg.gpio_pin_expression(config[CONF_NRST_PIN]) + cg.add(var.set_nrst_pin(nrst_pin)) + boot0_pin = yield cg.gpio_pin_expression(config[CONF_BOOT0_PIN]) + cg.add(var.set_boot0_pin(boot0_pin)) + + cg.add(var.set_leading_edge(config[CONF_LEADING_EDGE])) + cg.add(var.set_warmup_brightness(config[CONF_WARMUP_BRIGHTNESS])) + # cg.add(var.set_warmup_time(config[CONF_WARMUP_TIME])) + cg.add(var.set_min_brightness(config[CONF_MIN_BRIGHTNESS])) + cg.add(var.set_max_brightness(config[CONF_MAX_BRIGHTNESS])) + + for key in [CONF_POWER, CONF_VOLTAGE, CONF_CURRENT]: + if key not in config: + continue + + conf = config[key] + sens = yield sensor.new_sensor(conf) + cg.add(getattr(var, f"set_{key}_sensor")(sens)) diff --git a/esphome/components/shelly_dimmer/shelly_dimmer.cpp b/esphome/components/shelly_dimmer/shelly_dimmer.cpp new file mode 100644 index 0000000000..3b79d0bf57 --- /dev/null +++ b/esphome/components/shelly_dimmer/shelly_dimmer.cpp @@ -0,0 +1,526 @@ +#include "esphome/core/defines.h" +#include "esphome/core/helpers.h" + +#include "shelly_dimmer.h" +#ifdef USE_SHD_FIRMWARE_DATA +#include "stm32flash.h" +#endif + +#ifndef USE_ESP_IDF +#include +#endif + +#include +#include +#include +#include + +namespace { + +constexpr char TAG[] = "shelly_dimmer"; + +constexpr uint8_t SHELLY_DIMMER_ACK_TIMEOUT = 200; // ms +constexpr uint8_t SHELLY_DIMMER_MAX_RETRIES = 3; +constexpr uint16_t SHELLY_DIMMER_MAX_BRIGHTNESS = 1000; // 100% + +// Protocol framing. +constexpr uint8_t SHELLY_DIMMER_PROTO_START_BYTE = 0x01; +constexpr uint8_t SHELLY_DIMMER_PROTO_END_BYTE = 0x04; + +// Supported commands. +constexpr uint8_t SHELLY_DIMMER_PROTO_CMD_SWITCH = 0x01; +constexpr uint8_t SHELLY_DIMMER_PROTO_CMD_POLL = 0x10; +constexpr uint8_t SHELLY_DIMMER_PROTO_CMD_VERSION = 0x11; +constexpr uint8_t SHELLY_DIMMER_PROTO_CMD_SETTINGS = 0x20; + +// Command payload sizes. +constexpr uint8_t SHELLY_DIMMER_PROTO_CMD_SWITCH_SIZE = 2; +constexpr uint8_t SHELLY_DIMMER_PROTO_CMD_SETTINGS_SIZE = 10; +constexpr uint8_t SHELLY_DIMMER_PROTO_MAX_FRAME_SIZE = 4 + 72 + 3; + +// STM Firmware +#ifdef USE_SHD_FIRMWARE_DATA +constexpr uint8_t STM_FIRMWARE[] PROGMEM = USE_SHD_FIRMWARE_DATA; +constexpr uint32_t STM_FIRMWARE_SIZE_IN_BYTES = sizeof(STM_FIRMWARE); +#endif + +// Scaling Constants +constexpr float POWER_SCALING_FACTOR = 880373; +constexpr float VOLTAGE_SCALING_FACTOR = 347800; +constexpr float CURRENT_SCALING_FACTOR = 1448; + +// Esentially std::size() for pre c++17 +template constexpr size_t size(const T (&/*unused*/)[N]) noexcept { return N; } + +} // Anonymous namespace + +namespace esphome { +namespace shelly_dimmer { + +/// Computes a crappy checksum as defined by the Shelly Dimmer protocol. +uint16_t shelly_dimmer_checksum(const uint8_t *buf, int len) { + return std::accumulate(buf, buf + len, 0); +} + +void ShellyDimmer::setup() { + this->pin_nrst_->setup(); + this->pin_boot0_->setup(); + + ESP_LOGI(TAG, "Initializing Shelly Dimmer..."); + + // Reset the STM32 and check the firmware version. + for (int i = 0; i < 2; i++) { + this->reset_normal_boot_(); + this->send_command_(SHELLY_DIMMER_PROTO_CMD_VERSION, nullptr, 0); + ESP_LOGI(TAG, "STM32 current firmware version: %d.%d, desired version: %d.%d", this->version_major_, + this->version_minor_, USE_SHD_FIRMWARE_MAJOR_VERSION, USE_SHD_FIRMWARE_MINOR_VERSION); + if (this->version_major_ != USE_SHD_FIRMWARE_MAJOR_VERSION || + this->version_minor_ != USE_SHD_FIRMWARE_MINOR_VERSION) { +#ifdef USE_SHD_FIRMWARE_DATA + // Update firmware if needed. + ESP_LOGW(TAG, "Unsupported STM32 firmware version, flashing"); + if (i > 0) { + // Upgrade was already performed but the reported version is still not right. + ESP_LOGE(TAG, "STM32 firmware upgrade already performed, but version is still incorrect"); + this->mark_failed(); + return; + } + + if (!this->upgrade_firmware_()) { + ESP_LOGW(TAG, "Failed to upgrade firmware"); + this->mark_failed(); + return; + } + + // Firmware upgrade completed, do the checks again. + continue; +#else + ESP_LOGW(TAG, "Firmware version mismatch, put 'update: true' in the yaml to flash an update."); + this->mark_failed(); + return; +#endif + } + break; + } + + this->send_settings_(); + // Do an immediate poll to refresh current state. + this->send_command_(SHELLY_DIMMER_PROTO_CMD_POLL, nullptr, 0); + + this->ready_ = true; +} + +void ShellyDimmer::update() { this->send_command_(SHELLY_DIMMER_PROTO_CMD_POLL, nullptr, 0); } + +void ShellyDimmer::dump_config() { + ESP_LOGCONFIG(TAG, "ShellyDimmer:"); + LOG_PIN(" NRST Pin: ", this->pin_nrst_); + LOG_PIN(" BOOT0 Pin: ", this->pin_boot0_); + + ESP_LOGCONFIG(TAG, " Leading Edge: %s", YESNO(this->leading_edge_)); + ESP_LOGCONFIG(TAG, " Warmup Brightness: %d", this->warmup_brightness_); + // ESP_LOGCONFIG(TAG, " Warmup Time: %d", this->warmup_time_); + // ESP_LOGCONFIG(TAG, " Fade Rate: %d", this->fade_rate_); + ESP_LOGCONFIG(TAG, " Minimum Brightness: %d", this->min_brightness_); + ESP_LOGCONFIG(TAG, " Maximum Brightness: %d", this->max_brightness_); + + LOG_UPDATE_INTERVAL(this); + + ESP_LOGCONFIG(TAG, " STM32 current firmware version: %d.%d ", this->version_major_, this->version_minor_); + ESP_LOGCONFIG(TAG, " STM32 required firmware version: %d.%d", USE_SHD_FIRMWARE_MAJOR_VERSION, + USE_SHD_FIRMWARE_MINOR_VERSION); + + if (this->version_major_ != USE_SHD_FIRMWARE_MAJOR_VERSION || + this->version_minor_ != USE_SHD_FIRMWARE_MINOR_VERSION) { + ESP_LOGE(TAG, " Firmware version mismatch, put 'update: true' in the yaml to flash an update."); + } +} + +void ShellyDimmer::write_state(light::LightState *state) { + if (!this->ready_) { + return; + } + + float brightness; + state->current_values_as_brightness(&brightness); + + const uint16_t brightness_int = this->convert_brightness_(brightness); + if (brightness_int == this->brightness_) { + ESP_LOGV(TAG, "Not sending unchanged value"); + return; + } + ESP_LOGD(TAG, "Brightness update: %d (raw: %f)", brightness_int, brightness); + + this->send_brightness_(brightness_int); +} +#ifdef USE_SHD_FIRMWARE_DATA +bool ShellyDimmer::upgrade_firmware_() { + ESP_LOGW(TAG, "Starting STM32 firmware upgrade"); + this->reset_dfu_boot_(); + + // Could be constexpr in c++17 + static const auto CLOSE = [](stm32_t *stm32) { stm32_close(stm32); }; + + // Cleanup with RAII + std::unique_ptr stm32{stm32_init(this, STREAM_SERIAL, 1), CLOSE}; + + if (!stm32) { + ESP_LOGW(TAG, "Failed to initialize STM32"); + return false; + } + + // Erase STM32 flash. + if (stm32_erase_memory(stm32.get(), 0, STM32_MASS_ERASE) != STM32_ERR_OK) { + ESP_LOGW(TAG, "Failed to erase STM32 flash memory"); + return false; + } + + static constexpr uint32_t BUFFER_SIZE = 256; + + // Copy the STM32 firmware over in 256-byte chunks. Note that the firmware is stored + // in flash memory so all accesses need to be 4-byte aligned. + uint8_t buffer[BUFFER_SIZE]; + const uint8_t *p = STM_FIRMWARE; + uint32_t offset = 0; + uint32_t addr = stm32->dev->fl_start; + const uint32_t end = addr + STM_FIRMWARE_SIZE_IN_BYTES; + + while (addr < end && offset < STM_FIRMWARE_SIZE_IN_BYTES) { + const uint32_t left_of_buffer = std::min(end - addr, BUFFER_SIZE); + const uint32_t len = std::min(left_of_buffer, STM_FIRMWARE_SIZE_IN_BYTES - offset); + + if (len == 0) { + break; + } + + std::memcpy(buffer, p, BUFFER_SIZE); + p += BUFFER_SIZE; + + if (stm32_write_memory(stm32.get(), addr, buffer, len) != STM32_ERR_OK) { + ESP_LOGW(TAG, "Failed to write to STM32 flash memory"); + return false; + } + + addr += len; + offset += len; + } + + ESP_LOGI(TAG, "STM32 firmware upgrade successful"); + + return true; +} +#endif + +uint16_t ShellyDimmer::convert_brightness_(float brightness) { + // Special case for zero as only zero means turn off completely. + if (brightness == 0.0) { + return 0; + } + + return remap(brightness, 0.0f, 1.0f, this->min_brightness_, this->max_brightness_); +} + +void ShellyDimmer::send_brightness_(uint16_t brightness) { + const uint8_t payload[] = { + // Brightness (%) * 10. + static_cast(brightness & 0xff), + static_cast(brightness >> 8), + }; + static_assert(size(payload) == SHELLY_DIMMER_PROTO_CMD_SWITCH_SIZE, "Invalid payload size"); + + this->send_command_(SHELLY_DIMMER_PROTO_CMD_SWITCH, payload, SHELLY_DIMMER_PROTO_CMD_SWITCH_SIZE); + + this->brightness_ = brightness; +} + +void ShellyDimmer::send_settings_() { + const uint16_t fade_rate = std::min(uint16_t{100}, this->fade_rate_); + + float brightness = 0.0; + if (this->state_ != nullptr) { + this->state_->current_values_as_brightness(&brightness); + } + const uint16_t brightness_int = this->convert_brightness_(brightness); + ESP_LOGD(TAG, "Brightness update: %d (raw: %f)", brightness_int, brightness); + + const uint8_t payload[] = { + // Brightness (%) * 10. + static_cast(brightness_int & 0xff), + static_cast(brightness_int >> 8), + // Leading / trailing edge [0x01 = leading, 0x02 = trailing]. + this->leading_edge_ ? uint8_t{0x01} : uint8_t{0x02}, + 0x00, + // Fade rate. + static_cast(fade_rate & 0xff), + static_cast(fade_rate >> 8), + // Warmup brightness. + static_cast(this->warmup_brightness_ & 0xff), + static_cast(this->warmup_brightness_ >> 8), + // Warmup time. + static_cast(this->warmup_time_ & 0xff), + static_cast(this->warmup_time_ >> 8), + }; + static_assert(size(payload) == SHELLY_DIMMER_PROTO_CMD_SETTINGS_SIZE, "Invalid payload size"); + + this->send_command_(SHELLY_DIMMER_PROTO_CMD_SETTINGS, payload, SHELLY_DIMMER_PROTO_CMD_SETTINGS_SIZE); + + // Also send brightness separately as it is ignored above. + this->send_brightness_(brightness_int); +} + +bool ShellyDimmer::send_command_(uint8_t cmd, const uint8_t *const payload, uint8_t len) { + ESP_LOGD(TAG, "Sending command: 0x%02x (%d bytes) payload 0x%s", cmd, len, format_hex(payload, len).c_str()); + + // Prepare a command frame. + uint8_t frame[SHELLY_DIMMER_PROTO_MAX_FRAME_SIZE]; + const size_t frame_len = this->frame_command_(frame, cmd, payload, len); + + // Write the frame and wait for acknowledgement. + int retries = SHELLY_DIMMER_MAX_RETRIES; + while (retries--) { + this->write_array(frame, frame_len); + this->flush(); + + ESP_LOGD(TAG, "Command sent, waiting for reply"); + const uint32_t tx_time = millis(); + while (millis() - tx_time < SHELLY_DIMMER_ACK_TIMEOUT) { + if (this->read_frame_()) { + return true; + } + delay(1); + } + ESP_LOGW(TAG, "Timeout while waiting for reply"); + } + ESP_LOGW(TAG, "Failed to send command"); + return false; +} + +size_t ShellyDimmer::frame_command_(uint8_t *data, uint8_t cmd, const uint8_t *const payload, size_t len) { + size_t pos = 0; + + // Generate a frame. + data[0] = SHELLY_DIMMER_PROTO_START_BYTE; + data[1] = ++this->seq_; + data[2] = cmd; + data[3] = len; + pos += 4; + + if (payload != nullptr) { + std::memcpy(data + 4, payload, len); + pos += len; + } + + // Calculate checksum for the payload. + const uint16_t csum = shelly_dimmer_checksum(data + 1, 3 + len); + data[pos++] = static_cast(csum >> 8); + data[pos++] = static_cast(csum & 0xff); + data[pos++] = SHELLY_DIMMER_PROTO_END_BYTE; + return pos; +} + +int ShellyDimmer::handle_byte_(uint8_t c) { + const uint8_t pos = this->buffer_pos_; + + if (pos == 0) { + // Must be start byte. + return c == SHELLY_DIMMER_PROTO_START_BYTE ? 1 : -1; + } else if (pos < 4) { + // Header. + return 1; + } + + // Decode payload length from header. + const uint8_t payload_len = this->buffer_[3]; + if ((4 + payload_len + 3) > SHELLY_DIMMER_BUFFER_SIZE) { + return -1; + } + + if (pos < 4 + payload_len + 1) { + // Payload. + return 1; + } + + if (pos == 4 + payload_len + 1) { + // Verify checksum. + const uint16_t csum = (this->buffer_[pos - 1] << 8 | c); + const uint16_t csum_verify = shelly_dimmer_checksum(&this->buffer_[1], 3 + payload_len); + if (csum != csum_verify) { + return -1; + } + return 1; + } + + if (pos == 4 + payload_len + 2) { + // Must be end byte. + return c == SHELLY_DIMMER_PROTO_END_BYTE ? 0 : -1; + } + return -1; +} + +bool ShellyDimmer::read_frame_() { + while (this->available()) { + const uint8_t c = this->read(); + this->buffer_[this->buffer_pos_] = c; + + ESP_LOGV(TAG, "Read byte: 0x%02x (pos %d)", c, this->buffer_pos_); + + switch (this->handle_byte_(c)) { + case 0: { + // Frame successfully received. + this->handle_frame_(); + this->buffer_pos_ = 0; + return true; + } + case -1: { + // Failure. + this->buffer_pos_ = 0; + break; + } + case 1: { + // Need more data. + this->buffer_pos_++; + break; + } + } + } + return false; +} + +bool ShellyDimmer::handle_frame_() { + const uint8_t seq = this->buffer_[1]; + const uint8_t cmd = this->buffer_[2]; + const uint8_t payload_len = this->buffer_[3]; + + ESP_LOGD(TAG, "Got frame: 0x%02x", cmd); + + // Compare with expected identifier as the frame is always a response to + // our previously sent command. + if (seq != this->seq_) { + return false; + } + + const uint8_t *payload = &this->buffer_[4]; + + // Handle response. + switch (cmd) { + case SHELLY_DIMMER_PROTO_CMD_POLL: { + if (payload_len < 16) { + return false; + } + + const uint8_t hw_version = payload[0]; + // payload[1] is unused. + const uint16_t brightness = encode_uint16(payload[3], payload[2]); + + const uint32_t power_raw = encode_uint32(payload[7], payload[6], payload[5], payload[4]); + + const uint32_t voltage_raw = encode_uint32(payload[11], payload[10], payload[9], payload[8]); + + const uint32_t current_raw = encode_uint32(payload[15], payload[14], payload[13], payload[12]); + + const uint16_t fade_rate = payload[16]; + + float power = 0; + if (power_raw > 0) { + power = POWER_SCALING_FACTOR / static_cast(power_raw); + } + + float voltage = 0; + if (voltage_raw > 0) { + voltage = VOLTAGE_SCALING_FACTOR / static_cast(voltage_raw); + } + + float current = 0; + if (current_raw > 0) { + current = CURRENT_SCALING_FACTOR / static_cast(current_raw); + } + + ESP_LOGI(TAG, "Got dimmer data:"); + ESP_LOGI(TAG, " HW version: %d", hw_version); + ESP_LOGI(TAG, " Brightness: %d", brightness); + ESP_LOGI(TAG, " Fade rate: %d", fade_rate); + ESP_LOGI(TAG, " Power: %f W", power); + ESP_LOGI(TAG, " Voltage: %f V", voltage); + ESP_LOGI(TAG, " Current: %f A", current); + + // Update sensors. + if (this->power_sensor_ != nullptr) { + this->power_sensor_->publish_state(power); + } + if (this->voltage_sensor_ != nullptr) { + this->voltage_sensor_->publish_state(voltage); + } + if (this->current_sensor_ != nullptr) { + this->current_sensor_->publish_state(current); + } + + return true; + } + case SHELLY_DIMMER_PROTO_CMD_VERSION: { + if (payload_len < 2) { + return false; + } + + this->version_minor_ = payload[0]; + this->version_major_ = payload[1]; + return true; + } + case SHELLY_DIMMER_PROTO_CMD_SWITCH: + case SHELLY_DIMMER_PROTO_CMD_SETTINGS: { + return !(payload_len < 1 || payload[0] != 0x01); + } + default: { + return false; + } + } +} + +void ShellyDimmer::reset_(bool boot0) { + ESP_LOGD(TAG, "Reset STM32, boot0=%d", boot0); + + this->pin_boot0_->digital_write(boot0); + this->pin_nrst_->digital_write(false); + + // Wait 50ms for the STM32 to reset. + delay(50); // NOLINT + + // Clear receive buffer. + while (this->available()) { + this->read(); + } + + this->pin_nrst_->digital_write(true); + // Wait 50ms for the STM32 to boot. + delay(50); // NOLINT + + ESP_LOGD(TAG, "Reset STM32 done"); +} + +void ShellyDimmer::reset_normal_boot_() { + // set NONE parity in normal mode + +#ifndef USE_ESP_IDF // workaround for reconfiguring the uart + Serial.end(); + Serial.begin(115200, SERIAL_8N1); + Serial.flush(); +#endif + + this->flush(); + this->reset_(false); +} + +void ShellyDimmer::reset_dfu_boot_() { + // set EVEN parity in bootloader mode + +#ifndef USE_ESP_IDF // workaround for reconfiguring the uart + Serial.end(); + Serial.begin(115200, SERIAL_8E1); + Serial.flush(); +#endif + + this->flush(); + this->reset_(true); +} + +} // namespace shelly_dimmer +} // namespace esphome diff --git a/esphome/components/shelly_dimmer/shelly_dimmer.h b/esphome/components/shelly_dimmer/shelly_dimmer.h new file mode 100644 index 0000000000..b7d476279e --- /dev/null +++ b/esphome/components/shelly_dimmer/shelly_dimmer.h @@ -0,0 +1,117 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/log.h" +#include "esphome/components/light/light_output.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/uart/uart.h" + +#include + +namespace esphome { +namespace shelly_dimmer { + +class ShellyDimmer : public PollingComponent, public light::LightOutput, public uart::UARTDevice { + private: + static constexpr uint16_t SHELLY_DIMMER_BUFFER_SIZE = 256; + + public: + float get_setup_priority() const override { return setup_priority::LATE; } + + void setup() override; + void update() override; + void dump_config() override; + + light::LightTraits get_traits() override { + auto traits = light::LightTraits(); + traits.set_supported_color_modes({light::ColorMode::BRIGHTNESS}); + return traits; + } + + void setup_state(light::LightState *state) override { this->state_ = state; } + void write_state(light::LightState *state) override; + + void set_nrst_pin(GPIOPin *nrst_pin) { this->pin_nrst_ = nrst_pin; } + void set_boot0_pin(GPIOPin *boot0_pin) { this->pin_boot0_ = boot0_pin; } + + void set_leading_edge(bool leading_edge) { this->leading_edge_ = leading_edge; } + void set_warmup_brightness(uint16_t warmup_brightness) { this->warmup_brightness_ = warmup_brightness; } + void set_warmup_time(uint16_t warmup_time) { this->warmup_time_ = warmup_time; } + void set_fade_rate(uint16_t fade_rate) { this->fade_rate_ = fade_rate; } + void set_min_brightness(uint16_t min_brightness) { this->min_brightness_ = min_brightness; } + void set_max_brightness(uint16_t max_brightness) { this->max_brightness_ = max_brightness; } + + void set_power_sensor(sensor::Sensor *power_sensor) { this->power_sensor_ = power_sensor; } + void set_voltage_sensor(sensor::Sensor *voltage_sensor) { this->voltage_sensor_ = voltage_sensor; } + void set_current_sensor(sensor::Sensor *current_sensor) { this->current_sensor_ = current_sensor; } + + protected: + GPIOPin *pin_nrst_; + GPIOPin *pin_boot0_; + + // Frame parser state. + uint8_t seq_{0}; + std::array buffer_; + uint8_t buffer_pos_{0}; + + // Firmware version. + uint8_t version_major_; + uint8_t version_minor_; + + // Configuration. + bool leading_edge_{false}; + uint16_t warmup_brightness_{100}; + uint16_t warmup_time_{20}; + uint16_t fade_rate_{0}; + uint16_t min_brightness_{0}; + uint16_t max_brightness_{1000}; + + light::LightState *state_{nullptr}; + sensor::Sensor *power_sensor_{nullptr}; + sensor::Sensor *voltage_sensor_{nullptr}; + sensor::Sensor *current_sensor_{nullptr}; + + bool ready_{false}; + uint16_t brightness_; + + /// Convert relative brightness into a dimmer brightness value. + uint16_t convert_brightness_(float brightness); + + /// Sends the given brightness value. + void send_brightness_(uint16_t brightness); + + /// Sends dimmer configuration. + void send_settings_(); + + /// Performs a firmware upgrade. + bool upgrade_firmware_(); + + /// Sends a command and waits for an acknowledgement. + bool send_command_(uint8_t cmd, const uint8_t *payload, uint8_t len); + + /// Frames a given command payload. + size_t frame_command_(uint8_t *data, uint8_t cmd, const uint8_t *payload, size_t len); + + /// Handles a single byte as part of a protocol frame. + /// + /// Returns -1 on failure, 0 when finished and 1 when more bytes needed. + int handle_byte_(uint8_t c); + + /// Reads a response frame. + bool read_frame_(); + + /// Handles a complete frame. + bool handle_frame_(); + + /// Reset STM32 with the BOOT0 pin set to the given value. + void reset_(bool boot0); + + /// Reset STM32 to boot the regular firmware. + void reset_normal_boot_(); + + /// Reset STM32 to boot into DFU mode to enable firmware upgrades. + void reset_dfu_boot_(); +}; + +} // namespace shelly_dimmer +} // namespace esphome diff --git a/esphome/components/shelly_dimmer/stm32flash.cpp b/esphome/components/shelly_dimmer/stm32flash.cpp new file mode 100644 index 0000000000..4c777776fb --- /dev/null +++ b/esphome/components/shelly_dimmer/stm32flash.cpp @@ -0,0 +1,1061 @@ +/* + stm32flash - Open Source ST STM32 flash program for Arduino + Copyright 2010 Geoffrey McRae + Copyright 2012-2014 Tormod Volden + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#include "esphome/core/defines.h" +#ifdef USE_SHD_FIRMWARE_DATA + +#include + +#include "stm32flash.h" +#include "debug.h" + +#include "dev_table.h" +#include "esphome/core/log.h" + +#include +#include + +namespace { + +constexpr uint8_t STM32_ACK = 0x79; +constexpr uint8_t STM32_NACK = 0x1F; +constexpr uint8_t STM32_BUSY = 0x76; + +constexpr uint8_t STM32_CMD_INIT = 0x7F; +constexpr uint8_t STM32_CMD_GET = 0x00; /* get the version and command supported */ +constexpr uint8_t STM32_CMD_GVR = 0x01; /* get version and read protection status */ +constexpr uint8_t STM32_CMD_GID = 0x02; /* get ID */ +constexpr uint8_t STM32_CMD_RM = 0x11; /* read memory */ +constexpr uint8_t STM32_CMD_GO = 0x21; /* go */ +constexpr uint8_t STM32_CMD_WM = 0x31; /* write memory */ +constexpr uint8_t STM32_CMD_WM_NS = 0x32; /* no-stretch write memory */ +constexpr uint8_t STM32_CMD_ER = 0x43; /* erase */ +constexpr uint8_t STM32_CMD_EE = 0x44; /* extended erase */ +constexpr uint8_t STM32_CMD_EE_NS = 0x45; /* extended erase no-stretch */ +constexpr uint8_t STM32_CMD_WP = 0x63; /* write protect */ +constexpr uint8_t STM32_CMD_WP_NS = 0x64; /* write protect no-stretch */ +constexpr uint8_t STM32_CMD_UW = 0x73; /* write unprotect */ +constexpr uint8_t STM32_CMD_UW_NS = 0x74; /* write unprotect no-stretch */ +constexpr uint8_t STM32_CMD_RP = 0x82; /* readout protect */ +constexpr uint8_t STM32_CMD_RP_NS = 0x83; /* readout protect no-stretch */ +constexpr uint8_t STM32_CMD_UR = 0x92; /* readout unprotect */ +constexpr uint8_t STM32_CMD_UR_NS = 0x93; /* readout unprotect no-stretch */ +constexpr uint8_t STM32_CMD_CRC = 0xA1; /* compute CRC */ +constexpr uint8_t STM32_CMD_ERR = 0xFF; /* not a valid command */ + +constexpr uint32_t STM32_RESYNC_TIMEOUT = 35 * 1000; /* milliseconds */ +constexpr uint32_t STM32_MASSERASE_TIMEOUT = 35 * 1000; /* milliseconds */ +constexpr uint32_t STM32_PAGEERASE_TIMEOUT = 5 * 1000; /* milliseconds */ +constexpr uint32_t STM32_BLKWRITE_TIMEOUT = 1 * 1000; /* milliseconds */ +constexpr uint32_t STM32_WUNPROT_TIMEOUT = 1 * 1000; /* milliseconds */ +constexpr uint32_t STM32_WPROT_TIMEOUT = 1 * 1000; /* milliseconds */ +constexpr uint32_t STM32_RPROT_TIMEOUT = 1 * 1000; /* milliseconds */ +constexpr uint32_t DEFAULT_TIMEOUT = 5 * 1000; /* milliseconds */ + +constexpr uint8_t STM32_CMD_GET_LENGTH = 17; /* bytes in the reply */ + +/* Reset code for ARMv7-M (Cortex-M3) and ARMv6-M (Cortex-M0) + * see ARMv7-M or ARMv6-M Architecture Reference Manual (table B3-8) + * or "The definitive guide to the ARM Cortex-M3", section 14.4. + */ +constexpr uint8_t STM_RESET_CODE[] = { + 0x01, 0x49, // ldr r1, [pc, #4] ; () + 0x02, 0x4A, // ldr r2, [pc, #8] ; () + 0x0A, 0x60, // str r2, [r1, #0] + 0xfe, 0xe7, // endless: b endless + 0x0c, 0xed, 0x00, 0xe0, // .word 0xe000ed0c = NVIC AIRCR register address + 0x04, 0x00, 0xfa, 0x05 // .word 0x05fa0004 = VECTKEY | SYSRESETREQ +}; + +constexpr uint32_t STM_RESET_CODE_SIZE = sizeof(STM_RESET_CODE); + +/* RM0360, Empty check + * On STM32F070x6 and STM32F030xC devices only, internal empty check flag is + * implemented to allow easy programming of the virgin devices by the boot loader. This flag is + * used when BOOT0 pin is defining Main Flash memory as the target boot space. When the + * flag is set, the device is considered as empty and System memory (boot loader) is selected + * instead of the Main Flash as a boot space to allow user to program the Flash memory. + * This flag is updated only during Option bytes loading: it is set when the content of the + * address 0x08000 0000 is read as 0xFFFF FFFF, otherwise it is cleared. It means a power + * on or setting of OBL_LAUNCH bit in FLASH_CR register is needed to clear this flag after + * programming of a virgin device to execute user code after System reset. + */ +constexpr uint8_t STM_OBL_LAUNCH_CODE[] = { + 0x01, 0x49, // ldr r1, [pc, #4] ; () + 0x02, 0x4A, // ldr r2, [pc, #8] ; () + 0x0A, 0x60, // str r2, [r1, #0] + 0xfe, 0xe7, // endless: b endless + 0x10, 0x20, 0x02, 0x40, // address: FLASH_CR = 40022010 + 0x00, 0x20, 0x00, 0x00 // value: OBL_LAUNCH = 00002000 +}; + +constexpr uint32_t STM_OBL_LAUNCH_CODE_SIZE = sizeof(STM_OBL_LAUNCH_CODE); + +constexpr char TAG[] = "stm32flash"; + +} // Anonymous namespace + +namespace esphome { +namespace shelly_dimmer { + +namespace { + +int flash_addr_to_page_ceil(const stm32_t *stm, uint32_t addr) { + if (!(addr >= stm->dev->fl_start && addr <= stm->dev->fl_end)) + return 0; + + int page = 0; + addr -= stm->dev->fl_start; + const auto *psize = stm->dev->fl_ps; + + while (addr >= psize[0]) { + addr -= psize[0]; + page++; + if (psize[1]) + psize++; + } + + return addr ? page + 1 : page; +} + +stm32_err_t stm32_get_ack_timeout(const stm32_t *stm, uint32_t timeout) { + auto *stream = stm->stream; + uint8_t rxbyte; + + if (!(stm->flags & STREAM_OPT_RETRY)) + timeout = 0; + + if (timeout == 0) + timeout = DEFAULT_TIMEOUT; + + const uint32_t start_time = millis(); + do { + yield(); + if (!stream->available()) { + if (millis() < start_time + timeout) + continue; + ESP_LOGD(TAG, "Failed to read ACK timeout=%i", timeout); + return STM32_ERR_UNKNOWN; + } + + stream->read_byte(&rxbyte); + + if (rxbyte == STM32_ACK) + return STM32_ERR_OK; + if (rxbyte == STM32_NACK) + return STM32_ERR_NACK; + if (rxbyte != STM32_BUSY) { + ESP_LOGD(TAG, "Got byte 0x%02x instead of ACK", rxbyte); + return STM32_ERR_UNKNOWN; + } + } while (true); +} + +stm32_err_t stm32_get_ack(const stm32_t *stm) { return stm32_get_ack_timeout(stm, 0); } + +stm32_err_t stm32_send_command_timeout(const stm32_t *stm, const uint8_t cmd, const uint32_t timeout) { + auto *const stream = stm->stream; + + static constexpr auto BUFFER_SIZE = 2; + const uint8_t buf[] = { + cmd, + static_cast(cmd ^ 0xFF), + }; + static_assert(sizeof(buf) == BUFFER_SIZE, "Buf expected to be 2 bytes"); + + stream->write_array(buf, BUFFER_SIZE); + stream->flush(); + + stm32_err_t s_err = stm32_get_ack_timeout(stm, timeout); + if (s_err == STM32_ERR_OK) + return STM32_ERR_OK; + if (s_err == STM32_ERR_NACK) { + ESP_LOGD(TAG, "Got NACK from device on command 0x%02x", cmd); + } else { + ESP_LOGD(TAG, "Unexpected reply from device on command 0x%02x", cmd); + } + return STM32_ERR_UNKNOWN; +} + +stm32_err_t stm32_send_command(const stm32_t *stm, const uint8_t cmd) { + return stm32_send_command_timeout(stm, cmd, 0); +} + +/* if we have lost sync, send a wrong command and expect a NACK */ +stm32_err_t stm32_resync(const stm32_t *stm) { + auto *const stream = stm->stream; + uint32_t t0 = millis(); + auto t1 = t0; + + static constexpr auto BUFFER_SIZE = 2; + const uint8_t buf[] = { + STM32_CMD_ERR, + static_cast(STM32_CMD_ERR ^ 0xFF), + }; + static_assert(sizeof(buf) == BUFFER_SIZE, "Buf expected to be 2 bytes"); + + uint8_t ack; + while (t1 < t0 + STM32_RESYNC_TIMEOUT) { + stream->write_array(buf, BUFFER_SIZE); + stream->flush(); + if (!stream->read_array(&ack, 1)) { + t1 = millis(); + continue; + } + if (ack == STM32_NACK) + return STM32_ERR_OK; + t1 = millis(); + } + return STM32_ERR_UNKNOWN; +} + +/* + * some command receive reply frame with variable length, and length is + * embedded in reply frame itself. + * We can guess the length, but if we guess wrong the protocol gets out + * of sync. + * Use resync for frame oriented interfaces (e.g. I2C) and byte-by-byte + * read for byte oriented interfaces (e.g. UART). + * + * to run safely, data buffer should be allocated for 256+1 bytes + * + * len is value of the first byte in the frame. + */ +stm32_err_t stm32_guess_len_cmd(const stm32_t *stm, const uint8_t cmd, uint8_t *const data, unsigned int len) { + auto *const stream = stm->stream; + + if (stm32_send_command(stm, cmd) != STM32_ERR_OK) + return STM32_ERR_UNKNOWN; + if (stm->flags & STREAM_OPT_BYTE) { + /* interface is UART-like */ + if (!stream->read_array(data, 1)) + return STM32_ERR_UNKNOWN; + len = data[0]; + if (!stream->read_array(data + 1, len + 1)) + return STM32_ERR_UNKNOWN; + return STM32_ERR_OK; + } + + const auto ret = stream->read_array(data, len + 2); + if (ret && len == data[0]) + return STM32_ERR_OK; + if (!ret) { + /* restart with only one byte */ + if (stm32_resync(stm) != STM32_ERR_OK) + return STM32_ERR_UNKNOWN; + if (stm32_send_command(stm, cmd) != STM32_ERR_OK) + return STM32_ERR_UNKNOWN; + if (!stream->read_array(data, 1)) + return STM32_ERR_UNKNOWN; + } + + ESP_LOGD(TAG, "Re sync (len = %d)", data[0]); + if (stm32_resync(stm) != STM32_ERR_OK) + return STM32_ERR_UNKNOWN; + + len = data[0]; + if (stm32_send_command(stm, cmd) != STM32_ERR_OK) + return STM32_ERR_UNKNOWN; + + if (!stream->read_array(data, len + 2)) + return STM32_ERR_UNKNOWN; + return STM32_ERR_OK; +} + +/* + * Some interface, e.g. UART, requires a specific init sequence to let STM32 + * autodetect the interface speed. + * The sequence is only required one time after reset. + * This function sends the init sequence and, in case of timeout, recovers + * the interface. + */ +stm32_err_t stm32_send_init_seq(const stm32_t *stm) { + auto *const stream = stm->stream; + + stream->write_array(&STM32_CMD_INIT, 1); + stream->flush(); + + uint8_t byte; + bool ret = stream->read_array(&byte, 1); + if (ret && byte == STM32_ACK) + return STM32_ERR_OK; + if (ret && byte == STM32_NACK) { + /* We could get error later, but let's continue, for now. */ + ESP_LOGD(TAG, "Warning: the interface was not closed properly."); + return STM32_ERR_OK; + } + if (!ret) { + ESP_LOGD(TAG, "Failed to init device."); + return STM32_ERR_UNKNOWN; + } + + /* + * Check if previous STM32_CMD_INIT was taken as first byte + * of a command. Send a new byte, we should get back a NACK. + */ + stream->write_array(&STM32_CMD_INIT, 1); + stream->flush(); + + ret = stream->read_array(&byte, 1); + if (ret && byte == STM32_NACK) + return STM32_ERR_OK; + ESP_LOGD(TAG, "Failed to init device."); + return STM32_ERR_UNKNOWN; +} + +stm32_err_t stm32_mass_erase(const stm32_t *stm) { + auto *const stream = stm->stream; + + if (stm32_send_command(stm, stm->cmd->er) != STM32_ERR_OK) { + ESP_LOGD(TAG, "Can't initiate chip mass erase!"); + return STM32_ERR_UNKNOWN; + } + + /* regular erase (0x43) */ + if (stm->cmd->er == STM32_CMD_ER) { + const auto s_err = stm32_send_command_timeout(stm, 0xFF, STM32_MASSERASE_TIMEOUT); + if (s_err != STM32_ERR_OK) { + return STM32_ERR_UNKNOWN; + } + return STM32_ERR_OK; + } + + /* extended erase */ + static constexpr auto BUFFER_SIZE = 3; + const uint8_t buf[] = { + 0xFF, /* 0xFFFF the magic number for mass erase */ + 0xFF, 0x00, /* checksum */ + }; + static_assert(sizeof(buf) == BUFFER_SIZE, "Expected the buffer to be 3 bytes"); + stream->write_array(buf, 3); + stream->flush(); + + const auto s_err = stm32_get_ack_timeout(stm, STM32_MASSERASE_TIMEOUT); + if (s_err != STM32_ERR_OK) { + ESP_LOGD(TAG, "Mass erase failed. Try specifying the number of pages to be erased."); + return STM32_ERR_UNKNOWN; + } + return STM32_ERR_OK; +} + +template std::unique_ptr malloc_array_raii(size_t size) { + // Could be constexpr in c++17 + static const auto DELETOR = [](T *memory) { + free(memory); // NOLINT + }; + return std::unique_ptr{static_cast(malloc(size)), // NOLINT + DELETOR}; +} + +stm32_err_t stm32_pages_erase(const stm32_t *stm, const uint32_t spage, const uint32_t pages) { + auto *const stream = stm->stream; + uint8_t cs = 0; + int i = 0; + + /* The erase command reported by the bootloader is either 0x43, 0x44 or 0x45 */ + /* 0x44 is Extended Erase, a 2 byte based protocol and needs to be handled differently. */ + /* 0x45 is clock no-stretching version of Extended Erase for I2C port. */ + if (stm32_send_command(stm, stm->cmd->er) != STM32_ERR_OK) { + ESP_LOGD(TAG, "Can't initiate chip mass erase!"); + return STM32_ERR_UNKNOWN; + } + + /* regular erase (0x43) */ + if (stm->cmd->er == STM32_CMD_ER) { + // Free memory with RAII + auto buf = malloc_array_raii(1 + pages + 1); + + if (!buf) + return STM32_ERR_UNKNOWN; + + buf[i++] = pages - 1; + cs ^= (pages - 1); + for (auto pg_num = spage; pg_num < (pages + spage); pg_num++) { + buf[i++] = pg_num; + cs ^= pg_num; + } + buf[i++] = cs; + stream->write_array(&buf[0], i); + stream->flush(); + + const auto s_err = stm32_get_ack_timeout(stm, pages * STM32_PAGEERASE_TIMEOUT); + if (s_err != STM32_ERR_OK) { + return STM32_ERR_UNKNOWN; + } + return STM32_ERR_OK; + } + + /* extended erase */ + + // Free memory with RAII + auto buf = malloc_array_raii(2 + 2 * pages + 1); + + if (!buf) + return STM32_ERR_UNKNOWN; + + /* Number of pages to be erased - 1, two bytes, MSB first */ + uint8_t pg_byte = (pages - 1) >> 8; + buf[i++] = pg_byte; + cs ^= pg_byte; + pg_byte = (pages - 1) & 0xFF; + buf[i++] = pg_byte; + cs ^= pg_byte; + + for (auto pg_num = spage; pg_num < spage + pages; pg_num++) { + pg_byte = pg_num >> 8; + cs ^= pg_byte; + buf[i++] = pg_byte; + pg_byte = pg_num & 0xFF; + cs ^= pg_byte; + buf[i++] = pg_byte; + } + buf[i++] = cs; + stream->write_array(&buf[0], i); + stream->flush(); + + const auto s_err = stm32_get_ack_timeout(stm, pages * STM32_PAGEERASE_TIMEOUT); + if (s_err != STM32_ERR_OK) { + ESP_LOGD(TAG, "Page-by-page erase failed. Check the maximum pages your device supports."); + return STM32_ERR_UNKNOWN; + } + + return STM32_ERR_OK; +} + +template stm32_err_t stm32_check_ack_timeout(const stm32_err_t s_err, const T &&log) { + switch (s_err) { + case STM32_ERR_OK: + return STM32_ERR_OK; + case STM32_ERR_NACK: + log(); + // TODO: c++17 [[fallthrough]] + /* fallthrough */ + default: + return STM32_ERR_UNKNOWN; + } +} + +/* detect CPU endian */ +bool cpu_le() { + static constexpr int N = 1; + + // returns true if little endian + return *reinterpret_cast(&N) == 1; +} + +uint32_t le_u32(const uint32_t v) { + if (!cpu_le()) + return ((v & 0xFF000000) >> 24) | ((v & 0x00FF0000) >> 8) | ((v & 0x0000FF00) << 8) | ((v & 0x000000FF) << 24); + return v; +} + +template void populate_buffer_with_address(uint8_t (&buffer)[N], uint32_t address) { + buffer[0] = static_cast(address >> 24); + buffer[1] = static_cast((address >> 16) & 0xFF); + buffer[2] = static_cast((address >> 8) & 0xFF); + buffer[3] = static_cast(address & 0xFF); + buffer[4] = static_cast(buffer[0] ^ buffer[1] ^ buffer[2] ^ buffer[3]); +} + +} // Anonymous namespace + +} // namespace shelly_dimmer +} // namespace esphome + +namespace esphome { +namespace shelly_dimmer { + +/* find newer command by higher code */ +#define newer(prev, a) (((prev) == STM32_CMD_ERR) ? (a) : (((prev) > (a)) ? (prev) : (a))) + +stm32_t *stm32_init(uart::UARTDevice *stream, const uint8_t flags, const char init) { + uint8_t buf[257]; + + // Could be constexpr in c++17 + static const auto CLOSE = [](stm32_t *stm32) { stm32_close(stm32); }; + + // Cleanup with RAII + std::unique_ptr stm{static_cast(calloc(sizeof(stm32_t), 1)), // NOLINT + CLOSE}; + + if (!stm) { + return nullptr; + } + stm->stream = stream; + stm->flags = flags; + + stm->cmd = static_cast(malloc(sizeof(stm32_cmd_t))); // NOLINT + if (!stm->cmd) { + return nullptr; + } + memset(stm->cmd, STM32_CMD_ERR, sizeof(stm32_cmd_t)); + + if ((stm->flags & STREAM_OPT_CMD_INIT) && init) { + if (stm32_send_init_seq(stm.get()) != STM32_ERR_OK) + return nullptr; // NOLINT + } + + /* get the version and read protection status */ + if (stm32_send_command(stm.get(), STM32_CMD_GVR) != STM32_ERR_OK) { + return nullptr; // NOLINT + } + + /* From AN, only UART bootloader returns 3 bytes */ + { + const auto len = (stm->flags & STREAM_OPT_GVR_ETX) ? 3 : 1; + if (!stream->read_array(buf, len)) + return nullptr; // NOLINT + stm->version = buf[0]; + stm->option1 = (stm->flags & STREAM_OPT_GVR_ETX) ? buf[1] : 0; + stm->option2 = (stm->flags & STREAM_OPT_GVR_ETX) ? buf[2] : 0; + if (stm32_get_ack(stm.get()) != STM32_ERR_OK) { + return nullptr; + } + } + + { + const auto len = ([&]() { + /* get the bootloader information */ + if (stm->cmd_get_reply) { + for (auto i = 0; stm->cmd_get_reply[i].length; ++i) { + if (stm->version == stm->cmd_get_reply[i].version) { + return stm->cmd_get_reply[i].length; + } + } + } + + return STM32_CMD_GET_LENGTH; + })(); + + if (stm32_guess_len_cmd(stm.get(), STM32_CMD_GET, buf, len) != STM32_ERR_OK) + return nullptr; + } + + const auto stop = buf[0] + 1; + stm->bl_version = buf[1]; + int new_cmds = 0; + for (auto i = 1; i < stop; ++i) { + const auto val = buf[i + 1]; + switch (val) { + case STM32_CMD_GET: + stm->cmd->get = val; + break; + case STM32_CMD_GVR: + stm->cmd->gvr = val; + break; + case STM32_CMD_GID: + stm->cmd->gid = val; + break; + case STM32_CMD_RM: + stm->cmd->rm = val; + break; + case STM32_CMD_GO: + stm->cmd->go = val; + break; + case STM32_CMD_WM: + case STM32_CMD_WM_NS: + stm->cmd->wm = newer(stm->cmd->wm, val); + break; + case STM32_CMD_ER: + case STM32_CMD_EE: + case STM32_CMD_EE_NS: + stm->cmd->er = newer(stm->cmd->er, val); + break; + case STM32_CMD_WP: + case STM32_CMD_WP_NS: + stm->cmd->wp = newer(stm->cmd->wp, val); + break; + case STM32_CMD_UW: + case STM32_CMD_UW_NS: + stm->cmd->uw = newer(stm->cmd->uw, val); + break; + case STM32_CMD_RP: + case STM32_CMD_RP_NS: + stm->cmd->rp = newer(stm->cmd->rp, val); + break; + case STM32_CMD_UR: + case STM32_CMD_UR_NS: + stm->cmd->ur = newer(stm->cmd->ur, val); + break; + case STM32_CMD_CRC: + stm->cmd->crc = newer(stm->cmd->crc, val); + break; + default: + if (new_cmds++ == 0) { + ESP_LOGD(TAG, "GET returns unknown commands (0x%2x", val); + } else { + ESP_LOGD(TAG, ", 0x%2x", val); + } + } + } + if (new_cmds) + ESP_LOGD(TAG, ")"); + if (stm32_get_ack(stm.get()) != STM32_ERR_OK) { + return nullptr; + } + + if (stm->cmd->get == STM32_CMD_ERR || stm->cmd->gvr == STM32_CMD_ERR || stm->cmd->gid == STM32_CMD_ERR) { + ESP_LOGD(TAG, "Error: bootloader did not returned correct information from GET command"); + return nullptr; + } + + /* get the device ID */ + if (stm32_guess_len_cmd(stm.get(), stm->cmd->gid, buf, 1) != STM32_ERR_OK) { + return nullptr; + } + const auto returned = buf[0] + 1; + if (returned < 2) { + ESP_LOGD(TAG, "Only %d bytes sent in the PID, unknown/unsupported device", returned); + return nullptr; + } + stm->pid = (buf[1] << 8) | buf[2]; + if (returned > 2) { + ESP_LOGD(TAG, "This bootloader returns %d extra bytes in PID:", returned); + for (auto i = 2; i <= returned; i++) + ESP_LOGD(TAG, " %02x", buf[i]); + } + if (stm32_get_ack(stm.get()) != STM32_ERR_OK) { + return nullptr; + } + + stm->dev = DEVICES; + while (stm->dev->id != 0x00 && stm->dev->id != stm->pid) + ++stm->dev; + + if (!stm->dev->id) { + ESP_LOGD(TAG, "Unknown/unsupported device (Device ID: 0x%03x)", stm->pid); + return nullptr; + } + + // TODO: Would be much better if the unique_ptr was returned from this function + // Release ownership of unique_ptr + return stm.release(); // NOLINT +} + +void stm32_close(stm32_t *stm) { + if (stm) + free(stm->cmd); // NOLINT + free(stm); // NOLINT +} + +stm32_err_t stm32_read_memory(const stm32_t *stm, const uint32_t address, uint8_t *data, const unsigned int len) { + auto *const stream = stm->stream; + + if (!len) + return STM32_ERR_OK; + + if (len > 256) { + ESP_LOGD(TAG, "Error: READ length limit at 256 bytes"); + return STM32_ERR_UNKNOWN; + } + + if (stm->cmd->rm == STM32_CMD_ERR) { + ESP_LOGD(TAG, "Error: READ command not implemented in bootloader."); + return STM32_ERR_NO_CMD; + } + + if (stm32_send_command(stm, stm->cmd->rm) != STM32_ERR_OK) + return STM32_ERR_UNKNOWN; + + static constexpr auto BUFFER_SIZE = 5; + uint8_t buf[BUFFER_SIZE]; + populate_buffer_with_address(buf, address); + + stream->write_array(buf, BUFFER_SIZE); + stream->flush(); + + if (stm32_get_ack(stm) != STM32_ERR_OK) + return STM32_ERR_UNKNOWN; + + if (stm32_send_command(stm, len - 1) != STM32_ERR_OK) + return STM32_ERR_UNKNOWN; + + if (!stream->read_array(data, len)) + return STM32_ERR_UNKNOWN; + + return STM32_ERR_OK; +} + +stm32_err_t stm32_write_memory(const stm32_t *stm, uint32_t address, const uint8_t *data, const unsigned int len) { + auto *const stream = stm->stream; + + if (!len) + return STM32_ERR_OK; + + if (len > 256) { + ESP_LOGD(TAG, "Error: READ length limit at 256 bytes"); + return STM32_ERR_UNKNOWN; + } + + /* must be 32bit aligned */ + if (address & 0x3) { + ESP_LOGD(TAG, "Error: WRITE address must be 4 byte aligned"); + return STM32_ERR_UNKNOWN; + } + + if (stm->cmd->wm == STM32_CMD_ERR) { + ESP_LOGD(TAG, "Error: WRITE command not implemented in bootloader."); + return STM32_ERR_NO_CMD; + } + + /* send the address and checksum */ + if (stm32_send_command(stm, stm->cmd->wm) != STM32_ERR_OK) + return STM32_ERR_UNKNOWN; + + static constexpr auto BUFFER_SIZE = 5; + uint8_t buf1[BUFFER_SIZE]; + populate_buffer_with_address(buf1, address); + + stream->write_array(buf1, BUFFER_SIZE); + stream->flush(); + if (stm32_get_ack(stm) != STM32_ERR_OK) + return STM32_ERR_UNKNOWN; + + const unsigned int aligned_len = (len + 3) & ~3; + uint8_t cs = aligned_len - 1; + uint8_t buf[256 + 2]; + + buf[0] = aligned_len - 1; + for (auto i = 0; i < len; i++) { + cs ^= data[i]; + buf[i + 1] = data[i]; + } + /* padding data */ + for (auto i = len; i < aligned_len; i++) { + cs ^= 0xFF; + buf[i + 1] = 0xFF; + } + buf[aligned_len + 1] = cs; + stream->write_array(buf, aligned_len + 2); + stream->flush(); + + const auto s_err = stm32_get_ack_timeout(stm, STM32_BLKWRITE_TIMEOUT); + if (s_err != STM32_ERR_OK) { + return STM32_ERR_UNKNOWN; + } + return STM32_ERR_OK; +} + +stm32_err_t stm32_wunprot_memory(const stm32_t *stm) { + if (stm->cmd->uw == STM32_CMD_ERR) { + ESP_LOGD(TAG, "Error: WRITE UNPROTECT command not implemented in bootloader."); + return STM32_ERR_NO_CMD; + } + + if (stm32_send_command(stm, stm->cmd->uw) != STM32_ERR_OK) + return STM32_ERR_UNKNOWN; + + return stm32_check_ack_timeout(stm32_get_ack_timeout(stm, STM32_WUNPROT_TIMEOUT), + []() { ESP_LOGD(TAG, "Error: Failed to WRITE UNPROTECT"); }); +} + +stm32_err_t stm32_wprot_memory(const stm32_t *stm) { + if (stm->cmd->wp == STM32_CMD_ERR) { + ESP_LOGD(TAG, "Error: WRITE PROTECT command not implemented in bootloader."); + return STM32_ERR_NO_CMD; + } + + if (stm32_send_command(stm, stm->cmd->wp) != STM32_ERR_OK) + return STM32_ERR_UNKNOWN; + + return stm32_check_ack_timeout(stm32_get_ack_timeout(stm, STM32_WPROT_TIMEOUT), + []() { ESP_LOGD(TAG, "Error: Failed to WRITE PROTECT"); }); +} + +stm32_err_t stm32_runprot_memory(const stm32_t *stm) { + if (stm->cmd->ur == STM32_CMD_ERR) { + ESP_LOGD(TAG, "Error: READOUT UNPROTECT command not implemented in bootloader."); + return STM32_ERR_NO_CMD; + } + + if (stm32_send_command(stm, stm->cmd->ur) != STM32_ERR_OK) + return STM32_ERR_UNKNOWN; + + return stm32_check_ack_timeout(stm32_get_ack_timeout(stm, STM32_MASSERASE_TIMEOUT), + []() { ESP_LOGD(TAG, "Error: Failed to READOUT UNPROTECT"); }); +} + +stm32_err_t stm32_readprot_memory(const stm32_t *stm) { + if (stm->cmd->rp == STM32_CMD_ERR) { + ESP_LOGD(TAG, "Error: READOUT PROTECT command not implemented in bootloader."); + return STM32_ERR_NO_CMD; + } + + if (stm32_send_command(stm, stm->cmd->rp) != STM32_ERR_OK) + return STM32_ERR_UNKNOWN; + + return stm32_check_ack_timeout(stm32_get_ack_timeout(stm, STM32_RPROT_TIMEOUT), + []() { ESP_LOGD(TAG, "Error: Failed to READOUT PROTECT"); }); +} + +stm32_err_t stm32_erase_memory(const stm32_t *stm, uint32_t spage, uint32_t pages) { + if (!pages || spage > STM32_MAX_PAGES || ((pages != STM32_MASS_ERASE) && ((spage + pages) > STM32_MAX_PAGES))) + return STM32_ERR_OK; + + if (stm->cmd->er == STM32_CMD_ERR) { + ESP_LOGD(TAG, "Error: ERASE command not implemented in bootloader."); + return STM32_ERR_NO_CMD; + } + + if (pages == STM32_MASS_ERASE) { + /* + * Not all chips support mass erase. + * Mass erase can be obtained executing a "readout protect" + * followed by "readout un-protect". This method is not + * suggested because can hang the target if a debug SWD/JTAG + * is connected. When the target enters in "readout + * protection" mode it will consider the debug connection as + * a tentative of intrusion and will hang. + * Erasing the flash page-by-page is the safer way to go. + */ + if (!(stm->dev->flags & F_NO_ME)) + return stm32_mass_erase(stm); + + pages = flash_addr_to_page_ceil(stm, stm->dev->fl_end); + } + + /* + * Some device, like STM32L152, cannot erase more than 512 pages in + * one command. Split the call. + */ + static constexpr uint32_t MAX_PAGE_SIZE = 512; + while (pages) { + const auto n = std::min(pages, MAX_PAGE_SIZE); + const auto s_err = stm32_pages_erase(stm, spage, n); + if (s_err != STM32_ERR_OK) + return s_err; + spage += n; + pages -= n; + } + return STM32_ERR_OK; +} + +static stm32_err_t stm32_run_raw_code(const stm32_t *stm, uint32_t target_address, const uint8_t *code, + uint32_t code_size) { + static constexpr uint32_t BUFFER_SIZE = 256; + + const auto stack_le = le_u32(0x20002000); + const auto code_address_le = le_u32(target_address + 8 + 1); // thumb mode address (!) + uint32_t length = code_size + 8; + + /* Must be 32-bit aligned */ + if (target_address & 0x3) { + ESP_LOGD(TAG, "Error: code address must be 4 byte aligned"); + return STM32_ERR_UNKNOWN; + } + + // Could be constexpr in c++17 + static const auto DELETOR = [](uint8_t *memory) { + free(memory); // NOLINT + }; + + // Free memory with RAII + std::unique_ptr mem{static_cast(malloc(length)), // NOLINT + DELETOR}; + + if (!mem) + return STM32_ERR_UNKNOWN; + + memcpy(mem.get(), &stack_le, sizeof(stack_le)); + memcpy(mem.get() + 4, &code_address_le, sizeof(code_address_le)); + memcpy(mem.get() + 8, code, code_size); + + auto *pos = mem.get(); + auto address = target_address; + while (length > 0) { + const auto w = std::min(length, BUFFER_SIZE); + if (stm32_write_memory(stm, address, pos, w) != STM32_ERR_OK) { + return STM32_ERR_UNKNOWN; + } + + address += w; + pos += w; + length -= w; + } + + return stm32_go(stm, target_address); +} + +stm32_err_t stm32_go(const stm32_t *stm, const uint32_t address) { + auto *const stream = stm->stream; + + if (stm->cmd->go == STM32_CMD_ERR) { + ESP_LOGD(TAG, "Error: GO command not implemented in bootloader."); + return STM32_ERR_NO_CMD; + } + + if (stm32_send_command(stm, stm->cmd->go) != STM32_ERR_OK) + return STM32_ERR_UNKNOWN; + + static constexpr auto BUFFER_SIZE = 5; + uint8_t buf[BUFFER_SIZE]; + populate_buffer_with_address(buf, address); + + stream->write_array(buf, BUFFER_SIZE); + stream->flush(); + + if (stm32_get_ack(stm) != STM32_ERR_OK) + return STM32_ERR_UNKNOWN; + return STM32_ERR_OK; +} + +stm32_err_t stm32_reset_device(const stm32_t *stm) { + const auto target_address = stm->dev->ram_start; + + if (stm->dev->flags & F_OBLL) { + /* set the OBL_LAUNCH bit to reset device (see RM0360, 2.5) */ + return stm32_run_raw_code(stm, target_address, STM_OBL_LAUNCH_CODE, STM_OBL_LAUNCH_CODE_SIZE); + } else { + return stm32_run_raw_code(stm, target_address, STM_RESET_CODE, STM_RESET_CODE_SIZE); + } +} + +stm32_err_t stm32_crc_memory(const stm32_t *stm, const uint32_t address, const uint32_t length, uint32_t *const crc) { + static constexpr auto BUFFER_SIZE = 5; + auto *const stream = stm->stream; + + if (address & 0x3 || length & 0x3) { + ESP_LOGD(TAG, "Start and end addresses must be 4 byte aligned"); + return STM32_ERR_UNKNOWN; + } + + if (stm->cmd->crc == STM32_CMD_ERR) { + ESP_LOGD(TAG, "Error: CRC command not implemented in bootloader."); + return STM32_ERR_NO_CMD; + } + + if (stm32_send_command(stm, stm->cmd->crc) != STM32_ERR_OK) + return STM32_ERR_UNKNOWN; + + { + static constexpr auto BUFFER_SIZE = 5; + uint8_t buf[BUFFER_SIZE]; + populate_buffer_with_address(buf, address); + + stream->write_array(buf, BUFFER_SIZE); + stream->flush(); + } + + if (stm32_get_ack(stm) != STM32_ERR_OK) + return STM32_ERR_UNKNOWN; + + { + static constexpr auto BUFFER_SIZE = 5; + uint8_t buf[BUFFER_SIZE]; + populate_buffer_with_address(buf, address); + + stream->write_array(buf, BUFFER_SIZE); + stream->flush(); + } + + if (stm32_get_ack(stm) != STM32_ERR_OK) + return STM32_ERR_UNKNOWN; + + if (stm32_get_ack(stm) != STM32_ERR_OK) + return STM32_ERR_UNKNOWN; + + { + uint8_t buf[BUFFER_SIZE]; + if (!stream->read_array(buf, BUFFER_SIZE)) + return STM32_ERR_UNKNOWN; + + if (buf[4] != (buf[0] ^ buf[1] ^ buf[2] ^ buf[3])) + return STM32_ERR_UNKNOWN; + + *crc = (buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3]; + } + + return STM32_ERR_OK; +} + +/* + * CRC computed by STM32 is similar to the standard crc32_be() + * implemented, for example, in Linux kernel in ./lib/crc32.c + * But STM32 computes it on units of 32 bits word and swaps the + * bytes of the word before the computation. + * Due to byte swap, I cannot use any CRC available in existing + * libraries, so here is a simple not optimized implementation. + */ +uint32_t stm32_sw_crc(uint32_t crc, uint8_t *buf, unsigned int len) { + static constexpr uint32_t CRCPOLY_BE = 0x04c11db7; + static constexpr uint32_t CRC_MSBMASK = 0x80000000; + + if (len & 0x3) { + ESP_LOGD(TAG, "Buffer length must be multiple of 4 bytes"); + return 0; + } + + while (len) { + uint32_t data = *buf++; + data |= *buf++ << 8; + data |= *buf++ << 16; + data |= *buf++ << 24; + len -= 4; + + crc ^= data; + + for (size_t i = 0; i < 32; ++i) { + if (crc & CRC_MSBMASK) { + crc = (crc << 1) ^ CRCPOLY_BE; + } else { + crc = (crc << 1); + } + } + } + return crc; +} + +stm32_err_t stm32_crc_wrapper(const stm32_t *stm, uint32_t address, uint32_t length, uint32_t *crc) { + static constexpr uint32_t CRC_INIT_VALUE = 0xFFFFFFFF; + static constexpr uint32_t BUFFER_SIZE = 256; + + uint8_t buf[BUFFER_SIZE]; + + if (address & 0x3 || length & 0x3) { + ESP_LOGD(TAG, "Start and end addresses must be 4 byte aligned"); + return STM32_ERR_UNKNOWN; + } + + if (stm->cmd->crc != STM32_CMD_ERR) + return stm32_crc_memory(stm, address, length, crc); + + const auto start = address; + const auto total_len = length; + uint32_t current_crc = CRC_INIT_VALUE; + while (length) { + const auto len = std::min(BUFFER_SIZE, length); + if (stm32_read_memory(stm, address, buf, len) != STM32_ERR_OK) { + ESP_LOGD(TAG, "Failed to read memory at address 0x%08x, target write-protected?", address); + return STM32_ERR_UNKNOWN; + } + current_crc = stm32_sw_crc(current_crc, buf, len); + length -= len; + address += len; + + ESP_LOGD(TAG, "\rCRC address 0x%08x (%.2f%%) ", address, (100.0f / (float) total_len) * (float) (address - start)); + } + ESP_LOGD(TAG, "Done."); + *crc = current_crc; + return STM32_ERR_OK; +} + +} // namespace shelly_dimmer +} // namespace esphome +#endif diff --git a/esphome/components/shelly_dimmer/stm32flash.h b/esphome/components/shelly_dimmer/stm32flash.h new file mode 100644 index 0000000000..c561375c38 --- /dev/null +++ b/esphome/components/shelly_dimmer/stm32flash.h @@ -0,0 +1,129 @@ +/* + stm32flash - Open Source ST STM32 flash program for Arduino + Copyright (C) 2010 Geoffrey McRae + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#pragma once + +#include "esphome/core/defines.h" +#ifdef USE_SHD_FIRMWARE_DATA + +#include +#include "esphome/components/uart/uart.h" + +namespace esphome { +namespace shelly_dimmer { + +/* flags */ +constexpr auto STREAM_OPT_BYTE = (1 << 0); /* byte (not frame) oriented */ +constexpr auto STREAM_OPT_GVR_ETX = (1 << 1); /* cmd GVR returns protection status */ +constexpr auto STREAM_OPT_CMD_INIT = (1 << 2); /* use INIT cmd to autodetect speed */ +constexpr auto STREAM_OPT_RETRY = (1 << 3); /* allowed read() retry after timeout */ +constexpr auto STREAM_OPT_I2C = (1 << 4); /* i2c */ +constexpr auto STREAM_OPT_STRETCH_W = (1 << 5); /* warning for no-stretching commands */ + +constexpr auto STREAM_SERIAL = (STREAM_OPT_BYTE | STREAM_OPT_GVR_ETX | STREAM_OPT_CMD_INIT | STREAM_OPT_RETRY); +constexpr auto STREAM_I2C = (STREAM_OPT_I2C | STREAM_OPT_STRETCH_W); + +constexpr auto STM32_MAX_RX_FRAME = 256; /* cmd read memory */ +constexpr auto STM32_MAX_TX_FRAME = (1 + 256 + 1); /* cmd write memory */ + +constexpr auto STM32_MAX_PAGES = 0x0000ffff; +constexpr auto STM32_MASS_ERASE = 0x00100000; /* > 2 x max_pages */ + +using stm32_err_t = enum Stm32Err { + STM32_ERR_OK = 0, + STM32_ERR_UNKNOWN, /* Generic error */ + STM32_ERR_NACK, + STM32_ERR_NO_CMD, /* Command not available in bootloader */ +}; + +using flags_t = enum Flags { + F_NO_ME = 1 << 0, /* Mass-Erase not supported */ + F_OBLL = 1 << 1, /* OBL_LAUNCH required */ +}; + +using stm32_cmd_t = struct Stm32Cmd { + uint8_t get; + uint8_t gvr; + uint8_t gid; + uint8_t rm; + uint8_t go; + uint8_t wm; + uint8_t er; /* this may be extended erase */ + uint8_t wp; + uint8_t uw; + uint8_t rp; + uint8_t ur; + uint8_t crc; +}; + +using stm32_dev_t = struct Stm32Dev { // NOLINT + const uint16_t id; + const char *name; + const uint32_t ram_start, ram_end; + const uint32_t fl_start, fl_end; + const uint16_t fl_pps; // pages per sector + const uint32_t *fl_ps; // page size + const uint32_t opt_start, opt_end; + const uint32_t mem_start, mem_end; + const uint32_t flags; +}; + +using stm32_t = struct Stm32 { + uart::UARTDevice *stream; + uint8_t flags; + struct VarlenCmd *cmd_get_reply; + uint8_t bl_version; + uint8_t version; + uint8_t option1, option2; + uint16_t pid; + stm32_cmd_t *cmd; + const stm32_dev_t *dev; +}; + +/* + * Specify the length of reply for command GET + * This is helpful for frame-oriented protocols, e.g. i2c, to avoid time + * consuming try-fail-timeout-retry operation. + * On byte-oriented protocols, i.e. UART, this information would be skipped + * after read the first byte, so not needed. + */ +struct VarlenCmd { + uint8_t version; + uint8_t length; +}; + +stm32_t *stm32_init(uart::UARTDevice *stream, uint8_t flags, char init); +void stm32_close(stm32_t *stm); +stm32_err_t stm32_read_memory(const stm32_t *stm, uint32_t address, uint8_t *data, unsigned int len); +stm32_err_t stm32_write_memory(const stm32_t *stm, uint32_t address, const uint8_t *data, unsigned int len); +stm32_err_t stm32_wunprot_memory(const stm32_t *stm); +stm32_err_t stm32_wprot_memory(const stm32_t *stm); +stm32_err_t stm32_erase_memory(const stm32_t *stm, uint32_t spage, uint32_t pages); +stm32_err_t stm32_go(const stm32_t *stm, uint32_t address); +stm32_err_t stm32_reset_device(const stm32_t *stm); +stm32_err_t stm32_readprot_memory(const stm32_t *stm); +stm32_err_t stm32_runprot_memory(const stm32_t *stm); +stm32_err_t stm32_crc_memory(const stm32_t *stm, uint32_t address, uint32_t length, uint32_t *crc); +stm32_err_t stm32_crc_wrapper(const stm32_t *stm, uint32_t address, uint32_t length, uint32_t *crc); +uint32_t stm32_sw_crc(uint32_t crc, uint8_t *buf, unsigned int len); + +} // namespace shelly_dimmer +} // namespace esphome + +#endif // USE_SHD_FIRMWARE_DATA diff --git a/esphome/core/defines.h b/esphome/core/defines.h index f304f847a5..c854e2b987 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -93,3 +93,9 @@ //#define USE_BSEC // Requires a library with proprietary license. #define USE_DASHBOARD_IMPORT + +// Dummy firmware payload for shelly_dimmer +#define USE_SHD_FIRMWARE_MAJOR_VERSION 56 +#define USE_SHD_FIRMWARE_MINOR_VERSION 5 +#define USE_SHD_FIRMWARE_DATA \ + {} diff --git a/tests/test1.yaml b/tests/test1.yaml index 77c4a76bda..98a3ffcf4b 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -1751,6 +1751,18 @@ light: to: 25 - single_light_id: ${roomname}_lights + - platform: shelly_dimmer + name: "Shelly Dimmer Light" + power: + name: "Shelly Dimmer Power" + voltage: + name: "Shelly Dimmer Voltage" + current: + name: "Shelly Dimmer Current" + max_brightness: 500 + firmware: "51.6" + uart_id: uart0 + remote_transmitter: - pin: 32 carrier_duty_percent: 100% From 2243021b581ad1f855de5976e2152fcdc4e97f91 Mon Sep 17 00:00:00 2001 From: Janez Troha <239513+dz0ny@users.noreply.github.com> Date: Thu, 14 Apr 2022 03:42:43 +0200 Subject: [PATCH 0233/3388] Allocate smaller amount of buffer for JSON (#3384) --- esphome/components/json/json_util.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/esphome/components/json/json_util.cpp b/esphome/components/json/json_util.cpp index 10179c9954..2bd8112255 100644 --- a/esphome/components/json/json_util.cpp +++ b/esphome/components/json/json_util.cpp @@ -23,13 +23,13 @@ std::string build_json(const json_build_t &f) { #ifdef USE_ESP8266 const size_t free_heap = ESP.getMaxFreeBlockSize(); // NOLINT(readability-static-accessed-through-instance) #elif defined(USE_ESP32) - const size_t free_heap = heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL); + const size_t free_heap = heap_caps_get_largest_free_block(MALLOC_CAP_8BIT); #endif - const size_t request_size = std::min(free_heap - 2048, (size_t) 5120); + const size_t request_size = std::min(free_heap, (size_t) 512); DynamicJsonDocument json_document(request_size); - if (json_document.memoryPool().buffer() == nullptr) { + if (json_document.capacity() == 0) { ESP_LOGE(TAG, "Could not allocate memory for JSON document! Requested %u bytes, largest free heap block: %u bytes", request_size, free_heap); return "{}"; @@ -37,7 +37,7 @@ std::string build_json(const json_build_t &f) { JsonObject root = json_document.to(); f(root); json_document.shrinkToFit(); - + ESP_LOGV(TAG, "Size after shrink %u bytes", json_document.capacity()); std::string output; serializeJson(json_document, output); return output; @@ -51,13 +51,13 @@ void parse_json(const std::string &data, const json_parse_t &f) { #ifdef USE_ESP8266 const size_t free_heap = ESP.getMaxFreeBlockSize(); // NOLINT(readability-static-accessed-through-instance) #elif defined(USE_ESP32) - const size_t free_heap = heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL); + const size_t free_heap = heap_caps_get_largest_free_block(MALLOC_CAP_8BIT); #endif bool pass = false; - size_t request_size = std::min(free_heap - 2048, (size_t)(data.size() * 1.5)); + size_t request_size = std::min(free_heap, (size_t)(data.size() * 1.5)); do { DynamicJsonDocument json_document(request_size); - if (json_document.memoryPool().buffer() == nullptr) { + if (json_document.capacity() == 0) { ESP_LOGE(TAG, "Could not allocate memory for JSON document! Requested %u bytes, free heap: %u", request_size, free_heap); return; From dcb226b20221fd481d15e8f4925120b64b138fdf Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 14 Apr 2022 13:48:35 +1200 Subject: [PATCH 0234/3388] Bump version to 2022.4.0b2 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 01d2d59c3d..04fa5c2bf7 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2022.4.0b1" +__version__ = "2022.4.0b2" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" From 6b393438e9177bd3e547e962cff9d6559a161407 Mon Sep 17 00:00:00 2001 From: Michel van de Wetering Date: Mon, 18 Apr 2022 22:42:02 +0200 Subject: [PATCH 0235/3388] Fix power_delivered/produced_phase sensor deviceclass in DSMR (#3395) --- esphome/components/dsmr/sensor.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/esphome/components/dsmr/sensor.py b/esphome/components/dsmr/sensor.py index bb4722655c..0b0439baa4 100644 --- a/esphome/components/dsmr/sensor.py +++ b/esphome/components/dsmr/sensor.py @@ -143,37 +143,37 @@ CONFIG_SCHEMA = cv.Schema( cv.Optional("power_delivered_l1"): sensor.sensor_schema( unit_of_measurement=UNIT_KILOWATT, accuracy_decimals=3, - device_class=DEVICE_CLASS_CURRENT, + device_class=DEVICE_CLASS_POWER, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("power_delivered_l2"): sensor.sensor_schema( unit_of_measurement=UNIT_KILOWATT, accuracy_decimals=3, - device_class=DEVICE_CLASS_CURRENT, + device_class=DEVICE_CLASS_POWER, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("power_delivered_l3"): sensor.sensor_schema( unit_of_measurement=UNIT_KILOWATT, accuracy_decimals=3, - device_class=DEVICE_CLASS_CURRENT, + device_class=DEVICE_CLASS_POWER, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("power_returned_l1"): sensor.sensor_schema( unit_of_measurement=UNIT_KILOWATT, accuracy_decimals=3, - device_class=DEVICE_CLASS_CURRENT, + device_class=DEVICE_CLASS_POWER, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("power_returned_l2"): sensor.sensor_schema( unit_of_measurement=UNIT_KILOWATT, accuracy_decimals=3, - device_class=DEVICE_CLASS_CURRENT, + device_class=DEVICE_CLASS_POWER, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("power_returned_l3"): sensor.sensor_schema( unit_of_measurement=UNIT_KILOWATT, accuracy_decimals=3, - device_class=DEVICE_CLASS_CURRENT, + device_class=DEVICE_CLASS_POWER, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("reactive_power_delivered_l1"): sensor.sensor_schema( From 9283559c6b0649a4dc2f5b4795d9da6b49e3f771 Mon Sep 17 00:00:00 2001 From: rnauber <7414650+rnauber@users.noreply.github.com> Date: Mon, 18 Apr 2022 22:43:34 +0200 Subject: [PATCH 0236/3388] Shelly Dimmer: Delete obsolete LICENSE.txt (#3394) --- esphome/components/shelly_dimmer/LICENSE.txt | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 esphome/components/shelly_dimmer/LICENSE.txt diff --git a/esphome/components/shelly_dimmer/LICENSE.txt b/esphome/components/shelly_dimmer/LICENSE.txt deleted file mode 100644 index 524fe0d514..0000000000 --- a/esphome/components/shelly_dimmer/LICENSE.txt +++ /dev/null @@ -1,2 +0,0 @@ -The firmware files for the STM microcontroller (shelly-dimmer-stm32_*.bin) are taken from -https://github.com/jamesturton/shelly-dimmer-stm32 and GPLv3 licensed. From 712115b6ce682a03fce071f59a5d7e54b9c8905e Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 19 Apr 2022 12:33:38 +1200 Subject: [PATCH 0237/3388] Bump version to 2022.4.0b3 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 04fa5c2bf7..bca64e8175 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2022.4.0b2" +__version__ = "2022.4.0b3" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" From ad41c07a1f704d938cbc6a5ffb2542a5a438f76e Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 20 Apr 2022 06:56:09 +1200 Subject: [PATCH 0238/3388] Dont require {} for wifi ap with defaults (#3404) --- esphome/components/wifi/__init__.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/esphome/components/wifi/__init__.py b/esphome/components/wifi/__init__.py index 20f43cb450..b56902df2f 100644 --- a/esphome/components/wifi/__init__.py +++ b/esphome/components/wifi/__init__.py @@ -126,6 +126,13 @@ WIFI_NETWORK_AP = WIFI_NETWORK_BASE.extend( } ) + +def wifi_network_ap(value): + if value is None: + value = {} + return WIFI_NETWORK_AP(value) + + WIFI_NETWORK_STA = WIFI_NETWORK_BASE.extend( { cv.Optional(CONF_BSSID): cv.mac_address, @@ -252,7 +259,7 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_PASSWORD): validate_password, cv.Optional(CONF_MANUAL_IP): STA_MANUAL_IP_SCHEMA, cv.Optional(CONF_EAP): EAP_AUTH_SCHEMA, - cv.Optional(CONF_AP): WIFI_NETWORK_AP, + cv.Optional(CONF_AP): wifi_network_ap, cv.Optional(CONF_DOMAIN, default=".local"): cv.domain_name, cv.Optional( CONF_REBOOT_TIMEOUT, default="15min" From e26e0d7c01d7cfd0844866a2ec97273e34125955 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 20 Apr 2022 16:35:43 +1200 Subject: [PATCH 0239/3388] Bump version to 2022.4.0b4 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index bca64e8175..66f8b26d17 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2022.4.0b3" +__version__ = "2022.4.0b4" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" From a8c1b63edb20ab08895656d96f149ec4e1371c5d Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 20 Apr 2022 17:06:08 +1200 Subject: [PATCH 0240/3388] Bump version to 2022.4.0 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 66f8b26d17..161b60f7fa 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2022.4.0b4" +__version__ = "2022.4.0" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" From 3346bc8bba12add5050eb8b10078e7dc7872ed85 Mon Sep 17 00:00:00 2001 From: quentin9696 Date: Mon, 25 Apr 2022 18:09:49 -0400 Subject: [PATCH 0241/3388] feat: add openssh-client on docker image (#1681) (#3319) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- docker/Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/docker/Dockerfile b/docker/Dockerfile index 610b689298..dc8ba03f48 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -30,6 +30,7 @@ RUN \ iputils-ping=3:20210202-1 \ git=1:2.30.2-1 \ curl=7.74.0-1.3+deb11u1 \ + openssh-client=1:8.4p1-5 \ && rm -rf \ /tmp/* \ /var/{cache,log}/* \ From 256395c28d615197e129ebafccd4e530a32f8d5b Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 26 Apr 2022 21:02:08 +1200 Subject: [PATCH 0242/3388] Add duration device class for sensors (#3421) --- esphome/components/sensor/__init__.py | 2 ++ esphome/const.py | 1 + 2 files changed, 3 insertions(+) diff --git a/esphome/components/sensor/__init__.py b/esphome/components/sensor/__init__.py index 0c38ceeb37..d01a594889 100644 --- a/esphome/components/sensor/__init__.py +++ b/esphome/components/sensor/__init__.py @@ -29,6 +29,7 @@ from esphome.const import ( CONF_WINDOW_SIZE, CONF_MQTT_ID, CONF_FORCE_UPDATE, + DEVICE_CLASS_DURATION, DEVICE_CLASS_EMPTY, DEVICE_CLASS_AQI, DEVICE_CLASS_BATTERY, @@ -70,6 +71,7 @@ DEVICE_CLASSES = [ DEVICE_CLASS_CARBON_DIOXIDE, DEVICE_CLASS_CARBON_MONOXIDE, DEVICE_CLASS_CURRENT, + DEVICE_CLASS_DURATION, DEVICE_CLASS_ENERGY, DEVICE_CLASS_GAS, DEVICE_CLASS_HUMIDITY, diff --git a/esphome/const.py b/esphome/const.py index fa5baf4fe2..9f2bed28d1 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -905,6 +905,7 @@ DEVICE_CLASS_AQI = "aqi" DEVICE_CLASS_CARBON_DIOXIDE = "carbon_dioxide" DEVICE_CLASS_CARBON_MONOXIDE = "carbon_monoxide" DEVICE_CLASS_CURRENT = "current" +DEVICE_CLASS_DURATION = "duration" DEVICE_CLASS_ENERGY = "energy" DEVICE_CLASS_HUMIDITY = "humidity" DEVICE_CLASS_ILLUMINANCE = "illuminance" From 2bff9937b761f61821c804703b021f1c7c910fc4 Mon Sep 17 00:00:00 2001 From: code-review-doctor <72647856+code-review-doctor@users.noreply.github.com> Date: Tue, 26 Apr 2022 20:43:35 +0100 Subject: [PATCH 0243/3388] Fix issue probably-meant-fstring found at https://codereview.doctor (#3415) --- esphome/components/esp32/gpio.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/esp32/gpio.py b/esphome/components/esp32/gpio.py index 5819943f37..6c3fa92fcd 100644 --- a/esphome/components/esp32/gpio.py +++ b/esphome/components/esp32/gpio.py @@ -107,7 +107,7 @@ def validate_gpio_pin(value): value = _translate_pin(value) variant = CORE.data[KEY_ESP32][KEY_VARIANT] if variant not in _esp32_validations: - raise cv.Invalid("Unsupported ESP32 variant {variant}") + raise cv.Invalid(f"Unsupported ESP32 variant {variant}") return _esp32_validations[variant].pin_validation(value) @@ -121,7 +121,7 @@ def validate_supports(value): is_pulldown = mode[CONF_PULLDOWN] variant = CORE.data[KEY_ESP32][KEY_VARIANT] if variant not in _esp32_validations: - raise cv.Invalid("Unsupported ESP32 variant {variant}") + raise cv.Invalid(f"Unsupported ESP32 variant {variant}") if is_open_drain and not is_output: raise cv.Invalid( From ebf13a0ba0a48453c2cc80bfc942d6a588859233 Mon Sep 17 00:00:00 2001 From: Trevor North Date: Tue, 26 Apr 2022 20:51:22 +0100 Subject: [PATCH 0244/3388] Queue sensor publishes so we don't block for too long (#3422) --- .../components/bme680_bsec/bme680_bsec.cpp | 44 ++++++++++++------- esphome/components/bme680_bsec/bme680_bsec.h | 8 +++- 2 files changed, 33 insertions(+), 19 deletions(-) diff --git a/esphome/components/bme680_bsec/bme680_bsec.cpp b/esphome/components/bme680_bsec/bme680_bsec.cpp index 0a8ca7f3c3..b84ca3318b 100644 --- a/esphome/components/bme680_bsec/bme680_bsec.cpp +++ b/esphome/components/bme680_bsec/bme680_bsec.cpp @@ -169,6 +169,14 @@ void BME680BSECComponent::loop() { } else { this->status_clear_warning(); } + + // Process a single action from the queue. These are primarily sensor state publishes + // that in totality take too long to send in a single call. + if (this->queue_.size()) { + auto action = std::move(this->queue_.front()); + this->queue_.pop(); + action(); + } } void BME680BSECComponent::run_() { @@ -306,37 +314,39 @@ void BME680BSECComponent::read_(int64_t trigger_time_ns, bsec_bme_settings_t bme } void BME680BSECComponent::publish_(const bsec_output_t *outputs, uint8_t num_outputs) { - ESP_LOGV(TAG, "Publishing sensor states"); + ESP_LOGV(TAG, "Queuing sensor state publish actions"); for (uint8_t i = 0; i < num_outputs; i++) { + float signal = outputs[i].signal; switch (outputs[i].sensor_id) { case BSEC_OUTPUT_IAQ: - case BSEC_OUTPUT_STATIC_IAQ: - uint8_t accuracy; - accuracy = outputs[i].accuracy; - this->publish_sensor_state_(this->iaq_sensor_, outputs[i].signal); - this->publish_sensor_state_(this->iaq_accuracy_text_sensor_, IAQ_ACCURACY_STATES[accuracy]); - this->publish_sensor_state_(this->iaq_accuracy_sensor_, accuracy, true); + case BSEC_OUTPUT_STATIC_IAQ: { + uint8_t accuracy = outputs[i].accuracy; + this->queue_push_([this, signal]() { this->publish_sensor_(this->iaq_sensor_, signal); }); + this->queue_push_([this, accuracy]() { + this->publish_sensor_(this->iaq_accuracy_text_sensor_, IAQ_ACCURACY_STATES[accuracy]); + }); + this->queue_push_([this, accuracy]() { this->publish_sensor_(this->iaq_accuracy_sensor_, accuracy, true); }); // Queue up an opportunity to save state - this->defer("save_state", [this, accuracy]() { this->save_state_(accuracy); }); - break; + this->queue_push_([this, accuracy]() { this->save_state_(accuracy); }); + } break; case BSEC_OUTPUT_CO2_EQUIVALENT: - this->publish_sensor_state_(this->co2_equivalent_sensor_, outputs[i].signal); + this->queue_push_([this, signal]() { this->publish_sensor_(this->co2_equivalent_sensor_, signal); }); break; case BSEC_OUTPUT_BREATH_VOC_EQUIVALENT: - this->publish_sensor_state_(this->breath_voc_equivalent_sensor_, outputs[i].signal); + this->queue_push_([this, signal]() { this->publish_sensor_(this->breath_voc_equivalent_sensor_, signal); }); break; case BSEC_OUTPUT_RAW_PRESSURE: - this->publish_sensor_state_(this->pressure_sensor_, outputs[i].signal / 100.0f); + this->queue_push_([this, signal]() { this->publish_sensor_(this->pressure_sensor_, signal / 100.0f); }); break; case BSEC_OUTPUT_RAW_GAS: - this->publish_sensor_state_(this->gas_resistance_sensor_, outputs[i].signal); + this->queue_push_([this, signal]() { this->publish_sensor_(this->gas_resistance_sensor_, signal); }); break; case BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_TEMPERATURE: - this->publish_sensor_state_(this->temperature_sensor_, outputs[i].signal); + this->queue_push_([this, signal]() { this->publish_sensor_(this->temperature_sensor_, signal); }); break; case BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_HUMIDITY: - this->publish_sensor_state_(this->humidity_sensor_, outputs[i].signal); + this->queue_push_([this, signal]() { this->publish_sensor_(this->humidity_sensor_, signal); }); break; } } @@ -352,14 +362,14 @@ int64_t BME680BSECComponent::get_time_ns_() { return (time_ms + ((int64_t) this->millis_overflow_counter_ << 32)) * INT64_C(1000000); } -void BME680BSECComponent::publish_sensor_state_(sensor::Sensor *sensor, float value, bool change_only) { +void BME680BSECComponent::publish_sensor_(sensor::Sensor *sensor, float value, bool change_only) { if (!sensor || (change_only && sensor->has_state() && sensor->state == value)) { return; } sensor->publish_state(value); } -void BME680BSECComponent::publish_sensor_state_(text_sensor::TextSensor *sensor, const std::string &value) { +void BME680BSECComponent::publish_sensor_(text_sensor::TextSensor *sensor, const std::string &value) { if (!sensor || (sensor->has_state() && sensor->state == value)) { return; } diff --git a/esphome/components/bme680_bsec/bme680_bsec.h b/esphome/components/bme680_bsec/bme680_bsec.h index 53bc5c3280..650b4d2413 100644 --- a/esphome/components/bme680_bsec/bme680_bsec.h +++ b/esphome/components/bme680_bsec/bme680_bsec.h @@ -70,12 +70,14 @@ class BME680BSECComponent : public Component, public i2c::I2CDevice { void publish_(const bsec_output_t *outputs, uint8_t num_outputs); int64_t get_time_ns_(); - void publish_sensor_state_(sensor::Sensor *sensor, float value, bool change_only = false); - void publish_sensor_state_(text_sensor::TextSensor *sensor, const std::string &value); + void publish_sensor_(sensor::Sensor *sensor, float value, bool change_only = false); + void publish_sensor_(text_sensor::TextSensor *sensor, const std::string &value); void load_state_(); void save_state_(uint8_t accuracy); + void queue_push_(std::function &&f) { this->queue_.push(std::move(f)); } + struct bme680_dev bme680_; bsec_library_return_t bsec_status_{BSEC_OK}; int8_t bme680_status_{BME680_OK}; @@ -84,6 +86,8 @@ class BME680BSECComponent : public Component, public i2c::I2CDevice { uint32_t millis_overflow_counter_{0}; int64_t next_call_ns_{0}; + std::queue> queue_; + ESPPreferenceObject bsec_state_; uint32_t state_save_interval_ms_{21600000}; // 6 hours - 4 times a day uint32_t last_state_save_ms_ = 0; From 68dfaf238b8d16f052d24086464fc5c4a476745d Mon Sep 17 00:00:00 2001 From: LuBeDa Date: Tue, 26 Apr 2022 22:41:10 +0200 Subject: [PATCH 0245/3388] added RGB565 image type (#3229) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/animation/__init__.py | 23 +++++++++++++ esphome/components/display/display_buffer.cpp | 32 +++++++++++++++++++ esphome/components/display/display_buffer.h | 3 ++ esphome/components/image/__init__.py | 16 ++++++++++ 4 files changed, 74 insertions(+) diff --git a/esphome/components/animation/__init__.py b/esphome/components/animation/__init__.py index 7c9ff07f97..4cf0b0ed7d 100644 --- a/esphome/components/animation/__init__.py +++ b/esphome/components/animation/__init__.py @@ -92,6 +92,29 @@ async def to_code(config): data[pos] = pix[2] pos += 1 + elif config[CONF_TYPE] == "RGB565": + data = [0 for _ in range(height * width * 2 * frames)] + pos = 0 + for frameIndex in range(frames): + image.seek(frameIndex) + frame = image.convert("RGB") + if CONF_RESIZE in config: + frame = frame.resize([width, height]) + pixels = list(frame.getdata()) + if len(pixels) != height * width: + raise core.EsphomeError( + f"Unexpected number of pixels in {path} frame {frameIndex}: ({len(pixels)} != {height*width})" + ) + for pix in pixels: + R = pix[0] >> 3 + G = pix[1] >> 2 + B = pix[2] >> 3 + rgb = (R << 11) | (G << 5) | B + data[pos] = rgb >> 8 + pos += 1 + data[pos] = rgb & 255 + pos += 1 + elif config[CONF_TYPE] == "BINARY": width8 = ((width + 7) // 8) * 8 data = [0 for _ in range((height * width8 // 8) * frames)] diff --git a/esphome/components/display/display_buffer.cpp b/esphome/components/display/display_buffer.cpp index 4ad353a254..d00fdd5240 100644 --- a/esphome/components/display/display_buffer.cpp +++ b/esphome/components/display/display_buffer.cpp @@ -242,6 +242,13 @@ void DisplayBuffer::image(int x, int y, Image *image, Color color_on, Color colo } } break; + case IMAGE_TYPE_RGB565: + for (int img_x = 0; img_x < image->get_width(); img_x++) { + for (int img_y = 0; img_y < image->get_height(); img_y++) { + this->draw_pixel_at(x + img_x, y + img_y, image->get_rgb565_pixel(img_x, img_y)); + } + } + break; } } @@ -497,6 +504,17 @@ Color Image::get_color_pixel(int x, int y) const { (progmem_read_byte(this->data_start_ + pos + 0) << 16); return Color(color32); } +Color Image::get_rgb565_pixel(int x, int y) const { + if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_) + return Color::BLACK; + const uint32_t pos = (x + y * this->width_) * 2; + uint16_t rgb565 = + progmem_read_byte(this->data_start_ + pos + 0) << 8 | progmem_read_byte(this->data_start_ + pos + 1); + auto r = (rgb565 & 0xF800) >> 11; + auto g = (rgb565 & 0x07E0) >> 5; + auto b = rgb565 & 0x001F; + return Color((r << 3) | (r >> 2), (g << 2) | (g >> 4), (b << 3) | (b >> 2)); +} Color Image::get_grayscale_pixel(int x, int y) const { if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_) return Color::BLACK; @@ -532,6 +550,20 @@ Color Animation::get_color_pixel(int x, int y) const { (progmem_read_byte(this->data_start_ + pos + 0) << 16); return Color(color32); } +Color Animation::get_rgb565_pixel(int x, int y) const { + if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_) + return Color::BLACK; + const uint32_t frame_index = this->width_ * this->height_ * this->current_frame_; + if (frame_index >= (uint32_t)(this->width_ * this->height_ * this->animation_frame_count_)) + return Color::BLACK; + const uint32_t pos = (x + y * this->width_ + frame_index) * 2; + uint16_t rgb565 = + progmem_read_byte(this->data_start_ + pos + 0) << 8 | progmem_read_byte(this->data_start_ + pos + 1); + auto r = (rgb565 & 0xF800) >> 11; + auto g = (rgb565 & 0x07E0) >> 5; + auto b = rgb565 & 0x001F; + return Color((r << 3) | (r >> 2), (g << 2) | (g >> 4), (b << 3) | (b >> 2)); +} Color Animation::get_grayscale_pixel(int x, int y) const { if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_) return Color::BLACK; diff --git a/esphome/components/display/display_buffer.h b/esphome/components/display/display_buffer.h index 8ee1cd8779..86221c5f96 100644 --- a/esphome/components/display/display_buffer.h +++ b/esphome/components/display/display_buffer.h @@ -82,6 +82,7 @@ enum ImageType { IMAGE_TYPE_GRAYSCALE = 1, IMAGE_TYPE_RGB24 = 2, IMAGE_TYPE_TRANSPARENT_BINARY = 3, + IMAGE_TYPE_RGB565 = 4, }; enum DisplayRotation { @@ -453,6 +454,7 @@ class Image { Image(const uint8_t *data_start, int width, int height, ImageType type); virtual bool get_pixel(int x, int y) const; virtual Color get_color_pixel(int x, int y) const; + virtual Color get_rgb565_pixel(int x, int y) const; virtual Color get_grayscale_pixel(int x, int y) const; int get_width() const; int get_height() const; @@ -470,6 +472,7 @@ class Animation : public Image { Animation(const uint8_t *data_start, int width, int height, uint32_t animation_frame_count, ImageType type); bool get_pixel(int x, int y) const override; Color get_color_pixel(int x, int y) const override; + Color get_rgb565_pixel(int x, int y) const override; Color get_grayscale_pixel(int x, int y) const override; int get_animation_frame_count() const; diff --git a/esphome/components/image/__init__.py b/esphome/components/image/__init__.py index 70d77dfd14..0004391f20 100644 --- a/esphome/components/image/__init__.py +++ b/esphome/components/image/__init__.py @@ -25,6 +25,7 @@ IMAGE_TYPE = { "GRAYSCALE": ImageType.IMAGE_TYPE_GRAYSCALE, "RGB24": ImageType.IMAGE_TYPE_RGB24, "TRANSPARENT_BINARY": ImageType.IMAGE_TYPE_TRANSPARENT_BINARY, + "RGB565": ImageType.IMAGE_TYPE_RGB565, } Image_ = display.display_ns.class_("Image") @@ -89,6 +90,21 @@ async def to_code(config): data[pos] = pix[2] pos += 1 + elif config[CONF_TYPE] == "RGB565": + image = image.convert("RGB") + pixels = list(image.getdata()) + data = [0 for _ in range(height * width * 3)] + pos = 0 + for pix in pixels: + R = pix[0] >> 3 + G = pix[1] >> 2 + B = pix[2] >> 3 + rgb = (R << 11) | (G << 5) | B + data[pos] = rgb >> 8 + pos += 1 + data[pos] = rgb & 255 + pos += 1 + elif config[CONF_TYPE] == "BINARY": image = image.convert("1", dither=dither) width8 = ((width + 7) // 8) * 8 From 91895aa70c6f82866c182dee626b017ca2dd36a8 Mon Sep 17 00:00:00 2001 From: Dan Jackson Date: Tue, 3 May 2022 00:09:06 -0700 Subject: [PATCH 0246/3388] Allow wifi output_power down to 8.5dB (#3405) --- esphome/components/wifi/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/wifi/__init__.py b/esphome/components/wifi/__init__.py index b56902df2f..c3f70506e2 100644 --- a/esphome/components/wifi/__init__.py +++ b/esphome/components/wifi/__init__.py @@ -270,7 +270,7 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_FAST_CONNECT, default=False): cv.boolean, cv.Optional(CONF_USE_ADDRESS): cv.string_strict, cv.SplitDefault(CONF_OUTPUT_POWER, esp8266=20.0): cv.All( - cv.decibel, cv.float_range(min=10.0, max=20.5) + cv.decibel, cv.float_range(min=8.5, max=20.5) ), cv.Optional("enable_mdns"): cv.invalid( "This option has been removed. Please use the [disabled] option under the " From 64fb39a653be8633942cce7733f8379b6b0550ca Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 9 May 2022 10:18:24 +1200 Subject: [PATCH 0247/3388] Add help text to rename command (#3442) --- esphome/__main__.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/esphome/__main__.py b/esphome/__main__.py index 00770d6f05..80e8455465 100644 --- a/esphome/__main__.py +++ b/esphome/__main__.py @@ -776,7 +776,10 @@ def parse_args(argv): "configuration", help="Your YAML configuration file(s).", nargs=1 ) - parser_rename = subparsers.add_parser("rename") + parser_rename = subparsers.add_parser( + "rename", + help="Rename a device in YAML, compile the binary and upload it.", + ) parser_rename.add_argument( "configuration", help="Your YAML configuration file.", nargs=1 ) From 7c30d6254e0ee2d9656525cd2345411522972ec4 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sun, 8 May 2022 18:53:34 -0700 Subject: [PATCH 0248/3388] Add rename command handler (#3443) --- esphome/dashboard/dashboard.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/esphome/dashboard/dashboard.py b/esphome/dashboard/dashboard.py index af68f2ae08..b78d22cf7c 100644 --- a/esphome/dashboard/dashboard.py +++ b/esphome/dashboard/dashboard.py @@ -283,6 +283,18 @@ class EsphomeLogsHandler(EsphomeCommandWebSocket): ] +class EsphomeRenameHandler(EsphomeCommandWebSocket): + def build_command(self, json_message): + config_file = settings.rel_path(json_message["configuration"]) + return [ + "esphome", + "--dashboard", + "rename", + config_file, + json_message["newName"], + ] + + class EsphomeUploadHandler(EsphomeCommandWebSocket): def build_command(self, json_message): config_file = settings.rel_path(json_message["configuration"]) @@ -971,6 +983,7 @@ def make_app(debug=get_bool_env(ENV_DEV)): (f"{rel}devices", ListDevicesHandler), (f"{rel}import", ImportRequestHandler), (f"{rel}secret_keys", SecretKeysRequestHandler), + (f"{rel}rename", EsphomeRenameHandler), ], **app_settings, ) From d2f37cf3f9a87576f7a1b069cf5d9774e9caffaa Mon Sep 17 00:00:00 2001 From: Jens-Christian Skibakk Date: Mon, 9 May 2022 06:17:22 +0200 Subject: [PATCH 0249/3388] Support for Arduino 2 and serial port on ESP32-S2 and ESP32-C3 (#3436) --- .../improv_serial/improv_serial_component.h | 2 +- esphome/components/logger/logger.cpp | 22 ++++++++++--------- esphome/components/logger/logger.h | 4 ++-- 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/esphome/components/improv_serial/improv_serial_component.h b/esphome/components/improv_serial/improv_serial_component.h index c6b980ab99..6be5704b71 100644 --- a/esphome/components/improv_serial/improv_serial_component.h +++ b/esphome/components/improv_serial/improv_serial_component.h @@ -51,7 +51,7 @@ class ImprovSerialComponent : public Component { void write_data_(std::vector &data); #ifdef USE_ARDUINO - HardwareSerial *hw_serial_{nullptr}; + Stream *hw_serial_{nullptr}; #endif #ifdef USE_ESP_IDF uart_port_t uart_num_; diff --git a/esphome/components/logger/logger.cpp b/esphome/components/logger/logger.cpp index 11c0733701..3f4e4e7753 100644 --- a/esphome/components/logger/logger.cpp +++ b/esphome/components/logger/logger.cpp @@ -149,13 +149,25 @@ void Logger::pre_setup() { case UART_SELECTION_UART0_SWAP: #endif this->hw_serial_ = &Serial; + Serial.begin(this->baud_rate_); +#ifdef USE_ESP8266 + if (this->uart_ == UART_SELECTION_UART0_SWAP) { + Serial.swap(); + } + Serial.setDebugOutput(ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE); +#endif break; case UART_SELECTION_UART1: this->hw_serial_ = &Serial1; + Serial1.begin(this->baud_rate_); +#ifdef USE_ESP8266 + Serial1.setDebugOutput(ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE); +#endif break; #if defined(USE_ESP32) && !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32S2) case UART_SELECTION_UART2: this->hw_serial_ = &Serial2; + Serial2.begin(this->baud_rate_); break; #endif } @@ -186,16 +198,6 @@ void Logger::pre_setup() { // Install UART driver using an event queue here uart_driver_install(uart_num_, uart_buffer_size, uart_buffer_size, 10, nullptr, 0); #endif - -#ifdef USE_ARDUINO - this->hw_serial_->begin(this->baud_rate_); -#ifdef USE_ESP8266 - if (this->uart_ == UART_SELECTION_UART0_SWAP) { - this->hw_serial_->swap(); - } - this->hw_serial_->setDebugOutput(ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE); -#endif -#endif // USE_ARDUINO } #ifdef USE_ESP8266 else { diff --git a/esphome/components/logger/logger.h b/esphome/components/logger/logger.h index 8756bc2387..fa93972e19 100644 --- a/esphome/components/logger/logger.h +++ b/esphome/components/logger/logger.h @@ -40,7 +40,7 @@ class Logger : public Component { void set_baud_rate(uint32_t baud_rate); uint32_t get_baud_rate() const { return baud_rate_; } #ifdef USE_ARDUINO - HardwareSerial *get_hw_serial() const { return hw_serial_; } + Stream *get_hw_serial() const { return hw_serial_; } #endif #ifdef USE_ESP_IDF uart_port_t get_uart_num() const { return uart_num_; } @@ -119,7 +119,7 @@ class Logger : public Component { int tx_buffer_size_{0}; UARTSelection uart_{UART_SELECTION_UART0}; #ifdef USE_ARDUINO - HardwareSerial *hw_serial_{nullptr}; + Stream *hw_serial_{nullptr}; #endif #ifdef USE_ESP_IDF uart_port_t uart_num_; From 6f88f0ea3f229ef91652ba35d88d2d5116ca941c Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sun, 8 May 2022 22:17:21 -0700 Subject: [PATCH 0250/3388] Bump dashboard to 20220508.0 (#3448) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 465d961cb6..543999a9f3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,7 +9,7 @@ pyserial==3.5 platformio==5.2.5 # When updating platformio, also update Dockerfile esptool==3.3 click==8.1.2 -esphome-dashboard==20220309.0 +esphome-dashboard==20220508.0 aioesphomeapi==10.8.2 zeroconf==0.38.4 From 8e3af515c9941d4cd9535cb4388ce31c724fce18 Mon Sep 17 00:00:00 2001 From: Patrick van der Leer Date: Mon, 9 May 2022 07:17:36 +0200 Subject: [PATCH 0251/3388] Waveshare epaper 7in5 v2alt (#3276) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- .../components/waveshare_epaper/display.py | 4 + .../waveshare_epaper/waveshare_epaper.cpp | 137 ++++++++++++++++++ .../waveshare_epaper/waveshare_epaper.h | 19 +++ 3 files changed, 160 insertions(+) diff --git a/esphome/components/waveshare_epaper/display.py b/esphome/components/waveshare_epaper/display.py index fe5b51290e..a9d8350404 100644 --- a/esphome/components/waveshare_epaper/display.py +++ b/esphome/components/waveshare_epaper/display.py @@ -50,6 +50,9 @@ WaveshareEPaper7P5InBV2 = waveshare_epaper_ns.class_( WaveshareEPaper7P5InV2 = waveshare_epaper_ns.class_( "WaveshareEPaper7P5InV2", WaveshareEPaper ) +WaveshareEPaper7P5InV2alt = waveshare_epaper_ns.class_( + "WaveshareEPaper7P5InV2alt", WaveshareEPaper +) WaveshareEPaper7P5InHDB = waveshare_epaper_ns.class_( "WaveshareEPaper7P5InHDB", WaveshareEPaper ) @@ -79,6 +82,7 @@ MODELS = { "7.50in-bv2": ("b", WaveshareEPaper7P5InBV2), "7.50in-bc": ("b", WaveshareEPaper7P5InBC), "7.50inv2": ("b", WaveshareEPaper7P5InV2), + "7.50inv2alt": ("b", WaveshareEPaper7P5InV2alt), "7.50in-hd-b": ("b", WaveshareEPaper7P5InHDB), "2.13in-ttgo-dke": ("c", WaveshareEPaper2P13InDKE), } diff --git a/esphome/components/waveshare_epaper/waveshare_epaper.cpp b/esphome/components/waveshare_epaper/waveshare_epaper.cpp index 59b3e90b03..5580674c34 100644 --- a/esphome/components/waveshare_epaper/waveshare_epaper.cpp +++ b/esphome/components/waveshare_epaper/waveshare_epaper.cpp @@ -1137,6 +1137,143 @@ void HOT WaveshareEPaper7P5InV2::display() { int WaveshareEPaper7P5InV2::get_width_internal() { return 800; } int WaveshareEPaper7P5InV2::get_height_internal() { return 480; } void WaveshareEPaper7P5InV2::dump_config() { + LOG_DISPLAY("", "Waveshare E-Paper", this); + ESP_LOGCONFIG(TAG, " Model: 7.5inV2rev2"); + LOG_PIN(" Reset Pin: ", this->reset_pin_); + LOG_PIN(" DC Pin: ", this->dc_pin_); + LOG_PIN(" Busy Pin: ", this->busy_pin_); + LOG_UPDATE_INTERVAL(this); +} + +/* 7.50inV2alt */ +bool WaveshareEPaper7P5InV2alt::wait_until_idle_() { + if (this->busy_pin_ == nullptr) { + return true; + } + + const uint32_t start = millis(); + while (this->busy_pin_->digital_read()) { + this->command(0x71); + if (millis() - start > this->idle_timeout_()) { + ESP_LOGI(TAG, "Timeout while displaying image!"); + return false; + } + delay(10); + } + return true; +} + +void WaveshareEPaper7P5InV2alt::initialize() { + this->reset_(); + + // COMMAND POWER SETTING + this->command(0x01); + + // 1-0=11: internal power + this->data(0x17); + + this->data(0x17); // VGH&VGL + this->data(0x3F); // VSH + this->data(0x3F); // VSL + this->data(0x11); // VSHR + + // VCOM DC Setting + this->command(0x82); + this->data(0x24); // VCOM + + // Booster Setting + this->command(0x06); + this->data(0x27); + this->data(0x27); + this->data(0x2F); + this->data(0x17); + + // OSC Setting + this->command(0x30); + this->data(0x06); // 2-0=100: N=4 ; 5-3=111: M=7 ; 3C=50Hz 3A=100HZ + + // POWER ON + this->command(0x04); + + delay(100); // NOLINT + this->wait_until_idle_(); + // COMMAND PANEL SETTING + this->command(0x00); + this->data(0x3F); // KW-3f KWR-2F BWROTP 0f BWOTP 1f + + // COMMAND RESOLUTION SETTING + this->command(0x61); + this->data(0x03); // source 800 + this->data(0x20); + this->data(0x01); // gate 480 + this->data(0xE0); + // COMMAND ...? + this->command(0x15); + this->data(0x00); + // COMMAND VCOM AND DATA INTERVAL SETTING + this->command(0x50); + this->data(0x10); + this->data(0x07); + // COMMAND TCON SETTING + this->command(0x60); + this->data(0x22); + // Resolution setting + this->command(0x65); + this->data(0x00); + this->data(0x00); // 800*480 + this->data(0x00); + this->data(0x00); + + this->wait_until_idle_(); + + uint8_t lut_vcom_7_i_n5_v2[] = { + 0x0, 0xF, 0xF, 0x0, 0x0, 0x1, 0x0, 0xF, 0x1, 0xF, 0x1, 0x2, 0x0, 0xF, 0xF, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + }; + + uint8_t lut_ww_7_i_n5_v2[] = { + 0x10, 0xF, 0xF, 0x0, 0x0, 0x1, 0x84, 0xF, 0x1, 0xF, 0x1, 0x2, 0x20, 0xF, 0xF, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + }; + + uint8_t lut_bw_7_i_n5_v2[] = { + 0x10, 0xF, 0xF, 0x0, 0x0, 0x1, 0x84, 0xF, 0x1, 0xF, 0x1, 0x2, 0x20, 0xF, 0xF, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + }; + + uint8_t lut_wb_7_i_n5_v2[] = { + 0x80, 0xF, 0xF, 0x0, 0x0, 0x3, 0x84, 0xF, 0x1, 0xF, 0x1, 0x4, 0x40, 0xF, 0xF, 0x0, 0x0, 0x3, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + }; + + uint8_t lut_bb_7_i_n5_v2[] = { + 0x80, 0xF, 0xF, 0x0, 0x0, 0x1, 0x84, 0xF, 0x1, 0xF, 0x1, 0x2, 0x40, 0xF, 0xF, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + }; + + uint8_t count; + this->command(0x20); // VCOM + for (count = 0; count < 42; count++) + this->data(lut_vcom_7_i_n5_v2[count]); + + this->command(0x21); // LUTBW + for (count = 0; count < 42; count++) + this->data(lut_ww_7_i_n5_v2[count]); + + this->command(0x22); // LUTBW + for (count = 0; count < 42; count++) + this->data(lut_bw_7_i_n5_v2[count]); + + this->command(0x23); // LUTWB + for (count = 0; count < 42; count++) + this->data(lut_wb_7_i_n5_v2[count]); + + this->command(0x24); // LUTBB + for (count = 0; count < 42; count++) + this->data(lut_bb_7_i_n5_v2[count]); +} + +void WaveshareEPaper7P5InV2alt::dump_config() { LOG_DISPLAY("", "Waveshare E-Paper", this); ESP_LOGCONFIG(TAG, " Model: 7.5inV2"); LOG_PIN(" Reset Pin: ", this->reset_pin_); diff --git a/esphome/components/waveshare_epaper/waveshare_epaper.h b/esphome/components/waveshare_epaper/waveshare_epaper.h index 41b93978ab..7a88fecbdb 100644 --- a/esphome/components/waveshare_epaper/waveshare_epaper.h +++ b/esphome/components/waveshare_epaper/waveshare_epaper.h @@ -350,6 +350,25 @@ class WaveshareEPaper7P5InV2 : public WaveshareEPaper { int get_height_internal() override; }; +class WaveshareEPaper7P5InV2alt : public WaveshareEPaper7P5InV2 { + public: + bool wait_until_idle_(); + void initialize() override; + void dump_config() override; + + protected: + void reset_() { + if (this->reset_pin_ != nullptr) { + this->reset_pin_->digital_write(true); + delay(200); // NOLINT + this->reset_pin_->digital_write(false); + delay(2); + this->reset_pin_->digital_write(true); + delay(20); + } + }; +}; + class WaveshareEPaper7P5InHDB : public WaveshareEPaper { public: void initialize() override; From 2059283707fb0145dcf920d76e90afb6d80a20fb Mon Sep 17 00:00:00 2001 From: rainero84 Date: Mon, 9 May 2022 07:21:43 +0200 Subject: [PATCH 0252/3388] Early pin init (#3439) * Added early_pin_init configuration parameter for ESP8266 platform * Added #include to core * Updated test3.yaml to include early_pin_init parameter Co-authored-by: Rainer Oellermann --- esphome/components/esp8266/__init__.py | 5 +++++ esphome/components/esp8266/const.py | 1 + esphome/components/esp8266/core.cpp | 3 +++ tests/test3.yaml | 6 ++++-- 4 files changed, 13 insertions(+), 2 deletions(-) diff --git a/esphome/components/esp8266/__init__.py b/esphome/components/esp8266/__init__.py index 7b1be32e38..41d7688d44 100644 --- a/esphome/components/esp8266/__init__.py +++ b/esphome/components/esp8266/__init__.py @@ -19,6 +19,7 @@ from esphome.helpers import copy_file_if_changed from .const import ( CONF_RESTORE_FROM_FLASH, + CONF_EARLY_PIN_INIT, KEY_BOARD, KEY_ESP8266, KEY_PIN_INITIAL_STATES, @@ -148,6 +149,7 @@ CONFIG_SCHEMA = cv.All( cv.Required(CONF_BOARD): cv.string_strict, cv.Optional(CONF_FRAMEWORK, default={}): ARDUINO_FRAMEWORK_SCHEMA, cv.Optional(CONF_RESTORE_FROM_FLASH, default=False): cv.boolean, + cv.Optional(CONF_EARLY_PIN_INIT, default=True): cv.boolean, cv.Optional(CONF_BOARD_FLASH_MODE, default="dout"): cv.one_of( *BUILD_FLASH_MODES, lower=True ), @@ -197,6 +199,9 @@ async def to_code(config): if config[CONF_RESTORE_FROM_FLASH]: cg.add_define("USE_ESP8266_PREFERENCES_FLASH") + if config[CONF_EARLY_PIN_INIT]: + cg.add_define("USE_ESP8266_EARLY_PIN_INIT") + # Arduino 2 has a non-standards conformant new that returns a nullptr instead of failing when # out of memory and exceptions are disabled. Since Arduino 2.6.0, this flag can be used to make # new abort instead. Use it so that OOM fails early (on allocation) instead of on dereference of diff --git a/esphome/components/esp8266/const.py b/esphome/components/esp8266/const.py index 70429297e0..7740a97ff4 100644 --- a/esphome/components/esp8266/const.py +++ b/esphome/components/esp8266/const.py @@ -4,6 +4,7 @@ KEY_ESP8266 = "esp8266" KEY_BOARD = "board" KEY_PIN_INITIAL_STATES = "pin_initial_states" CONF_RESTORE_FROM_FLASH = "restore_from_flash" +CONF_EARLY_PIN_INIT = "early_pin_init" # esp8266 namespace is already defined by arduino, manually prefix esphome esp8266_ns = cg.global_ns.namespace("esphome").namespace("esp8266") diff --git a/esphome/components/esp8266/core.cpp b/esphome/components/esp8266/core.cpp index a9460f51f2..2d3959b031 100644 --- a/esphome/components/esp8266/core.cpp +++ b/esphome/components/esp8266/core.cpp @@ -1,6 +1,7 @@ #ifdef USE_ESP8266 #include "core.h" +#include "esphome/core/defines.h" #include "esphome/core/hal.h" #include "esphome/core/helpers.h" #include "preferences.h" @@ -55,6 +56,7 @@ extern "C" void resetPins() { // NOLINT // ourselves and this causes pins to toggle during reboot. force_link_symbols(); +#ifdef USE_ESP8266_EARLY_PIN_INIT for (int i = 0; i < 16; i++) { uint8_t mode = ESPHOME_ESP8266_GPIO_INITIAL_MODE[i]; uint8_t level = ESPHOME_ESP8266_GPIO_INITIAL_LEVEL[i]; @@ -63,6 +65,7 @@ extern "C" void resetPins() { // NOLINT if (level != 255) digitalWrite(i, level); // NOLINT } +#endif } } // namespace esphome diff --git a/tests/test3.yaml b/tests/test3.yaml index 29a70d3cc3..e3818d87ec 100644 --- a/tests/test3.yaml +++ b/tests/test3.yaml @@ -1,8 +1,6 @@ esphome: name: $device_name comment: $device_comment - platform: ESP8266 - board: d1_mini build_path: build/test3 on_boot: - if: @@ -15,6 +13,10 @@ esphome: includes: - custom.h +esp8266: + board: d1_mini + early_pin_init: True + substitutions: device_name: test3 device_comment: test3 device From 50a32b387e85f3f305b23a4d0c4d7e4fc1523045 Mon Sep 17 00:00:00 2001 From: Ingo Theiss Date: Mon, 9 May 2022 07:23:38 +0200 Subject: [PATCH 0253/3388] Add ENS210 Humidity & Temperature sensor component (#2942) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- CODEOWNERS | 1 + esphome/components/ens210/__init__.py | 0 esphome/components/ens210/ens210.cpp | 230 ++++++++++++++++++++++++++ esphome/components/ens210/ens210.h | 39 +++++ esphome/components/ens210/sensor.py | 58 +++++++ tests/test1.yaml | 11 +- 6 files changed, 337 insertions(+), 2 deletions(-) create mode 100644 esphome/components/ens210/__init__.py create mode 100644 esphome/components/ens210/ens210.cpp create mode 100644 esphome/components/ens210/ens210.h create mode 100644 esphome/components/ens210/sensor.py diff --git a/CODEOWNERS b/CODEOWNERS index e2a356360a..51719ef1aa 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -60,6 +60,7 @@ esphome/components/dht/* @OttoWinter esphome/components/ds1307/* @badbadc0ffee esphome/components/dsmr/* @glmnet @zuidwijk esphome/components/ektf2232/* @jesserockz +esphome/components/ens210/* @itn3rd77 esphome/components/esp32/* @esphome/core esphome/components/esp32_ble/* @jesserockz esphome/components/esp32_ble_server/* @jesserockz diff --git a/esphome/components/ens210/__init__.py b/esphome/components/ens210/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esphome/components/ens210/ens210.cpp b/esphome/components/ens210/ens210.cpp new file mode 100644 index 0000000000..9a89e85da2 --- /dev/null +++ b/esphome/components/ens210/ens210.cpp @@ -0,0 +1,230 @@ +// ENS210 relative humidity and temperature sensor with I2C interface from ScioSense +// +// Datasheet: https://www.sciosense.com/wp-content/uploads/2021/01/ENS210.pdf +// +// Implementation based on: +// https://github.com/maarten-pennings/ENS210 +// https://github.com/sciosense/ENS210_driver + +#include "ens210.h" +#include "esphome/core/log.h" +#include "esphome/core/hal.h" + +namespace esphome { +namespace ens210 { + +static const char *const TAG = "ens210"; + +// ENS210 chip constants +static const uint8_t ENS210_BOOTING_MS = 2; // Booting time in ms (also after reset, or going to high power) +static const uint8_t ENS210_SINGLE_MEASURMENT_CONVERSION_TIME_MS = + 130; // Conversion time in ms for single shot T/H measurement +static const uint16_t ENS210_PART_ID = 0x0210; // The expected part id of the ENS210 + +// Addresses of the ENS210 registers +static const uint8_t ENS210_REGISTER_PART_ID = 0x00; +static const uint8_t ENS210_REGISTER_UID = 0x04; +static const uint8_t ENS210_REGISTER_SYS_CTRL = 0x10; +static const uint8_t ENS210_REGISTER_SYS_STAT = 0x11; +static const uint8_t ENS210_REGISTER_SENS_RUN = 0x21; +static const uint8_t ENS210_REGISTER_SENS_START = 0x22; +static const uint8_t ENS210_REGISTER_SENS_STOP = 0x23; +static const uint8_t ENS210_REGISTER_SENS_STAT = 0x24; +static const uint8_t ENS210_REGISTER_T_VAL = 0x30; +static const uint8_t ENS210_REGISTER_H_VAL = 0x33; + +// CRC-7 constants +static const uint8_t CRC7_WIDTH = 7; // A 7 bits CRC has polynomial of 7th order, which has 8 terms +static const uint8_t CRC7_POLY = 0x89; // The 8 coefficients of the polynomial +static const uint8_t CRC7_IVEC = 0x7F; // Initial vector has all 7 bits high + +// Payload data constants +static const uint8_t DATA7_WIDTH = 17; +static const uint32_t DATA7_MASK = ((1UL << DATA7_WIDTH) - 1); // 0b 0 1111 1111 1111 1111 +static const uint32_t DATA7_MSB = (1UL << (DATA7_WIDTH - 1)); // 0b 1 0000 0000 0000 0000 + +// Converts a status to a human readable string +static const LogString *ens210_status_to_human(int status) { + switch (status) { + case ENS210Component::ENS210_STATUS_I2C_ERROR: + return LOG_STR("I2C error - communication with ENS210 failed!"); + case ENS210Component::ENS210_STATUS_CRC_ERROR: + return LOG_STR("CRC error"); + case ENS210Component::ENS210_STATUS_INVALID: + return LOG_STR("Invalid data"); + case ENS210Component::ENS210_STATUS_OK: + return LOG_STR("Status OK"); + case ENS210Component::ENS210_WRONG_CHIP_ID: + return LOG_STR("ENS210 has wrong chip ID! Is it a ENS210?"); + default: + return LOG_STR("Unknown"); + } +} + +// Compute the CRC-7 of 'value' (should only have 17 bits) +// https://en.wikipedia.org/wiki/Cyclic_redundancy_check#Computation +static uint32_t crc7(uint32_t value) { + // Setup polynomial + uint32_t polynomial = CRC7_POLY; + // Align polynomial with data + polynomial = polynomial << (DATA7_WIDTH - CRC7_WIDTH - 1); + // Loop variable (indicates which bit to test, start with highest) + uint32_t bit = DATA7_MSB; + // Make room for CRC value + value = value << CRC7_WIDTH; + bit = bit << CRC7_WIDTH; + polynomial = polynomial << CRC7_WIDTH; + // Insert initial vector + value |= CRC7_IVEC; + // Apply division until all bits done + while (bit & (DATA7_MASK << CRC7_WIDTH)) { + if (bit & value) + value ^= polynomial; + bit >>= 1; + polynomial >>= 1; + } + return value; +} + +void ENS210Component::setup() { + ESP_LOGCONFIG(TAG, "Setting up ENS210..."); + uint8_t data[2]; + uint16_t part_id = 0; + // Reset + if (!this->write_byte(ENS210_REGISTER_SYS_CTRL, 0x80)) { + this->write_byte(ENS210_REGISTER_SYS_CTRL, 0x80); + this->error_code_ = ENS210_STATUS_I2C_ERROR; + this->mark_failed(); + return; + } + // Wait to boot after reset + delay(ENS210_BOOTING_MS); + // Must disable low power to read PART_ID + if (!set_low_power_(false)) { + // Try to go back to default mode (low power enabled) + set_low_power_(true); + this->error_code_ = ENS210_STATUS_I2C_ERROR; + this->mark_failed(); + return; + } + // Read the PART_ID + if (!this->read_bytes(ENS210_REGISTER_PART_ID, data, 2)) { + // Try to go back to default mode (low power enabled) + set_low_power_(true); + this->error_code_ = ENS210_STATUS_I2C_ERROR; + this->mark_failed(); + return; + } + // Pack bytes into partid + part_id = data[1] * 256U + data[0] * 1U; + // Check expected part id of the ENS210 + if (part_id != ENS210_PART_ID) { + this->error_code_ = ENS210_WRONG_CHIP_ID; + this->mark_failed(); + } + // Set default power mode (low power enabled) + set_low_power_(true); +} + +void ENS210Component::dump_config() { + ESP_LOGCONFIG(TAG, "ENS210:"); + LOG_I2C_DEVICE(this); + if (this->is_failed()) { + ESP_LOGE(TAG, "%s", LOG_STR_ARG(ens210_status_to_human(this->error_code_))); + } + LOG_UPDATE_INTERVAL(this); + LOG_SENSOR(" ", "Temperature", this->temperature_sensor_); + LOG_SENSOR(" ", "Humidity", this->humidity_sensor_); +} + +float ENS210Component::get_setup_priority() const { return setup_priority::DATA; } + +void ENS210Component::update() { + // Execute a single measurement + if (!this->write_byte(ENS210_REGISTER_SENS_RUN, 0x00)) { + ESP_LOGE(TAG, "Starting single measurement failed!"); + this->status_set_warning(); + return; + } + // Trigger measurement + if (!this->write_byte(ENS210_REGISTER_SENS_START, 0x03)) { + ESP_LOGE(TAG, "Trigger of measurement failed!"); + this->status_set_warning(); + return; + } + // Wait for measurement to complete + this->set_timeout("data", uint32_t(ENS210_SINGLE_MEASURMENT_CONVERSION_TIME_MS), [this]() { + int temperature_data, temperature_status, humidity_data, humidity_status; + uint8_t data[6]; + uint32_t h_val_data, t_val_data; + // Set default status for early bail out + temperature_status = ENS210_STATUS_I2C_ERROR; + humidity_status = ENS210_STATUS_I2C_ERROR; + + // Read T_VAL and H_VAL + if (!this->read_bytes(ENS210_REGISTER_T_VAL, data, 6)) { + ESP_LOGE(TAG, "Communication with ENS210 failed!"); + this->status_set_warning(); + return; + } + // Pack bytes for humidity + h_val_data = (uint32_t)((uint32_t) data[5] << 16 | (uint32_t) data[4] << 8 | (uint32_t) data[3]); + // Extract humidity data and update the status + extract_measurement_(h_val_data, &humidity_data, &humidity_status); + + if (humidity_status == ENS210_STATUS_OK) { + if (this->humidity_sensor_ != nullptr) { + float humidity = (humidity_data & 0xFFFF) / 512.0; + this->humidity_sensor_->publish_state(humidity); + } + } else { + ESP_LOGW(TAG, "Humidity status failure: %s", LOG_STR_ARG(ens210_status_to_human(humidity_status))); + this->status_set_warning(); + return; + } + // Pack bytes for temperature + t_val_data = (uint32_t)((uint32_t) data[2] << 16 | (uint32_t) data[1] << 8 | (uint32_t) data[0]); + // Extract temperature data and update the status + extract_measurement_(t_val_data, &temperature_data, &temperature_status); + + if (temperature_status == ENS210_STATUS_OK) { + if (this->temperature_sensor_ != nullptr) { + // Temperature in Celsius + float temperature = (temperature_data & 0xFFFF) / 64.0 - 27315L / 100.0; + this->temperature_sensor_->publish_state(temperature); + } + } else { + ESP_LOGW(TAG, "Temperature status failure: %s", LOG_STR_ARG(ens210_status_to_human(temperature_status))); + } + }); +} + +// Extracts measurement 'data' and 'status' from a 'val' obtained from measurment. +void ENS210Component::extract_measurement_(uint32_t val, int *data, int *status) { + *data = (val >> 0) & 0xffff; + int valid = (val >> 16) & 0x1; + uint32_t crc = (val >> 17) & 0x7f; + uint32_t payload = (val >> 0) & 0x1ffff; + // Check CRC + uint8_t crc_ok = crc7(payload) == crc; + + if (!crc_ok) { + *status = ENS210_STATUS_CRC_ERROR; + } else if (!valid) { + *status = ENS210_STATUS_INVALID; + } else { + *status = ENS210_STATUS_OK; + } +} + +// Sets ENS210 to low (true) or high (false) power. Returns false on I2C problems. +bool ENS210Component::set_low_power_(bool enable) { + uint8_t low_power_cmd = enable ? 0x01 : 0x00; + ESP_LOGD(TAG, "Enable low power: %s", enable ? "true" : "false"); + bool result = this->write_byte(ENS210_REGISTER_SYS_CTRL, low_power_cmd); + delay(ENS210_BOOTING_MS); + return result; +} + +} // namespace ens210 +} // namespace esphome diff --git a/esphome/components/ens210/ens210.h b/esphome/components/ens210/ens210.h new file mode 100644 index 0000000000..342be04799 --- /dev/null +++ b/esphome/components/ens210/ens210.h @@ -0,0 +1,39 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/i2c/i2c.h" + +namespace esphome { +namespace ens210 { + +/// This class implements support for the ENS210 relative humidity and temperature i2c sensor. +class ENS210Component : public PollingComponent, public i2c::I2CDevice { + public: + float get_setup_priority() const override; + void dump_config() override; + void setup() override; + void update() override; + + void set_humidity_sensor(sensor::Sensor *humidity_sensor) { humidity_sensor_ = humidity_sensor; } + void set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_sensor; } + + enum ErrorCode { + ENS210_STATUS_OK = 0, // The value was read, the CRC matches, and data is valid + ENS210_STATUS_INVALID, // The value was read, the CRC matches, but the data is invalid (e.g. the measurement was + // not yet finished) + ENS210_STATUS_CRC_ERROR, // The value was read, but the CRC over the payload (valid and data) does not match + ENS210_STATUS_I2C_ERROR, // There was an I2C communication error + ENS210_WRONG_CHIP_ID // The read PART_ID is not the expected part id of the ENS210 + } error_code_{ENS210_STATUS_OK}; + + protected: + bool set_low_power_(bool enable); + void extract_measurement_(uint32_t val, int *data, int *status); + + sensor::Sensor *temperature_sensor_; + sensor::Sensor *humidity_sensor_; +}; + +} // namespace ens210 +} // namespace esphome diff --git a/esphome/components/ens210/sensor.py b/esphome/components/ens210/sensor.py new file mode 100644 index 0000000000..3037156e01 --- /dev/null +++ b/esphome/components/ens210/sensor.py @@ -0,0 +1,58 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import i2c, sensor +from esphome.const import ( + CONF_HUMIDITY, + CONF_ID, + CONF_TEMPERATURE, + DEVICE_CLASS_HUMIDITY, + DEVICE_CLASS_TEMPERATURE, + STATE_CLASS_MEASUREMENT, + UNIT_CELSIUS, + UNIT_PERCENT, +) + +CODEOWNERS = ["@itn3rd77"] +DEPENDENCIES = ["i2c"] + +ens210_ns = cg.esphome_ns.namespace("ens210") + +ENS210Component = ens210_ns.class_( + "ENS210Component", cg.PollingComponent, i2c.I2CDevice +) + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(ENS210Component), + cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=1, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_HUMIDITY): sensor.sensor_schema( + unit_of_measurement=UNIT_PERCENT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_HUMIDITY, + state_class=STATE_CLASS_MEASUREMENT, + ), + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x43)) +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await i2c.register_i2c_device(var, config) + + if CONF_TEMPERATURE in config: + sens = await sensor.new_sensor(config[CONF_TEMPERATURE]) + cg.add(var.set_temperature_sensor(sens)) + + if CONF_HUMIDITY in config: + sens = await sensor.new_sensor(config[CONF_HUMIDITY]) + cg.add(var.set_humidity_sensor(sens)) diff --git a/tests/test1.yaml b/tests/test1.yaml index 375499942b..aba37976aa 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -547,11 +547,18 @@ sensor: - platform: esp32_hall name: "ESP32 Hall Sensor" update_interval: 15s - - platform: hdc1080 + - platform: ens210 temperature: name: "Living Room Temperature 5" humidity: - name: "Living Room Pressure 5" + name: 'Living Room Humidity 5' + update_interval: 15s + i2c_id: i2c_bus + - platform: hdc1080 + temperature: + name: 'Living Room Temperature 6' + humidity: + name: 'Living Room Humidity 5' update_interval: 15s i2c_id: i2c_bus - platform: hlw8012 From 2e4645310b2b8a893d75e508b82e3f0ba176754f Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 9 May 2022 19:16:46 +1200 Subject: [PATCH 0254/3388] Also rename yaml filename with rename command (#3447) --- esphome/__main__.py | 138 ++++++++++++++++++++++---------------------- 1 file changed, 70 insertions(+), 68 deletions(-) diff --git a/esphome/__main__.py b/esphome/__main__.py index 80e8455465..c336336f18 100644 --- a/esphome/__main__.py +++ b/esphome/__main__.py @@ -496,83 +496,85 @@ def command_rename(args, config): ) ) return 1 + # Load existing yaml file with open(CORE.config_path, mode="r+", encoding="utf-8") as raw_file: raw_contents = raw_file.read() - yaml = yaml_util.load_yaml(CORE.config_path) - if CONF_ESPHOME not in yaml or CONF_NAME not in yaml[CONF_ESPHOME]: - print( - color( - Fore.BOLD_RED, "Complex YAML files cannot be automatically renamed." + + yaml = yaml_util.load_yaml(CORE.config_path) + if CONF_ESPHOME not in yaml or CONF_NAME not in yaml[CONF_ESPHOME]: + print( + color(Fore.BOLD_RED, "Complex YAML files cannot be automatically renamed.") + ) + return 1 + old_name = yaml[CONF_ESPHOME][CONF_NAME] + match = re.match(r"^\$\{?([a-zA-Z0-9_]+)\}?$", old_name) + if match is None: + new_raw = re.sub( + rf"name:\s+[\"']?{old_name}[\"']?", + f'name: "{args.name}"', + raw_contents, + ) + else: + old_name = yaml[CONF_SUBSTITUTIONS][match.group(1)] + if ( + len( + re.findall( + rf"^\s+{match.group(1)}:\s+[\"']?{old_name}[\"']?", + raw_contents, + flags=re.MULTILINE, ) ) - return 1 - old_name = yaml[CONF_ESPHOME][CONF_NAME] - match = re.match(r"^\$\{?([a-zA-Z0-9_]+)\}?$", old_name) - if match is None: - new_raw = re.sub( - rf"name:\s+[\"']?{old_name}[\"']?", - f'name: "{args.name}"', - raw_contents, - ) - else: - old_name = yaml[CONF_SUBSTITUTIONS][match.group(1)] - if ( - len( - re.findall( - rf"^\s+{match.group(1)}:\s+[\"']?{old_name}[\"']?", - raw_contents, - flags=re.MULTILINE, - ) - ) - > 1 - ): - print(color(Fore.BOLD_RED, "Too many matches in YAML to safely rename")) - return 1 - - new_raw = re.sub( - rf"^(\s+{match.group(1)}):\s+[\"']?{old_name}[\"']?", - f'\\1: "{args.name}"', - raw_contents, - flags=re.MULTILINE, - ) - - raw_file.seek(0) - raw_file.write(new_raw) - raw_file.flush() - - print(f"Updating {color(Fore.CYAN, CORE.config_path)}") - print() - - rc = run_external_process("esphome", "config", CORE.config_path) - if rc != 0: - raw_file.seek(0) - raw_file.write(raw_contents) - print(color(Fore.BOLD_RED, "Rename failed. Reverting changes.")) + > 1 + ): + print(color(Fore.BOLD_RED, "Too many matches in YAML to safely rename")) return 1 - cli_args = [ - "run", - CORE.config_path, - "--no-logs", - "--device", - CORE.address, - ] + new_raw = re.sub( + rf"^(\s+{match.group(1)}):\s+[\"']?{old_name}[\"']?", + f'\\1: "{args.name}"', + raw_contents, + flags=re.MULTILINE, + ) - if args.dashboard: - cli_args.insert(0, "--dashboard") + new_path = os.path.join(CORE.config_dir, args.name + ".yaml") + print( + f"Updating {color(Fore.CYAN, CORE.config_path)} to {color(Fore.CYAN, new_path)}" + ) + print() - try: - rc = run_external_process("esphome", *cli_args) - except KeyboardInterrupt: - rc = 1 - if rc != 0: - raw_file.seek(0) - raw_file.write(raw_contents) - return 1 + with open(new_path, mode="w", encoding="utf-8") as new_file: + new_file.write(new_raw) - print(color(Fore.BOLD_GREEN, "SUCCESS")) - print() - return 0 + rc = run_external_process("esphome", "config", new_path) + if rc != 0: + print(color(Fore.BOLD_RED, "Rename failed. Reverting changes.")) + os.remove(new_path) + return 1 + + cli_args = [ + "run", + new_path, + "--no-logs", + "--device", + CORE.address, + ] + + if args.dashboard: + cli_args.insert(0, "--dashboard") + + try: + rc = run_external_process("esphome", *cli_args) + except KeyboardInterrupt: + rc = 1 + if rc != 0: + os.remove(new_path) + return 1 + + os.remove(CORE.config_path) + + print(color(Fore.BOLD_GREEN, "SUCCESS")) + print() + return 0 PRE_CONFIG_ACTIONS = { From e5b3625f73e7be85342071897f6d52463de3e010 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 May 2022 19:22:47 +1200 Subject: [PATCH 0255/3388] Bump click from 8.1.2 to 8.1.3 (#3426) Bumps [click](https://github.com/pallets/click) from 8.1.2 to 8.1.3. - [Release notes](https://github.com/pallets/click/releases) - [Changelog](https://github.com/pallets/click/blob/main/CHANGES.rst) - [Commits](https://github.com/pallets/click/compare/8.1.2...8.1.3) --- updated-dependencies: - dependency-name: click dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 543999a9f3..e62ef86765 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,7 +8,7 @@ tzdata>=2021.1 # from time pyserial==3.5 platformio==5.2.5 # When updating platformio, also update Dockerfile esptool==3.3 -click==8.1.2 +click==8.1.3 esphome-dashboard==20220508.0 aioesphomeapi==10.8.2 zeroconf==0.38.4 From 8236e840a76fc94ac6ae3f023f8d26f619567033 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 9 May 2022 19:24:27 +1200 Subject: [PATCH 0256/3388] Fix spi transfer with miso pin defined on espidf (#3450) --- esphome/components/spi/spi.h | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/esphome/components/spi/spi.h b/esphome/components/spi/spi.h index 6c3fd17e56..7f0b0f481a 100644 --- a/esphome/components/spi/spi.h +++ b/esphome/components/spi/spi.h @@ -156,15 +156,17 @@ class SPIComponent : public Component { template uint8_t transfer_byte(uint8_t data) { -#ifdef USE_SPI_ARDUINO_BACKEND if (this->miso_ != nullptr) { +#ifdef USE_SPI_ARDUINO_BACKEND if (this->hw_spi_ != nullptr) { return this->hw_spi_->transfer(data); } else { - return this->transfer_(data); - } - } #endif // USE_SPI_ARDUINO_BACKEND + return this->transfer_(data); +#ifdef USE_SPI_ARDUINO_BACKEND + } +#endif // USE_SPI_ARDUINO_BACKEND + } this->write_byte(data); return 0; } From df999723f86b42697b8018fa860289134f048afd Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 9 May 2022 19:43:09 +1200 Subject: [PATCH 0257/3388] Force using name substitution when adopting a device (#3451) --- esphome/components/dashboard_import/__init__.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/esphome/components/dashboard_import/__init__.py b/esphome/components/dashboard_import/__init__.py index 6194a55205..41b4a8bed1 100644 --- a/esphome/components/dashboard_import/__init__.py +++ b/esphome/components/dashboard_import/__init__.py @@ -64,7 +64,10 @@ def import_config(path: str, name: str, project_name: str, import_url: str) -> N config = { "substitutions": {"name": name}, "packages": {project_name: import_url}, - "esphome": {"name_add_mac_suffix": False}, + "esphome": { + "name": "${name}", + "name_add_mac_suffix": False, + }, } p.write_text( dump(config) + WIFI_CONFIG, From d13a397f8ee2e13c834977238f6f7f01c3858ff8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 May 2022 19:44:54 +1200 Subject: [PATCH 0258/3388] Bump pyupgrade from 2.32.0 to 2.32.1 (#3452) 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 083050252d..68da13aade 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,7 +1,7 @@ pylint==2.13.5 flake8==4.0.1 black==22.3.0 -pyupgrade==2.32.0 +pyupgrade==2.32.1 pre-commit # Unit tests From a35f36ad39ead5a9da7681fa76c1d3380b3eeab6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 May 2022 20:28:21 +1200 Subject: [PATCH 0259/3388] Bump pylint from 2.13.5 to 2.13.8 (#3432) 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 68da13aade..4b5db8ce87 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,4 +1,4 @@ -pylint==2.13.5 +pylint==2.13.8 flake8==4.0.1 black==22.3.0 pyupgrade==2.32.1 From 47898b527cd974068af04723f253b16009c72735 Mon Sep 17 00:00:00 2001 From: MFlasskamp Date: Mon, 9 May 2022 10:32:14 +0200 Subject: [PATCH 0260/3388] Esp32c3 deepsleep fix (#3433) --- .../components/deep_sleep/deep_sleep_component.cpp | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/esphome/components/deep_sleep/deep_sleep_component.cpp b/esphome/components/deep_sleep/deep_sleep_component.cpp index 1bb70e0d7e..23f2a7a70c 100644 --- a/esphome/components/deep_sleep/deep_sleep_component.cpp +++ b/esphome/components/deep_sleep/deep_sleep_component.cpp @@ -76,12 +76,14 @@ void DeepSleepComponent::set_sleep_duration(uint32_t time_ms) { this->sleep_dura void DeepSleepComponent::set_wakeup_pin_mode(WakeupPinMode wakeup_pin_mode) { this->wakeup_pin_mode_ = wakeup_pin_mode; } +#if !defined(USE_ESP32_VARIANT_ESP32C3) void DeepSleepComponent::set_ext1_wakeup(Ext1Wakeup ext1_wakeup) { this->ext1_wakeup_ = ext1_wakeup; } void DeepSleepComponent::set_touch_wakeup(bool touch_wakeup) { this->touch_wakeup_ = touch_wakeup; } void DeepSleepComponent::set_run_duration(WakeupCauseToRunDuration wakeup_cause_to_run_duration) { wakeup_cause_to_run_duration_ = wakeup_cause_to_run_duration; } #endif +#endif void DeepSleepComponent::set_run_duration(uint32_t time_ms) { this->run_duration_ = time_ms; } void DeepSleepComponent::begin_sleep(bool manual) { if (this->prevent_ && !manual) { @@ -107,7 +109,8 @@ void DeepSleepComponent::begin_sleep(bool manual) { App.run_safe_shutdown_hooks(); -#if defined(USE_ESP32) && !defined(USE_ESP32_VARIANT_ESP32C3) +#if defined(USE_ESP32) +#if !defined(USE_ESP32_VARIANT_ESP32C3) if (this->sleep_duration_.has_value()) esp_sleep_enable_timer_wakeup(*this->sleep_duration_); if (this->wakeup_pin_ != nullptr) { @@ -125,10 +128,7 @@ void DeepSleepComponent::begin_sleep(bool manual) { esp_sleep_enable_touchpad_wakeup(); esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON); } - - esp_deep_sleep_start(); #endif - #ifdef USE_ESP32_VARIANT_ESP32C3 if (this->sleep_duration_.has_value()) esp_sleep_enable_timer_wakeup(*this->sleep_duration_); @@ -137,9 +137,12 @@ void DeepSleepComponent::begin_sleep(bool manual) { if (this->wakeup_pin_mode_ == WAKEUP_PIN_MODE_INVERT_WAKEUP && this->wakeup_pin_->digital_read()) { level = !level; } - esp_deep_sleep_enable_gpio_wakeup(gpio_num_t(this->wakeup_pin_->get_pin()), level); + esp_deep_sleep_enable_gpio_wakeup(gpio_num_t(this->wakeup_pin_->get_pin()), + static_cast(level)); } #endif + esp_deep_sleep_start(); +#endif #ifdef USE_ESP8266 ESP.deepSleep(*this->sleep_duration_); // NOLINT(readability-static-accessed-through-instance) From 3a3d97dfa79bd65b4eec7609d035299903baa717 Mon Sep 17 00:00:00 2001 From: Unai Date: Tue, 10 May 2022 03:28:22 +0200 Subject: [PATCH 0261/3388] Add SERIAL_JTAG/CDC logger option for ESP-IDF platform for ESP32-S2/S3/C3 (#3105) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/logger/__init__.py | 62 +++++++++++++++------ esphome/components/logger/logger.cpp | 79 ++++++++++++++++++++------- esphome/components/logger/logger.h | 12 +++- 3 files changed, 114 insertions(+), 39 deletions(-) diff --git a/esphome/components/logger/__init__.py b/esphome/components/logger/__init__.py index d11b00405d..43d87bcefe 100644 --- a/esphome/components/logger/__init__.py +++ b/esphome/components/logger/__init__.py @@ -19,8 +19,13 @@ from esphome.const import ( CONF_TX_BUFFER_SIZE, ) from esphome.core import CORE, EsphomeError, Lambda, coroutine_with_priority -from esphome.components.esp32 import get_esp32_variant -from esphome.components.esp32.const import VARIANT_ESP32S2, VARIANT_ESP32C3 +from esphome.components.esp32 import add_idf_sdkconfig_option, get_esp32_variant +from esphome.components.esp32.const import ( + VARIANT_ESP32, + VARIANT_ESP32S2, + VARIANT_ESP32C3, + VARIANT_ESP32S3, +) CODEOWNERS = ["@esphome/core"] logger_ns = cg.esphome_ns.namespace("logger") @@ -54,36 +59,51 @@ LOG_LEVEL_SEVERITY = [ "VERY_VERBOSE", ] -ESP32_REDUCED_VARIANTS = [VARIANT_ESP32C3, VARIANT_ESP32S2] +UART0 = "UART0" +UART1 = "UART1" +UART2 = "UART2" +UART0_SWAP = "UART0_SWAP" +USB_SERIAL_JTAG = "USB_SERIAL_JTAG" +USB_CDC = "USB_CDC" -UART_SELECTION_ESP32_REDUCED = ["UART0", "UART1"] +UART_SELECTION_ESP32 = { + VARIANT_ESP32: [UART0, UART1, UART2], + VARIANT_ESP32S2: [UART0, UART1, USB_CDC], + VARIANT_ESP32S3: [UART0, UART1, USB_CDC, USB_SERIAL_JTAG], + VARIANT_ESP32C3: [UART0, UART1, USB_SERIAL_JTAG], +} -UART_SELECTION_ESP32 = ["UART0", "UART1", "UART2"] +UART_SELECTION_ESP8266 = [UART0, UART0_SWAP, UART1] -UART_SELECTION_ESP8266 = ["UART0", "UART0_SWAP", "UART1"] +ESP_IDF_UARTS = [USB_CDC, USB_SERIAL_JTAG] HARDWARE_UART_TO_UART_SELECTION = { - "UART0": logger_ns.UART_SELECTION_UART0, - "UART0_SWAP": logger_ns.UART_SELECTION_UART0_SWAP, - "UART1": logger_ns.UART_SELECTION_UART1, - "UART2": logger_ns.UART_SELECTION_UART2, + UART0: logger_ns.UART_SELECTION_UART0, + UART0_SWAP: logger_ns.UART_SELECTION_UART0_SWAP, + UART1: logger_ns.UART_SELECTION_UART1, + UART2: logger_ns.UART_SELECTION_UART2, + USB_CDC: logger_ns.UART_SELECTION_USB_CDC, + USB_SERIAL_JTAG: logger_ns.UART_SELECTION_USB_SERIAL_JTAG, } HARDWARE_UART_TO_SERIAL = { - "UART0": cg.global_ns.Serial, - "UART0_SWAP": cg.global_ns.Serial, - "UART1": cg.global_ns.Serial1, - "UART2": cg.global_ns.Serial2, + UART0: cg.global_ns.Serial, + UART0_SWAP: cg.global_ns.Serial, + UART1: cg.global_ns.Serial1, + UART2: cg.global_ns.Serial2, } is_log_level = cv.one_of(*LOG_LEVELS, upper=True) def uart_selection(value): + if value.upper() in ESP_IDF_UARTS: + if not CORE.using_esp_idf: + raise cv.Invalid(f"Only esp-idf framework supports {value}.") if CORE.is_esp32: - if get_esp32_variant() in ESP32_REDUCED_VARIANTS: - return cv.one_of(*UART_SELECTION_ESP32_REDUCED, upper=True)(value) - return cv.one_of(*UART_SELECTION_ESP32, upper=True)(value) + variant = get_esp32_variant() + if variant in UART_SELECTION_ESP32: + return cv.one_of(*UART_SELECTION_ESP32[variant], upper=True)(value) if CORE.is_esp8266: return cv.one_of(*UART_SELECTION_ESP8266, upper=True)(value) raise NotImplementedError @@ -113,7 +133,7 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_BAUD_RATE, default=115200): cv.positive_int, cv.Optional(CONF_TX_BUFFER_SIZE, default=512): cv.validate_bytes, cv.Optional(CONF_DEASSERT_RTS_DTR, default=False): cv.boolean, - cv.Optional(CONF_HARDWARE_UART, default="UART0"): uart_selection, + cv.Optional(CONF_HARDWARE_UART, default=UART0): uart_selection, cv.Optional(CONF_LEVEL, default="DEBUG"): is_log_level, cv.Optional(CONF_LOGS, default={}): cv.Schema( { @@ -185,6 +205,12 @@ async def to_code(config): if config.get(CONF_ESP8266_STORE_LOG_STRINGS_IN_FLASH): cg.add_build_flag("-DUSE_STORE_LOG_STR_IN_FLASH") + if CORE.using_esp_idf: + if config[CONF_HARDWARE_UART] == USB_CDC: + add_idf_sdkconfig_option("CONFIG_ESP_CONSOLE_USB_CDC", True) + elif config[CONF_HARDWARE_UART] == USB_SERIAL_JTAG: + add_idf_sdkconfig_option("CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG", True) + # Register at end for safe mode await cg.register_component(log, config) diff --git a/esphome/components/logger/logger.cpp b/esphome/components/logger/logger.cpp index 3f4e4e7753..08c83035b6 100644 --- a/esphome/components/logger/logger.cpp +++ b/esphome/components/logger/logger.cpp @@ -116,8 +116,22 @@ void HOT Logger::log_message_(int level, const char *tag, int offset) { this->hw_serial_->println(msg); #endif // USE_ARDUINO #ifdef USE_ESP_IDF - uart_write_bytes(uart_num_, msg, strlen(msg)); - uart_write_bytes(uart_num_, "\n", 1); + if ( +#if defined(USE_ESP32_VARIANT_ESP32S2) + uart_ == UART_SELECTION_USB_CDC +#elif defined(USE_ESP32_VARIANT_ESP32C3) + uart_ == UART_SELECTION_USB_SERIAL_JTAG +#elif defined(USE_ESP32_VARIANT_ESP32S3) + uart_ == UART_SELECTION_USB_CDC || uart_ == UART_SELECTION_USB_SERIAL_JTAG +#else + /* DISABLES CODE */ (false) +#endif + ) { + puts(msg); + } else { + uart_write_bytes(uart_num_, msg, strlen(msg)); + uart_write_bytes(uart_num_, "\n", 1); + } #endif } @@ -181,29 +195,41 @@ void Logger::pre_setup() { case UART_SELECTION_UART1: uart_num_ = UART_NUM_1; break; -#if defined(USE_ESP32) && !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32S2) +#if !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32S2) && !defined(USE_ESP32_VARIANT_ESP32S3) case UART_SELECTION_UART2: uart_num_ = UART_NUM_2; break; -#endif +#endif // !USE_ESP32_VARIANT_ESP32C3 && !USE_ESP32_VARIANT_ESP32S2 && !USE_ESP32_VARIANT_ESP32S3 +#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) + case UART_SELECTION_USB_CDC: + uart_num_ = -1; + break; +#endif // USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3 +#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S3) + case UART_SELECTION_USB_SERIAL_JTAG: + uart_num_ = -1; + break; +#endif // USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32S3 } - uart_config_t uart_config{}; - uart_config.baud_rate = (int) baud_rate_; - uart_config.data_bits = UART_DATA_8_BITS; - uart_config.parity = UART_PARITY_DISABLE; - uart_config.stop_bits = UART_STOP_BITS_1; - uart_config.flow_ctrl = UART_HW_FLOWCTRL_DISABLE; - uart_param_config(uart_num_, &uart_config); - const int uart_buffer_size = tx_buffer_size_; - // Install UART driver using an event queue here - uart_driver_install(uart_num_, uart_buffer_size, uart_buffer_size, 10, nullptr, 0); -#endif + if (uart_num_ >= 0) { + uart_config_t uart_config{}; + uart_config.baud_rate = (int) baud_rate_; + uart_config.data_bits = UART_DATA_8_BITS; + uart_config.parity = UART_PARITY_DISABLE; + uart_config.stop_bits = UART_STOP_BITS_1; + uart_config.flow_ctrl = UART_HW_FLOWCTRL_DISABLE; + uart_param_config(uart_num_, &uart_config); + const int uart_buffer_size = tx_buffer_size_; + // Install UART driver using an event queue here + uart_driver_install(uart_num_, uart_buffer_size, uart_buffer_size, 10, nullptr, 0); + } +#endif // USE_ESP_IDF } #ifdef USE_ESP8266 else { uart_set_debug(UART_NO); } -#endif +#endif // USE_ESP8266 global_logger = this; #if defined(USE_ESP_IDF) || defined(USE_ESP32_FRAMEWORK_ARDUINO) @@ -211,7 +237,7 @@ void Logger::pre_setup() { if (ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE) { esp_log_level_set("*", ESP_LOG_VERBOSE); } -#endif +#endif // USE_ESP_IDF || USE_ESP32_FRAMEWORK_ARDUINO ESP_LOGI(TAG, "Log initialized"); } @@ -226,11 +252,24 @@ void Logger::add_on_log_callback(std::function Date: Tue, 10 May 2022 06:41:16 +0200 Subject: [PATCH 0262/3388] Select enhancement (#3423) Co-authored-by: Maurice Makaay --- esphome/codegen.py | 1 + esphome/components/api/api_server.cpp | 2 +- esphome/components/api/api_server.h | 2 +- .../components/copy/select/copy_select.cpp | 2 +- esphome/components/mqtt/mqtt_select.cpp | 2 +- esphome/components/select/__init__.py | 126 +++++++++++++++++- esphome/components/select/automation.h | 40 +++++- esphome/components/select/select.cpp | 66 +++++---- esphome/components/select/select.h | 48 ++----- esphome/components/select/select_call.cpp | 122 +++++++++++++++++ esphome/components/select/select_call.h | 48 +++++++ esphome/components/select/select_traits.cpp | 11 ++ esphome/components/select/select_traits.h | 19 +++ esphome/components/web_server/web_server.cpp | 2 +- esphome/components/web_server/web_server.h | 2 +- esphome/const.py | 2 + esphome/core/controller.cpp | 6 +- esphome/core/controller.h | 2 +- esphome/cpp_types.py | 1 + tests/test5.yaml | 35 ++++- 20 files changed, 461 insertions(+), 78 deletions(-) create mode 100644 esphome/components/select/select_call.cpp create mode 100644 esphome/components/select/select_call.h create mode 100644 esphome/components/select/select_traits.cpp create mode 100644 esphome/components/select/select_traits.h diff --git a/esphome/codegen.py b/esphome/codegen.py index b862a8ce86..185e6599b1 100644 --- a/esphome/codegen.py +++ b/esphome/codegen.py @@ -64,6 +64,7 @@ from esphome.cpp_types import ( # noqa uint64, int32, int64, + size_t, const_char_ptr, NAN, esphome_ns, diff --git a/esphome/components/api/api_server.cpp b/esphome/components/api/api_server.cpp index 4521cc5bfc..1f2800f298 100644 --- a/esphome/components/api/api_server.cpp +++ b/esphome/components/api/api_server.cpp @@ -255,7 +255,7 @@ void APIServer::on_number_update(number::Number *obj, float state) { #endif #ifdef USE_SELECT -void APIServer::on_select_update(select::Select *obj, const std::string &state) { +void APIServer::on_select_update(select::Select *obj, const std::string &state, size_t index) { if (obj->is_internal()) return; for (auto &c : this->clients_) diff --git a/esphome/components/api/api_server.h b/esphome/components/api/api_server.h index fdc46922ad..f03a83fc7b 100644 --- a/esphome/components/api/api_server.h +++ b/esphome/components/api/api_server.h @@ -64,7 +64,7 @@ class APIServer : public Component, public Controller { void on_number_update(number::Number *obj, float state) override; #endif #ifdef USE_SELECT - void on_select_update(select::Select *obj, const std::string &state) override; + void on_select_update(select::Select *obj, const std::string &state, size_t index) override; #endif #ifdef USE_LOCK void on_lock_update(lock::Lock *obj) override; diff --git a/esphome/components/copy/select/copy_select.cpp b/esphome/components/copy/select/copy_select.cpp index 0f01c2692c..bdcbd0b42c 100644 --- a/esphome/components/copy/select/copy_select.cpp +++ b/esphome/components/copy/select/copy_select.cpp @@ -7,7 +7,7 @@ namespace copy { static const char *const TAG = "copy.select"; void CopySelect::setup() { - source_->add_on_state_callback([this](const std::string &value) { this->publish_state(value); }); + source_->add_on_state_callback([this](const std::string &value, size_t index) { this->publish_state(value); }); traits.set_options(source_->traits.get_options()); diff --git a/esphome/components/mqtt/mqtt_select.cpp b/esphome/components/mqtt/mqtt_select.cpp index 7ecbf9425e..ea5130f823 100644 --- a/esphome/components/mqtt/mqtt_select.cpp +++ b/esphome/components/mqtt/mqtt_select.cpp @@ -21,7 +21,7 @@ void MQTTSelectComponent::setup() { call.set_option(state); call.perform(); }); - this->select_->add_on_state_callback([this](const std::string &state) { this->publish_state(state); }); + this->select_->add_on_state_callback([this](const std::string &state, size_t index) { this->publish_state(state); }); } void MQTTSelectComponent::dump_config() { diff --git a/esphome/components/select/__init__.py b/esphome/components/select/__init__.py index c15036e9f9..a1c73c385e 100644 --- a/esphome/components/select/__init__.py +++ b/esphome/components/select/__init__.py @@ -9,6 +9,10 @@ from esphome.const import ( CONF_OPTION, CONF_TRIGGER_ID, CONF_MQTT_ID, + CONF_CYCLE, + CONF_MODE, + CONF_OPERATION, + CONF_INDEX, ) from esphome.core import CORE, coroutine_with_priority from esphome.cpp_helpers import setup_entity @@ -22,14 +26,27 @@ SelectPtr = Select.operator("ptr") # Triggers SelectStateTrigger = select_ns.class_( - "SelectStateTrigger", automation.Trigger.template(cg.float_) + "SelectStateTrigger", + automation.Trigger.template(cg.std_string, cg.size_t), ) # Actions SelectSetAction = select_ns.class_("SelectSetAction", automation.Action) +SelectSetIndexAction = select_ns.class_("SelectSetIndexAction", automation.Action) +SelectOperationAction = select_ns.class_("SelectOperationAction", automation.Action) + +# Enums +SelectOperation = select_ns.enum("SelectOperation") +SELECT_OPERATION_OPTIONS = { + "NEXT": SelectOperation.SELECT_OP_NEXT, + "PREVIOUS": SelectOperation.SELECT_OP_PREVIOUS, + "FIRST": SelectOperation.SELECT_OP_FIRST, + "LAST": SelectOperation.SELECT_OP_LAST, +} icon = cv.icon + SELECT_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend( { cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTSelectComponent), @@ -50,7 +67,9 @@ async def setup_select_core_(var, config, *, options: List[str]): for conf in config.get(CONF_ON_VALUE, []): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [(cg.std_string, "x")], conf) + await automation.build_automation( + trigger, [(cg.std_string, "x"), (cg.size_t, "i")], conf + ) if CONF_MQTT_ID in config: mqtt_ = cg.new_Pvariable(config[CONF_MQTT_ID], var) @@ -76,12 +95,18 @@ async def to_code(config): cg.add_global(select_ns.using) +OPERATION_BASE_SCHEMA = cv.Schema( + { + cv.Required(CONF_ID): cv.use_id(Select), + } +) + + @automation.register_action( "select.set", SelectSetAction, - cv.Schema( + OPERATION_BASE_SCHEMA.extend( { - cv.Required(CONF_ID): cv.use_id(Select), cv.Required(CONF_OPTION): cv.templatable(cv.string_strict), } ), @@ -92,3 +117,96 @@ async def select_set_to_code(config, action_id, template_arg, args): template_ = await cg.templatable(config[CONF_OPTION], args, cg.std_string) cg.add(var.set_option(template_)) return var + + +@automation.register_action( + "select.set_index", + SelectSetIndexAction, + OPERATION_BASE_SCHEMA.extend( + { + cv.Required(CONF_INDEX): cv.templatable(cv.positive_int), + } + ), +) +async def select_set_index_to_code(config, action_id, template_arg, args): + paren = await cg.get_variable(config[CONF_ID]) + var = cg.new_Pvariable(action_id, template_arg, paren) + template_ = await cg.templatable(config[CONF_INDEX], args, cg.size_t) + cg.add(var.set_index(template_)) + return var + + +@automation.register_action( + "select.operation", + SelectOperationAction, + OPERATION_BASE_SCHEMA.extend( + { + cv.Required(CONF_OPERATION): cv.templatable( + cv.enum(SELECT_OPERATION_OPTIONS, upper=True) + ), + cv.Optional(CONF_CYCLE, default=True): cv.templatable(cv.boolean), + } + ), +) +@automation.register_action( + "select.next", + SelectOperationAction, + automation.maybe_simple_id( + OPERATION_BASE_SCHEMA.extend( + { + cv.Optional(CONF_MODE, default="NEXT"): cv.one_of("NEXT", upper=True), + cv.Optional(CONF_CYCLE, default=True): cv.boolean, + } + ) + ), +) +@automation.register_action( + "select.previous", + SelectOperationAction, + automation.maybe_simple_id( + OPERATION_BASE_SCHEMA.extend( + { + cv.Optional(CONF_MODE, default="PREVIOUS"): cv.one_of( + "PREVIOUS", upper=True + ), + cv.Optional(CONF_CYCLE, default=True): cv.boolean, + } + ) + ), +) +@automation.register_action( + "select.first", + SelectOperationAction, + automation.maybe_simple_id( + OPERATION_BASE_SCHEMA.extend( + { + cv.Optional(CONF_MODE, default="FIRST"): cv.one_of("FIRST", upper=True), + } + ) + ), +) +@automation.register_action( + "select.last", + SelectOperationAction, + automation.maybe_simple_id( + OPERATION_BASE_SCHEMA.extend( + { + cv.Optional(CONF_MODE, default="LAST"): cv.one_of("LAST", upper=True), + } + ) + ), +) +async def select_operation_to_code(config, action_id, template_arg, args): + paren = await cg.get_variable(config[CONF_ID]) + var = cg.new_Pvariable(action_id, template_arg, paren) + if CONF_OPERATION in config: + op_ = await cg.templatable(config[CONF_OPERATION], args, SelectOperation) + cg.add(var.set_operation(op_)) + if CONF_CYCLE in config: + cycle_ = await cg.templatable(config[CONF_CYCLE], args, bool) + cg.add(var.set_cycle(cycle_)) + if CONF_MODE in config: + cg.add(var.set_operation(SELECT_OPERATION_OPTIONS[config[CONF_MODE]])) + if CONF_CYCLE in config: + cg.add(var.set_cycle(config[CONF_CYCLE])) + return var diff --git a/esphome/components/select/automation.h b/esphome/components/select/automation.h index 1e0bfed63d..1250665188 100644 --- a/esphome/components/select/automation.h +++ b/esphome/components/select/automation.h @@ -7,16 +7,16 @@ namespace esphome { namespace select { -class SelectStateTrigger : public Trigger { +class SelectStateTrigger : public Trigger { public: explicit SelectStateTrigger(Select *parent) { - parent->add_on_state_callback([this](const std::string &value) { this->trigger(value); }); + parent->add_on_state_callback([this](const std::string &value, size_t index) { this->trigger(value, index); }); } }; template class SelectSetAction : public Action { public: - SelectSetAction(Select *select) : select_(select) {} + explicit SelectSetAction(Select *select) : select_(select) {} TEMPLATABLE_VALUE(std::string, option) void play(Ts... x) override { @@ -29,5 +29,39 @@ template class SelectSetAction : public Action { Select *select_; }; +template class SelectSetIndexAction : public Action { + public: + explicit SelectSetIndexAction(Select *select) : select_(select) {} + TEMPLATABLE_VALUE(size_t, index) + + void play(Ts... x) override { + auto call = this->select_->make_call(); + call.set_index(this->index_.value(x...)); + call.perform(); + } + + protected: + Select *select_; +}; + +template class SelectOperationAction : public Action { + public: + explicit SelectOperationAction(Select *select) : select_(select) {} + TEMPLATABLE_VALUE(bool, cycle) + TEMPLATABLE_VALUE(SelectOperation, operation) + + void play(Ts... x) override { + auto call = this->select_->make_call(); + call.with_operation(this->operation_.value(x...)); + if (this->cycle_.has_value()) { + call.with_cycle(this->cycle_.value(x...)); + } + call.perform(); + } + + protected: + Select *select_; +}; + } // namespace select } // namespace esphome diff --git a/esphome/components/select/select.cpp b/esphome/components/select/select.cpp index 14f4d9277d..75edb5c8ba 100644 --- a/esphome/components/select/select.cpp +++ b/esphome/components/select/select.cpp @@ -6,37 +6,53 @@ namespace select { static const char *const TAG = "select"; -void SelectCall::perform() { - ESP_LOGD(TAG, "'%s' - Setting", this->parent_->get_name().c_str()); - if (!this->option_.has_value()) { - ESP_LOGW(TAG, "No value set for SelectCall"); - return; - } - - const auto &traits = this->parent_->traits; - auto value = *this->option_; - auto options = traits.get_options(); - - if (std::find(options.begin(), options.end(), value) == options.end()) { - ESP_LOGW(TAG, " Option %s is not a valid option.", value.c_str()); - return; - } - - ESP_LOGD(TAG, " Option: %s", (*this->option_).c_str()); - this->parent_->control(*this->option_); -} - void Select::publish_state(const std::string &state) { - this->has_state_ = true; - this->state = state; - ESP_LOGD(TAG, "'%s': Sending state %s", this->get_name().c_str(), state.c_str()); - this->state_callback_.call(state); + auto index = this->index_of(state); + const auto *name = this->get_name().c_str(); + if (index.has_value()) { + this->has_state_ = true; + this->state = state; + ESP_LOGD(TAG, "'%s': Sending state %s (index %d)", name, state.c_str(), index.value()); + this->state_callback_.call(state, index.value()); + } else { + ESP_LOGE(TAG, "'%s': invalid state for publish_state(): %s", name, state.c_str()); + } } -void Select::add_on_state_callback(std::function &&callback) { +void Select::add_on_state_callback(std::function &&callback) { this->state_callback_.add(std::move(callback)); } +size_t Select::size() const { + auto options = traits.get_options(); + return options.size(); +} + +optional Select::index_of(const std::string &option) const { + auto options = traits.get_options(); + auto it = std::find(options.begin(), options.end(), option); + if (it == options.end()) { + return {}; + } + return std::distance(options.begin(), it); +} + +optional Select::active_index() const { + if (this->has_state()) { + return this->index_of(this->state); + } else { + return {}; + } +} + +optional Select::at(size_t index) const { + auto options = traits.get_options(); + if (index >= options.size()) { + return {}; + } + return options.at(index); +} + uint32_t Select::hash_base() { return 2812997003UL; } } // namespace select diff --git a/esphome/components/select/select.h b/esphome/components/select/select.h index db655ea34e..64870fc9a3 100644 --- a/esphome/components/select/select.h +++ b/esphome/components/select/select.h @@ -1,10 +1,10 @@ #pragma once -#include -#include #include "esphome/core/component.h" #include "esphome/core/entity_base.h" #include "esphome/core/helpers.h" +#include "select_call.h" +#include "select_traits.h" namespace esphome { namespace select { @@ -17,33 +17,6 @@ namespace select { } \ } -class Select; - -class SelectCall { - public: - explicit SelectCall(Select *parent) : parent_(parent) {} - void perform(); - - SelectCall &set_option(const std::string &option) { - option_ = option; - return *this; - } - const optional &get_option() const { return option_; } - - protected: - Select *const parent_; - optional option_; -}; - -class SelectTraits { - public: - void set_options(std::vector options) { this->options_ = std::move(options); } - std::vector get_options() const { return this->options_; } - - protected: - std::vector options_; -}; - /** Base-class for all selects. * * A select can use publish_state to send out a new value. @@ -51,18 +24,23 @@ class SelectTraits { class Select : public EntityBase { public: std::string state; + SelectTraits traits; void publish_state(const std::string &state); + /// Return whether this select has gotten a full state yet. + bool has_state() const { return has_state_; } + SelectCall make_call() { return SelectCall(this); } void set(const std::string &value) { make_call().set_option(value).perform(); } - void add_on_state_callback(std::function &&callback); + // Methods that provide an API to index-based access. + size_t size() const; + optional index_of(const std::string &option) const; + optional active_index() const; + optional at(size_t index) const; - SelectTraits traits; - - /// Return whether this select has gotten a full state yet. - bool has_state() const { return has_state_; } + void add_on_state_callback(std::function &&callback); protected: friend class SelectCall; @@ -77,7 +55,7 @@ class Select : public EntityBase { uint32_t hash_base() override; - CallbackManager state_callback_; + CallbackManager state_callback_; bool has_state_{false}; }; diff --git a/esphome/components/select/select_call.cpp b/esphome/components/select/select_call.cpp new file mode 100644 index 0000000000..9442598740 --- /dev/null +++ b/esphome/components/select/select_call.cpp @@ -0,0 +1,122 @@ +#include "select_call.h" +#include "select.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace select { + +static const char *const TAG = "select"; + +SelectCall &SelectCall::set_option(const std::string &option) { + return with_operation(SELECT_OP_SET).with_option(option); +} + +SelectCall &SelectCall::set_index(size_t index) { return with_operation(SELECT_OP_SET_INDEX).with_index(index); } + +const optional &SelectCall::get_option() const { return option_; } + +SelectCall &SelectCall::select_next(bool cycle) { return with_operation(SELECT_OP_NEXT).with_cycle(cycle); } + +SelectCall &SelectCall::select_previous(bool cycle) { return with_operation(SELECT_OP_PREVIOUS).with_cycle(cycle); } + +SelectCall &SelectCall::select_first() { return with_operation(SELECT_OP_FIRST); } + +SelectCall &SelectCall::select_last() { return with_operation(SELECT_OP_LAST); } + +SelectCall &SelectCall::with_operation(SelectOperation operation) { + this->operation_ = operation; + return *this; +} + +SelectCall &SelectCall::with_cycle(bool cycle) { + this->cycle_ = cycle; + return *this; +} + +SelectCall &SelectCall::with_option(const std::string &option) { + this->option_ = option; + return *this; +} + +SelectCall &SelectCall::with_index(size_t index) { + this->index_ = index; + return *this; +} + +void SelectCall::perform() { + auto *parent = this->parent_; + const auto *name = parent->get_name().c_str(); + const auto &traits = parent->traits; + auto options = traits.get_options(); + + if (this->operation_ == SELECT_OP_NONE) { + ESP_LOGW(TAG, "'%s' - SelectCall performed without selecting an operation", name); + return; + } + if (options.empty()) { + ESP_LOGW(TAG, "'%s' - Cannot perform SelectCall, select has no options", name); + return; + } + + std::string target_value; + + if (this->operation_ == SELECT_OP_SET) { + ESP_LOGD(TAG, "'%s' - Setting", name); + if (!this->option_.has_value()) { + ESP_LOGW(TAG, "'%s' - No option value set for SelectCall", name); + return; + } + target_value = this->option_.value(); + } else if (this->operation_ == SELECT_OP_SET_INDEX) { + if (!this->index_.has_value()) { + ESP_LOGW(TAG, "'%s' - No index value set for SelectCall", name); + return; + } + if (this->index_.value() >= options.size()) { + ESP_LOGW(TAG, "'%s' - Index value %d out of bounds", name, this->index_.value()); + return; + } + target_value = options[this->index_.value()]; + } else if (this->operation_ == SELECT_OP_FIRST) { + target_value = options.front(); + } else if (this->operation_ == SELECT_OP_LAST) { + target_value = options.back(); + } else if (this->operation_ == SELECT_OP_NEXT || this->operation_ == SELECT_OP_PREVIOUS) { + auto cycle = this->cycle_; + ESP_LOGD(TAG, "'%s' - Selecting %s, with%s cycling", name, this->operation_ == SELECT_OP_NEXT ? "next" : "previous", + cycle ? "" : "out"); + if (!parent->has_state()) { + target_value = this->operation_ == SELECT_OP_NEXT ? options.front() : options.back(); + } else { + auto index = parent->index_of(parent->state); + if (index.has_value()) { + auto size = options.size(); + if (cycle) { + auto use_index = (size + index.value() + (this->operation_ == SELECT_OP_NEXT ? +1 : -1)) % size; + target_value = options[use_index]; + } else { + if (this->operation_ == SELECT_OP_PREVIOUS && index.value() > 0) { + target_value = options[index.value() - 1]; + } else if (this->operation_ == SELECT_OP_NEXT && index.value() < options.size() - 1) { + target_value = options[index.value() + 1]; + } else { + return; + } + } + } else { + target_value = this->operation_ == SELECT_OP_NEXT ? options.front() : options.back(); + } + } + } + + if (std::find(options.begin(), options.end(), target_value) == options.end()) { + ESP_LOGW(TAG, "'%s' - Option %s is not a valid option", name, target_value.c_str()); + return; + } + + ESP_LOGD(TAG, "'%s' - Set selected option to: %s", name, target_value.c_str()); + parent->control(target_value); +} + +} // namespace select +} // namespace esphome diff --git a/esphome/components/select/select_call.h b/esphome/components/select/select_call.h new file mode 100644 index 0000000000..ea4d34ab5f --- /dev/null +++ b/esphome/components/select/select_call.h @@ -0,0 +1,48 @@ +#pragma once + +#include "esphome/core/helpers.h" + +namespace esphome { +namespace select { + +class Select; + +enum SelectOperation { + SELECT_OP_NONE, + SELECT_OP_SET, + SELECT_OP_SET_INDEX, + SELECT_OP_NEXT, + SELECT_OP_PREVIOUS, + SELECT_OP_FIRST, + SELECT_OP_LAST +}; + +class SelectCall { + public: + explicit SelectCall(Select *parent) : parent_(parent) {} + void perform(); + + SelectCall &set_option(const std::string &option); + SelectCall &set_index(size_t index); + const optional &get_option() const; + + SelectCall &select_next(bool cycle); + SelectCall &select_previous(bool cycle); + SelectCall &select_first(); + SelectCall &select_last(); + + SelectCall &with_operation(SelectOperation operation); + SelectCall &with_cycle(bool cycle); + SelectCall &with_option(const std::string &option); + SelectCall &with_index(size_t index); + + protected: + Select *const parent_; + optional option_; + optional index_; + SelectOperation operation_{SELECT_OP_NONE}; + bool cycle_; +}; + +} // namespace select +} // namespace esphome diff --git a/esphome/components/select/select_traits.cpp b/esphome/components/select/select_traits.cpp new file mode 100644 index 0000000000..89da30c405 --- /dev/null +++ b/esphome/components/select/select_traits.cpp @@ -0,0 +1,11 @@ +#include "select_traits.h" + +namespace esphome { +namespace select { + +void SelectTraits::set_options(std::vector options) { this->options_ = std::move(options); } + +std::vector SelectTraits::get_options() const { return this->options_; } + +} // namespace select +} // namespace esphome diff --git a/esphome/components/select/select_traits.h b/esphome/components/select/select_traits.h new file mode 100644 index 0000000000..ccf23dc6d0 --- /dev/null +++ b/esphome/components/select/select_traits.h @@ -0,0 +1,19 @@ +#pragma once + +#include +#include + +namespace esphome { +namespace select { + +class SelectTraits { + public: + void set_options(std::vector options); + std::vector get_options() const; + + protected: + std::vector options_; +}; + +} // namespace select +} // namespace esphome diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index 0dfd608661..6822ce9953 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -755,7 +755,7 @@ std::string WebServer::number_json(number::Number *obj, float value, JsonDetail #endif #ifdef USE_SELECT -void WebServer::on_select_update(select::Select *obj, const std::string &state) { +void WebServer::on_select_update(select::Select *obj, const std::string &state, size_t index) { this->events_.send(this->select_json(obj, state, DETAIL_STATE).c_str(), "state"); } void WebServer::handle_select_request(AsyncWebServerRequest *request, const UrlMatch &match) { diff --git a/esphome/components/web_server/web_server.h b/esphome/components/web_server/web_server.h index bd7acd91a0..78d0597e61 100644 --- a/esphome/components/web_server/web_server.h +++ b/esphome/components/web_server/web_server.h @@ -185,7 +185,7 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { #endif #ifdef USE_SELECT - void on_select_update(select::Select *obj, const std::string &state) override; + void on_select_update(select::Select *obj, const std::string &state, size_t index) override; /// Handle a select request under '/select/'. void handle_select_request(AsyncWebServerRequest *request, const UrlMatch &match); diff --git a/esphome/const.py b/esphome/const.py index 9f2bed28d1..fc928dc530 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -138,6 +138,7 @@ CONF_CUSTOM_FAN_MODE = "custom_fan_mode" CONF_CUSTOM_FAN_MODES = "custom_fan_modes" CONF_CUSTOM_PRESET = "custom_preset" CONF_CUSTOM_PRESETS = "custom_presets" +CONF_CYCLE = "cycle" CONF_DALLAS_ID = "dallas_id" CONF_DATA = "data" CONF_DATA_PIN = "data_pin" @@ -458,6 +459,7 @@ CONF_OPEN_DRAIN = "open_drain" CONF_OPEN_DRAIN_INTERRUPT = "open_drain_interrupt" CONF_OPEN_DURATION = "open_duration" CONF_OPEN_ENDSTOP = "open_endstop" +CONF_OPERATION = "operation" CONF_OPTIMISTIC = "optimistic" CONF_OPTION = "option" CONF_OPTIONS = "options" diff --git a/esphome/core/controller.cpp b/esphome/core/controller.cpp index dfcef5e4c1..7d63a3d143 100644 --- a/esphome/core/controller.cpp +++ b/esphome/core/controller.cpp @@ -61,8 +61,10 @@ void Controller::setup_controller(bool include_internal) { #endif #ifdef USE_SELECT for (auto *obj : App.get_selects()) { - if (include_internal || !obj->is_internal()) - obj->add_on_state_callback([this, obj](const std::string &state) { this->on_select_update(obj, state); }); + if (include_internal || !obj->is_internal()) { + obj->add_on_state_callback( + [this, obj](const std::string &state, size_t index) { this->on_select_update(obj, state, index); }); + } } #endif #ifdef USE_LOCK diff --git a/esphome/core/controller.h b/esphome/core/controller.h index 0be854828b..419624a2ae 100644 --- a/esphome/core/controller.h +++ b/esphome/core/controller.h @@ -71,7 +71,7 @@ class Controller { virtual void on_number_update(number::Number *obj, float state){}; #endif #ifdef USE_SELECT - virtual void on_select_update(select::Select *obj, const std::string &state){}; + virtual void on_select_update(select::Select *obj, const std::string &state, size_t index){}; #endif #ifdef USE_LOCK virtual void on_lock_update(lock::Lock *obj){}; diff --git a/esphome/cpp_types.py b/esphome/cpp_types.py index 2323b2578f..aafe765111 100644 --- a/esphome/cpp_types.py +++ b/esphome/cpp_types.py @@ -16,6 +16,7 @@ uint32 = global_ns.namespace("uint32_t") uint64 = global_ns.namespace("uint64_t") int32 = global_ns.namespace("int32_t") int64 = global_ns.namespace("int64_t") +size_t = global_ns.namespace("size_t") const_char_ptr = global_ns.namespace("const char *") NAN = global_ns.namespace("NAN") esphome_ns = global_ns # using namespace esphome; diff --git a/tests/test5.yaml b/tests/test5.yaml index ee90cc1149..e38f39eab6 100644 --- a/tests/test5.yaml +++ b/tests/test5.yaml @@ -154,8 +154,8 @@ select: restore_value: true on_value: - logger.log: - format: "Select changed to %s" - args: ["x.c_str()"] + format: "Select changed to %s (index %d)" + args: ["x.c_str()", "i"] set_action: - logger.log: format: "Template Select set to %s" @@ -163,11 +163,42 @@ select: - select.set: id: template_select_id option: two + - select.first: template_select_id + - select.last: + id: template_select_id + - select.previous: template_select_id + - select.next: + id: template_select_id + cycle: false + - select.operation: + id: template_select_id + operation: Previous + cycle: false + - select.operation: + id: template_select_id + operation: !lambda "return SELECT_OP_PREVIOUS;" + cycle: !lambda "return true;" + - select.set_index: + id: template_select_id + index: 1 + - select.set_index: + id: template_select_id + index: !lambda "return 1 + 1;" options: - one - two - three + - platform: modbus_controller + name: "Modbus Select Register 1000" + address: 1000 + value_type: U_WORD + optionsmap: + "Zero": 0 + "One": 1 + "Two": 2 + "Three": 3 + sensor: - platform: selec_meter total_active_energy: From d9caab41081f5230ac99252bc2bd645638c43f1b Mon Sep 17 00:00:00 2001 From: Maurice Makaay Date: Tue, 10 May 2022 06:58:56 +0200 Subject: [PATCH 0263/3388] Number enhancement (#3429) Co-authored-by: Maurice Makaay Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/number/__init__.py | 104 ++++++++++++++++- esphome/components/number/automation.h | 19 ++++ esphome/components/number/number.cpp | 33 ------ esphome/components/number/number.h | 50 +-------- esphome/components/number/number_call.cpp | 118 ++++++++++++++++++++ esphome/components/number/number_call.h | 45 ++++++++ esphome/components/number/number_traits.cpp | 20 ++++ esphome/components/number/number_traits.h | 44 ++++++++ tests/test5.yaml | 35 +++++- 9 files changed, 380 insertions(+), 88 deletions(-) create mode 100644 esphome/components/number/number_call.cpp create mode 100644 esphome/components/number/number_call.h create mode 100644 esphome/components/number/number_traits.cpp create mode 100644 esphome/components/number/number_traits.h diff --git a/esphome/components/number/__init__.py b/esphome/components/number/__init__.py index 89788f1e98..f809fff529 100644 --- a/esphome/components/number/__init__.py +++ b/esphome/components/number/__init__.py @@ -14,6 +14,8 @@ from esphome.const import ( CONF_UNIT_OF_MEASUREMENT, CONF_MQTT_ID, CONF_VALUE, + CONF_OPERATION, + CONF_CYCLE, ) from esphome.core import CORE, coroutine_with_priority from esphome.cpp_helpers import setup_entity @@ -35,6 +37,7 @@ ValueRangeTrigger = number_ns.class_( # Actions NumberSetAction = number_ns.class_("NumberSetAction", automation.Action) +NumberOperationAction = number_ns.class_("NumberOperationAction", automation.Action) # Conditions NumberInRangeCondition = number_ns.class_( @@ -49,6 +52,15 @@ NUMBER_MODES = { "SLIDER": NumberMode.NUMBER_MODE_SLIDER, } +NumberOperation = number_ns.enum("NumberOperation") + +NUMBER_OPERATION_OPTIONS = { + "INCREMENT": NumberOperation.NUMBER_OP_INCREMENT, + "DECREMENT": NumberOperation.NUMBER_OP_DECREMENT, + "TO_MIN": NumberOperation.NUMBER_OP_TO_MIN, + "TO_MAX": NumberOperation.NUMBER_OP_TO_MAX, +} + icon = cv.icon NUMBER_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend( @@ -159,12 +171,18 @@ async def to_code(config): cg.add_global(number_ns.using) +OPERATION_BASE_SCHEMA = cv.Schema( + { + cv.Required(CONF_ID): cv.use_id(Number), + } +) + + @automation.register_action( "number.set", NumberSetAction, - cv.Schema( + OPERATION_BASE_SCHEMA.extend( { - cv.Required(CONF_ID): cv.use_id(Number), cv.Required(CONF_VALUE): cv.templatable(cv.float_), } ), @@ -175,3 +193,85 @@ async def number_set_to_code(config, action_id, template_arg, args): template_ = await cg.templatable(config[CONF_VALUE], args, float) cg.add(var.set_value(template_)) return var + + +@automation.register_action( + "number.increment", + NumberOperationAction, + automation.maybe_simple_id( + OPERATION_BASE_SCHEMA.extend( + { + cv.Optional(CONF_MODE, default="INCREMENT"): cv.one_of( + "INCREMENT", upper=True + ), + cv.Optional(CONF_CYCLE, default=True): cv.boolean, + } + ) + ), +) +@automation.register_action( + "number.decrement", + NumberOperationAction, + automation.maybe_simple_id( + OPERATION_BASE_SCHEMA.extend( + { + cv.Optional(CONF_MODE, default="DECREMENT"): cv.one_of( + "DECREMENT", upper=True + ), + cv.Optional(CONF_CYCLE, default=True): cv.boolean, + } + ) + ), +) +@automation.register_action( + "number.to_min", + NumberOperationAction, + automation.maybe_simple_id( + OPERATION_BASE_SCHEMA.extend( + { + cv.Optional(CONF_MODE, default="TO_MIN"): cv.one_of( + "TO_MIN", upper=True + ), + } + ) + ), +) +@automation.register_action( + "number.to_max", + NumberOperationAction, + automation.maybe_simple_id( + OPERATION_BASE_SCHEMA.extend( + { + cv.Optional(CONF_MODE, default="TO_MAX"): cv.one_of( + "TO_MAX", upper=True + ), + } + ) + ), +) +@automation.register_action( + "number.operation", + NumberOperationAction, + OPERATION_BASE_SCHEMA.extend( + { + cv.Required(CONF_OPERATION): cv.templatable( + cv.enum(NUMBER_OPERATION_OPTIONS, upper=True) + ), + cv.Optional(CONF_CYCLE, default=True): cv.templatable(cv.boolean), + } + ), +) +async def number_to_to_code(config, action_id, template_arg, args): + paren = await cg.get_variable(config[CONF_ID]) + var = cg.new_Pvariable(action_id, template_arg, paren) + if CONF_OPERATION in config: + to_ = await cg.templatable(config[CONF_OPERATION], args, NumberOperation) + cg.add(var.set_operation(to_)) + if CONF_CYCLE in config: + cycle_ = await cg.templatable(config[CONF_CYCLE], args, bool) + cg.add(var.set_cycle(cycle_)) + if CONF_MODE in config: + cg.add(var.set_operation(NUMBER_OPERATION_OPTIONS[config[CONF_MODE]])) + if CONF_CYCLE in config: + cg.add(var.set_cycle(config[CONF_CYCLE])) + return var diff --git a/esphome/components/number/automation.h b/esphome/components/number/automation.h index 98554a346a..33f0f9727e 100644 --- a/esphome/components/number/automation.h +++ b/esphome/components/number/automation.h @@ -29,6 +29,25 @@ template class NumberSetAction : public Action { Number *number_; }; +template class NumberOperationAction : public Action { + public: + explicit NumberOperationAction(Number *number) : number_(number) {} + TEMPLATABLE_VALUE(NumberOperation, operation) + TEMPLATABLE_VALUE(bool, cycle) + + void play(Ts... x) override { + auto call = this->number_->make_call(); + call.with_operation(this->operation_.value(x...)); + if (this->cycle_.has_value()) { + call.with_cycle(this->cycle_.value(x...)); + } + call.perform(); + } + + protected: + Number *number_; +}; + class ValueRangeTrigger : public Trigger, public Component { public: explicit ValueRangeTrigger(Number *parent) : parent_(parent) {} diff --git a/esphome/components/number/number.cpp b/esphome/components/number/number.cpp index 99a2c04a22..03a7cc6ce3 100644 --- a/esphome/components/number/number.cpp +++ b/esphome/components/number/number.cpp @@ -6,30 +6,6 @@ namespace number { static const char *const TAG = "number"; -void NumberCall::perform() { - ESP_LOGD(TAG, "'%s' - Setting", this->parent_->get_name().c_str()); - if (!this->value_.has_value() || std::isnan(*this->value_)) { - ESP_LOGW(TAG, "No value set for NumberCall"); - return; - } - - const auto &traits = this->parent_->traits; - auto value = *this->value_; - - float min_value = traits.get_min_value(); - if (value < min_value) { - ESP_LOGW(TAG, " Value %f must not be less than minimum %f", value, min_value); - return; - } - float max_value = traits.get_max_value(); - if (value > max_value) { - ESP_LOGW(TAG, " Value %f must not be greater than maximum %f", value, max_value); - return; - } - ESP_LOGD(TAG, " Value: %f", *this->value_); - this->parent_->control(*this->value_); -} - void Number::publish_state(float state) { this->has_state_ = true; this->state = state; @@ -41,15 +17,6 @@ void Number::add_on_state_callback(std::function &&callback) { this->state_callback_.add(std::move(callback)); } -std::string NumberTraits::get_unit_of_measurement() { - if (this->unit_of_measurement_.has_value()) - return *this->unit_of_measurement_; - return ""; -} -void NumberTraits::set_unit_of_measurement(const std::string &unit_of_measurement) { - this->unit_of_measurement_ = unit_of_measurement; -} - uint32_t Number::hash_base() { return 2282307003UL; } } // namespace number diff --git a/esphome/components/number/number.h b/esphome/components/number/number.h index 40fdfceec1..8f9bf8c2e1 100644 --- a/esphome/components/number/number.h +++ b/esphome/components/number/number.h @@ -3,6 +3,8 @@ #include "esphome/core/component.h" #include "esphome/core/entity_base.h" #include "esphome/core/helpers.h" +#include "number_call.h" +#include "number_traits.h" namespace esphome { namespace number { @@ -20,54 +22,6 @@ namespace number { class Number; -class NumberCall { - public: - explicit NumberCall(Number *parent) : parent_(parent) {} - void perform(); - - NumberCall &set_value(float value) { - value_ = value; - return *this; - } - const optional &get_value() const { return value_; } - - protected: - Number *const parent_; - optional value_; -}; - -enum NumberMode : uint8_t { - NUMBER_MODE_AUTO = 0, - NUMBER_MODE_BOX = 1, - NUMBER_MODE_SLIDER = 2, -}; - -class NumberTraits { - public: - void set_min_value(float min_value) { min_value_ = min_value; } - float get_min_value() const { return min_value_; } - void set_max_value(float max_value) { max_value_ = max_value; } - float get_max_value() const { return max_value_; } - void set_step(float step) { step_ = step; } - float get_step() const { return step_; } - - /// Get the unit of measurement, using the manual override if set. - std::string get_unit_of_measurement(); - /// Manually set the unit of measurement. - void set_unit_of_measurement(const std::string &unit_of_measurement); - - // Get/set the frontend mode. - NumberMode get_mode() const { return this->mode_; } - void set_mode(NumberMode mode) { this->mode_ = mode; } - - protected: - float min_value_ = NAN; - float max_value_ = NAN; - float step_ = NAN; - optional unit_of_measurement_; ///< Unit of measurement override - NumberMode mode_{NUMBER_MODE_AUTO}; -}; - /** Base-class for all numbers. * * A number can use publish_state to send out a new value. diff --git a/esphome/components/number/number_call.cpp b/esphome/components/number/number_call.cpp new file mode 100644 index 0000000000..4219f85328 --- /dev/null +++ b/esphome/components/number/number_call.cpp @@ -0,0 +1,118 @@ +#include "number_call.h" +#include "number.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace number { + +static const char *const TAG = "number"; + +NumberCall &NumberCall::set_value(float value) { return this->with_operation(NUMBER_OP_SET).with_value(value); } + +NumberCall &NumberCall::number_increment(bool cycle) { + return this->with_operation(NUMBER_OP_INCREMENT).with_cycle(cycle); +} + +NumberCall &NumberCall::number_decrement(bool cycle) { + return this->with_operation(NUMBER_OP_DECREMENT).with_cycle(cycle); +} + +NumberCall &NumberCall::number_to_min() { return this->with_operation(NUMBER_OP_TO_MIN); } + +NumberCall &NumberCall::number_to_max() { return this->with_operation(NUMBER_OP_TO_MAX); } + +NumberCall &NumberCall::with_operation(NumberOperation operation) { + this->operation_ = operation; + return *this; +} + +NumberCall &NumberCall::with_value(float value) { + this->value_ = value; + return *this; +} + +NumberCall &NumberCall::with_cycle(bool cycle) { + this->cycle_ = cycle; + return *this; +} + +void NumberCall::perform() { + auto *parent = this->parent_; + const auto *name = parent->get_name().c_str(); + const auto &traits = parent->traits; + + if (this->operation_ == NUMBER_OP_NONE) { + ESP_LOGW(TAG, "'%s' - NumberCall performed without selecting an operation", name); + return; + } + + float target_value = NAN; + float min_value = traits.get_min_value(); + float max_value = traits.get_max_value(); + + if (this->operation_ == NUMBER_OP_SET) { + ESP_LOGD(TAG, "'%s' - Setting number value", name); + if (!this->value_.has_value() || std::isnan(*this->value_)) { + ESP_LOGW(TAG, "'%s' - No value set for NumberCall", name); + return; + } + target_value = this->value_.value(); + } else if (this->operation_ == NUMBER_OP_TO_MIN) { + if (std::isnan(min_value)) { + ESP_LOGW(TAG, "'%s' - Can't set to min value through NumberCall: no min_value defined", name); + } else { + target_value = min_value; + } + } else if (this->operation_ == NUMBER_OP_TO_MAX) { + if (std::isnan(max_value)) { + ESP_LOGW(TAG, "'%s' - Can't set to max value through NumberCall: no max_value defined", name); + } else { + target_value = max_value; + } + } else if (this->operation_ == NUMBER_OP_INCREMENT) { + ESP_LOGD(TAG, "'%s' - Increment number, with%s cycling", name, this->cycle_ ? "" : "out"); + if (!parent->has_state()) { + ESP_LOGW(TAG, "'%s' - Can't increment number through NumberCall: no active state to modify", name); + return; + } + auto step = traits.get_step(); + target_value = parent->state + (std::isnan(step) ? 1 : step); + if (target_value > max_value) { + if (this->cycle_ && !std::isnan(min_value)) { + target_value = min_value; + } else { + target_value = max_value; + } + } + } else if (this->operation_ == NUMBER_OP_DECREMENT) { + ESP_LOGD(TAG, "'%s' - Decrement number, with%s cycling", name, this->cycle_ ? "" : "out"); + if (!parent->has_state()) { + ESP_LOGW(TAG, "'%s' - Can't decrement number through NumberCall: no active state to modify", name); + return; + } + auto step = traits.get_step(); + target_value = parent->state - (std::isnan(step) ? 1 : step); + if (target_value < min_value) { + if (this->cycle_ && !std::isnan(max_value)) { + target_value = max_value; + } else { + target_value = min_value; + } + } + } + + if (target_value < min_value) { + ESP_LOGW(TAG, "'%s' - Value %f must not be less than minimum %f", name, target_value, min_value); + return; + } + if (target_value > max_value) { + ESP_LOGW(TAG, "'%s' - Value %f must not be greater than maximum %f", name, target_value, max_value); + return; + } + + ESP_LOGD(TAG, " New number value: %f", target_value); + this->parent_->control(target_value); +} + +} // namespace number +} // namespace esphome diff --git a/esphome/components/number/number_call.h b/esphome/components/number/number_call.h new file mode 100644 index 0000000000..9a3dad560f --- /dev/null +++ b/esphome/components/number/number_call.h @@ -0,0 +1,45 @@ +#pragma once + +#include "esphome/core/helpers.h" +#include "number_traits.h" + +namespace esphome { +namespace number { + +class Number; + +enum NumberOperation { + NUMBER_OP_NONE, + NUMBER_OP_SET, + NUMBER_OP_INCREMENT, + NUMBER_OP_DECREMENT, + NUMBER_OP_TO_MIN, + NUMBER_OP_TO_MAX, +}; + +class NumberCall { + public: + explicit NumberCall(Number *parent) : parent_(parent) {} + void perform(); + + NumberCall &set_value(float value); + const optional &get_value() const { return value_; } + + NumberCall &number_increment(bool cycle); + NumberCall &number_decrement(bool cycle); + NumberCall &number_to_min(); + NumberCall &number_to_max(); + + NumberCall &with_operation(NumberOperation operation); + NumberCall &with_value(float value); + NumberCall &with_cycle(bool cycle); + + protected: + Number *const parent_; + NumberOperation operation_{NUMBER_OP_NONE}; + optional value_; + bool cycle_; +}; + +} // namespace number +} // namespace esphome diff --git a/esphome/components/number/number_traits.cpp b/esphome/components/number/number_traits.cpp new file mode 100644 index 0000000000..dcd05daa2a --- /dev/null +++ b/esphome/components/number/number_traits.cpp @@ -0,0 +1,20 @@ +#include "esphome/core/log.h" +#include "number_traits.h" + +namespace esphome { +namespace number { + +static const char *const TAG = "number"; + +void NumberTraits::set_unit_of_measurement(const std::string &unit_of_measurement) { + this->unit_of_measurement_ = unit_of_measurement; +} + +std::string NumberTraits::get_unit_of_measurement() { + if (this->unit_of_measurement_.has_value()) + return *this->unit_of_measurement_; + return ""; +} + +} // namespace number +} // namespace esphome diff --git a/esphome/components/number/number_traits.h b/esphome/components/number/number_traits.h new file mode 100644 index 0000000000..47756ff66f --- /dev/null +++ b/esphome/components/number/number_traits.h @@ -0,0 +1,44 @@ +#pragma once + +#include "esphome/core/helpers.h" + +namespace esphome { +namespace number { + +enum NumberMode : uint8_t { + NUMBER_MODE_AUTO = 0, + NUMBER_MODE_BOX = 1, + NUMBER_MODE_SLIDER = 2, +}; + +class NumberTraits { + public: + // Set/get the number value boundaries. + void set_min_value(float min_value) { min_value_ = min_value; } + float get_min_value() const { return min_value_; } + void set_max_value(float max_value) { max_value_ = max_value; } + float get_max_value() const { return max_value_; } + + // Set/get the step size for incrementing or decrementing the number value. + void set_step(float step) { step_ = step; } + float get_step() const { return step_; } + + /// Manually set the unit of measurement. + void set_unit_of_measurement(const std::string &unit_of_measurement); + /// Get the unit of measurement, using the manual override if set. + std::string get_unit_of_measurement(); + + // Set/get the frontend mode. + void set_mode(NumberMode mode) { this->mode_ = mode; } + NumberMode get_mode() const { return this->mode_; } + + protected: + float min_value_ = NAN; + float max_value_ = NAN; + float step_ = NAN; + optional unit_of_measurement_; ///< Unit of measurement override + NumberMode mode_{NUMBER_MODE_AUTO}; +}; + +} // namespace number +} // namespace esphome diff --git a/tests/test5.yaml b/tests/test5.yaml index e38f39eab6..ffda860377 100644 --- a/tests/test5.yaml +++ b/tests/test5.yaml @@ -120,6 +120,11 @@ number: name: My template number id: template_number_id optimistic: true + max_value: 100 + min_value: 0 + step: 5 + unit_of_measurement: "%" + mode: slider on_value: - logger.log: format: "Number changed to %f" @@ -128,11 +133,31 @@ number: - logger.log: format: "Template Number set to %f" args: ["x"] - max_value: 100 - min_value: 0 - step: 5 - unit_of_measurement: "%" - mode: slider + - number.set: + id: template_number_id + value: 50 + - number.to_min: template_number_id + - number.to_min: + id: template_number_id + - number.to_max: template_number_id + - number.to_max: + id: template_number_id + - number.increment: template_number_id + - number.increment: + id: template_number_id + cycle: false + - number.decrement: template_number_id + - number.decrement: + id: template_number_id + cycle: false + - number.operation: + id: template_number_id + operation: Increment + cycle: false + - number.operation: + id: template_number_id + operation: !lambda "return NUMBER_OP_INCREMENT;" + cycle: !lambda "return false;" - id: modbus_numbertest platform: modbus_controller From d685fdf54a2cfbae32fa98eaf6b11ec1111e2912 Mon Sep 17 00:00:00 2001 From: MFlasskamp Date: Tue, 10 May 2022 07:16:16 +0200 Subject: [PATCH 0264/3388] mask deprecated adc_gpio_init() for esp32-s2 (#3445) --- esphome/components/adc/adc_sensor.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/adc/adc_sensor.cpp b/esphome/components/adc/adc_sensor.cpp index ad9cf29b6f..ca87c8d11f 100644 --- a/esphome/components/adc/adc_sensor.cpp +++ b/esphome/components/adc/adc_sensor.cpp @@ -51,8 +51,8 @@ void ADCSensor::setup() { } } - // adc_gpio_init doesn't exist on ESP32-C3 or ESP32-H2 -#if !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32H2) + // adc_gpio_init doesn't exist on ESP32-S2, ESP32-C3 or ESP32-H2 +#if !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32H2) && !defined(USE_ESP32_VARIANT_ESP32S2) adc_gpio_init(ADC_UNIT_1, (adc_channel_t) channel_); #endif #endif // USE_ESP32 From 86b52df839075e956f307c2666ae5719a992efbc Mon Sep 17 00:00:00 2001 From: Martin <25747549+martgras@users.noreply.github.com> Date: Tue, 10 May 2022 07:17:55 +0200 Subject: [PATCH 0265/3388] tca9548a fix channel selection (#3417) --- esphome/components/tca9548a/tca9548a.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/tca9548a/tca9548a.cpp b/esphome/components/tca9548a/tca9548a.cpp index de0d21b968..caa3dd0655 100644 --- a/esphome/components/tca9548a/tca9548a.cpp +++ b/esphome/components/tca9548a/tca9548a.cpp @@ -41,7 +41,7 @@ i2c::ErrorCode TCA9548AComponent::switch_to_channel(uint8_t channel) { return i2c::ERROR_OK; uint8_t channel_val = 1 << channel; - auto err = this->write_register(0x70, &channel_val, 1); + auto err = this->write(&channel_val, 1); if (err == i2c::ERROR_OK) { current_channel_ = channel; } From 0e547390da32bd773a2a6b0d575feaa981275ecf Mon Sep 17 00:00:00 2001 From: Martin <25747549+martgras@users.noreply.github.com> Date: Tue, 10 May 2022 10:15:02 +0200 Subject: [PATCH 0266/3388] add support for Sen5x sensor series (#3383) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- CODEOWNERS | 1 + esphome/components/sen5x/__init__.py | 0 esphome/components/sen5x/automation.h | 21 ++ esphome/components/sen5x/sen5x.cpp | 413 ++++++++++++++++++++++++++ esphome/components/sen5x/sen5x.h | 128 ++++++++ esphome/components/sen5x/sensor.py | 241 +++++++++++++++ tests/test5.yaml | 43 +++ 7 files changed, 847 insertions(+) create mode 100644 esphome/components/sen5x/__init__.py create mode 100644 esphome/components/sen5x/automation.h create mode 100644 esphome/components/sen5x/sen5x.cpp create mode 100644 esphome/components/sen5x/sen5x.h create mode 100644 esphome/components/sen5x/sensor.py diff --git a/CODEOWNERS b/CODEOWNERS index 51719ef1aa..77c9d30c5d 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -173,6 +173,7 @@ esphome/components/sdm_meter/* @jesserockz @polyfaces esphome/components/sdp3x/* @Azimath esphome/components/selec_meter/* @sourabhjaiswal esphome/components/select/* @esphome/core +esphome/components/sen5x/* @martgras esphome/components/sensirion_common/* @martgras esphome/components/sensor/* @esphome/core esphome/components/sgp40/* @SenexCrenshaw diff --git a/esphome/components/sen5x/__init__.py b/esphome/components/sen5x/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esphome/components/sen5x/automation.h b/esphome/components/sen5x/automation.h new file mode 100644 index 0000000000..423b942000 --- /dev/null +++ b/esphome/components/sen5x/automation.h @@ -0,0 +1,21 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/automation.h" +#include "sen5x.h" + +namespace esphome { +namespace sen5x { + +template class StartFanAction : public Action { + public: + explicit StartFanAction(SEN5XComponent *sen5x) : sen5x_(sen5x) {} + + void play(Ts... x) override { this->sen5x_->start_fan_cleaning(); } + + protected: + SEN5XComponent *sen5x_; +}; + +} // namespace sen5x +} // namespace esphome diff --git a/esphome/components/sen5x/sen5x.cpp b/esphome/components/sen5x/sen5x.cpp new file mode 100644 index 0000000000..865fae373b --- /dev/null +++ b/esphome/components/sen5x/sen5x.cpp @@ -0,0 +1,413 @@ +#include "sen5x.h" +#include "esphome/core/hal.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace sen5x { + +static const char *const TAG = "sen5x"; + +static const uint16_t SEN5X_CMD_AUTO_CLEANING_INTERVAL = 0x8004; +static const uint16_t SEN5X_CMD_GET_DATA_READY_STATUS = 0x0202; +static const uint16_t SEN5X_CMD_GET_FIRMWARE_VERSION = 0xD100; +static const uint16_t SEN5X_CMD_GET_PRODUCT_NAME = 0xD014; +static const uint16_t SEN5X_CMD_GET_SERIAL_NUMBER = 0xD033; +static const uint16_t SEN5X_CMD_NOX_ALGORITHM_TUNING = 0x60E1; +static const uint16_t SEN5X_CMD_READ_MEASUREMENT = 0x03C4; +static const uint16_t SEN5X_CMD_RHT_ACCELERATION_MODE = 0x60F7; +static const uint16_t SEN5X_CMD_START_CLEANING_FAN = 0x5607; +static const uint16_t SEN5X_CMD_START_MEASUREMENTS = 0x0021; +static const uint16_t SEN5X_CMD_START_MEASUREMENTS_RHT_ONLY = 0x0037; +static const uint16_t SEN5X_CMD_STOP_MEASUREMENTS = 0x3f86; +static const uint16_t SEN5X_CMD_TEMPERATURE_COMPENSATION = 0x60B2; +static const uint16_t SEN5X_CMD_VOC_ALGORITHM_STATE = 0x6181; +static const uint16_t SEN5X_CMD_VOC_ALGORITHM_TUNING = 0x60D0; + +void SEN5XComponent::setup() { + ESP_LOGCONFIG(TAG, "Setting up sen5x..."); + + // the sensor needs 1000 ms to enter the idle state + this->set_timeout(1000, [this]() { + // Check if measurement is ready before reading the value + if (!this->write_command(SEN5X_CMD_GET_DATA_READY_STATUS)) { + ESP_LOGE(TAG, "Failed to write data ready status command"); + this->mark_failed(); + return; + } + + uint16_t raw_read_status; + if (!this->read_data(raw_read_status)) { + ESP_LOGE(TAG, "Failed to read data ready status"); + this->mark_failed(); + return; + } + + uint32_t stop_measurement_delay = 0; + // In order to query the device periodic measurement must be ceased + if (raw_read_status) { + ESP_LOGD(TAG, "Sensor has data available, stopping periodic measurement"); + if (!this->write_command(SEN5X_CMD_STOP_MEASUREMENTS)) { + ESP_LOGE(TAG, "Failed to stop measurements"); + this->mark_failed(); + return; + } + // According to the SEN5x datasheet the sensor will only respond to other commands after waiting 200 ms after + // issuing the stop_periodic_measurement command + stop_measurement_delay = 200; + } + this->set_timeout(stop_measurement_delay, [this]() { + uint16_t raw_serial_number[3]; + if (!this->get_register(SEN5X_CMD_GET_SERIAL_NUMBER, raw_serial_number, 3, 20)) { + ESP_LOGE(TAG, "Failed to read serial number"); + this->error_code_ = SERIAL_NUMBER_IDENTIFICATION_FAILED; + this->mark_failed(); + return; + } + this->serial_number_[0] = static_cast(uint16_t(raw_serial_number[0]) & 0xFF); + this->serial_number_[1] = static_cast(raw_serial_number[0] & 0xFF); + this->serial_number_[2] = static_cast(raw_serial_number[1] >> 8); + ESP_LOGD(TAG, "Serial number %02d.%02d.%02d", serial_number_[0], serial_number_[1], serial_number_[2]); + + uint16_t raw_product_name[16]; + if (!this->get_register(SEN5X_CMD_GET_PRODUCT_NAME, raw_product_name, 16, 20)) { + ESP_LOGE(TAG, "Failed to read product name"); + this->error_code_ = PRODUCT_NAME_FAILED; + this->mark_failed(); + return; + } + // 2 ASCII bytes are encoded in an int + const uint16_t *current_int = raw_product_name; + char current_char; + uint8_t max = 16; + do { + // first char + current_char = *current_int >> 8; + if (current_char) { + product_name_.push_back(current_char); + // second char + current_char = *current_int & 0xFF; + if (current_char) + product_name_.push_back(current_char); + } + current_int++; + } while (current_char && --max); + + Sen5xType sen5x_type = UNKNOWN; + if (product_name_ == "SEN50") { + sen5x_type = SEN50; + } else { + if (product_name_ == "SEN54") { + sen5x_type = SEN54; + } else { + if (product_name_ == "SEN55") { + sen5x_type = SEN55; + } + } + ESP_LOGD(TAG, "Productname %s", product_name_.c_str()); + } + if (this->humidity_sensor_ && sen5x_type == SEN50) { + ESP_LOGE(TAG, "For Relative humidity a SEN54 OR SEN55 is required. You are using a <%s> sensor", + this->product_name_.c_str()); + this->humidity_sensor_ = nullptr; // mark as not used + } + if (this->temperature_sensor_ && sen5x_type == SEN50) { + ESP_LOGE(TAG, "For Temperature a SEN54 OR SEN55 is required. You are using a <%s> sensor", + this->product_name_.c_str()); + this->temperature_sensor_ = nullptr; // mark as not used + } + if (this->voc_sensor_ && sen5x_type == SEN50) { + ESP_LOGE(TAG, "For VOC a SEN54 OR SEN55 is required. You are using a <%s> sensor", this->product_name_.c_str()); + this->voc_sensor_ = nullptr; // mark as not used + } + if (this->nox_sensor_ && sen5x_type != SEN55) { + ESP_LOGE(TAG, "For NOx a SEN55 is required. You are using a <%s> sensor", this->product_name_.c_str()); + this->nox_sensor_ = nullptr; // mark as not used + } + + if (!this->get_register(SEN5X_CMD_GET_FIRMWARE_VERSION, this->firmware_version_, 20)) { + ESP_LOGE(TAG, "Failed to read firmware version"); + this->error_code_ = FIRMWARE_FAILED; + this->mark_failed(); + return; + } + this->firmware_version_ >>= 8; + ESP_LOGD(TAG, "Firmware version %d", this->firmware_version_); + + if (this->voc_sensor_ && this->store_baseline_) { + // Hash with compilation time + // This ensures the baseline storage is cleared after OTA + uint32_t hash = fnv1_hash(App.get_compilation_time()); + this->pref_ = global_preferences->make_preference(hash, true); + + if (this->pref_.load(&this->voc_baselines_storage_)) { + ESP_LOGI(TAG, "Loaded VOC baseline state0: 0x%04X, state1: 0x%04X", this->voc_baselines_storage_.state0, + voc_baselines_storage_.state1); + } + + // Initialize storage timestamp + this->seconds_since_last_store_ = 0; + + if (this->voc_baselines_storage_.state0 > 0 && this->voc_baselines_storage_.state1 > 0) { + ESP_LOGI(TAG, "Setting VOC baseline from save state0: 0x%04X, state1: 0x%04X", + this->voc_baselines_storage_.state0, voc_baselines_storage_.state1); + uint16_t states[4]; + + states[0] = voc_baselines_storage_.state0 >> 16; + states[1] = voc_baselines_storage_.state0 & 0xFFFF; + states[2] = voc_baselines_storage_.state1 >> 16; + states[3] = voc_baselines_storage_.state1 & 0xFFFF; + + if (!this->write_command(SEN5X_CMD_VOC_ALGORITHM_STATE, states, 4)) { + ESP_LOGE(TAG, "Failed to set VOC baseline from saved state"); + } + } + } + bool result; + if (this->auto_cleaning_interval_.has_value()) { + // override default value + result = write_command(SEN5X_CMD_AUTO_CLEANING_INTERVAL, this->auto_cleaning_interval_.value()); + } else { + result = write_command(SEN5X_CMD_AUTO_CLEANING_INTERVAL); + } + if (result) { + delay(20); + uint16_t secs[2]; + if (this->read_data(secs, 2)) { + auto_cleaning_interval_ = secs[0] << 16 | secs[1]; + } + } + if (acceleration_mode_.has_value()) { + result = this->write_command(SEN5X_CMD_RHT_ACCELERATION_MODE, acceleration_mode_.value()); + } else { + result = this->write_command(SEN5X_CMD_RHT_ACCELERATION_MODE); + } + if (!result) { + ESP_LOGE(TAG, "Failed to set rh/t acceleration mode"); + this->error_code_ = COMMUNICATION_FAILED; + this->mark_failed(); + return; + } + delay(20); + if (!acceleration_mode_.has_value()) { + uint16_t mode; + if (this->read_data(mode)) { + this->acceleration_mode_ = RhtAccelerationMode(mode); + } else { + ESP_LOGE(TAG, "Failed to read RHT Acceleration mode"); + } + } + if (this->voc_tuning_params_.has_value()) + this->write_tuning_parameters_(SEN5X_CMD_VOC_ALGORITHM_TUNING, this->voc_tuning_params_.value()); + if (this->nox_tuning_params_.has_value()) + this->write_tuning_parameters_(SEN5X_CMD_NOX_ALGORITHM_TUNING, this->nox_tuning_params_.value()); + + if (this->temperature_compensation_.has_value()) + this->write_temperature_compensation_(this->temperature_compensation_.value()); + + // Finally start sensor measurements + auto cmd = SEN5X_CMD_START_MEASUREMENTS_RHT_ONLY; + if (this->pm_1_0_sensor_ || this->pm_2_5_sensor_ || this->pm_4_0_sensor_ || this->pm_10_0_sensor_) { + // if any of the gas sensors are active we need a full measurement + cmd = SEN5X_CMD_START_MEASUREMENTS; + } + + if (!this->write_command(cmd)) { + ESP_LOGE(TAG, "Error starting continuous measurements."); + this->error_code_ = MEASUREMENT_INIT_FAILED; + this->mark_failed(); + return; + } + initialized_ = true; + ESP_LOGD(TAG, "Sensor initialized"); + }); + }); +} + +void SEN5XComponent::dump_config() { + ESP_LOGCONFIG(TAG, "sen5x:"); + LOG_I2C_DEVICE(this); + if (this->is_failed()) { + switch (this->error_code_) { + case COMMUNICATION_FAILED: + ESP_LOGW(TAG, "Communication failed! Is the sensor connected?"); + break; + case MEASUREMENT_INIT_FAILED: + ESP_LOGW(TAG, "Measurement Initialization failed!"); + break; + case SERIAL_NUMBER_IDENTIFICATION_FAILED: + ESP_LOGW(TAG, "Unable to read sensor serial id"); + break; + case PRODUCT_NAME_FAILED: + ESP_LOGW(TAG, "Unable to read product name"); + break; + case FIRMWARE_FAILED: + ESP_LOGW(TAG, "Unable to read sensor firmware version"); + break; + default: + ESP_LOGW(TAG, "Unknown setup error!"); + break; + } + } + ESP_LOGCONFIG(TAG, " Productname: %s", this->product_name_.c_str()); + ESP_LOGCONFIG(TAG, " Firmware version: %d", this->firmware_version_); + ESP_LOGCONFIG(TAG, " Serial number %02d.%02d.%02d", serial_number_[0], serial_number_[1], serial_number_[2]); + if (this->auto_cleaning_interval_.has_value()) { + ESP_LOGCONFIG(TAG, " Auto auto cleaning interval %d seconds", auto_cleaning_interval_.value()); + } + if (this->acceleration_mode_.has_value()) { + switch (this->acceleration_mode_.value()) { + case LOW_ACCELERATION: + ESP_LOGCONFIG(TAG, " Low RH/T acceleration mode"); + break; + case MEDIUM_ACCELERATION: + ESP_LOGCONFIG(TAG, " Medium RH/T accelertion mode"); + break; + case HIGH_ACCELERATION: + ESP_LOGCONFIG(TAG, " High RH/T accelertion mode"); + break; + } + } + LOG_UPDATE_INTERVAL(this); + LOG_SENSOR(" ", "PM 1.0", this->pm_1_0_sensor_); + LOG_SENSOR(" ", "PM 2.5", this->pm_2_5_sensor_); + LOG_SENSOR(" ", "PM 4.0", this->pm_4_0_sensor_); + LOG_SENSOR(" ", "PM 10.0", this->pm_10_0_sensor_); + LOG_SENSOR(" ", "Temperature", this->temperature_sensor_); + LOG_SENSOR(" ", "Humidity", this->humidity_sensor_); + LOG_SENSOR(" ", "VOC", this->voc_sensor_); // SEN54 and SEN55 only + LOG_SENSOR(" ", "NOx", this->nox_sensor_); // SEN55 only +} + +void SEN5XComponent::update() { + if (!initialized_) { + return; + } + + // Store baselines after defined interval or if the difference between current and stored baseline becomes too + // much + if (this->store_baseline_ && this->seconds_since_last_store_ > SHORTEST_BASELINE_STORE_INTERVAL) { + if (this->write_command(SEN5X_CMD_VOC_ALGORITHM_STATE)) { + // run it a bit later to avoid adding a delay here + this->set_timeout(550, [this]() { + uint16_t states[4]; + if (this->read_data(states, 4)) { + uint32_t state0 = states[0] << 16 | states[1]; + uint32_t state1 = states[2] << 16 | states[3]; + if ((uint32_t) std::abs(static_cast(this->voc_baselines_storage_.state0 - state0)) > + MAXIMUM_STORAGE_DIFF || + (uint32_t) std::abs(static_cast(this->voc_baselines_storage_.state1 - state1)) > + MAXIMUM_STORAGE_DIFF) { + this->seconds_since_last_store_ = 0; + this->voc_baselines_storage_.state0 = state0; + this->voc_baselines_storage_.state1 = state1; + + if (this->pref_.save(&this->voc_baselines_storage_)) { + ESP_LOGI(TAG, "Stored VOC baseline state0: 0x%04X ,state1: 0x%04X", this->voc_baselines_storage_.state0, + voc_baselines_storage_.state1); + } else { + ESP_LOGW(TAG, "Could not store VOC baselines"); + } + } + } + }); + } + } + + if (!this->write_command(SEN5X_CMD_READ_MEASUREMENT)) { + this->status_set_warning(); + ESP_LOGD(TAG, "write error read measurement (%d)", this->last_error_); + return; + } + this->set_timeout(20, [this]() { + uint16_t measurements[8]; + + if (!this->read_data(measurements, 8)) { + this->status_set_warning(); + ESP_LOGD(TAG, "read data error (%d)", this->last_error_); + return; + } + float pm_1_0 = measurements[0] / 10.0; + if (measurements[0] == 0xFFFF) + pm_1_0 = NAN; + float pm_2_5 = measurements[1] / 10.0; + if (measurements[1] == 0xFFFF) + pm_2_5 = NAN; + float pm_4_0 = measurements[2] / 10.0; + if (measurements[2] == 0xFFFF) + pm_4_0 = NAN; + float pm_10_0 = measurements[3] / 10.0; + if (measurements[3] == 0xFFFF) + pm_10_0 = NAN; + float humidity = measurements[4] / 100.0; + if (measurements[4] == 0xFFFF) + humidity = NAN; + float temperature = measurements[5] / 200.0; + if (measurements[5] == 0xFFFF) + temperature = NAN; + float voc = measurements[6] / 10.0; + if (measurements[6] == 0xFFFF) + voc = NAN; + float nox = measurements[7] / 10.0; + if (measurements[7] == 0xFFFF) + nox = NAN; + + if (this->pm_1_0_sensor_ != nullptr) + this->pm_1_0_sensor_->publish_state(pm_1_0); + if (this->pm_2_5_sensor_ != nullptr) + this->pm_2_5_sensor_->publish_state(pm_2_5); + if (this->pm_4_0_sensor_ != nullptr) + this->pm_4_0_sensor_->publish_state(pm_4_0); + if (this->pm_10_0_sensor_ != nullptr) + this->pm_10_0_sensor_->publish_state(pm_10_0); + if (this->temperature_sensor_ != nullptr) + this->temperature_sensor_->publish_state(temperature); + if (this->humidity_sensor_ != nullptr) + this->humidity_sensor_->publish_state(humidity); + if (this->voc_sensor_ != nullptr) + this->voc_sensor_->publish_state(voc); + if (this->nox_sensor_ != nullptr) + this->nox_sensor_->publish_state(nox); + this->status_clear_warning(); + }); +} + +bool SEN5XComponent::write_tuning_parameters_(uint16_t i2c_command, const GasTuning &tuning) { + uint16_t params[6]; + params[0] = tuning.index_offset; + params[1] = tuning.learning_time_offset_hours; + params[2] = tuning.learning_time_gain_hours; + params[3] = tuning.gating_max_duration_minutes; + params[4] = tuning.std_initial; + params[5] = tuning.gain_factor; + auto result = write_command(i2c_command, params, 6); + if (!result) { + ESP_LOGE(TAG, "set tuning parameters failed. i2c command=%0xX, err=%d", i2c_command, this->last_error_); + } + return result; +} + +bool SEN5XComponent::write_temperature_compensation_(const TemperatureCompensation &compensation) { + uint16_t params[3]; + params[0] = compensation.offset; + params[1] = compensation.normalized_offset_slope; + params[2] = compensation.time_constant; + if (!write_command(SEN5X_CMD_TEMPERATURE_COMPENSATION, params, 3)) { + ESP_LOGE(TAG, "set temperature_compensation failed. Err=%d", this->last_error_); + return false; + } + return true; +} + +bool SEN5XComponent::start_fan_cleaning() { + if (!write_command(SEN5X_CMD_START_CLEANING_FAN)) { + this->status_set_warning(); + ESP_LOGE(TAG, "write error start fan (%d)", this->last_error_); + return false; + } else { + ESP_LOGD(TAG, "Fan auto clean started"); + } + return true; +} + +} // namespace sen5x +} // namespace esphome diff --git a/esphome/components/sen5x/sen5x.h b/esphome/components/sen5x/sen5x.h new file mode 100644 index 0000000000..f306003a82 --- /dev/null +++ b/esphome/components/sen5x/sen5x.h @@ -0,0 +1,128 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/sensirion_common/i2c_sensirion.h" +#include "esphome/core/application.h" +#include "esphome/core/preferences.h" + +namespace esphome { +namespace sen5x { + +enum ERRORCODE { + COMMUNICATION_FAILED, + SERIAL_NUMBER_IDENTIFICATION_FAILED, + MEASUREMENT_INIT_FAILED, + PRODUCT_NAME_FAILED, + FIRMWARE_FAILED, + UNKNOWN +}; + +// Shortest time interval of 3H for storing baseline values. +// Prevents wear of the flash because of too many write operations +const uint32_t SHORTEST_BASELINE_STORE_INTERVAL = 10800; +// Store anyway if the baseline difference exceeds the max storage diff value +const uint32_t MAXIMUM_STORAGE_DIFF = 50; + +struct Sen5xBaselines { + int32_t state0; + int32_t state1; +} PACKED; // NOLINT + +enum RhtAccelerationMode : uint16_t { LOW_ACCELERATION = 0, MEDIUM_ACCELERATION = 1, HIGH_ACCELERATION = 2 }; + +struct GasTuning { + uint16_t index_offset; + uint16_t learning_time_offset_hours; + uint16_t learning_time_gain_hours; + uint16_t gating_max_duration_minutes; + uint16_t std_initial; + uint16_t gain_factor; +}; + +struct TemperatureCompensation { + uint16_t offset; + uint16_t normalized_offset_slope; + uint16_t time_constant; +}; + +class SEN5XComponent : public PollingComponent, public sensirion_common::SensirionI2CDevice { + public: + float get_setup_priority() const override { return setup_priority::DATA; } + void setup() override; + void dump_config() override; + void update() override; + + enum Sen5xType { SEN50, SEN54, SEN55, UNKNOWN }; + + void set_pm_1_0_sensor(sensor::Sensor *pm_1_0) { pm_1_0_sensor_ = pm_1_0; } + void set_pm_2_5_sensor(sensor::Sensor *pm_2_5) { pm_2_5_sensor_ = pm_2_5; } + void set_pm_4_0_sensor(sensor::Sensor *pm_4_0) { pm_4_0_sensor_ = pm_4_0; } + void set_pm_10_0_sensor(sensor::Sensor *pm_10_0) { pm_10_0_sensor_ = pm_10_0; } + + void set_voc_sensor(sensor::Sensor *voc_sensor) { voc_sensor_ = voc_sensor; } + void set_nox_sensor(sensor::Sensor *nox_sensor) { nox_sensor_ = nox_sensor; } + void set_humidity_sensor(sensor::Sensor *humidity_sensor) { humidity_sensor_ = humidity_sensor; } + void set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_sensor; } + void set_store_baseline(bool store_baseline) { store_baseline_ = store_baseline; } + void set_acceleration_mode(RhtAccelerationMode mode) { acceleration_mode_ = mode; } + void set_auto_cleaning_interval(uint32_t auto_cleaning_interval) { auto_cleaning_interval_ = auto_cleaning_interval; } + void set_voc_algorithm_tuning(uint16_t index_offset, uint16_t learning_time_offset_hours, + uint16_t learning_time_gain_hours, uint16_t gating_max_duration_minutes, + uint16_t std_initial, uint16_t gain_factor) { + voc_tuning_params_.value().index_offset = index_offset; + voc_tuning_params_.value().learning_time_offset_hours = learning_time_offset_hours; + voc_tuning_params_.value().learning_time_gain_hours = learning_time_gain_hours; + voc_tuning_params_.value().gating_max_duration_minutes = gating_max_duration_minutes; + voc_tuning_params_.value().std_initial = std_initial; + voc_tuning_params_.value().gain_factor = gain_factor; + } + void set_nox_algorithm_tuning(uint16_t index_offset, uint16_t learning_time_offset_hours, + uint16_t learning_time_gain_hours, uint16_t gating_max_duration_minutes, + uint16_t gain_factor) { + nox_tuning_params_.value().index_offset = index_offset; + nox_tuning_params_.value().learning_time_offset_hours = learning_time_offset_hours; + nox_tuning_params_.value().learning_time_gain_hours = learning_time_gain_hours; + nox_tuning_params_.value().gating_max_duration_minutes = gating_max_duration_minutes; + nox_tuning_params_.value().std_initial = 50; + nox_tuning_params_.value().gain_factor = gain_factor; + } + void set_temperature_compensation(float offset, float normalized_offset_slope, uint16_t time_constant) { + temperature_compensation_.value().offset = offset * 200; + temperature_compensation_.value().normalized_offset_slope = normalized_offset_slope * 100; + temperature_compensation_.value().time_constant = time_constant; + } + bool start_fan_cleaning(); + + protected: + bool write_tuning_parameters_(uint16_t i2c_command, const GasTuning &tuning); + bool write_temperature_compensation_(const TemperatureCompensation &compensation); + ERRORCODE error_code_; + bool initialized_{false}; + sensor::Sensor *pm_1_0_sensor_{nullptr}; + sensor::Sensor *pm_2_5_sensor_{nullptr}; + sensor::Sensor *pm_4_0_sensor_{nullptr}; + sensor::Sensor *pm_10_0_sensor_{nullptr}; + // SEN54 and SEN55 only + sensor::Sensor *temperature_sensor_{nullptr}; + sensor::Sensor *humidity_sensor_{nullptr}; + sensor::Sensor *voc_sensor_{nullptr}; + // SEN55 only + sensor::Sensor *nox_sensor_{nullptr}; + + std::string product_name_; + uint8_t serial_number_[4]; + uint16_t firmware_version_; + Sen5xBaselines voc_baselines_storage_; + bool store_baseline_; + uint32_t seconds_since_last_store_; + ESPPreferenceObject pref_; + optional acceleration_mode_; + optional auto_cleaning_interval_; + optional voc_tuning_params_; + optional nox_tuning_params_; + optional temperature_compensation_; +}; + +} // namespace sen5x +} // namespace esphome diff --git a/esphome/components/sen5x/sensor.py b/esphome/components/sen5x/sensor.py new file mode 100644 index 0000000000..489fda8335 --- /dev/null +++ b/esphome/components/sen5x/sensor.py @@ -0,0 +1,241 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import i2c, sensor, sensirion_common +from esphome import automation +from esphome.automation import maybe_simple_id + +from esphome.const import ( + CONF_HUMIDITY, + CONF_ID, + CONF_OFFSET, + CONF_PM_1_0, + CONF_PM_10_0, + CONF_PM_2_5, + CONF_PM_4_0, + CONF_STORE_BASELINE, + CONF_TEMPERATURE, + DEVICE_CLASS_HUMIDITY, + DEVICE_CLASS_NITROUS_OXIDE, + DEVICE_CLASS_PM1, + DEVICE_CLASS_PM10, + DEVICE_CLASS_PM25, + DEVICE_CLASS_TEMPERATURE, + DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS, + ICON_CHEMICAL_WEAPON, + ICON_RADIATOR, + ICON_THERMOMETER, + ICON_WATER_PERCENT, + STATE_CLASS_MEASUREMENT, + UNIT_CELSIUS, + UNIT_MICROGRAMS_PER_CUBIC_METER, + UNIT_PERCENT, +) + +CODEOWNERS = ["@martgras"] +DEPENDENCIES = ["i2c"] +AUTO_LOAD = ["sensirion_common"] + +sen5x_ns = cg.esphome_ns.namespace("sen5x") +SEN5XComponent = sen5x_ns.class_( + "SEN5XComponent", cg.PollingComponent, sensirion_common.SensirionI2CDevice +) +RhtAccelerationMode = sen5x_ns.enum("RhtAccelerationMode") + +CONF_ACCELERATION_MODE = "acceleration_mode" +CONF_ALGORITHM_TUNING = "algorithm_tuning" +CONF_AUTO_CLEANING_INTERVAL = "auto_cleaning_interval" +CONF_GAIN_FACTOR = "gain_factor" +CONF_GATING_MAX_DURATION_MINUTES = "gating_max_duration_minutes" +CONF_INDEX_OFFSET = "index_offset" +CONF_LEARNING_TIME_GAIN_HOURS = "learning_time_gain_hours" +CONF_LEARNING_TIME_OFFSET_HOURS = "learning_time_offset_hours" +CONF_NORMALIZED_OFFSET_SLOPE = "normalized_offset_slope" +CONF_NOX = "nox" +CONF_STD_INITIAL = "std_initial" +CONF_TEMPERATURE_COMPENSATION = "temperature_compensation" +CONF_TIME_CONSTANT = "time_constant" +CONF_VOC = "voc" +CONF_VOC_BASELINE = "voc_baseline" + + +# Actions +StartFanAction = sen5x_ns.class_("StartFanAction", automation.Action) + +ACCELERATION_MODES = { + "low": RhtAccelerationMode.LOW_ACCELERATION, + "medium": RhtAccelerationMode.MEDIUM_ACCELERATION, + "high": RhtAccelerationMode.HIGH_ACCELERATION, +} + +GAS_SENSOR = cv.Schema( + { + cv.Optional(CONF_ALGORITHM_TUNING): cv.Schema( + { + cv.Optional(CONF_INDEX_OFFSET, default=100): cv.int_range(1, 250), + cv.Optional(CONF_LEARNING_TIME_OFFSET_HOURS, default=12): cv.int_range( + 1, 1000 + ), + cv.Optional(CONF_LEARNING_TIME_GAIN_HOURS, default=12): cv.int_range( + 1, 1000 + ), + cv.Optional( + CONF_GATING_MAX_DURATION_MINUTES, default=720 + ): cv.int_range(0, 3000), + cv.Optional(CONF_STD_INITIAL, default=50): cv.int_, + cv.Optional(CONF_GAIN_FACTOR, default=230): cv.int_range(1, 1000), + } + ) + } +) + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(SEN5XComponent), + cv.Optional(CONF_PM_1_0): sensor.sensor_schema( + unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER, + icon=ICON_CHEMICAL_WEAPON, + accuracy_decimals=2, + device_class=DEVICE_CLASS_PM1, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_PM_2_5): sensor.sensor_schema( + unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER, + icon=ICON_CHEMICAL_WEAPON, + accuracy_decimals=2, + device_class=DEVICE_CLASS_PM25, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_PM_4_0): sensor.sensor_schema( + unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER, + icon=ICON_CHEMICAL_WEAPON, + accuracy_decimals=2, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_PM_10_0): sensor.sensor_schema( + unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER, + icon=ICON_CHEMICAL_WEAPON, + accuracy_decimals=2, + device_class=DEVICE_CLASS_PM10, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_AUTO_CLEANING_INTERVAL): cv.time_period_in_seconds_, + cv.Optional(CONF_VOC): sensor.sensor_schema( + icon=ICON_RADIATOR, + accuracy_decimals=0, + device_class=DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS, + state_class=STATE_CLASS_MEASUREMENT, + ).extend(GAS_SENSOR), + cv.Optional(CONF_NOX): sensor.sensor_schema( + icon=ICON_RADIATOR, + accuracy_decimals=0, + device_class=DEVICE_CLASS_NITROUS_OXIDE, + state_class=STATE_CLASS_MEASUREMENT, + ).extend(GAS_SENSOR), + cv.Optional(CONF_STORE_BASELINE, default=True): cv.boolean, + cv.Optional(CONF_VOC_BASELINE): cv.hex_uint16_t, + cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( + unit_of_measurement=UNIT_CELSIUS, + icon=ICON_THERMOMETER, + accuracy_decimals=2, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_HUMIDITY): sensor.sensor_schema( + unit_of_measurement=UNIT_PERCENT, + icon=ICON_WATER_PERCENT, + accuracy_decimals=2, + device_class=DEVICE_CLASS_HUMIDITY, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_TEMPERATURE_COMPENSATION): cv.Schema( + { + cv.Optional(CONF_OFFSET, default=0): cv.float_, + cv.Optional(CONF_NORMALIZED_OFFSET_SLOPE, default=0): cv.percentage, + cv.Optional(CONF_TIME_CONSTANT, default=0): cv.int_, + } + ), + cv.Optional(CONF_ACCELERATION_MODE): cv.enum(ACCELERATION_MODES), + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x69)) +) + +SENSOR_MAP = { + CONF_PM_1_0: "set_pm_1_0_sensor", + CONF_PM_2_5: "set_pm_2_5_sensor", + CONF_PM_4_0: "set_pm_4_0_sensor", + CONF_PM_10_0: "set_pm_10_0_sensor", + CONF_VOC: "set_voc_sensor", + CONF_NOX: "set_nox_sensor", + CONF_TEMPERATURE: "set_temperature_sensor", + CONF_HUMIDITY: "set_humidity_sensor", +} + +SETTING_MAP = { + CONF_AUTO_CLEANING_INTERVAL: "set_auto_cleaning_interval", + CONF_ACCELERATION_MODE: "set_acceleration_mode", +} + + +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) + + for key, funcName in SETTING_MAP.items(): + if key in config: + cg.add(getattr(var, funcName)(config[key])) + + for key, funcName in SENSOR_MAP.items(): + if key in config: + sens = await sensor.new_sensor(config[key]) + cg.add(getattr(var, funcName)(sens)) + + if CONF_VOC in config and CONF_ALGORITHM_TUNING in config[CONF_VOC]: + cfg = config[CONF_VOC][CONF_ALGORITHM_TUNING] + cg.add( + var.set_voc_algorithm_tuning( + cfg[CONF_INDEX_OFFSET], + cfg[CONF_LEARNING_TIME_OFFSET_HOURS], + cfg[CONF_LEARNING_TIME_GAIN_HOURS], + cfg[CONF_GATING_MAX_DURATION_MINUTES], + cfg[CONF_STD_INITIAL], + cfg[CONF_GAIN_FACTOR], + ) + ) + if CONF_NOX in config and CONF_ALGORITHM_TUNING in config[CONF_NOX]: + cfg = config[CONF_NOX][CONF_ALGORITHM_TUNING] + cg.add( + var.set_nox_algorithm_tuning( + cfg[CONF_INDEX_OFFSET], + cfg[CONF_LEARNING_TIME_OFFSET_HOURS], + cfg[CONF_LEARNING_TIME_GAIN_HOURS], + cfg[CONF_GATING_MAX_DURATION_MINUTES], + cfg[CONF_GAIN_FACTOR], + ) + ) + if CONF_TEMPERATURE_COMPENSATION in config: + cg.add( + var.set_temperature_compensation( + config[CONF_TEMPERATURE_COMPENSATION][CONF_OFFSET], + config[CONF_TEMPERATURE_COMPENSATION][CONF_NORMALIZED_OFFSET_SLOPE], + config[CONF_TEMPERATURE_COMPENSATION][CONF_TIME_CONSTANT], + ) + ) + + +SEN5X_ACTION_SCHEMA = maybe_simple_id( + { + cv.Required(CONF_ID): cv.use_id(SEN5XComponent), + } +) + + +@automation.register_action( + "sen5x.start_fan_autoclean", StartFanAction, SEN5X_ACTION_SCHEMA +) +async def sen54_fan_to_code(config, action_id, template_arg, args): + paren = await cg.get_variable(config[CONF_ID]) + return cg.new_Pvariable(action_id, template_arg, paren) diff --git a/tests/test5.yaml b/tests/test5.yaml index ffda860377..35f6b14f2a 100644 --- a/tests/test5.yaml +++ b/tests/test5.yaml @@ -285,6 +285,49 @@ sensor: address: 0x77 iir_filter: 2X + - platform: sen5x + id: sen54 + temperature: + name: "Temperature" + accuracy_decimals: 1 + humidity: + name: "Humidity" + accuracy_decimals: 0 + pm_1_0: + name: " PM <1µm Weight concentration" + id: pm_1_0 + accuracy_decimals: 1 + pm_2_5: + name: " PM <2.5µm Weight concentration" + id: pm_2_5 + accuracy_decimals: 1 + pm_4_0: + name: " PM <4µm Weight concentration" + id: pm_4_0 + accuracy_decimals: 1 + pm_10_0: + name: " PM <10µm Weight concentration" + id: pm_10_0 + accuracy_decimals: 1 + nox: + name: "NOx" + voc: + name: "VOC" + algorithm_tuning: + index_offset: 100 + learning_time_offset_hours: 12 + learning_time_gain_hours: 12 + gating_max_duration_minutes: 180 + std_initial: 50 + gain_factor: 230 + temperature_compensation: + offset: 0 + normalized_offset_slope: 0 + time_constant: 0 + acceleration_mode: low + store_baseline: true + address: 0x69 + script: - id: automation_test then: From 53e0fe8e51c4f357e7e9a5974fc6c95143e3200d Mon Sep 17 00:00:00 2001 From: Andre Lengwenus Date: Tue, 10 May 2022 11:05:49 +0200 Subject: [PATCH 0267/3388] Add SML (Smart Message Language) platform for energy meters (#2396) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- CODEOWNERS | 1 + esphome/components/sml/__init__.py | 38 +++++ esphome/components/sml/constants.h | 48 ++++++ esphome/components/sml/sensor/__init__.py | 30 ++++ esphome/components/sml/sensor/sml_sensor.cpp | 41 +++++ esphome/components/sml/sensor/sml_sensor.h | 16 ++ esphome/components/sml/sml.cpp | 146 ++++++++++++++++++ esphome/components/sml/sml.h | 47 ++++++ esphome/components/sml/sml_parser.cpp | 131 ++++++++++++++++ esphome/components/sml/sml_parser.h | 54 +++++++ .../components/sml/text_sensor/__init__.py | 43 ++++++ .../sml/text_sensor/sml_text_sensor.cpp | 54 +++++++ .../sml/text_sensor/sml_text_sensor.h | 21 +++ 13 files changed, 670 insertions(+) create mode 100644 esphome/components/sml/__init__.py create mode 100644 esphome/components/sml/constants.h create mode 100644 esphome/components/sml/sensor/__init__.py create mode 100644 esphome/components/sml/sensor/sml_sensor.cpp create mode 100644 esphome/components/sml/sensor/sml_sensor.h create mode 100644 esphome/components/sml/sml.cpp create mode 100644 esphome/components/sml/sml.h create mode 100644 esphome/components/sml/sml_parser.cpp create mode 100644 esphome/components/sml/sml_parser.h create mode 100644 esphome/components/sml/text_sensor/__init__.py create mode 100644 esphome/components/sml/text_sensor/sml_text_sensor.cpp create mode 100644 esphome/components/sml/text_sensor/sml_text_sensor.h diff --git a/CODEOWNERS b/CODEOWNERS index 77c9d30c5d..3a511275e1 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -182,6 +182,7 @@ esphome/components/sht4x/* @sjtrny esphome/components/shutdown/* @esphome/core @jsuanet esphome/components/sim800l/* @glmnet esphome/components/sm2135/* @BoukeHaarsma23 +esphome/components/sml/* @alengwenus esphome/components/socket/* @esphome/core esphome/components/sonoff_d1/* @anatoly-savchenkov esphome/components/spi/* @esphome/core diff --git a/esphome/components/sml/__init__.py b/esphome/components/sml/__init__.py new file mode 100644 index 0000000000..f3b6dd95ef --- /dev/null +++ b/esphome/components/sml/__init__.py @@ -0,0 +1,38 @@ +import re + +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import uart +from esphome.const import CONF_ID + +CODEOWNERS = ["@alengwenus"] + +DEPENDENCIES = ["uart"] + +sml_ns = cg.esphome_ns.namespace("sml") +Sml = sml_ns.class_("Sml", cg.Component, uart.UARTDevice) +MULTI_CONF = True + +CONF_SML_ID = "sml_id" +CONF_OBIS_CODE = "obis_code" +CONF_SERVER_ID = "server_id" + +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.declare_id(Sml), + } +).extend(uart.UART_DEVICE_SCHEMA) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await uart.register_uart_device(var, config) + + +def obis_code(value): + value = cv.string(value) + match = re.match(r"^\d{1,3}-\d{1,3}:\d{1,3}\.\d{1,3}\.\d{1,3}$", value) + if match is None: + raise cv.Invalid(f"{value} is not a valid OBIS code") + return value diff --git a/esphome/components/sml/constants.h b/esphome/components/sml/constants.h new file mode 100644 index 0000000000..22114fd233 --- /dev/null +++ b/esphome/components/sml/constants.h @@ -0,0 +1,48 @@ +#pragma once + +#include + +namespace esphome { +namespace sml { + +enum SmlType : uint8_t { + SML_OCTET = 0, + SML_BOOL = 4, + SML_INT = 5, + SML_UINT = 6, + SML_LIST = 7, + SML_HEX = 10, + SML_UNDEFINED = 255 +}; + +enum SmlMessageType : uint16_t { SML_PUBLIC_OPEN_RES = 0x0101, SML_GET_LIST_RES = 0x701 }; + +enum Crc16CheckResult : uint8_t { CHECK_CRC16_FAILED, CHECK_CRC16_X25_SUCCESS, CHECK_CRC16_KERMIT_SUCCESS }; + +// masks with two-bit mapping 0x1b -> 0b01; 0x01 -> 0b10; 0x1a -> 0b11 +const uint16_t START_MASK = 0x55aa; // 0x1b 1b 1b 1b 1b 01 01 01 01 +const uint16_t END_MASK = 0x0157; // 0x1b 1b 1b 1b 1a + +const uint16_t CRC16_X25_TABLE[256] = { + 0x0000, 0x1189, 0x2312, 0x329b, 0x4624, 0x57ad, 0x6536, 0x74bf, 0x8c48, 0x9dc1, 0xaf5a, 0xbed3, 0xca6c, 0xdbe5, + 0xe97e, 0xf8f7, 0x1081, 0x0108, 0x3393, 0x221a, 0x56a5, 0x472c, 0x75b7, 0x643e, 0x9cc9, 0x8d40, 0xbfdb, 0xae52, + 0xdaed, 0xcb64, 0xf9ff, 0xe876, 0x2102, 0x308b, 0x0210, 0x1399, 0x6726, 0x76af, 0x4434, 0x55bd, 0xad4a, 0xbcc3, + 0x8e58, 0x9fd1, 0xeb6e, 0xfae7, 0xc87c, 0xd9f5, 0x3183, 0x200a, 0x1291, 0x0318, 0x77a7, 0x662e, 0x54b5, 0x453c, + 0xbdcb, 0xac42, 0x9ed9, 0x8f50, 0xfbef, 0xea66, 0xd8fd, 0xc974, 0x4204, 0x538d, 0x6116, 0x709f, 0x0420, 0x15a9, + 0x2732, 0x36bb, 0xce4c, 0xdfc5, 0xed5e, 0xfcd7, 0x8868, 0x99e1, 0xab7a, 0xbaf3, 0x5285, 0x430c, 0x7197, 0x601e, + 0x14a1, 0x0528, 0x37b3, 0x263a, 0xdecd, 0xcf44, 0xfddf, 0xec56, 0x98e9, 0x8960, 0xbbfb, 0xaa72, 0x6306, 0x728f, + 0x4014, 0x519d, 0x2522, 0x34ab, 0x0630, 0x17b9, 0xef4e, 0xfec7, 0xcc5c, 0xddd5, 0xa96a, 0xb8e3, 0x8a78, 0x9bf1, + 0x7387, 0x620e, 0x5095, 0x411c, 0x35a3, 0x242a, 0x16b1, 0x0738, 0xffcf, 0xee46, 0xdcdd, 0xcd54, 0xb9eb, 0xa862, + 0x9af9, 0x8b70, 0x8408, 0x9581, 0xa71a, 0xb693, 0xc22c, 0xd3a5, 0xe13e, 0xf0b7, 0x0840, 0x19c9, 0x2b52, 0x3adb, + 0x4e64, 0x5fed, 0x6d76, 0x7cff, 0x9489, 0x8500, 0xb79b, 0xa612, 0xd2ad, 0xc324, 0xf1bf, 0xe036, 0x18c1, 0x0948, + 0x3bd3, 0x2a5a, 0x5ee5, 0x4f6c, 0x7df7, 0x6c7e, 0xa50a, 0xb483, 0x8618, 0x9791, 0xe32e, 0xf2a7, 0xc03c, 0xd1b5, + 0x2942, 0x38cb, 0x0a50, 0x1bd9, 0x6f66, 0x7eef, 0x4c74, 0x5dfd, 0xb58b, 0xa402, 0x9699, 0x8710, 0xf3af, 0xe226, + 0xd0bd, 0xc134, 0x39c3, 0x284a, 0x1ad1, 0x0b58, 0x7fe7, 0x6e6e, 0x5cf5, 0x4d7c, 0xc60c, 0xd785, 0xe51e, 0xf497, + 0x8028, 0x91a1, 0xa33a, 0xb2b3, 0x4a44, 0x5bcd, 0x6956, 0x78df, 0x0c60, 0x1de9, 0x2f72, 0x3efb, 0xd68d, 0xc704, + 0xf59f, 0xe416, 0x90a9, 0x8120, 0xb3bb, 0xa232, 0x5ac5, 0x4b4c, 0x79d7, 0x685e, 0x1ce1, 0x0d68, 0x3ff3, 0x2e7a, + 0xe70e, 0xf687, 0xc41c, 0xd595, 0xa12a, 0xb0a3, 0x8238, 0x93b1, 0x6b46, 0x7acf, 0x4854, 0x59dd, 0x2d62, 0x3ceb, + 0x0e70, 0x1ff9, 0xf78f, 0xe606, 0xd49d, 0xc514, 0xb1ab, 0xa022, 0x92b9, 0x8330, 0x7bc7, 0x6a4e, 0x58d5, 0x495c, + 0x3de3, 0x2c6a, 0x1ef1, 0x0f78}; + +} // namespace sml +} // namespace esphome diff --git a/esphome/components/sml/sensor/__init__.py b/esphome/components/sml/sensor/__init__.py new file mode 100644 index 0000000000..a1fcc5e7ae --- /dev/null +++ b/esphome/components/sml/sensor/__init__.py @@ -0,0 +1,30 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import sensor +from esphome.const import CONF_ID + +from .. import CONF_OBIS_CODE, CONF_SERVER_ID, CONF_SML_ID, Sml, obis_code, sml_ns + +AUTO_LOAD = ["sml"] + +SmlSensor = sml_ns.class_("SmlSensor", sensor.Sensor, cg.Component) + + +CONFIG_SCHEMA = sensor.sensor_schema().extend( + { + cv.GenerateID(): cv.declare_id(SmlSensor), + cv.GenerateID(CONF_SML_ID): cv.use_id(Sml), + cv.Required(CONF_OBIS_CODE): obis_code, + cv.Optional(CONF_SERVER_ID, default=""): cv.string, + } +) + + +async def to_code(config): + var = cg.new_Pvariable( + config[CONF_ID], config[CONF_SERVER_ID], config[CONF_OBIS_CODE] + ) + await cg.register_component(var, config) + await sensor.register_sensor(var, config) + sml = await cg.get_variable(config[CONF_SML_ID]) + cg.add(sml.register_sml_listener(var)) diff --git a/esphome/components/sml/sensor/sml_sensor.cpp b/esphome/components/sml/sensor/sml_sensor.cpp new file mode 100644 index 0000000000..e9a384d275 --- /dev/null +++ b/esphome/components/sml/sensor/sml_sensor.cpp @@ -0,0 +1,41 @@ +#include "esphome/core/log.h" +#include "sml_sensor.h" +#include "../sml_parser.h" + +namespace esphome { +namespace sml { + +static const char *const TAG = "sml_sensor"; + +SmlSensor::SmlSensor(std::string server_id, std::string obis_code) + : SmlListener(std::move(server_id), std::move(obis_code)) {} + +void SmlSensor::publish_val(const ObisInfo &obis_info) { + switch (obis_info.value_type) { + case SML_INT: { + publish_state(bytes_to_int(obis_info.value)); + break; + } + case SML_BOOL: + case SML_UINT: { + publish_state(bytes_to_uint(obis_info.value)); + break; + } + case SML_OCTET: { + ESP_LOGW(TAG, "No number conversion for (%s) %s. Consider using SML TextSensor instead.", + bytes_repr(obis_info.server_id).c_str(), obis_info.code_repr().c_str()); + break; + } + } +} + +void SmlSensor::dump_config() { + LOG_SENSOR("", "SML", this); + if (!this->server_id.empty()) { + ESP_LOGCONFIG(TAG, " Server ID: %s", this->server_id.c_str()); + } + ESP_LOGCONFIG(TAG, " OBIS Code: %s", this->obis_code.c_str()); +} + +} // namespace sml +} // namespace esphome diff --git a/esphome/components/sml/sensor/sml_sensor.h b/esphome/components/sml/sensor/sml_sensor.h new file mode 100644 index 0000000000..eb7b108f94 --- /dev/null +++ b/esphome/components/sml/sensor/sml_sensor.h @@ -0,0 +1,16 @@ +#pragma once +#include "esphome/components/sml/sml.h" +#include "esphome/components/sensor/sensor.h" + +namespace esphome { +namespace sml { + +class SmlSensor : public SmlListener, public sensor::Sensor, public Component { + public: + SmlSensor(std::string server_id, std::string obis_code); + void publish_val(const ObisInfo &obis_info) override; + void dump_config() override; +}; + +} // namespace sml +} // namespace esphome diff --git a/esphome/components/sml/sml.cpp b/esphome/components/sml/sml.cpp new file mode 100644 index 0000000000..c6fe7d64cd --- /dev/null +++ b/esphome/components/sml/sml.cpp @@ -0,0 +1,146 @@ +#include "sml.h" +#include "esphome/core/log.h" +#include "sml_parser.h" + +namespace esphome { +namespace sml { + +static const char *const TAG = "sml"; + +const char START_BYTES_DETECTED = 1; +const char END_BYTES_DETECTED = 2; + +SmlListener::SmlListener(std::string server_id, std::string obis_code) + : server_id(std::move(server_id)), obis_code(std::move(obis_code)) {} + +char Sml::check_start_end_bytes_(uint8_t byte) { + this->incoming_mask_ = (this->incoming_mask_ << 2) | get_code(byte); + + if (this->incoming_mask_ == START_MASK) + return START_BYTES_DETECTED; + if ((this->incoming_mask_ >> 6) == END_MASK) + return END_BYTES_DETECTED; + return 0; +} + +void Sml::loop() { + while (available()) { + const char c = read(); + + if (this->record_) + this->sml_data_.emplace_back(c); + + switch (this->check_start_end_bytes_(c)) { + case START_BYTES_DETECTED: { + this->record_ = true; + this->sml_data_.clear(); + break; + }; + case END_BYTES_DETECTED: { + if (this->record_) { + this->record_ = false; + + if (!check_sml_data(this->sml_data_)) + break; + + // remove footer bytes + this->sml_data_.resize(this->sml_data_.size() - 8); + this->process_sml_file_(this->sml_data_); + } + break; + }; + }; + } +} + +void Sml::process_sml_file_(const bytes &sml_data) { + SmlFile sml_file = SmlFile(sml_data); + std::vector obis_info = sml_file.get_obis_info(); + this->publish_obis_info_(obis_info); + + this->log_obis_info_(obis_info); +} + +void Sml::log_obis_info_(const std::vector &obis_info_vec) { + ESP_LOGD(TAG, "OBIS info:"); + for (auto const &obis_info : obis_info_vec) { + std::string info; + info += " (" + bytes_repr(obis_info.server_id) + ") "; + info += obis_info.code_repr(); + info += " [0x" + bytes_repr(obis_info.value) + "]"; + ESP_LOGD(TAG, "%s", info.c_str()); + } +} + +void Sml::publish_obis_info_(const std::vector &obis_info_vec) { + for (auto const &obis_info : obis_info_vec) { + this->publish_value_(obis_info); + } +} + +void Sml::publish_value_(const ObisInfo &obis_info) { + for (auto const &sml_listener : sml_listeners_) { + if ((!sml_listener->server_id.empty()) && (bytes_repr(obis_info.server_id) != sml_listener->server_id)) + continue; + if (obis_info.code_repr() != sml_listener->obis_code) + continue; + sml_listener->publish_val(obis_info); + } +} + +void Sml::dump_config() { ESP_LOGCONFIG(TAG, "SML:"); } + +void Sml::register_sml_listener(SmlListener *listener) { sml_listeners_.emplace_back(listener); } + +bool check_sml_data(const bytes &buffer) { + if (buffer.size() < 2) { + ESP_LOGW(TAG, "Checksum error in received SML data."); + return false; + } + + uint16_t crc_received = (buffer.at(buffer.size() - 2) << 8) | buffer.at(buffer.size() - 1); + if (crc_received == calc_crc16_x25(buffer.begin(), buffer.end() - 2, 0x6e23)) { + ESP_LOGV(TAG, "Checksum verification successful with CRC16/X25."); + return true; + } + + if (crc_received == calc_crc16_kermit(buffer.begin(), buffer.end() - 2, 0xed50)) { + ESP_LOGV(TAG, "Checksum verification successful with CRC16/KERMIT."); + return true; + } + + ESP_LOGW(TAG, "Checksum error in received SML data."); + return false; +} + +uint16_t calc_crc16_p1021(bytes::const_iterator begin, bytes::const_iterator end, uint16_t crcsum) { + for (auto it = begin; it != end; it++) { + crcsum = (crcsum >> 8) ^ CRC16_X25_TABLE[(crcsum & 0xff) ^ *it]; + } + return crcsum; +} + +uint16_t calc_crc16_x25(bytes::const_iterator begin, bytes::const_iterator end, uint16_t crcsum = 0) { + crcsum = calc_crc16_p1021(begin, end, crcsum ^ 0xffff) ^ 0xffff; + return (crcsum >> 8) | ((crcsum & 0xff) << 8); +} + +uint16_t calc_crc16_kermit(bytes::const_iterator begin, bytes::const_iterator end, uint16_t crcsum = 0) { + return calc_crc16_p1021(begin, end, crcsum); +} + +uint8_t get_code(uint8_t byte) { + switch (byte) { + case 0x1b: + return 1; + case 0x01: + return 2; + case 0x1a: + return 3; + default: + return 0; + } +} + +} // namespace sml +} // namespace esphome diff --git a/esphome/components/sml/sml.h b/esphome/components/sml/sml.h new file mode 100644 index 0000000000..ac7befb043 --- /dev/null +++ b/esphome/components/sml/sml.h @@ -0,0 +1,47 @@ +#pragma once + +#include +#include +#include "esphome/core/component.h" +#include "esphome/components/uart/uart.h" +#include "sml_parser.h" + +namespace esphome { +namespace sml { + +class SmlListener { + public: + std::string server_id; + std::string obis_code; + SmlListener(std::string server_id, std::string obis_code); + virtual void publish_val(const ObisInfo &obis_info){}; +}; + +class Sml : public Component, public uart::UARTDevice { + public: + void register_sml_listener(SmlListener *listener); + void loop() override; + void dump_config() override; + std::vector sml_listeners_{}; + + protected: + void process_sml_file_(const bytes &sml_data); + void log_obis_info_(const std::vector &obis_info_vec); + void publish_obis_info_(const std::vector &obis_info_vec); + char check_start_end_bytes_(uint8_t byte); + void publish_value_(const ObisInfo &obis_info); + + // Serial parser + bool record_ = false; + uint16_t incoming_mask_ = 0; + bytes sml_data_; +}; + +bool check_sml_data(const bytes &buffer); +uint16_t calc_crc16_p1021(bytes::const_iterator begin, bytes::const_iterator end, uint16_t crcsum); +uint16_t calc_crc16_x25(bytes::const_iterator begin, bytes::const_iterator end, uint16_t crcsum); +uint16_t calc_crc16_kermit(bytes::const_iterator begin, bytes::const_iterator end, uint16_t crcsum); + +uint8_t get_code(uint8_t byte); +} // namespace sml +} // namespace esphome diff --git a/esphome/components/sml/sml_parser.cpp b/esphome/components/sml/sml_parser.cpp new file mode 100644 index 0000000000..ff7da4cabd --- /dev/null +++ b/esphome/components/sml/sml_parser.cpp @@ -0,0 +1,131 @@ +#include "esphome/core/helpers.h" +#include "constants.h" +#include "sml_parser.h" + +namespace esphome { +namespace sml { + +SmlFile::SmlFile(bytes buffer) : buffer_(std::move(buffer)) { + // extract messages + this->pos_ = 0; + while (this->pos_ < this->buffer_.size()) { + if (this->buffer_[this->pos_] == 0x00) + break; // fill byte detected -> no more messages + + SmlNode message = SmlNode(); + if (!this->setup_node(&message)) + break; + this->messages.emplace_back(message); + } +} + +bool SmlFile::setup_node(SmlNode *node) { + uint8_t type = this->buffer_[this->pos_] >> 4; // type including overlength info + uint8_t length = this->buffer_[this->pos_] & 0x0f; // length including TL bytes + bool is_list = (type & 0x07) == SML_LIST; + bool has_extended_length = type & 0x08; // we have a long list/value (>15 entries) + uint8_t parse_length = length; + if (has_extended_length) { + length = (length << 4) + (this->buffer_[this->pos_ + 1] & 0x0f); + parse_length = length - 1; + this->pos_ += 1; + } + + if (this->pos_ + parse_length >= this->buffer_.size()) + return false; + + node->type = type & 0x07; + node->nodes.clear(); + node->value_bytes.clear(); + if (this->buffer_[this->pos_] == 0x00) { // end of message + this->pos_ += 1; + } else if (is_list) { // list + this->pos_ += 1; + node->nodes.reserve(parse_length); + for (size_t i = 0; i != parse_length; i++) { + SmlNode child_node = SmlNode(); + if (!this->setup_node(&child_node)) + return false; + node->nodes.emplace_back(child_node); + } + } else { // value + node->value_bytes = + bytes(this->buffer_.begin() + this->pos_ + 1, this->buffer_.begin() + this->pos_ + parse_length); + this->pos_ += parse_length; + } + return true; +} + +std::vector SmlFile::get_obis_info() { + std::vector obis_info; + for (auto const &message : messages) { + SmlNode message_body = message.nodes[3]; + uint16_t message_type = bytes_to_uint(message_body.nodes[0].value_bytes); + if (message_type != SML_GET_LIST_RES) + continue; + + SmlNode get_list_response = message_body.nodes[1]; + bytes server_id = get_list_response.nodes[1].value_bytes; + SmlNode val_list = get_list_response.nodes[4]; + + for (auto const &val_list_entry : val_list.nodes) { + obis_info.emplace_back(server_id, val_list_entry); + } + } + return obis_info; +} + +std::string bytes_repr(const bytes &buffer) { + std::string repr; + for (auto const value : buffer) { + repr += str_sprintf("%02x", value & 0xff); + } + return repr; +} + +uint64_t bytes_to_uint(const bytes &buffer) { + uint64_t val = 0; + for (auto const value : buffer) { + val = (val << 8) + value; + } + return val; +} + +int64_t bytes_to_int(const bytes &buffer) { + uint64_t tmp = bytes_to_uint(buffer); + int64_t val; + + switch (buffer.size()) { + case 1: // int8 + val = (int8_t) tmp; + break; + case 2: // int16 + val = (int16_t) tmp; + break; + case 4: // int32 + val = (int32_t) tmp; + break; + default: // int64 + val = (int64_t) tmp; + } + return val; +} + +std::string bytes_to_string(const bytes &buffer) { return std::string(buffer.begin(), buffer.end()); } + +ObisInfo::ObisInfo(bytes server_id, SmlNode val_list_entry) : server_id(std::move(server_id)) { + this->code = val_list_entry.nodes[0].value_bytes; + this->status = val_list_entry.nodes[1].value_bytes; + this->unit = bytes_to_uint(val_list_entry.nodes[3].value_bytes); + this->scaler = bytes_to_int(val_list_entry.nodes[4].value_bytes); + SmlNode value_node = val_list_entry.nodes[5]; + this->value = value_node.value_bytes; + this->value_type = value_node.type; +} + +std::string ObisInfo::code_repr() const { + return str_sprintf("%d-%d:%d.%d.%d", this->code[0], this->code[1], this->code[2], this->code[3], this->code[4]); +} + +} // namespace sml +} // namespace esphome diff --git a/esphome/components/sml/sml_parser.h b/esphome/components/sml/sml_parser.h new file mode 100644 index 0000000000..fca859d4b8 --- /dev/null +++ b/esphome/components/sml/sml_parser.h @@ -0,0 +1,54 @@ +#pragma once + +#include +#include +#include +#include +#include "constants.h" + +namespace esphome { +namespace sml { + +using bytes = std::vector; + +class SmlNode { + public: + uint8_t type; + bytes value_bytes; + std::vector nodes; +}; + +class ObisInfo { + public: + ObisInfo(bytes server_id, SmlNode val_list_entry); + bytes server_id; + bytes code; + bytes status; + char unit; + char scaler; + bytes value; + uint16_t value_type; + std::string code_repr() const; +}; + +class SmlFile { + public: + SmlFile(bytes buffer); + bool setup_node(SmlNode *node); + std::vector messages; + std::vector get_obis_info(); + + protected: + const bytes buffer_; + size_t pos_; +}; + +std::string bytes_repr(const bytes &buffer); + +uint64_t bytes_to_uint(const bytes &buffer); + +int64_t bytes_to_int(const bytes &buffer); + +std::string bytes_to_string(const bytes &buffer); +} // namespace sml +} // namespace esphome diff --git a/esphome/components/sml/text_sensor/__init__.py b/esphome/components/sml/text_sensor/__init__.py new file mode 100644 index 0000000000..81564bf65b --- /dev/null +++ b/esphome/components/sml/text_sensor/__init__.py @@ -0,0 +1,43 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import text_sensor +from esphome.const import CONF_FORMAT, CONF_ID + +from .. import CONF_OBIS_CODE, CONF_SERVER_ID, CONF_SML_ID, Sml, obis_code, sml_ns + +AUTO_LOAD = ["sml"] + +SmlType = sml_ns.enum("SmlType") +SML_TYPES = { + "text": SmlType.SML_OCTET, + "bool": SmlType.SML_BOOL, + "int": SmlType.SML_INT, + "uint": SmlType.SML_UINT, + "hex": SmlType.SML_HEX, + "": SmlType.SML_UNDEFINED, +} + +SmlTextSensor = sml_ns.class_("SmlTextSensor", text_sensor.TextSensor, cg.Component) + +CONFIG_SCHEMA = text_sensor.TEXT_SENSOR_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(SmlTextSensor), + cv.GenerateID(CONF_SML_ID): cv.use_id(Sml), + cv.Required(CONF_OBIS_CODE): obis_code, + cv.Optional(CONF_SERVER_ID, default=""): cv.string, + cv.Optional(CONF_FORMAT, default=""): cv.enum(SML_TYPES, lower=True), + } +) + + +async def to_code(config): + var = cg.new_Pvariable( + config[CONF_ID], + config[CONF_SERVER_ID], + config[CONF_OBIS_CODE], + config[CONF_FORMAT], + ) + await cg.register_component(var, config) + await text_sensor.register_text_sensor(var, config) + sml = await cg.get_variable(config[CONF_SML_ID]) + cg.add(sml.register_sml_listener(var)) diff --git a/esphome/components/sml/text_sensor/sml_text_sensor.cpp b/esphome/components/sml/text_sensor/sml_text_sensor.cpp new file mode 100644 index 0000000000..64f10698f0 --- /dev/null +++ b/esphome/components/sml/text_sensor/sml_text_sensor.cpp @@ -0,0 +1,54 @@ +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" +#include "sml_text_sensor.h" +#include "../sml_parser.h" + +namespace esphome { +namespace sml { + +static const char *const TAG = "sml_text_sensor"; + +SmlTextSensor::SmlTextSensor(std::string server_id, std::string obis_code, SmlType format) + : SmlListener(std::move(server_id), std::move(obis_code)), format_(format) {} + +void SmlTextSensor::publish_val(const ObisInfo &obis_info) { + uint8_t value_type; + if (this->format_ == SML_UNDEFINED) { + value_type = obis_info.value_type; + } else { + value_type = this->format_; + } + + switch (value_type) { + case SML_HEX: { + publish_state("0x" + bytes_repr(obis_info.value)); + break; + } + case SML_INT: { + publish_state(to_string(bytes_to_int(obis_info.value))); + break; + } + case SML_BOOL: + publish_state(bytes_to_uint(obis_info.value) ? "True" : "False"); + break; + case SML_UINT: { + publish_state(to_string(bytes_to_uint(obis_info.value))); + break; + } + case SML_OCTET: { + publish_state(std::string(obis_info.value.begin(), obis_info.value.end())); + break; + } + } +} + +void SmlTextSensor::dump_config() { + LOG_TEXT_SENSOR("", "SML", this); + if (!this->server_id.empty()) { + ESP_LOGCONFIG(TAG, " Server ID: %s", this->server_id.c_str()); + } + ESP_LOGCONFIG(TAG, " OBIS Code: %s", this->obis_code.c_str()); +} + +} // namespace sml +} // namespace esphome diff --git a/esphome/components/sml/text_sensor/sml_text_sensor.h b/esphome/components/sml/text_sensor/sml_text_sensor.h new file mode 100644 index 0000000000..20d27c9f71 --- /dev/null +++ b/esphome/components/sml/text_sensor/sml_text_sensor.h @@ -0,0 +1,21 @@ +#pragma once + +#include "esphome/components/sml/sml.h" +#include "esphome/components/text_sensor/text_sensor.h" +#include "../constants.h" + +namespace esphome { +namespace sml { + +class SmlTextSensor : public SmlListener, public text_sensor::TextSensor, public Component { + public: + SmlTextSensor(std::string server_id, std::string obis_code, SmlType format); + void publish_val(const ObisInfo &obis_info) override; + void dump_config() override; + + protected: + SmlType format_; +}; + +} // namespace sml +} // namespace esphome From 4e1f6518e829bcd80422cdf76b9d359603f23ae9 Mon Sep 17 00:00:00 2001 From: George Date: Tue, 10 May 2022 19:22:22 +1000 Subject: [PATCH 0268/3388] Delonghi Penguino PAC W120HP ir support (#3124) --- CODEOWNERS | 1 + esphome/components/delonghi/__init__.py | 1 + esphome/components/delonghi/climate.py | 20 +++ esphome/components/delonghi/delonghi.cpp | 186 +++++++++++++++++++++++ esphome/components/delonghi/delonghi.h | 64 ++++++++ tests/test1.yaml | 2 + 6 files changed, 274 insertions(+) create mode 100644 esphome/components/delonghi/__init__.py create mode 100644 esphome/components/delonghi/climate.py create mode 100644 esphome/components/delonghi/delonghi.cpp create mode 100644 esphome/components/delonghi/delonghi.h diff --git a/CODEOWNERS b/CODEOWNERS index 3a511275e1..16b9008379 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -55,6 +55,7 @@ esphome/components/current_based/* @djwmarcx esphome/components/daly_bms/* @s1lvi0 esphome/components/dashboard_import/* @esphome/core esphome/components/debug/* @OttoWinter +esphome/components/delonghi/* @grob6000 esphome/components/dfplayer/* @glmnet esphome/components/dht/* @OttoWinter esphome/components/ds1307/* @badbadc0ffee diff --git a/esphome/components/delonghi/__init__.py b/esphome/components/delonghi/__init__.py new file mode 100644 index 0000000000..0a81eb2da7 --- /dev/null +++ b/esphome/components/delonghi/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@grob6000"] diff --git a/esphome/components/delonghi/climate.py b/esphome/components/delonghi/climate.py new file mode 100644 index 0000000000..614706defe --- /dev/null +++ b/esphome/components/delonghi/climate.py @@ -0,0 +1,20 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import climate_ir +from esphome.const import CONF_ID + +AUTO_LOAD = ["climate_ir"] + +delonghi_ns = cg.esphome_ns.namespace("delonghi") +DelonghiClimate = delonghi_ns.class_("DelonghiClimate", climate_ir.ClimateIR) + +CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(DelonghiClimate), + } +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await climate_ir.register_climate_ir(var, config) diff --git a/esphome/components/delonghi/delonghi.cpp b/esphome/components/delonghi/delonghi.cpp new file mode 100644 index 0000000000..9bc0b5753d --- /dev/null +++ b/esphome/components/delonghi/delonghi.cpp @@ -0,0 +1,186 @@ +#include "delonghi.h" +#include "esphome/components/remote_base/remote_base.h" + +namespace esphome { +namespace delonghi { + +static const char *const TAG = "delonghi.climate"; + +void DelonghiClimate::transmit_state() { + uint8_t remote_state[DELONGHI_STATE_FRAME_SIZE] = {0}; + remote_state[0] = DELONGHI_ADDRESS; + remote_state[1] = this->temperature_(); + remote_state[1] |= (this->fan_speed_()) << 5; + remote_state[2] = this->operation_mode_(); + // Calculate checksum + for (int i = 0; i < DELONGHI_STATE_FRAME_SIZE - 1; i++) { + remote_state[DELONGHI_STATE_FRAME_SIZE - 1] += remote_state[i]; + } + + auto transmit = this->transmitter_->transmit(); + auto *data = transmit.get_data(); + data->set_carrier_frequency(DELONGHI_IR_FREQUENCY); + + data->mark(DELONGHI_HEADER_MARK); + data->space(DELONGHI_HEADER_SPACE); + for (unsigned char b : remote_state) { + for (uint8_t mask = 1; mask > 0; mask <<= 1) { // iterate through bit mask + data->mark(DELONGHI_BIT_MARK); + bool bit = b & mask; + data->space(bit ? DELONGHI_ONE_SPACE : DELONGHI_ZERO_SPACE); + } + } + data->mark(DELONGHI_BIT_MARK); + data->space(0); + + transmit.perform(); +} + +uint8_t DelonghiClimate::operation_mode_() { + uint8_t operating_mode = DELONGHI_MODE_ON; + switch (this->mode) { + case climate::CLIMATE_MODE_COOL: + operating_mode |= DELONGHI_MODE_COOL; + break; + case climate::CLIMATE_MODE_DRY: + operating_mode |= DELONGHI_MODE_DRY; + break; + case climate::CLIMATE_MODE_HEAT: + operating_mode |= DELONGHI_MODE_HEAT; + break; + case climate::CLIMATE_MODE_HEAT_COOL: + operating_mode |= DELONGHI_MODE_AUTO; + break; + case climate::CLIMATE_MODE_FAN_ONLY: + operating_mode |= DELONGHI_MODE_FAN; + break; + case climate::CLIMATE_MODE_OFF: + default: + operating_mode = DELONGHI_MODE_OFF; + break; + } + return operating_mode; +} + +uint16_t DelonghiClimate::fan_speed_() { + uint16_t fan_speed; + switch (this->fan_mode.value()) { + case climate::CLIMATE_FAN_LOW: + fan_speed = DELONGHI_FAN_LOW; + break; + case climate::CLIMATE_FAN_MEDIUM: + fan_speed = DELONGHI_FAN_MEDIUM; + break; + case climate::CLIMATE_FAN_HIGH: + fan_speed = DELONGHI_FAN_HIGH; + break; + case climate::CLIMATE_FAN_AUTO: + default: + fan_speed = DELONGHI_FAN_AUTO; + } + return fan_speed; +} + +uint8_t DelonghiClimate::temperature_() { + // Force special temperatures depending on the mode + uint8_t temperature = 0b0001; + switch (this->mode) { + case climate::CLIMATE_MODE_HEAT: + temperature = (uint8_t) roundf(this->target_temperature) - DELONGHI_TEMP_OFFSET_HEAT; + break; + case climate::CLIMATE_MODE_COOL: + case climate::CLIMATE_MODE_DRY: + case climate::CLIMATE_MODE_HEAT_COOL: + case climate::CLIMATE_MODE_FAN_ONLY: + case climate::CLIMATE_MODE_OFF: + default: + temperature = (uint8_t) roundf(this->target_temperature) - DELONGHI_TEMP_OFFSET_COOL; + } + if (temperature > 0x0F) { + temperature = 0x0F; // clamp maximum + } + return temperature; +} + +bool DelonghiClimate::parse_state_frame_(const uint8_t frame[]) { + uint8_t checksum = 0; + for (int i = 0; i < (DELONGHI_STATE_FRAME_SIZE - 1); i++) { + checksum += frame[i]; + } + if (frame[DELONGHI_STATE_FRAME_SIZE - 1] != checksum) { + return false; + } + uint8_t mode = frame[2] & 0x0F; + if (mode & DELONGHI_MODE_ON) { + switch (mode & 0x0E) { + case DELONGHI_MODE_COOL: + this->mode = climate::CLIMATE_MODE_COOL; + break; + case DELONGHI_MODE_DRY: + this->mode = climate::CLIMATE_MODE_DRY; + break; + case DELONGHI_MODE_HEAT: + this->mode = climate::CLIMATE_MODE_HEAT; + break; + case DELONGHI_MODE_AUTO: + this->mode = climate::CLIMATE_MODE_HEAT_COOL; + break; + case DELONGHI_MODE_FAN: + this->mode = climate::CLIMATE_MODE_FAN_ONLY; + break; + } + } else { + this->mode = climate::CLIMATE_MODE_OFF; + } + uint8_t temperature = frame[1] & 0x0F; + if (this->mode == climate::CLIMATE_MODE_HEAT) { + this->target_temperature = temperature + DELONGHI_TEMP_OFFSET_HEAT; + } else { + this->target_temperature = temperature + DELONGHI_TEMP_OFFSET_COOL; + } + uint8_t fan_mode = frame[1] >> 5; + switch (fan_mode) { + case DELONGHI_FAN_LOW: + this->fan_mode = climate::CLIMATE_FAN_LOW; + break; + case DELONGHI_FAN_MEDIUM: + this->fan_mode = climate::CLIMATE_FAN_MEDIUM; + break; + case DELONGHI_FAN_HIGH: + this->fan_mode = climate::CLIMATE_FAN_HIGH; + break; + case DELONGHI_FAN_AUTO: + this->fan_mode = climate::CLIMATE_FAN_AUTO; + break; + } + this->publish_state(); + return true; +} + +bool DelonghiClimate::on_receive(remote_base::RemoteReceiveData data) { + uint8_t state_frame[DELONGHI_STATE_FRAME_SIZE] = {}; + if (!data.expect_item(DELONGHI_HEADER_MARK, DELONGHI_HEADER_SPACE)) { + return false; + } + for (uint8_t pos = 0; pos < DELONGHI_STATE_FRAME_SIZE; pos++) { + uint8_t byte = 0; + for (int8_t bit = 0; bit < 8; bit++) { + if (data.expect_item(DELONGHI_BIT_MARK, DELONGHI_ONE_SPACE)) { + byte |= 1 << bit; + } else if (!data.expect_item(DELONGHI_BIT_MARK, DELONGHI_ZERO_SPACE)) { + return false; + } + } + state_frame[pos] = byte; + if (pos == 0) { + // frame header + if (byte != DELONGHI_ADDRESS) { + return false; + } + } + } + return this->parse_state_frame_(state_frame); +} + +} // namespace delonghi +} // namespace esphome diff --git a/esphome/components/delonghi/delonghi.h b/esphome/components/delonghi/delonghi.h new file mode 100644 index 0000000000..d310a58aee --- /dev/null +++ b/esphome/components/delonghi/delonghi.h @@ -0,0 +1,64 @@ +#pragma once + +#include "esphome/components/climate_ir/climate_ir.h" + +namespace esphome { +namespace delonghi { + +// Values for DELONGHI ARC43XXX IR Controllers +const uint8_t DELONGHI_ADDRESS = 83; + +// Temperature +const uint8_t DELONGHI_TEMP_MIN = 13; // Celsius +const uint8_t DELONGHI_TEMP_MAX = 32; // Celsius +const uint8_t DELONGHI_TEMP_OFFSET_COOL = 17; // Celsius +const uint8_t DELONGHI_TEMP_OFFSET_HEAT = 12; // Celsius + +// Modes +const uint8_t DELONGHI_MODE_AUTO = 0b1000; +const uint8_t DELONGHI_MODE_COOL = 0b0000; +const uint8_t DELONGHI_MODE_HEAT = 0b0110; +const uint8_t DELONGHI_MODE_DRY = 0b0010; +const uint8_t DELONGHI_MODE_FAN = 0b0100; +const uint8_t DELONGHI_MODE_OFF = 0b0000; +const uint8_t DELONGHI_MODE_ON = 0b0001; + +// Fan Speed +const uint8_t DELONGHI_FAN_AUTO = 0b00; +const uint8_t DELONGHI_FAN_HIGH = 0b01; +const uint8_t DELONGHI_FAN_MEDIUM = 0b10; +const uint8_t DELONGHI_FAN_LOW = 0b11; + +// IR Transmission - similar to NEC1 +const uint32_t DELONGHI_IR_FREQUENCY = 38000; +const uint32_t DELONGHI_HEADER_MARK = 9000; +const uint32_t DELONGHI_HEADER_SPACE = 4500; +const uint32_t DELONGHI_BIT_MARK = 465; +const uint32_t DELONGHI_ONE_SPACE = 1750; +const uint32_t DELONGHI_ZERO_SPACE = 670; + +// State Frame size +const uint8_t DELONGHI_STATE_FRAME_SIZE = 8; + +class DelonghiClimate : public climate_ir::ClimateIR { + public: + DelonghiClimate() + : climate_ir::ClimateIR(DELONGHI_TEMP_MIN, DELONGHI_TEMP_MAX, 1.0f, true, true, + {climate::CLIMATE_FAN_AUTO, climate::CLIMATE_FAN_LOW, climate::CLIMATE_FAN_MEDIUM, + climate::CLIMATE_FAN_HIGH}, + {climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_VERTICAL, + climate::CLIMATE_SWING_HORIZONTAL, climate::CLIMATE_SWING_BOTH}) {} + + protected: + // Transmit via IR the state of this climate controller. + void transmit_state() override; + uint8_t operation_mode_(); + uint16_t fan_speed_(); + uint8_t temperature_(); + // Handle received IR Buffer + bool on_receive(remote_base::RemoteReceiveData data) override; + bool parse_state_frame_(const uint8_t frame[]); +}; + +} // namespace delonghi +} // namespace esphome diff --git a/tests/test1.yaml b/tests/test1.yaml index aba37976aa..deaf1c237e 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -1811,6 +1811,8 @@ climate: name: Fujitsu General Climate - platform: daikin name: Daikin Climate + - platform: delonghi + name: Delonghi Climate - platform: yashima name: Yashima Climate - platform: mitsubishi From 782186e13d98cf87e99fce261d92663feae17393 Mon Sep 17 00:00:00 2001 From: Martin <25747549+martgras@users.noreply.github.com> Date: Tue, 10 May 2022 11:25:44 +0200 Subject: [PATCH 0269/3388] extend scd4x (#3409) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- CODEOWNERS | 2 +- esphome/components/scd4x/automation.h | 28 ++++ esphome/components/scd4x/scd4x.cpp | 233 ++++++++++++++++++-------- esphome/components/scd4x/scd4x.h | 19 ++- esphome/components/scd4x/sensor.py | 69 +++++++- tests/test1.yaml | 12 ++ 6 files changed, 287 insertions(+), 76 deletions(-) create mode 100644 esphome/components/scd4x/automation.h diff --git a/CODEOWNERS b/CODEOWNERS index 16b9008379..e2b29547cb 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -168,7 +168,7 @@ esphome/components/rf_bridge/* @jesserockz esphome/components/rgbct/* @jesserockz esphome/components/rtttl/* @glmnet esphome/components/safe_mode/* @jsuanet @paulmonigatti -esphome/components/scd4x/* @sjtrny +esphome/components/scd4x/* @martgras @sjtrny esphome/components/script/* @esphome/core esphome/components/sdm_meter/* @jesserockz @polyfaces esphome/components/sdp3x/* @Azimath diff --git a/esphome/components/scd4x/automation.h b/esphome/components/scd4x/automation.h new file mode 100644 index 0000000000..21ecb2ea4c --- /dev/null +++ b/esphome/components/scd4x/automation.h @@ -0,0 +1,28 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/automation.h" +#include "scd4x.h" + +namespace esphome { +namespace scd4x { + +template class PerformForcedCalibrationAction : public Action, public Parented { + public: + void play(Ts... x) override { + if (this->value_.has_value()) { + this->parent_->perform_forced_calibration(value_.value()); + } + } + + protected: + TEMPLATABLE_VALUE(uint16_t, value) +}; + +template class FactoryResetAction : public Action, public Parented { + public: + void play(Ts... x) override { this->parent_->factory_reset(); } +}; + +} // namespace scd4x +} // namespace esphome diff --git a/esphome/components/scd4x/scd4x.cpp b/esphome/components/scd4x/scd4x.cpp index 559c95df32..cbda996a4c 100644 --- a/esphome/components/scd4x/scd4x.cpp +++ b/esphome/components/scd4x/scd4x.cpp @@ -13,39 +13,32 @@ static const uint16_t SCD4X_CMD_ALTITUDE_COMPENSATION = 0x2427; static const uint16_t SCD4X_CMD_AMBIENT_PRESSURE_COMPENSATION = 0xe000; static const uint16_t SCD4X_CMD_AUTOMATIC_SELF_CALIBRATION = 0x2416; static const uint16_t SCD4X_CMD_START_CONTINUOUS_MEASUREMENTS = 0x21b1; +static const uint16_t SCD4X_CMD_START_LOW_POWER_CONTINUOUS_MEASUREMENTS = 0x21ac; +static const uint16_t SCD4X_CMD_START_LOW_POWER_SINGLE_SHOT = 0x219d; // SCD41 only +static const uint16_t SCD4X_CMD_START_LOW_POWER_SINGLE_SHOT_RHT_ONLY = 0x2196; static const uint16_t SCD4X_CMD_GET_DATA_READY_STATUS = 0xe4b8; static const uint16_t SCD4X_CMD_READ_MEASUREMENT = 0xec05; static const uint16_t SCD4X_CMD_PERFORM_FORCED_CALIBRATION = 0x362f; static const uint16_t SCD4X_CMD_STOP_MEASUREMENTS = 0x3f86; - +static const uint16_t SCD4X_CMD_FACTORY_RESET = 0x3632; +static const uint16_t SCD4X_CMD_GET_FEATURESET = 0x202f; static const float SCD4X_TEMPERATURE_OFFSET_MULTIPLIER = (1 << 16) / 175.0f; +static const uint16_t SCD41_ID = 0x1408; +static const uint16_t SCD40_ID = 0x440; void SCD4XComponent::setup() { ESP_LOGCONFIG(TAG, "Setting up scd4x..."); - // the sensor needs 1000 ms to enter the idle state this->set_timeout(1000, [this]() { - uint16_t raw_read_status; - if (!this->get_register(SCD4X_CMD_GET_DATA_READY_STATUS, raw_read_status)) { - ESP_LOGE(TAG, "Failed to read data ready status"); + this->status_clear_error(); + if (!this->write_command(SCD4X_CMD_STOP_MEASUREMENTS)) { + ESP_LOGE(TAG, "Failed to stop measurements"); this->mark_failed(); return; } - - uint32_t stop_measurement_delay = 0; - // In order to query the device periodic measurement must be ceased - if (raw_read_status) { - ESP_LOGD(TAG, "Sensor has data available, stopping periodic measurement"); - if (!this->write_command(SCD4X_CMD_STOP_MEASUREMENTS)) { - ESP_LOGE(TAG, "Failed to stop measurements"); - this->mark_failed(); - return; - } - // According to the SCD4x datasheet the sensor will only respond to other commands after waiting 500 ms after - // issuing the stop_periodic_measurement command - stop_measurement_delay = 500; - } - this->set_timeout(stop_measurement_delay, [this]() { + // According to the SCD4x datasheet the sensor will only respond to other commands after waiting 500 ms after + // issuing the stop_periodic_measurement command + this->set_timeout(500, [this]() { uint16_t raw_serial_number[3]; if (!this->get_register(SCD4X_CMD_GET_SERIAL_NUMBER, raw_serial_number, 3, 1)) { ESP_LOGE(TAG, "Failed to read serial number"); @@ -89,15 +82,9 @@ void SCD4XComponent::setup() { return; } - // Finally start sensor measurements - if (!this->write_command(SCD4X_CMD_START_CONTINUOUS_MEASUREMENTS)) { - ESP_LOGE(TAG, "Error starting continuous measurements."); - this->error_code_ = MEASUREMENT_INIT_FAILED; - this->mark_failed(); - return; - } - initialized_ = true; + // Finally start sensor measurements + this->start_measurement_(); ESP_LOGD(TAG, "Sensor initialized"); }); }); @@ -123,12 +110,31 @@ void SCD4XComponent::dump_config() { } } ESP_LOGCONFIG(TAG, " Automatic self calibration: %s", ONOFF(this->enable_asc_)); - if (this->ambient_pressure_compensation_) { - ESP_LOGCONFIG(TAG, " Altitude compensation disabled"); - ESP_LOGCONFIG(TAG, " Ambient pressure compensation: %dmBar", this->ambient_pressure_); + if (this->ambient_pressure_source_ != nullptr) { + ESP_LOGCONFIG(TAG, " Dynamic ambient pressure compensation using sensor '%s'", + this->ambient_pressure_source_->get_name().c_str()); } else { - ESP_LOGCONFIG(TAG, " Ambient pressure compensation disabled"); - ESP_LOGCONFIG(TAG, " Altitude compensation: %dm", this->altitude_compensation_); + if (this->ambient_pressure_compensation_) { + ESP_LOGCONFIG(TAG, " Altitude compensation disabled"); + ESP_LOGCONFIG(TAG, " Ambient pressure compensation: %dmBar", this->ambient_pressure_); + } else { + ESP_LOGCONFIG(TAG, " Ambient pressure compensation disabled"); + ESP_LOGCONFIG(TAG, " Altitude compensation: %dm", this->altitude_compensation_); + } + } + switch (this->measurement_mode_) { + case PERIODIC: + ESP_LOGCONFIG(TAG, " Measurement mode: periodic (5s)"); + break; + case LOW_POWER_PERIODIC: + ESP_LOGCONFIG(TAG, " Measurement mode: low power periodic (30s)"); + break; + case SINGLE_SHOT: + ESP_LOGCONFIG(TAG, " Measurement mode: single shot"); + break; + case SINGLE_SHOT_RHT_ONLY: + ESP_LOGCONFIG(TAG, " Measurement mode: single shot rht only"); + break; } ESP_LOGCONFIG(TAG, " Temperature offset: %.2f °C", this->temperature_offset_); LOG_UPDATE_INTERVAL(this); @@ -149,47 +155,105 @@ void SCD4XComponent::update() { } } - // Check if data is ready - if (!this->write_command(SCD4X_CMD_GET_DATA_READY_STATUS)) { - this->status_set_warning(); - return; + uint32_t wait_time = 0; + if (this->measurement_mode_ == SINGLE_SHOT || this->measurement_mode_ == SINGLE_SHOT_RHT_ONLY) { + start_measurement_(); + wait_time = + this->measurement_mode_ == SINGLE_SHOT ? 5000 : 50; // Single shot measurement takes 5 secs rht mode 50 ms } + this->set_timeout(wait_time, [this]() { + // Check if data is ready + if (!this->write_command(SCD4X_CMD_GET_DATA_READY_STATUS)) { + this->status_set_warning(); + return; + } - uint16_t raw_read_status; - if (!this->read_data(raw_read_status) || raw_read_status == 0x00) { - this->status_set_warning(); - ESP_LOGW(TAG, "Data not ready yet!"); - return; - } + uint16_t raw_read_status; - if (!this->write_command(SCD4X_CMD_READ_MEASUREMENT)) { - ESP_LOGW(TAG, "Error reading measurement!"); - this->status_set_warning(); - return; - } + if (!this->read_data(raw_read_status) || raw_read_status == 0x00) { + this->status_set_warning(); + ESP_LOGW(TAG, "Data not ready yet!"); + return; + } - // Read off sensor data - uint16_t raw_data[3]; - if (!this->read_data(raw_data, 3)) { - this->status_set_warning(); - return; - } + if (!this->write_command(SCD4X_CMD_READ_MEASUREMENT)) { + ESP_LOGW(TAG, "Error reading measurement!"); + this->status_set_warning(); + return; // NO RETRY + } + // Read off sensor data + uint16_t raw_data[3]; + if (!this->read_data(raw_data, 3)) { + this->status_set_warning(); + return; + } + if (this->co2_sensor_ != nullptr) + this->co2_sensor_->publish_state(raw_data[0]); - if (this->co2_sensor_ != nullptr) - this->co2_sensor_->publish_state(raw_data[0]); - - if (this->temperature_sensor_ != nullptr) { - const float temperature = -45.0f + (175.0f * (raw_data[1])) / (1 << 16); - this->temperature_sensor_->publish_state(temperature); - } - - if (this->humidity_sensor_ != nullptr) { - const float humidity = (100.0f * raw_data[2]) / (1 << 16); - this->humidity_sensor_->publish_state(humidity); - } - - this->status_clear_warning(); + if (this->temperature_sensor_ != nullptr) { + const float temperature = -45.0f + (175.0f * (raw_data[1])) / (1 << 16); + this->temperature_sensor_->publish_state(temperature); + } + if (this->humidity_sensor_ != nullptr) { + const float humidity = (100.0f * raw_data[2]) / (1 << 16); + this->humidity_sensor_->publish_state(humidity); + } + this->status_clear_warning(); + }); // set_timeout } + +bool SCD4XComponent::perform_forced_calibration(uint16_t current_co2_concentration) { + /* + Operate the SCD4x in the operation mode later used in normal sensor operation (periodic measurement, low power + periodic measurement or single shot) for > 3 minutes in an environment with homogenous and constant CO2 + concentration before performing a forced recalibration. + */ + if (!this->write_command(SCD4X_CMD_STOP_MEASUREMENTS)) { + ESP_LOGE(TAG, "Failed to stop measurements"); + this->status_set_warning(); + } + this->set_timeout(500, [this, current_co2_concentration]() { + if (this->write_command(SCD4X_CMD_PERFORM_FORCED_CALIBRATION, current_co2_concentration)) { + ESP_LOGD(TAG, "setting forced calibration Co2 level %d ppm", current_co2_concentration); + // frc takes 400 ms + // because this method will be used very rarly + // the simple aproach with delay is ok + delay(400); // NOLINT' + if (!this->start_measurement_()) { + return false; + } else { + ESP_LOGD(TAG, "forced calibration complete"); + } + return true; + } else { + ESP_LOGE(TAG, "force calibration failed"); + this->error_code_ = FRC_FAILED; + this->status_set_warning(); + return false; + } + }); + return true; +} + +bool SCD4XComponent::factory_reset() { + if (!this->write_command(SCD4X_CMD_STOP_MEASUREMENTS)) { + ESP_LOGE(TAG, "Failed to stop measurements"); + this->status_set_warning(); + return false; + } + + this->set_timeout(500, [this]() { + if (!this->write_command(SCD4X_CMD_FACTORY_RESET)) { + ESP_LOGE(TAG, "Failed to send factory reset command"); + this->status_set_warning(); + return false; + } + ESP_LOGD(TAG, "Factory reset complete"); + return true; + }); + return true; +} + // Note pressure in bar here. Convert to hPa void SCD4XComponent::set_ambient_pressure_compensation(float pressure_in_bar) { ambient_pressure_compensation_ = true; @@ -213,5 +277,38 @@ bool SCD4XComponent::update_ambient_pressure_compensation_(uint16_t pressure_in_ } } +bool SCD4XComponent::start_measurement_() { + uint16_t measurement_command = SCD4X_CMD_START_CONTINUOUS_MEASUREMENTS; + switch (this->measurement_mode_) { + case PERIODIC: + measurement_command = SCD4X_CMD_START_CONTINUOUS_MEASUREMENTS; + break; + case LOW_POWER_PERIODIC: + measurement_command = SCD4X_CMD_START_LOW_POWER_CONTINUOUS_MEASUREMENTS; + break; + case SINGLE_SHOT: + measurement_command = SCD4X_CMD_START_LOW_POWER_SINGLE_SHOT; + break; + case SINGLE_SHOT_RHT_ONLY: + measurement_command = SCD4X_CMD_START_LOW_POWER_SINGLE_SHOT_RHT_ONLY; + break; + } + + static uint8_t remaining_retries = 3; + while (remaining_retries) { + if (!this->write_command(measurement_command)) { + ESP_LOGE(TAG, "Error starting measurements."); + this->error_code_ = MEASUREMENT_INIT_FAILED; + this->status_set_warning(); + if (--remaining_retries == 0) + return false; + delay(50); // NOLINT wait 50 ms and try again + } + this->status_clear_warning(); + return true; + } + return false; +} + } // namespace scd4x } // namespace esphome diff --git a/esphome/components/scd4x/scd4x.h b/esphome/components/scd4x/scd4x.h index 3e534bcf98..23c3766e60 100644 --- a/esphome/components/scd4x/scd4x.h +++ b/esphome/components/scd4x/scd4x.h @@ -1,5 +1,6 @@ #pragma once - +#include +#include "esphome/core/application.h" #include "esphome/core/component.h" #include "esphome/components/sensor/sensor.h" #include "esphome/components/sensirion_common/i2c_sensirion.h" @@ -7,7 +8,14 @@ namespace esphome { namespace scd4x { -enum ERRORCODE { COMMUNICATION_FAILED, SERIAL_NUMBER_IDENTIFICATION_FAILED, MEASUREMENT_INIT_FAILED, UNKNOWN }; +enum ERRORCODE { + COMMUNICATION_FAILED, + SERIAL_NUMBER_IDENTIFICATION_FAILED, + MEASUREMENT_INIT_FAILED, + FRC_FAILED, + UNKNOWN +}; +enum MeasurementMode { PERIODIC, LOW_POWER_PERIODIC, SINGLE_SHOT, SINGLE_SHOT_RHT_ONLY }; class SCD4XComponent : public PollingComponent, public sensirion_common::SensirionI2CDevice { public: @@ -25,10 +33,13 @@ class SCD4XComponent : public PollingComponent, public sensirion_common::Sensiri void set_co2_sensor(sensor::Sensor *co2) { co2_sensor_ = co2; } void set_temperature_sensor(sensor::Sensor *temperature) { temperature_sensor_ = temperature; }; void set_humidity_sensor(sensor::Sensor *humidity) { humidity_sensor_ = humidity; } + void set_measurement_mode(MeasurementMode mode) { measurement_mode_ = mode; } + bool perform_forced_calibration(uint16_t current_co2_concentration); + bool factory_reset(); protected: bool update_ambient_pressure_compensation_(uint16_t pressure_in_hpa); - + bool start_measurement_(); ERRORCODE error_code_; bool initialized_{false}; @@ -38,7 +49,7 @@ class SCD4XComponent : public PollingComponent, public sensirion_common::Sensiri bool ambient_pressure_compensation_; uint16_t ambient_pressure_; bool enable_asc_; - + MeasurementMode measurement_mode_{PERIODIC}; sensor::Sensor *co2_sensor_{nullptr}; sensor::Sensor *temperature_sensor_{nullptr}; sensor::Sensor *humidity_sensor_{nullptr}; diff --git a/esphome/components/scd4x/sensor.py b/esphome/components/scd4x/sensor.py index 6ab0e1ba99..4c94d4257f 100644 --- a/esphome/components/scd4x/sensor.py +++ b/esphome/components/scd4x/sensor.py @@ -2,11 +2,15 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import i2c, sensor from esphome.components import sensirion_common +from esphome import automation +from esphome.automation import maybe_simple_id + from esphome.const import ( CONF_ID, CONF_CO2, CONF_HUMIDITY, CONF_TEMPERATURE, + CONF_VALUE, DEVICE_CLASS_CARBON_DIOXIDE, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_TEMPERATURE, @@ -19,7 +23,7 @@ from esphome.const import ( UNIT_PERCENT, ) -CODEOWNERS = ["@sjtrny"] +CODEOWNERS = ["@sjtrny", "@martgras"] DEPENDENCIES = ["i2c"] AUTO_LOAD = ["sensirion_common"] @@ -27,12 +31,29 @@ scd4x_ns = cg.esphome_ns.namespace("scd4x") SCD4XComponent = scd4x_ns.class_( "SCD4XComponent", cg.PollingComponent, sensirion_common.SensirionI2CDevice ) +MeasurementMode = scd4x_ns.enum("MEASUREMENT_MODE") +MEASUREMENT_MODE_OPTIONS = { + "periodic": MeasurementMode.PERIODIC, + "low_power_periodic": MeasurementMode.LOW_POWER_PERIODIC, + "single_shot": MeasurementMode.SINGLE_SHOT, + "single_shot_rht_only": MeasurementMode.SINGLE_SHOT_RHT_ONLY, +} + + +# Actions +PerformForcedCalibrationAction = scd4x_ns.class_( + "PerformForcedCalibrationAction", automation.Action +) +FactoryResetAction = scd4x_ns.class_("FactoryResetAction", automation.Action) + -CONF_AUTOMATIC_SELF_CALIBRATION = "automatic_self_calibration" CONF_ALTITUDE_COMPENSATION = "altitude_compensation" CONF_AMBIENT_PRESSURE_COMPENSATION = "ambient_pressure_compensation" -CONF_TEMPERATURE_OFFSET = "temperature_offset" CONF_AMBIENT_PRESSURE_COMPENSATION_SOURCE = "ambient_pressure_compensation_source" +CONF_AUTOMATIC_SELF_CALIBRATION = "automatic_self_calibration" +CONF_MEASUREMENT_MODE = "measurement_mode" +CONF_TEMPERATURE_OFFSET = "temperature_offset" + CONFIG_SCHEMA = ( cv.Schema( @@ -69,6 +90,9 @@ CONFIG_SCHEMA = ( cv.Optional(CONF_AMBIENT_PRESSURE_COMPENSATION_SOURCE): cv.use_id( sensor.Sensor ), + cv.Optional(CONF_MEASUREMENT_MODE, default="periodic"): cv.enum( + MEASUREMENT_MODE_OPTIONS, lower=True + ), } ) .extend(cv.polling_component_schema("60s")) @@ -106,3 +130,42 @@ async def to_code(config): if CONF_AMBIENT_PRESSURE_COMPENSATION_SOURCE in config: sens = await cg.get_variable(config[CONF_AMBIENT_PRESSURE_COMPENSATION_SOURCE]) cg.add(var.set_ambient_pressure_source(sens)) + + cg.add(var.set_measurement_mode(config[CONF_MEASUREMENT_MODE])) + + +SCD4X_ACTION_SCHEMA = maybe_simple_id( + { + cv.GenerateID(): cv.use_id(SCD4XComponent), + cv.Required(CONF_VALUE): cv.templatable(cv.positive_int), + } +) + + +@automation.register_action( + "scd4x.perform_forced_calibration", + PerformForcedCalibrationAction, + SCD4X_ACTION_SCHEMA, +) +async def scd4x_frc_to_code(config, action_id, template_arg, args): + var = cg.new_Pvariable(action_id, template_arg) + await cg.register_parented(var, config[CONF_ID]) + template_ = await cg.templatable(config[CONF_VALUE], args, cg.uint16) + cg.add(var.set_value(template_)) + return var + + +SCD4X_RESET_ACTION_SCHEMA = maybe_simple_id( + { + cv.Required(CONF_ID): cv.use_id(SCD4XComponent), + } +) + + +@automation.register_action( + "scd4x.factory_reset", FactoryResetAction, SCD4X_RESET_ACTION_SCHEMA +) +async def scd4x_reset_to_code(config, action_id, template_arg, args): + var = cg.new_Pvariable(action_id, template_arg) + await cg.register_parented(var, config[CONF_ID]) + return var diff --git a/tests/test1.yaml b/tests/test1.yaml index deaf1c237e..e15b2cf6b7 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -890,6 +890,7 @@ sensor: temperature_offset: 4.2C i2c_id: i2c_bus - platform: scd4x + id: scd40 co2: name: "SCD4X CO2" temperature: @@ -2822,3 +2823,14 @@ lock: - platform: copy source_id: test_lock2 name: Generic Output Lock Copy + +button: + - platform: template + name: "Start calibration" + on_press: + - scd4x.perform_forced_calibration: + value: 419 + id: scd40 + - scd4x.factory_reset: + id: scd40 + From 98c733108e430b7828dc7f884942ab063f5e616f Mon Sep 17 00:00:00 2001 From: Matthew Garrett Date: Tue, 10 May 2022 02:35:43 -0700 Subject: [PATCH 0270/3388] PMSX003: Add support for specifying the update interval and spinning down (#3053) Co-authored-by: Otto Winter --- esphome/components/pmsx003/pmsx003.cpp | 68 ++++++++++++++++++++++++++ esphome/components/pmsx003/pmsx003.h | 21 ++++++++ esphome/components/pmsx003/sensor.py | 28 +++++++++++ tests/test3.yaml | 11 +++-- 4 files changed, 125 insertions(+), 3 deletions(-) diff --git a/esphome/components/pmsx003/pmsx003.cpp b/esphome/components/pmsx003/pmsx003.cpp index 5de94699f0..43f2e12f55 100644 --- a/esphome/components/pmsx003/pmsx003.cpp +++ b/esphome/components/pmsx003/pmsx003.cpp @@ -49,6 +49,47 @@ void PMSX003Component::set_formaldehyde_sensor(sensor::Sensor *formaldehyde_sens void PMSX003Component::loop() { const uint32_t now = millis(); + + // If we update less often than it takes the device to stabilise, spin the fan down + // rather than running it constantly. It does take some time to stabilise, so we + // need to keep track of what state we're in. + if (this->update_interval_ > PMS_STABILISING_MS) { + if (this->initialised_ == 0) { + this->send_command_(PMS_CMD_AUTO_MANUAL, 0); + this->send_command_(PMS_CMD_ON_STANDBY, 1); + this->initialised_ = 1; + } + switch (this->state_) { + case PMSX003_STATE_IDLE: + // Power on the sensor now so it'll be ready when we hit the update time + if (now - this->last_update_ < (this->update_interval_ - PMS_STABILISING_MS)) + return; + + this->state_ = PMSX003_STATE_STABILISING; + this->send_command_(PMS_CMD_ON_STANDBY, 1); + this->fan_on_time_ = now; + return; + case PMSX003_STATE_STABILISING: + // wait for the sensor to be stable + if (now - this->fan_on_time_ < PMS_STABILISING_MS) + return; + // consume any command responses that are in the serial buffer + while (this->available()) + this->read_byte(&this->data_[0]); + // Trigger a new read + this->send_command_(PMS_CMD_TRIG_MANUAL, 0); + this->state_ = PMSX003_STATE_WAITING; + break; + case PMSX003_STATE_WAITING: + // Just go ahead and read stuff + break; + } + } else if (now - this->last_update_ < this->update_interval_) { + // Otherwise just leave the sensor powered up and come back when we hit the update + // time + return; + } + if (now - this->last_transmission_ >= 500) { // last transmission too long ago. Reset RX index. this->data_index_ = 0; @@ -65,6 +106,7 @@ void PMSX003Component::loop() { // finished this->parse_data_(); this->data_index_ = 0; + this->last_update_ = now; } else if (!*check) { // wrong data this->data_index_ = 0; @@ -131,6 +173,25 @@ optional PMSX003Component::check_byte_() { return {}; } +void PMSX003Component::send_command_(uint8_t cmd, uint16_t data) { + this->data_index_ = 0; + this->data_[data_index_++] = 0x42; + this->data_[data_index_++] = 0x4D; + this->data_[data_index_++] = cmd; + this->data_[data_index_++] = (data >> 8) & 0xFF; + this->data_[data_index_++] = (data >> 0) & 0xFF; + int sum = 0; + for (int i = 0; i < data_index_; i++) { + sum += this->data_[i]; + } + this->data_[data_index_++] = (sum >> 8) & 0xFF; + this->data_[data_index_++] = (sum >> 0) & 0xFF; + for (int i = 0; i < data_index_; i++) { + this->write_byte(this->data_[i]); + } + this->data_index_ = 0; +} + void PMSX003Component::parse_data_() { switch (this->type_) { case PMSX003_TYPE_5003ST: { @@ -218,6 +279,13 @@ void PMSX003Component::parse_data_() { } } + // Spin down the sensor again if we aren't going to need it until more time has + // passed than it takes to stabilise + if (this->update_interval_ > PMS_STABILISING_MS) { + this->send_command_(PMS_CMD_ON_STANDBY, 0); + this->state_ = PMSX003_STATE_IDLE; + } + this->status_clear_warning(); } uint16_t PMSX003Component::get_16_bit_uint_(uint8_t start_index) { diff --git a/esphome/components/pmsx003/pmsx003.h b/esphome/components/pmsx003/pmsx003.h index fd6364c70c..eb33f66909 100644 --- a/esphome/components/pmsx003/pmsx003.h +++ b/esphome/components/pmsx003/pmsx003.h @@ -7,6 +7,13 @@ namespace esphome { namespace pmsx003 { +// known command bytes +#define PMS_CMD_AUTO_MANUAL 0xE1 // data=0: perform measurement manually, data=1: perform measurement automatically +#define PMS_CMD_TRIG_MANUAL 0xE2 // trigger a manual measurement +#define PMS_CMD_ON_STANDBY 0xE4 // data=0: go to standby mode, data=1: go to normal mode + +static const uint16_t PMS_STABILISING_MS = 30000; // time taken for the sensor to become stable after power on + enum PMSX003Type { PMSX003_TYPE_X003 = 0, PMSX003_TYPE_5003T, @@ -14,6 +21,12 @@ enum PMSX003Type { PMSX003_TYPE_5003S, }; +enum PMSX003State { + PMSX003_STATE_IDLE = 0, + PMSX003_STATE_STABILISING, + PMSX003_STATE_WAITING, +}; + class PMSX003Component : public uart::UARTDevice, public Component { public: PMSX003Component() = default; @@ -23,6 +36,8 @@ class PMSX003Component : public uart::UARTDevice, public Component { void set_type(PMSX003Type type) { type_ = type; } + void set_update_interval(uint32_t val) { update_interval_ = val; }; + void set_pm_1_0_std_sensor(sensor::Sensor *pm_1_0_std_sensor); void set_pm_2_5_std_sensor(sensor::Sensor *pm_2_5_std_sensor); void set_pm_10_0_std_sensor(sensor::Sensor *pm_10_0_std_sensor); @@ -45,11 +60,17 @@ class PMSX003Component : public uart::UARTDevice, public Component { protected: optional check_byte_(); void parse_data_(); + void send_command_(uint8_t cmd, uint16_t data); uint16_t get_16_bit_uint_(uint8_t start_index); uint8_t data_[64]; uint8_t data_index_{0}; + uint8_t initialised_{0}; + uint32_t fan_on_time_{0}; + uint32_t last_update_{0}; uint32_t last_transmission_{0}; + uint32_t update_interval_{0}; + PMSX003State state_{PMSX003_STATE_IDLE}; PMSX003Type type_; // "Standard Particle" diff --git a/esphome/components/pmsx003/sensor.py b/esphome/components/pmsx003/sensor.py index b731e48e31..f3552f4081 100644 --- a/esphome/components/pmsx003/sensor.py +++ b/esphome/components/pmsx003/sensor.py @@ -1,6 +1,7 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import sensor, uart + from esphome.const import ( CONF_FORMALDEHYDE, CONF_HUMIDITY, @@ -17,6 +18,7 @@ from esphome.const import ( CONF_PM_2_5UM, CONF_PM_5_0UM, CONF_PM_10_0UM, + CONF_UPDATE_INTERVAL, CONF_TEMPERATURE, CONF_TYPE, DEVICE_CLASS_PM1, @@ -44,6 +46,7 @@ TYPE_PMS5003ST = "PMS5003ST" TYPE_PMS5003S = "PMS5003S" PMSX003Type = pmsx003_ns.enum("PMSX003Type") + PMSX003_TYPES = { TYPE_PMSX003: PMSX003Type.PMSX003_TYPE_X003, TYPE_PMS5003T: PMSX003Type.PMSX003_TYPE_5003T, @@ -68,6 +71,17 @@ def validate_pmsx003_sensors(value): return value +def validate_update_interval(value): + value = cv.positive_time_period_milliseconds(value) + if value == cv.time_period("0s"): + return value + if value < cv.time_period("30s"): + raise cv.Invalid( + "Update interval must be greater than or equal to 30 seconds if set." + ) + return value + + CONFIG_SCHEMA = ( cv.Schema( { @@ -157,6 +171,7 @@ CONFIG_SCHEMA = ( accuracy_decimals=0, state_class=STATE_CLASS_MEASUREMENT, ), + cv.Optional(CONF_UPDATE_INTERVAL, default="0s"): validate_update_interval, } ) .extend(cv.COMPONENT_SCHEMA) @@ -164,6 +179,17 @@ CONFIG_SCHEMA = ( ) +def final_validate(config): + require_tx = config[CONF_UPDATE_INTERVAL] > cv.time_period("0s") + schema = uart.final_validate_device_schema( + "pmsx003", baud_rate=9600, require_rx=True, require_tx=require_tx + ) + schema(config) + + +FINAL_VALIDATE_SCHEMA = final_validate + + async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) @@ -230,3 +256,5 @@ async def to_code(config): if CONF_FORMALDEHYDE in config: sens = await sensor.new_sensor(config[CONF_FORMALDEHYDE]) cg.add(var.set_formaldehyde_sensor(sens)) + + cg.add(var.set_update_interval(config[CONF_UPDATE_INTERVAL])) diff --git a/tests/test3.yaml b/tests/test3.yaml index e3818d87ec..ec768e17e5 100644 --- a/tests/test3.yaml +++ b/tests/test3.yaml @@ -266,6 +266,10 @@ uart: stop_bits: 2 # Specifically added for testing debug with no options at all. debug: + - id: uart8 + tx_pin: GPIO4 + rx_pin: GPIO5 + baud_rate: 9600 modbus: uart_id: uart1 @@ -559,7 +563,7 @@ sensor: name: 'AQI' calculation_type: 'AQI' - platform: pmsx003 - uart_id: uart2 + uart_id: uart8 type: PMSX003 pm_1_0: name: 'PM 1.0 Concentration' @@ -585,8 +589,9 @@ sensor: name: 'Particulate Count >5.0um' pm_10_0um: name: 'Particulate Count >10.0um' + update_interval: 30s - platform: pmsx003 - uart_id: uart2 + uart_id: uart5 type: PMS5003T pm_2_5: name: 'PM 2.5 Concentration' @@ -595,7 +600,7 @@ sensor: humidity: name: 'PMS Humidity' - platform: pmsx003 - uart_id: uart2 + uart_id: uart6 type: PMS5003ST pm_1_0: name: 'PM 1.0 Concentration' From 5fac67ce15dbbd8c9fc1da89264a71fcfe87258c Mon Sep 17 00:00:00 2001 From: Felix Storm Date: Tue, 10 May 2022 11:39:18 +0200 Subject: [PATCH 0271/3388] CAN bus: on_frame remote_transmission_request (#3376) --- esphome/components/canbus/__init__.py | 13 ++++++++++++- esphome/components/canbus/canbus.cpp | 6 ++++-- esphome/components/canbus/canbus.h | 8 +++++++- 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/esphome/components/canbus/__init__.py b/esphome/components/canbus/__init__.py index 20f2642144..1dbd743c75 100644 --- a/esphome/components/canbus/__init__.py +++ b/esphome/components/canbus/__init__.py @@ -78,6 +78,7 @@ CANBUS_SCHEMA = cv.Schema( min=0, max=0x1FFFFFFF ), cv.Optional(CONF_USE_EXTENDED_ID, default=False): cv.boolean, + cv.Optional(CONF_REMOTE_TRANSMISSION_REQUEST): cv.boolean, }, validate_id, ), @@ -100,10 +101,20 @@ async def setup_canbus_core_(var, config): trigger = cg.new_Pvariable( conf[CONF_TRIGGER_ID], var, can_id, can_id_mask, ext_id ) + if CONF_REMOTE_TRANSMISSION_REQUEST in conf: + cg.add( + trigger.set_remote_transmission_request( + conf[CONF_REMOTE_TRANSMISSION_REQUEST] + ) + ) await cg.register_component(trigger, conf) await automation.build_automation( trigger, - [(cg.std_vector.template(cg.uint8), "x"), (cg.uint32, "can_id")], + [ + (cg.std_vector.template(cg.uint8), "x"), + (cg.uint32, "can_id"), + (cg.bool_, "remote_transmission_request"), + ], conf, ) diff --git a/esphome/components/canbus/canbus.cpp b/esphome/components/canbus/canbus.cpp index 5d9084706b..3fe0d50f06 100644 --- a/esphome/components/canbus/canbus.cpp +++ b/esphome/components/canbus/canbus.cpp @@ -81,8 +81,10 @@ void Canbus::loop() { // fire all triggers for (auto *trigger : this->triggers_) { if ((trigger->can_id_ == (can_message.can_id & trigger->can_id_mask_)) && - (trigger->use_extended_id_ == can_message.use_extended_id)) { - trigger->trigger(data, can_message.can_id); + (trigger->use_extended_id_ == can_message.use_extended_id) && + (!trigger->remote_transmission_request_.has_value() || + trigger->remote_transmission_request_.value() == can_message.remote_transmission_request)) { + trigger->trigger(data, can_message.can_id, can_message.remote_transmission_request); } } } diff --git a/esphome/components/canbus/canbus.h b/esphome/components/canbus/canbus.h index 20c490c083..06b15c0db5 100644 --- a/esphome/components/canbus/canbus.h +++ b/esphome/components/canbus/canbus.h @@ -126,13 +126,18 @@ template class CanbusSendAction : public Action, public P std::vector data_static_{}; }; -class CanbusTrigger : public Trigger, uint32_t>, public Component { +class CanbusTrigger : public Trigger, uint32_t, bool>, public Component { friend class Canbus; public: explicit CanbusTrigger(Canbus *parent, const std::uint32_t can_id, const std::uint32_t can_id_mask, const bool use_extended_id) : parent_(parent), can_id_(can_id), can_id_mask_(can_id_mask), use_extended_id_(use_extended_id){}; + + void set_remote_transmission_request(bool remote_transmission_request) { + this->remote_transmission_request_ = remote_transmission_request; + } + void setup() override { this->parent_->add_trigger(this); } protected: @@ -140,6 +145,7 @@ class CanbusTrigger : public Trigger, uint32_t>, public Com uint32_t can_id_; uint32_t can_id_mask_; bool use_extended_id_; + optional remote_transmission_request_{}; }; } // namespace canbus From 7cba0c6fb0a95573f21e192921f130933b79162a Mon Sep 17 00:00:00 2001 From: Dennis <51165557+dennisvbussel@users.noreply.github.com> Date: Tue, 10 May 2022 11:42:31 +0200 Subject: [PATCH 0272/3388] =?UTF-8?q?Fix=20cover=20set=20position=20by=20f?= =?UTF-8?q?orce=20pushing=20position=5Fid=20datapoint=20(simila=E2=80=A6?= =?UTF-8?q?=20(#3435)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- esphome/components/tuya/cover/tuya_cover.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/tuya/cover/tuya_cover.cpp b/esphome/components/tuya/cover/tuya_cover.cpp index b63eb9109d..b55873c3c1 100644 --- a/esphome/components/tuya/cover/tuya_cover.cpp +++ b/esphome/components/tuya/cover/tuya_cover.cpp @@ -66,7 +66,7 @@ void TuyaCover::control(const cover::CoverCall &call) { auto position_int = static_cast(pos * this->value_range_); position_int = position_int + this->min_value_; - parent_->set_integer_datapoint_value(*this->position_id_, position_int); + parent_->force_set_integer_datapoint_value(*this->position_id_, position_int); } } if (call.get_position().has_value()) { @@ -82,7 +82,7 @@ void TuyaCover::control(const cover::CoverCall &call) { auto position_int = static_cast(pos * this->value_range_); position_int = position_int + this->min_value_; - parent_->set_integer_datapoint_value(*this->position_id_, position_int); + parent_->force_set_integer_datapoint_value(*this->position_id_, position_int); } } From 69118120d9145f24a40c70675de8d5dfda5483a8 Mon Sep 17 00:00:00 2001 From: LuBeDa Date: Tue, 10 May 2022 11:56:29 +0200 Subject: [PATCH 0273/3388] added prev_frame for animation (#3427) --- esphome/components/display/display_buffer.cpp | 6 ++++++ esphome/components/display/display_buffer.h | 1 + 2 files changed, 7 insertions(+) diff --git a/esphome/components/display/display_buffer.cpp b/esphome/components/display/display_buffer.cpp index d00fdd5240..ca866a43d2 100644 --- a/esphome/components/display/display_buffer.cpp +++ b/esphome/components/display/display_buffer.cpp @@ -584,6 +584,12 @@ void Animation::next_frame() { this->current_frame_ = 0; } } +void Animation::prev_frame() { + this->current_frame_--; + if (this->current_frame_ < 0) { + this->current_frame_ = this->animation_frame_count_ - 1; + } +} DisplayPage::DisplayPage(display_writer_t writer) : writer_(std::move(writer)) {} void DisplayPage::show() { this->parent_->show_page(this); } diff --git a/esphome/components/display/display_buffer.h b/esphome/components/display/display_buffer.h index 86221c5f96..33f55aa3d1 100644 --- a/esphome/components/display/display_buffer.h +++ b/esphome/components/display/display_buffer.h @@ -478,6 +478,7 @@ class Animation : public Image { int get_animation_frame_count() const; int get_current_frame() const; void next_frame(); + void prev_frame(); protected: int current_frame_; From b7e52812f8d713827c25299a76bdb4c7195a72ca Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 10 May 2022 22:02:58 +1200 Subject: [PATCH 0274/3388] Fix tests (#3455) --- tests/test3.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test3.yaml b/tests/test3.yaml index ec768e17e5..af0e784465 100644 --- a/tests/test3.yaml +++ b/tests/test3.yaml @@ -266,7 +266,7 @@ uart: stop_bits: 2 # Specifically added for testing debug with no options at all. debug: - - id: uart8 + - id: uart9 tx_pin: GPIO4 rx_pin: GPIO5 baud_rate: 9600 @@ -563,7 +563,7 @@ sensor: name: 'AQI' calculation_type: 'AQI' - platform: pmsx003 - uart_id: uart8 + uart_id: uart9 type: PMSX003 pm_1_0: name: 'PM 1.0 Concentration' From 4822abde860670ada7733338548be4ccf6e4c4f2 Mon Sep 17 00:00:00 2001 From: Massimo Cetra Date: Tue, 10 May 2022 12:03:40 +0200 Subject: [PATCH 0275/3388] Fix BLE280 setup when the sensor is marked as failed. (#3396) --- esphome/components/bme280/bme280.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/esphome/components/bme280/bme280.cpp b/esphome/components/bme280/bme280.cpp index fcb293afa0..a4ea8d608e 100644 --- a/esphome/components/bme280/bme280.cpp +++ b/esphome/components/bme280/bme280.cpp @@ -81,6 +81,11 @@ static const char *iir_filter_to_str(BME280IIRFilter filter) { void BME280Component::setup() { ESP_LOGCONFIG(TAG, "Setting up BME280..."); uint8_t chip_id = 0; + + // Mark as not failed before initializing. Some devices will turn off sensors to save on batteries + // and when they come back on, the COMPONENT_STATE_FAILED bit must be unset on the component. + this->component_state_ &= ~COMPONENT_STATE_FAILED; + if (!this->read_byte(BME280_REGISTER_CHIPID, &chip_id)) { this->error_code_ = COMMUNICATION_FAILED; this->mark_failed(); From e541ae400c3a5d43c2e3d621ac4c7c99799ecb36 Mon Sep 17 00:00:00 2001 From: MFlasskamp Date: Tue, 10 May 2022 12:03:59 +0200 Subject: [PATCH 0276/3388] Esp32c3 deepsleep fix (#3454) --- .../components/deep_sleep/deep_sleep_component.cpp | 14 ++++++++++++-- .../components/deep_sleep/deep_sleep_component.h | 6 ++++-- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/esphome/components/deep_sleep/deep_sleep_component.cpp b/esphome/components/deep_sleep/deep_sleep_component.cpp index 23f2a7a70c..1e31cef33e 100644 --- a/esphome/components/deep_sleep/deep_sleep_component.cpp +++ b/esphome/components/deep_sleep/deep_sleep_component.cpp @@ -21,6 +21,7 @@ optional DeepSleepComponent::get_run_duration_() const { switch (wakeup_cause) { case ESP_SLEEP_WAKEUP_EXT0: case ESP_SLEEP_WAKEUP_EXT1: + case ESP_SLEEP_WAKEUP_GPIO: return this->wakeup_cause_to_run_duration_->gpio_cause; case ESP_SLEEP_WAKEUP_TOUCHPAD: return this->wakeup_cause_to_run_duration_->touch_cause; @@ -72,18 +73,27 @@ float DeepSleepComponent::get_loop_priority() const { return -100.0f; // run after everything else is ready } void DeepSleepComponent::set_sleep_duration(uint32_t time_ms) { this->sleep_duration_ = uint64_t(time_ms) * 1000; } -#ifdef USE_ESP32 +#if defined(USE_ESP32) void DeepSleepComponent::set_wakeup_pin_mode(WakeupPinMode wakeup_pin_mode) { this->wakeup_pin_mode_ = wakeup_pin_mode; } +#endif + +#if defined(USE_ESP32) #if !defined(USE_ESP32_VARIANT_ESP32C3) + void DeepSleepComponent::set_ext1_wakeup(Ext1Wakeup ext1_wakeup) { this->ext1_wakeup_ = ext1_wakeup; } + void DeepSleepComponent::set_touch_wakeup(bool touch_wakeup) { this->touch_wakeup_ = touch_wakeup; } + +#endif + void DeepSleepComponent::set_run_duration(WakeupCauseToRunDuration wakeup_cause_to_run_duration) { wakeup_cause_to_run_duration_ = wakeup_cause_to_run_duration; } + #endif -#endif + void DeepSleepComponent::set_run_duration(uint32_t time_ms) { this->run_duration_ = time_ms; } void DeepSleepComponent::begin_sleep(bool manual) { if (this->prevent_ && !manual) { diff --git a/esphome/components/deep_sleep/deep_sleep_component.h b/esphome/components/deep_sleep/deep_sleep_component.h index 5e90d4b89d..1ff681693f 100644 --- a/esphome/components/deep_sleep/deep_sleep_component.h +++ b/esphome/components/deep_sleep/deep_sleep_component.h @@ -70,17 +70,19 @@ class DeepSleepComponent : public Component { void set_wakeup_pin_mode(WakeupPinMode wakeup_pin_mode); #endif -#if defined(USE_ESP32) && !defined(USE_ESP32_VARIANT_ESP32C3) +#if defined(USE_ESP32) +#if !defined(USE_ESP32_VARIANT_ESP32C3) void set_ext1_wakeup(Ext1Wakeup ext1_wakeup); void set_touch_wakeup(bool touch_wakeup); +#endif // Set the duration in ms for how long the code should run before entering // deep sleep mode, according to the cause the ESP32 has woken. void set_run_duration(WakeupCauseToRunDuration wakeup_cause_to_run_duration); - #endif + /// Set a duration in ms for how long the code should run before entering deep sleep mode. void set_run_duration(uint32_t time_ms); From 235a97ea1014e13454db4693e57db64ae153c4bc Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Tue, 10 May 2022 21:54:00 +0200 Subject: [PATCH 0277/3388] Make retry scheduler efficient (#3225) --- esphome/core/component.h | 2 +- esphome/core/scheduler.cpp | 71 ++++++++++++++++++++------------------ esphome/core/scheduler.h | 19 +++------- 3 files changed, 43 insertions(+), 49 deletions(-) diff --git a/esphome/core/component.h b/esphome/core/component.h index c3a4ac3782..e394736653 100644 --- a/esphome/core/component.h +++ b/esphome/core/component.h @@ -61,7 +61,7 @@ extern const uint32_t STATUS_LED_OK; extern const uint32_t STATUS_LED_WARNING; extern const uint32_t STATUS_LED_ERROR; -enum RetryResult { DONE, RETRY }; +enum class RetryResult { DONE, RETRY }; class Component { public: diff --git a/esphome/core/scheduler.cpp b/esphome/core/scheduler.cpp index 56f823556b..cc4074b94d 100644 --- a/esphome/core/scheduler.cpp +++ b/esphome/core/scheduler.cpp @@ -14,7 +14,7 @@ static const uint32_t MAX_LOGICALLY_DELETED_ITEMS = 10; // #define ESPHOME_DEBUG_SCHEDULER void HOT Scheduler::set_timeout(Component *component, const std::string &name, uint32_t timeout, - std::function &&func) { + std::function func) { const uint32_t now = this->millis_(); if (!name.empty()) @@ -32,7 +32,7 @@ void HOT Scheduler::set_timeout(Component *component, const std::string &name, u item->timeout = timeout; item->last_execution = now; item->last_execution_major = this->millis_major_; - item->void_callback = std::move(func); + item->callback = std::move(func); item->remove = false; this->push_(std::move(item)); } @@ -40,7 +40,7 @@ bool HOT Scheduler::cancel_timeout(Component *component, const std::string &name return this->cancel_item_(component, name, SchedulerItem::TIMEOUT); } void HOT Scheduler::set_interval(Component *component, const std::string &name, uint32_t interval, - std::function &&func) { + std::function func) { const uint32_t now = this->millis_(); if (!name.empty()) @@ -65,7 +65,7 @@ void HOT Scheduler::set_interval(Component *component, const std::string &name, item->last_execution_major = this->millis_major_; if (item->last_execution > now) item->last_execution_major--; - item->void_callback = std::move(func); + item->callback = std::move(func); item->remove = false; this->push_(std::move(item)); } @@ -73,37 +73,48 @@ bool HOT Scheduler::cancel_interval(Component *component, const std::string &nam return this->cancel_item_(component, name, SchedulerItem::INTERVAL); } -void HOT Scheduler::set_retry(Component *component, const std::string &name, uint32_t initial_wait_time, - uint8_t max_attempts, std::function &&func, - float backoff_increase_factor) { - const uint32_t now = this->millis_(); +struct RetryArgs { + std::function func; + uint8_t retry_countdown; + uint32_t current_interval; + Component *component; + std::string name; + float backoff_increase_factor; + Scheduler *scheduler; +}; +static void retry_handler(const std::shared_ptr &args) { + RetryResult retry_result = args->func(); + if (retry_result == RetryResult::DONE || --args->retry_countdown <= 0) + return; + args->current_interval *= args->backoff_increase_factor; + args->scheduler->set_timeout(args->component, args->name, args->current_interval, [args]() { retry_handler(args); }); +} + +void HOT Scheduler::set_retry(Component *component, const std::string &name, uint32_t initial_wait_time, + uint8_t max_attempts, std::function func, float backoff_increase_factor) { if (!name.empty()) this->cancel_retry(component, name); if (initial_wait_time == SCHEDULER_DONT_RUN) return; - ESP_LOGVV(TAG, "set_retry(name='%s', initial_wait_time=%u,max_attempts=%u, backoff_factor=%0.1f)", name.c_str(), + ESP_LOGVV(TAG, "set_retry(name='%s', initial_wait_time=%u, max_attempts=%u, backoff_factor=%0.1f)", name.c_str(), initial_wait_time, max_attempts, backoff_increase_factor); - auto item = make_unique(); - item->component = component; - item->name = name; - item->type = SchedulerItem::RETRY; - item->interval = initial_wait_time; - item->retry_countdown = max_attempts; - item->backoff_multiplier = backoff_increase_factor; - item->last_execution = now - initial_wait_time; - item->last_execution_major = this->millis_major_; - if (item->last_execution > now) - item->last_execution_major--; - item->retry_callback = std::move(func); - item->remove = false; - this->push_(std::move(item)); + auto args = std::make_shared(); + args->func = std::move(func); + args->retry_countdown = max_attempts; + args->current_interval = initial_wait_time; + args->component = component; + args->name = "retry$" + name; + args->backoff_increase_factor = backoff_increase_factor; + args->scheduler = this; + + this->set_timeout(component, args->name, initial_wait_time, [args]() { retry_handler(args); }); } bool HOT Scheduler::cancel_retry(Component *component, const std::string &name) { - return this->cancel_item_(component, name, SchedulerItem::RETRY); + return this->cancel_timeout(component, "retry$" + name); } optional HOT Scheduler::next_schedule_in() { @@ -162,7 +173,6 @@ void HOT Scheduler::call() { } while (!this->empty_()) { - RetryResult retry_result = RETRY; // use scoping to indicate visibility of `item` variable { // Don't copy-by value yet @@ -191,11 +201,7 @@ void HOT Scheduler::call() { // - timeouts/intervals get cancelled { WarnIfComponentBlockingGuard guard{item->component}; - if (item->type == SchedulerItem::RETRY) { - retry_result = item->retry_callback(); - } else { - item->void_callback(); - } + item->callback(); } } @@ -213,16 +219,13 @@ void HOT Scheduler::call() { continue; } - if (item->type == SchedulerItem::INTERVAL || - (item->type == SchedulerItem::RETRY && (--item->retry_countdown > 0 && retry_result != RetryResult::DONE))) { + if (item->type == SchedulerItem::INTERVAL) { if (item->interval != 0) { const uint32_t before = item->last_execution; const uint32_t amount = (now - item->last_execution) / item->interval; item->last_execution += amount * item->interval; if (item->last_execution < before) item->last_execution_major++; - if (item->type == SchedulerItem::RETRY) - item->interval *= item->backoff_multiplier; } this->push_(std::move(item)); } diff --git a/esphome/core/scheduler.h b/esphome/core/scheduler.h index dc96d58329..111bee1df2 100644 --- a/esphome/core/scheduler.h +++ b/esphome/core/scheduler.h @@ -10,13 +10,13 @@ class Component; class Scheduler { public: - void set_timeout(Component *component, const std::string &name, uint32_t timeout, std::function &&func); + void set_timeout(Component *component, const std::string &name, uint32_t timeout, std::function func); bool cancel_timeout(Component *component, const std::string &name); - void set_interval(Component *component, const std::string &name, uint32_t interval, std::function &&func); + void set_interval(Component *component, const std::string &name, uint32_t interval, std::function func); bool cancel_interval(Component *component, const std::string &name); void set_retry(Component *component, const std::string &name, uint32_t initial_wait_time, uint8_t max_attempts, - std::function &&func, float backoff_increase_factor = 1.0f); + std::function func, float backoff_increase_factor = 1.0f); bool cancel_retry(Component *component, const std::string &name); optional next_schedule_in(); @@ -29,20 +29,13 @@ class Scheduler { struct SchedulerItem { Component *component; std::string name; - enum Type { TIMEOUT, INTERVAL, RETRY } type; + enum Type { TIMEOUT, INTERVAL } type; union { uint32_t interval; uint32_t timeout; }; uint32_t last_execution; - // Ideally this should be a union or std::variant - // but unions don't work with object like std::function - // union CallBack_{ - std::function void_callback; - std::function retry_callback; - // }; - uint8_t retry_countdown{3}; - float backoff_multiplier{1.0f}; + std::function callback; bool remove; uint8_t last_execution_major; @@ -60,8 +53,6 @@ class Scheduler { switch (this->type) { case SchedulerItem::INTERVAL: return "interval"; - case SchedulerItem::RETRY: - return "retry"; case SchedulerItem::TIMEOUT: return "timeout"; default: From 62f9e181e03e6f3ae1aea18833b686b069fb0680 Mon Sep 17 00:00:00 2001 From: Maurice Makaay Date: Wed, 11 May 2022 00:58:28 +0200 Subject: [PATCH 0278/3388] Code cleanup fixes for the select component (#3457) Co-authored-by: Maurice Makaay --- esphome/components/select/select.cpp | 11 ++++++++--- esphome/components/select/select.h | 18 +++++++++++++++--- esphome/components/select/select_call.cpp | 2 -- esphome/components/select/select_call.h | 1 - .../template/select/template_select.cpp | 19 ++++++++++--------- 5 files changed, 33 insertions(+), 18 deletions(-) diff --git a/esphome/components/select/select.cpp b/esphome/components/select/select.cpp index 75edb5c8ba..35f1cfba46 100644 --- a/esphome/components/select/select.cpp +++ b/esphome/components/select/select.cpp @@ -23,6 +23,10 @@ void Select::add_on_state_callback(std::function &&ca this->state_callback_.add(std::move(callback)); } +bool Select::has_option(const std::string &option) const { return this->index_of(option).has_value(); } + +bool Select::has_index(size_t index) const { return index < this->size(); } + size_t Select::size() const { auto options = traits.get_options(); return options.size(); @@ -46,11 +50,12 @@ optional Select::active_index() const { } optional Select::at(size_t index) const { - auto options = traits.get_options(); - if (index >= options.size()) { + if (this->has_index(index)) { + auto options = traits.get_options(); + return options.at(index); + } else { return {}; } - return options.at(index); } uint32_t Select::hash_base() { return 2812997003UL; } diff --git a/esphome/components/select/select.h b/esphome/components/select/select.h index 64870fc9a3..b11c6404a0 100644 --- a/esphome/components/select/select.h +++ b/esphome/components/select/select.h @@ -28,16 +28,28 @@ class Select : public EntityBase { void publish_state(const std::string &state); - /// Return whether this select has gotten a full state yet. + /// Return whether this select component has gotten a full state yet. bool has_state() const { return has_state_; } + /// Instantiate a SelectCall object to modify this select component's state. SelectCall make_call() { return SelectCall(this); } - void set(const std::string &value) { make_call().set_option(value).perform(); } - // Methods that provide an API to index-based access. + /// Return whether this select component contains the provided option. + bool has_option(const std::string &option) const; + + /// Return whether this select component contains the provided index offset. + bool has_index(size_t index) const; + + /// Return the number of options in this select component. size_t size() const; + + /// Find the (optional) index offset of the provided option value. optional index_of(const std::string &option) const; + + /// Return the (optional) index offset of the currently active option. optional active_index() const; + + /// Return the (optional) option value at the provided index offset. optional at(size_t index) const; void add_on_state_callback(std::function &&callback); diff --git a/esphome/components/select/select_call.cpp b/esphome/components/select/select_call.cpp index 9442598740..6ee41b1029 100644 --- a/esphome/components/select/select_call.cpp +++ b/esphome/components/select/select_call.cpp @@ -13,8 +13,6 @@ SelectCall &SelectCall::set_option(const std::string &option) { SelectCall &SelectCall::set_index(size_t index) { return with_operation(SELECT_OP_SET_INDEX).with_index(index); } -const optional &SelectCall::get_option() const { return option_; } - SelectCall &SelectCall::select_next(bool cycle) { return with_operation(SELECT_OP_NEXT).with_cycle(cycle); } SelectCall &SelectCall::select_previous(bool cycle) { return with_operation(SELECT_OP_PREVIOUS).with_cycle(cycle); } diff --git a/esphome/components/select/select_call.h b/esphome/components/select/select_call.h index ea4d34ab5f..efc9a982ec 100644 --- a/esphome/components/select/select_call.h +++ b/esphome/components/select/select_call.h @@ -24,7 +24,6 @@ class SelectCall { SelectCall &set_option(const std::string &option); SelectCall &set_index(size_t index); - const optional &get_option() const; SelectCall &select_next(bool cycle); SelectCall &select_previous(bool cycle); diff --git a/esphome/components/template/select/template_select.cpp b/esphome/components/template/select/template_select.cpp index 219c341ec9..88a0d66ed6 100644 --- a/esphome/components/template/select/template_select.cpp +++ b/esphome/components/template/select/template_select.cpp @@ -20,9 +20,12 @@ void TemplateSelect::setup() { this->pref_ = global_preferences->make_preference(this->get_object_id_hash()); if (!this->pref_.load(&index)) { value = this->initial_option_; - ESP_LOGD(TAG, "State from initial (could not load): %s", value.c_str()); + ESP_LOGD(TAG, "State from initial (could not load stored index): %s", value.c_str()); + } else if (!this->has_index(index)) { + value = this->initial_option_; + ESP_LOGD(TAG, "State from initial (restored index %d out of bounds): %s", index, value.c_str()); } else { - value = this->traits.get_options().at(index); + value = this->at(index).value(); ESP_LOGD(TAG, "State from restore: %s", value.c_str()); } } @@ -38,9 +41,8 @@ void TemplateSelect::update() { if (!val.has_value()) return; - auto options = this->traits.get_options(); - if (std::find(options.begin(), options.end(), *val) == options.end()) { - ESP_LOGE(TAG, "lambda returned an invalid option %s", (*val).c_str()); + if (!this->has_option(*val)) { + ESP_LOGE(TAG, "Lambda returned an invalid option: %s", (*val).c_str()); return; } @@ -54,12 +56,11 @@ void TemplateSelect::control(const std::string &value) { this->publish_state(value); if (this->restore_value_) { - auto options = this->traits.get_options(); - size_t index = std::find(options.begin(), options.end(), value) - options.begin(); - - this->pref_.save(&index); + auto index = this->index_of(value); + this->pref_.save(&index.value()); } } + void TemplateSelect::dump_config() { LOG_SELECT("", "Template Select", this); LOG_UPDATE_INTERVAL(this); From c569f5ddcfbb301424860055f1ef14e7bf7c8850 Mon Sep 17 00:00:00 2001 From: Maurice Makaay Date: Wed, 11 May 2022 01:02:49 +0200 Subject: [PATCH 0279/3388] Code cleanup fixes for the number component (#3458) Co-authored-by: Maurice Makaay --- esphome/components/number/number.h | 1 - esphome/components/number/number_call.h | 2 -- 2 files changed, 3 deletions(-) diff --git a/esphome/components/number/number.h b/esphome/components/number/number.h index 8f9bf8c2e1..ad058e3a0e 100644 --- a/esphome/components/number/number.h +++ b/esphome/components/number/number.h @@ -33,7 +33,6 @@ class Number : public EntityBase { void publish_state(float state); NumberCall make_call() { return NumberCall(this); } - void set(float value) { make_call().set_value(value).perform(); } void add_on_state_callback(std::function &&callback); diff --git a/esphome/components/number/number_call.h b/esphome/components/number/number_call.h index 9a3dad560f..bd50170be5 100644 --- a/esphome/components/number/number_call.h +++ b/esphome/components/number/number_call.h @@ -23,8 +23,6 @@ class NumberCall { void perform(); NumberCall &set_value(float value); - const optional &get_value() const { return value_; } - NumberCall &number_increment(bool cycle); NumberCall &number_decrement(bool cycle); NumberCall &number_to_min(); From 0b69f7231585cd8d110efeb08a992647b1bbb6a3 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Wed, 11 May 2022 01:38:05 +0200 Subject: [PATCH 0280/3388] Enable api transport encryption for new projects (#3142) * Enable api transport encryption for new projects * Format --- esphome/dashboard/dashboard.py | 3 +++ esphome/wizard.py | 2 ++ 2 files changed, 5 insertions(+) diff --git a/esphome/dashboard/dashboard.py b/esphome/dashboard/dashboard.py index b78d22cf7c..1fadac968d 100644 --- a/esphome/dashboard/dashboard.py +++ b/esphome/dashboard/dashboard.py @@ -1,5 +1,6 @@ # pylint: disable=wrong-import-position +import base64 import codecs import collections import functools @@ -378,6 +379,8 @@ class WizardRequestHandler(BaseHandler): if k in ("name", "platform", "board", "ssid", "psk", "password") } kwargs["ota_password"] = secrets.token_hex(16) + noise_psk = secrets.token_bytes(32) + kwargs["api_encryption_key"] = base64.b64encode(noise_psk).decode() destination = settings.rel_path(f"{kwargs['name']}.yaml") wizard.wizard_write(path=destination, **kwargs) self.set_status(200) diff --git a/esphome/wizard.py b/esphome/wizard.py index 34930ff66f..469219300b 100644 --- a/esphome/wizard.py +++ b/esphome/wizard.py @@ -111,6 +111,8 @@ def wizard_file(**kwargs): # Configure API if "password" in kwargs: config += f" password: \"{kwargs['password']}\"\n" + if "api_encryption_key" in kwargs: + config += f" encryption:\n key: \"{kwargs['api_encryption_key']}\"\n" # Configure OTA config += "\nota:\n" From 4116caff6ae1262502dd7c4d6952a140105146f8 Mon Sep 17 00:00:00 2001 From: Ruben De Smet Date: Wed, 11 May 2022 01:44:52 +0200 Subject: [PATCH 0281/3388] Implement allow_deep_sleep (#3282) --- esphome/components/deep_sleep/deep_sleep_component.cpp | 1 + esphome/components/deep_sleep/deep_sleep_component.h | 1 + 2 files changed, 2 insertions(+) diff --git a/esphome/components/deep_sleep/deep_sleep_component.cpp b/esphome/components/deep_sleep/deep_sleep_component.cpp index 1e31cef33e..8db100f236 100644 --- a/esphome/components/deep_sleep/deep_sleep_component.cpp +++ b/esphome/components/deep_sleep/deep_sleep_component.cpp @@ -160,6 +160,7 @@ void DeepSleepComponent::begin_sleep(bool manual) { } float DeepSleepComponent::get_setup_priority() const { return setup_priority::LATE; } void DeepSleepComponent::prevent_deep_sleep() { this->prevent_ = true; } +void DeepSleepComponent::allow_deep_sleep() { this->prevent_ = false; } } // namespace deep_sleep } // namespace esphome diff --git a/esphome/components/deep_sleep/deep_sleep_component.h b/esphome/components/deep_sleep/deep_sleep_component.h index 1ff681693f..f384dee01d 100644 --- a/esphome/components/deep_sleep/deep_sleep_component.h +++ b/esphome/components/deep_sleep/deep_sleep_component.h @@ -96,6 +96,7 @@ class DeepSleepComponent : public Component { void begin_sleep(bool manual = false); void prevent_deep_sleep(); + void allow_deep_sleep(); protected: // Returns nullopt if no run duration is set. Otherwise, returns the run From 40ad9f491182c58453bc94b49d9a934d0c25d46c Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 11 May 2022 12:47:50 +1200 Subject: [PATCH 0282/3388] Add deep_sleep.allow YAML action (#3459) --- esphome/components/deep_sleep/__init__.py | 63 ++++++++++++------- .../deep_sleep/deep_sleep_component.h | 12 ++-- tests/test1.yaml | 2 +- 3 files changed, 48 insertions(+), 29 deletions(-) diff --git a/esphome/components/deep_sleep/__init__.py b/esphome/components/deep_sleep/__init__.py index 058358fa67..8b60b4eb5f 100644 --- a/esphome/components/deep_sleep/__init__.py +++ b/esphome/components/deep_sleep/__init__.py @@ -93,7 +93,14 @@ deep_sleep_ns = cg.esphome_ns.namespace("deep_sleep") DeepSleepComponent = deep_sleep_ns.class_("DeepSleepComponent", cg.Component) EnterDeepSleepAction = deep_sleep_ns.class_("EnterDeepSleepAction", automation.Action) PreventDeepSleepAction = deep_sleep_ns.class_( - "PreventDeepSleepAction", automation.Action + "PreventDeepSleepAction", + automation.Action, + cg.Parented.template(DeepSleepComponent), +) +AllowDeepSleepAction = deep_sleep_ns.class_( + "AllowDeepSleepAction", + automation.Action, + cg.Parented.template(DeepSleepComponent), ) WakeupPinMode = deep_sleep_ns.enum("WakeupPinMode") @@ -208,28 +215,32 @@ async def to_code(config): cg.add_define("USE_DEEP_SLEEP") -DEEP_SLEEP_ENTER_SCHEMA = cv.All( - automation.maybe_simple_id( - { - cv.GenerateID(): cv.use_id(DeepSleepComponent), - cv.Exclusive(CONF_SLEEP_DURATION, "time"): cv.templatable( - cv.positive_time_period_milliseconds - ), - # Only on ESP32 due to how long the RTC on ESP8266 can stay asleep - cv.Exclusive(CONF_UNTIL, "time"): cv.All(cv.only_on_esp32, cv.time_of_day), - cv.Optional(CONF_TIME_ID): cv.use_id(time.RealTimeClock), - } - ), - cv.has_none_or_all_keys(CONF_UNTIL, CONF_TIME_ID), -) - - -DEEP_SLEEP_PREVENT_SCHEMA = automation.maybe_simple_id( +DEEP_SLEEP_ACTION_SCHEMA = cv.Schema( { cv.GenerateID(): cv.use_id(DeepSleepComponent), } ) +DEEP_SLEEP_ENTER_SCHEMA = cv.All( + automation.maybe_simple_id( + DEEP_SLEEP_ACTION_SCHEMA.extend( + cv.Schema( + { + cv.Exclusive(CONF_SLEEP_DURATION, "time"): cv.templatable( + cv.positive_time_period_milliseconds + ), + # Only on ESP32 due to how long the RTC on ESP8266 can stay asleep + cv.Exclusive(CONF_UNTIL, "time"): cv.All( + cv.only_on_esp32, cv.time_of_day + ), + cv.Optional(CONF_TIME_ID): cv.use_id(time.RealTimeClock), + } + ) + ) + ), + cv.has_none_or_all_keys(CONF_UNTIL, CONF_TIME_ID), +) + @automation.register_action( "deep_sleep.enter", EnterDeepSleepAction, DEEP_SLEEP_ENTER_SCHEMA @@ -252,8 +263,16 @@ async def deep_sleep_enter_to_code(config, action_id, template_arg, args): @automation.register_action( - "deep_sleep.prevent", PreventDeepSleepAction, DEEP_SLEEP_PREVENT_SCHEMA + "deep_sleep.prevent", + PreventDeepSleepAction, + automation.maybe_simple_id(DEEP_SLEEP_ACTION_SCHEMA), ) -async def deep_sleep_prevent_to_code(config, action_id, template_arg, args): - paren = await cg.get_variable(config[CONF_ID]) - return cg.new_Pvariable(action_id, template_arg, paren) +@automation.register_action( + "deep_sleep.allow", + AllowDeepSleepAction, + automation.maybe_simple_id(DEEP_SLEEP_ACTION_SCHEMA), +) +async def deep_sleep_action_to_code(config, action_id, template_arg, args): + var = cg.new_Pvariable(action_id, template_arg) + await cg.register_parented(var, config[CONF_ID]) + return var diff --git a/esphome/components/deep_sleep/deep_sleep_component.h b/esphome/components/deep_sleep/deep_sleep_component.h index f384dee01d..8dc87cece8 100644 --- a/esphome/components/deep_sleep/deep_sleep_component.h +++ b/esphome/components/deep_sleep/deep_sleep_component.h @@ -190,14 +190,14 @@ template class EnterDeepSleepAction : public Action { #endif }; -template class PreventDeepSleepAction : public Action { +template class PreventDeepSleepAction : public Action, public Parented { public: - PreventDeepSleepAction(DeepSleepComponent *deep_sleep) : deep_sleep_(deep_sleep) {} + void play(Ts... x) override { this->parent_->prevent_deep_sleep(); } +}; - void play(Ts... x) override { this->deep_sleep_->prevent_deep_sleep(); } - - protected: - DeepSleepComponent *deep_sleep_; +template class AllowDeepSleepAction : public Action, public Parented { + public: + void play(Ts... x) override { this->parent_->allow_deep_sleep(); } }; } // namespace deep_sleep diff --git a/tests/test1.yaml b/tests/test1.yaml index e15b2cf6b7..7bb1fbe954 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -121,6 +121,7 @@ mqtt: - topic: livingroom/ota_mode then: - deep_sleep.prevent + - deep_sleep.allow - topic: livingroom/ota_mode then: - deep_sleep.enter: @@ -2833,4 +2834,3 @@ button: id: scd40 - scd4x.factory_reset: id: scd40 - From d6e039a1d14ab6473321d2fc6d1a47158accd672 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 11 May 2022 12:50:42 +1200 Subject: [PATCH 0283/3388] Bump version to 2022.5.0b1 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index fc928dc530..c254edf1ee 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2022.5.0-dev" +__version__ = "2022.5.0b1" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" From f8a1bd4e79449a07420f09b5a88321dbf1ca6d35 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 11 May 2022 12:50:42 +1200 Subject: [PATCH 0284/3388] Bump version to 2022.6.0-dev --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index fc928dc530..9a2e41d69f 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2022.5.0-dev" +__version__ = "2022.6.0-dev" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" From 3f678e218d22194de8f2c19dd2a9fa5b009d5d77 Mon Sep 17 00:00:00 2001 From: Maurice Makaay Date: Wed, 11 May 2022 23:25:00 +0200 Subject: [PATCH 0285/3388] On epoch sync, restore local TZ (#3462) Co-authored-by: Maurice Makaay --- esphome/components/time/real_time_clock.cpp | 12 ++++++++++-- esphome/components/time/real_time_clock.h | 1 + 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/esphome/components/time/real_time_clock.cpp b/esphome/components/time/real_time_clock.cpp index 36c5f4161d..7b5f0aa49b 100644 --- a/esphome/components/time/real_time_clock.cpp +++ b/esphome/components/time/real_time_clock.cpp @@ -13,11 +13,11 @@ static const char *const TAG = "time"; RealTimeClock::RealTimeClock() = default; void RealTimeClock::call_setup() { - setenv("TZ", this->timezone_.c_str(), 1); - tzset(); + this->apply_timezone_(); PollingComponent::call_setup(); } void RealTimeClock::synchronize_epoch_(uint32_t epoch) { + // Update UTC epoch time. struct timeval timev { .tv_sec = static_cast(epoch), .tv_usec = 0, }; @@ -30,6 +30,9 @@ void RealTimeClock::synchronize_epoch_(uint32_t epoch) { ret = settimeofday(&timev, nullptr); } + // Move timezone back to local timezone. + this->apply_timezone_(); + if (ret != 0) { ESP_LOGW(TAG, "setimeofday() failed with code %d", ret); } @@ -41,6 +44,11 @@ void RealTimeClock::synchronize_epoch_(uint32_t epoch) { this->time_sync_callback_.call(); } +void RealTimeClock::apply_timezone_() { + setenv("TZ", this->timezone_.c_str(), 1); + tzset(); +} + size_t ESPTime::strftime(char *buffer, size_t buffer_len, const char *format) { struct tm c_tm = this->to_c_tm(); return ::strftime(buffer, buffer_len, format, &c_tm); diff --git a/esphome/components/time/real_time_clock.h b/esphome/components/time/real_time_clock.h index b22c6f04d7..7f4afee306 100644 --- a/esphome/components/time/real_time_clock.h +++ b/esphome/components/time/real_time_clock.h @@ -137,6 +137,7 @@ class RealTimeClock : public PollingComponent { void synchronize_epoch_(uint32_t epoch); std::string timezone_{}; + void apply_timezone_(); CallbackManager time_sync_callback_; }; From c2aaae4818266bf82cfb8650650e9929ad954331 Mon Sep 17 00:00:00 2001 From: Niclas Larsson Date: Thu, 12 May 2022 00:26:51 +0200 Subject: [PATCH 0286/3388] Shelly dimmer: Use unique_ptr to handle the lifetime of stm32_t (#3400) Co-authored-by: Martin <25747549+martgras@users.noreply.github.com> Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- .../shelly_dimmer/shelly_dimmer.cpp | 9 +- .../components/shelly_dimmer/stm32flash.cpp | 119 +++++++++--------- esphome/components/shelly_dimmer/stm32flash.h | 28 +++-- 3 files changed, 79 insertions(+), 77 deletions(-) diff --git a/esphome/components/shelly_dimmer/shelly_dimmer.cpp b/esphome/components/shelly_dimmer/shelly_dimmer.cpp index 3b79d0bf57..32c556da5e 100644 --- a/esphome/components/shelly_dimmer/shelly_dimmer.cpp +++ b/esphome/components/shelly_dimmer/shelly_dimmer.cpp @@ -158,11 +158,8 @@ bool ShellyDimmer::upgrade_firmware_() { ESP_LOGW(TAG, "Starting STM32 firmware upgrade"); this->reset_dfu_boot_(); - // Could be constexpr in c++17 - static const auto CLOSE = [](stm32_t *stm32) { stm32_close(stm32); }; - // Cleanup with RAII - std::unique_ptr stm32{stm32_init(this, STREAM_SERIAL, 1), CLOSE}; + auto stm32 = stm32_init(this, STREAM_SERIAL, 1); if (!stm32) { ESP_LOGW(TAG, "Failed to initialize STM32"); @@ -170,7 +167,7 @@ bool ShellyDimmer::upgrade_firmware_() { } // Erase STM32 flash. - if (stm32_erase_memory(stm32.get(), 0, STM32_MASS_ERASE) != STM32_ERR_OK) { + if (stm32_erase_memory(stm32, 0, STM32_MASS_ERASE) != STM32_ERR_OK) { ESP_LOGW(TAG, "Failed to erase STM32 flash memory"); return false; } @@ -196,7 +193,7 @@ bool ShellyDimmer::upgrade_firmware_() { std::memcpy(buffer, p, BUFFER_SIZE); p += BUFFER_SIZE; - if (stm32_write_memory(stm32.get(), addr, buffer, len) != STM32_ERR_OK) { + if (stm32_write_memory(stm32, addr, buffer, len) != STM32_ERR_OK) { ESP_LOGW(TAG, "Failed to write to STM32 flash memory"); return false; } diff --git a/esphome/components/shelly_dimmer/stm32flash.cpp b/esphome/components/shelly_dimmer/stm32flash.cpp index 4c777776fb..e688f2de36 100644 --- a/esphome/components/shelly_dimmer/stm32flash.cpp +++ b/esphome/components/shelly_dimmer/stm32flash.cpp @@ -117,7 +117,7 @@ namespace shelly_dimmer { namespace { -int flash_addr_to_page_ceil(const stm32_t *stm, uint32_t addr) { +int flash_addr_to_page_ceil(const stm32_unique_ptr &stm, uint32_t addr) { if (!(addr >= stm->dev->fl_start && addr <= stm->dev->fl_end)) return 0; @@ -135,7 +135,7 @@ int flash_addr_to_page_ceil(const stm32_t *stm, uint32_t addr) { return addr ? page + 1 : page; } -stm32_err_t stm32_get_ack_timeout(const stm32_t *stm, uint32_t timeout) { +stm32_err_t stm32_get_ack_timeout(const stm32_unique_ptr &stm, uint32_t timeout) { auto *stream = stm->stream; uint8_t rxbyte; @@ -168,9 +168,9 @@ stm32_err_t stm32_get_ack_timeout(const stm32_t *stm, uint32_t timeout) { } while (true); } -stm32_err_t stm32_get_ack(const stm32_t *stm) { return stm32_get_ack_timeout(stm, 0); } +stm32_err_t stm32_get_ack(const stm32_unique_ptr &stm) { return stm32_get_ack_timeout(stm, 0); } -stm32_err_t stm32_send_command_timeout(const stm32_t *stm, const uint8_t cmd, const uint32_t timeout) { +stm32_err_t stm32_send_command_timeout(const stm32_unique_ptr &stm, const uint8_t cmd, const uint32_t timeout) { auto *const stream = stm->stream; static constexpr auto BUFFER_SIZE = 2; @@ -194,12 +194,12 @@ stm32_err_t stm32_send_command_timeout(const stm32_t *stm, const uint8_t cmd, co return STM32_ERR_UNKNOWN; } -stm32_err_t stm32_send_command(const stm32_t *stm, const uint8_t cmd) { +stm32_err_t stm32_send_command(const stm32_unique_ptr &stm, const uint8_t cmd) { return stm32_send_command_timeout(stm, cmd, 0); } /* if we have lost sync, send a wrong command and expect a NACK */ -stm32_err_t stm32_resync(const stm32_t *stm) { +stm32_err_t stm32_resync(const stm32_unique_ptr &stm) { auto *const stream = stm->stream; uint32_t t0 = millis(); auto t1 = t0; @@ -238,7 +238,7 @@ stm32_err_t stm32_resync(const stm32_t *stm) { * * len is value of the first byte in the frame. */ -stm32_err_t stm32_guess_len_cmd(const stm32_t *stm, const uint8_t cmd, uint8_t *const data, unsigned int len) { +stm32_err_t stm32_guess_len_cmd(const stm32_unique_ptr &stm, const uint8_t cmd, uint8_t *const data, unsigned int len) { auto *const stream = stm->stream; if (stm32_send_command(stm, cmd) != STM32_ERR_OK) @@ -286,7 +286,7 @@ stm32_err_t stm32_guess_len_cmd(const stm32_t *stm, const uint8_t cmd, uint8_t * * This function sends the init sequence and, in case of timeout, recovers * the interface. */ -stm32_err_t stm32_send_init_seq(const stm32_t *stm) { +stm32_err_t stm32_send_init_seq(const stm32_unique_ptr &stm) { auto *const stream = stm->stream; stream->write_array(&STM32_CMD_INIT, 1); @@ -320,7 +320,7 @@ stm32_err_t stm32_send_init_seq(const stm32_t *stm) { return STM32_ERR_UNKNOWN; } -stm32_err_t stm32_mass_erase(const stm32_t *stm) { +stm32_err_t stm32_mass_erase(const stm32_unique_ptr &stm) { auto *const stream = stm->stream; if (stm32_send_command(stm, stm->cmd->er) != STM32_ERR_OK) { @@ -364,7 +364,7 @@ template std::unique_ptr malloc_array_raii DELETOR}; } -stm32_err_t stm32_pages_erase(const stm32_t *stm, const uint32_t spage, const uint32_t pages) { +stm32_err_t stm32_pages_erase(const stm32_unique_ptr &stm, const uint32_t spage, const uint32_t pages) { auto *const stream = stm->stream; uint8_t cs = 0; int i = 0; @@ -474,6 +474,18 @@ template void populate_buffer_with_address(uint8_t (&buffer)[N], uint3 buffer[4] = static_cast(buffer[0] ^ buffer[1] ^ buffer[2] ^ buffer[3]); } +template stm32_unique_ptr make_stm32_with_deletor(T ptr) { + static const auto CLOSE = [](stm32_t *stm32) { + if (stm32) { + free(stm32->cmd); // NOLINT + } + free(stm32); // NOLINT + }; + + // Cleanup with RAII + return std::unique_ptr{ptr, CLOSE}; +} + } // Anonymous namespace } // namespace shelly_dimmer @@ -485,48 +497,44 @@ namespace shelly_dimmer { /* find newer command by higher code */ #define newer(prev, a) (((prev) == STM32_CMD_ERR) ? (a) : (((prev) > (a)) ? (prev) : (a))) -stm32_t *stm32_init(uart::UARTDevice *stream, const uint8_t flags, const char init) { +stm32_unique_ptr stm32_init(uart::UARTDevice *stream, const uint8_t flags, const char init) { uint8_t buf[257]; - // Could be constexpr in c++17 - static const auto CLOSE = [](stm32_t *stm32) { stm32_close(stm32); }; - - // Cleanup with RAII - std::unique_ptr stm{static_cast(calloc(sizeof(stm32_t), 1)), // NOLINT - CLOSE}; + auto stm = make_stm32_with_deletor(static_cast(calloc(sizeof(stm32_t), 1))); // NOLINT if (!stm) { - return nullptr; + return make_stm32_with_deletor(nullptr); } stm->stream = stream; stm->flags = flags; stm->cmd = static_cast(malloc(sizeof(stm32_cmd_t))); // NOLINT if (!stm->cmd) { - return nullptr; + return make_stm32_with_deletor(nullptr); } memset(stm->cmd, STM32_CMD_ERR, sizeof(stm32_cmd_t)); if ((stm->flags & STREAM_OPT_CMD_INIT) && init) { - if (stm32_send_init_seq(stm.get()) != STM32_ERR_OK) - return nullptr; // NOLINT + if (stm32_send_init_seq(stm) != STM32_ERR_OK) + return make_stm32_with_deletor(nullptr); } /* get the version and read protection status */ - if (stm32_send_command(stm.get(), STM32_CMD_GVR) != STM32_ERR_OK) { - return nullptr; // NOLINT + if (stm32_send_command(stm, STM32_CMD_GVR) != STM32_ERR_OK) { + return make_stm32_with_deletor(nullptr); } /* From AN, only UART bootloader returns 3 bytes */ { const auto len = (stm->flags & STREAM_OPT_GVR_ETX) ? 3 : 1; if (!stream->read_array(buf, len)) - return nullptr; // NOLINT + return make_stm32_with_deletor(nullptr); + stm->version = buf[0]; stm->option1 = (stm->flags & STREAM_OPT_GVR_ETX) ? buf[1] : 0; stm->option2 = (stm->flags & STREAM_OPT_GVR_ETX) ? buf[2] : 0; - if (stm32_get_ack(stm.get()) != STM32_ERR_OK) { - return nullptr; + if (stm32_get_ack(stm) != STM32_ERR_OK) { + return make_stm32_with_deletor(nullptr); } } @@ -544,8 +552,8 @@ stm32_t *stm32_init(uart::UARTDevice *stream, const uint8_t flags, const char in return STM32_CMD_GET_LENGTH; })(); - if (stm32_guess_len_cmd(stm.get(), STM32_CMD_GET, buf, len) != STM32_ERR_OK) - return nullptr; + if (stm32_guess_len_cmd(stm, STM32_CMD_GET, buf, len) != STM32_ERR_OK) + return make_stm32_with_deletor(nullptr); } const auto stop = buf[0] + 1; @@ -607,23 +615,23 @@ stm32_t *stm32_init(uart::UARTDevice *stream, const uint8_t flags, const char in } if (new_cmds) ESP_LOGD(TAG, ")"); - if (stm32_get_ack(stm.get()) != STM32_ERR_OK) { - return nullptr; + if (stm32_get_ack(stm) != STM32_ERR_OK) { + return make_stm32_with_deletor(nullptr); } if (stm->cmd->get == STM32_CMD_ERR || stm->cmd->gvr == STM32_CMD_ERR || stm->cmd->gid == STM32_CMD_ERR) { ESP_LOGD(TAG, "Error: bootloader did not returned correct information from GET command"); - return nullptr; + return make_stm32_with_deletor(nullptr); } /* get the device ID */ - if (stm32_guess_len_cmd(stm.get(), stm->cmd->gid, buf, 1) != STM32_ERR_OK) { - return nullptr; + if (stm32_guess_len_cmd(stm, stm->cmd->gid, buf, 1) != STM32_ERR_OK) { + return make_stm32_with_deletor(nullptr); } const auto returned = buf[0] + 1; if (returned < 2) { ESP_LOGD(TAG, "Only %d bytes sent in the PID, unknown/unsupported device", returned); - return nullptr; + return make_stm32_with_deletor(nullptr); } stm->pid = (buf[1] << 8) | buf[2]; if (returned > 2) { @@ -631,8 +639,8 @@ stm32_t *stm32_init(uart::UARTDevice *stream, const uint8_t flags, const char in for (auto i = 2; i <= returned; i++) ESP_LOGD(TAG, " %02x", buf[i]); } - if (stm32_get_ack(stm.get()) != STM32_ERR_OK) { - return nullptr; + if (stm32_get_ack(stm) != STM32_ERR_OK) { + return make_stm32_with_deletor(nullptr); } stm->dev = DEVICES; @@ -641,21 +649,14 @@ stm32_t *stm32_init(uart::UARTDevice *stream, const uint8_t flags, const char in if (!stm->dev->id) { ESP_LOGD(TAG, "Unknown/unsupported device (Device ID: 0x%03x)", stm->pid); - return nullptr; + return make_stm32_with_deletor(nullptr); } - // TODO: Would be much better if the unique_ptr was returned from this function - // Release ownership of unique_ptr - return stm.release(); // NOLINT + return stm; } -void stm32_close(stm32_t *stm) { - if (stm) - free(stm->cmd); // NOLINT - free(stm); // NOLINT -} - -stm32_err_t stm32_read_memory(const stm32_t *stm, const uint32_t address, uint8_t *data, const unsigned int len) { +stm32_err_t stm32_read_memory(const stm32_unique_ptr &stm, const uint32_t address, uint8_t *data, + const unsigned int len) { auto *const stream = stm->stream; if (!len) @@ -693,7 +694,8 @@ stm32_err_t stm32_read_memory(const stm32_t *stm, const uint32_t address, uint8_ return STM32_ERR_OK; } -stm32_err_t stm32_write_memory(const stm32_t *stm, uint32_t address, const uint8_t *data, const unsigned int len) { +stm32_err_t stm32_write_memory(const stm32_unique_ptr &stm, uint32_t address, const uint8_t *data, + const unsigned int len) { auto *const stream = stm->stream; if (!len) @@ -753,7 +755,7 @@ stm32_err_t stm32_write_memory(const stm32_t *stm, uint32_t address, const uint8 return STM32_ERR_OK; } -stm32_err_t stm32_wunprot_memory(const stm32_t *stm) { +stm32_err_t stm32_wunprot_memory(const stm32_unique_ptr &stm) { if (stm->cmd->uw == STM32_CMD_ERR) { ESP_LOGD(TAG, "Error: WRITE UNPROTECT command not implemented in bootloader."); return STM32_ERR_NO_CMD; @@ -766,7 +768,7 @@ stm32_err_t stm32_wunprot_memory(const stm32_t *stm) { []() { ESP_LOGD(TAG, "Error: Failed to WRITE UNPROTECT"); }); } -stm32_err_t stm32_wprot_memory(const stm32_t *stm) { +stm32_err_t stm32_wprot_memory(const stm32_unique_ptr &stm) { if (stm->cmd->wp == STM32_CMD_ERR) { ESP_LOGD(TAG, "Error: WRITE PROTECT command not implemented in bootloader."); return STM32_ERR_NO_CMD; @@ -779,7 +781,7 @@ stm32_err_t stm32_wprot_memory(const stm32_t *stm) { []() { ESP_LOGD(TAG, "Error: Failed to WRITE PROTECT"); }); } -stm32_err_t stm32_runprot_memory(const stm32_t *stm) { +stm32_err_t stm32_runprot_memory(const stm32_unique_ptr &stm) { if (stm->cmd->ur == STM32_CMD_ERR) { ESP_LOGD(TAG, "Error: READOUT UNPROTECT command not implemented in bootloader."); return STM32_ERR_NO_CMD; @@ -792,7 +794,7 @@ stm32_err_t stm32_runprot_memory(const stm32_t *stm) { []() { ESP_LOGD(TAG, "Error: Failed to READOUT UNPROTECT"); }); } -stm32_err_t stm32_readprot_memory(const stm32_t *stm) { +stm32_err_t stm32_readprot_memory(const stm32_unique_ptr &stm) { if (stm->cmd->rp == STM32_CMD_ERR) { ESP_LOGD(TAG, "Error: READOUT PROTECT command not implemented in bootloader."); return STM32_ERR_NO_CMD; @@ -805,7 +807,7 @@ stm32_err_t stm32_readprot_memory(const stm32_t *stm) { []() { ESP_LOGD(TAG, "Error: Failed to READOUT PROTECT"); }); } -stm32_err_t stm32_erase_memory(const stm32_t *stm, uint32_t spage, uint32_t pages) { +stm32_err_t stm32_erase_memory(const stm32_unique_ptr &stm, uint32_t spage, uint32_t pages) { if (!pages || spage > STM32_MAX_PAGES || ((pages != STM32_MASS_ERASE) && ((spage + pages) > STM32_MAX_PAGES))) return STM32_ERR_OK; @@ -847,7 +849,7 @@ stm32_err_t stm32_erase_memory(const stm32_t *stm, uint32_t spage, uint32_t page return STM32_ERR_OK; } -static stm32_err_t stm32_run_raw_code(const stm32_t *stm, uint32_t target_address, const uint8_t *code, +static stm32_err_t stm32_run_raw_code(const stm32_unique_ptr &stm, uint32_t target_address, const uint8_t *code, uint32_t code_size) { static constexpr uint32_t BUFFER_SIZE = 256; @@ -893,7 +895,7 @@ static stm32_err_t stm32_run_raw_code(const stm32_t *stm, uint32_t target_addres return stm32_go(stm, target_address); } -stm32_err_t stm32_go(const stm32_t *stm, const uint32_t address) { +stm32_err_t stm32_go(const stm32_unique_ptr &stm, const uint32_t address) { auto *const stream = stm->stream; if (stm->cmd->go == STM32_CMD_ERR) { @@ -916,7 +918,7 @@ stm32_err_t stm32_go(const stm32_t *stm, const uint32_t address) { return STM32_ERR_OK; } -stm32_err_t stm32_reset_device(const stm32_t *stm) { +stm32_err_t stm32_reset_device(const stm32_unique_ptr &stm) { const auto target_address = stm->dev->ram_start; if (stm->dev->flags & F_OBLL) { @@ -927,7 +929,8 @@ stm32_err_t stm32_reset_device(const stm32_t *stm) { } } -stm32_err_t stm32_crc_memory(const stm32_t *stm, const uint32_t address, const uint32_t length, uint32_t *const crc) { +stm32_err_t stm32_crc_memory(const stm32_unique_ptr &stm, const uint32_t address, const uint32_t length, + uint32_t *const crc) { static constexpr auto BUFFER_SIZE = 5; auto *const stream = stm->stream; @@ -1022,7 +1025,7 @@ uint32_t stm32_sw_crc(uint32_t crc, uint8_t *buf, unsigned int len) { return crc; } -stm32_err_t stm32_crc_wrapper(const stm32_t *stm, uint32_t address, uint32_t length, uint32_t *crc) { +stm32_err_t stm32_crc_wrapper(const stm32_unique_ptr &stm, uint32_t address, uint32_t length, uint32_t *crc) { static constexpr uint32_t CRC_INIT_VALUE = 0xFFFFFFFF; static constexpr uint32_t BUFFER_SIZE = 256; diff --git a/esphome/components/shelly_dimmer/stm32flash.h b/esphome/components/shelly_dimmer/stm32flash.h index c561375c38..d973b35222 100644 --- a/esphome/components/shelly_dimmer/stm32flash.h +++ b/esphome/components/shelly_dimmer/stm32flash.h @@ -23,6 +23,7 @@ #ifdef USE_SHD_FIRMWARE_DATA #include +#include #include "esphome/components/uart/uart.h" namespace esphome { @@ -108,19 +109,20 @@ struct VarlenCmd { uint8_t length; }; -stm32_t *stm32_init(uart::UARTDevice *stream, uint8_t flags, char init); -void stm32_close(stm32_t *stm); -stm32_err_t stm32_read_memory(const stm32_t *stm, uint32_t address, uint8_t *data, unsigned int len); -stm32_err_t stm32_write_memory(const stm32_t *stm, uint32_t address, const uint8_t *data, unsigned int len); -stm32_err_t stm32_wunprot_memory(const stm32_t *stm); -stm32_err_t stm32_wprot_memory(const stm32_t *stm); -stm32_err_t stm32_erase_memory(const stm32_t *stm, uint32_t spage, uint32_t pages); -stm32_err_t stm32_go(const stm32_t *stm, uint32_t address); -stm32_err_t stm32_reset_device(const stm32_t *stm); -stm32_err_t stm32_readprot_memory(const stm32_t *stm); -stm32_err_t stm32_runprot_memory(const stm32_t *stm); -stm32_err_t stm32_crc_memory(const stm32_t *stm, uint32_t address, uint32_t length, uint32_t *crc); -stm32_err_t stm32_crc_wrapper(const stm32_t *stm, uint32_t address, uint32_t length, uint32_t *crc); +using stm32_unique_ptr = std::unique_ptr; + +stm32_unique_ptr stm32_init(uart::UARTDevice *stream, uint8_t flags, char init); +stm32_err_t stm32_read_memory(const stm32_unique_ptr &stm, uint32_t address, uint8_t *data, unsigned int len); +stm32_err_t stm32_write_memory(const stm32_unique_ptr &stm, uint32_t address, const uint8_t *data, unsigned int len); +stm32_err_t stm32_wunprot_memory(const stm32_unique_ptr &stm); +stm32_err_t stm32_wprot_memory(const stm32_unique_ptr &stm); +stm32_err_t stm32_erase_memory(const stm32_unique_ptr &stm, uint32_t spage, uint32_t pages); +stm32_err_t stm32_go(const stm32_unique_ptr &stm, uint32_t address); +stm32_err_t stm32_reset_device(const stm32_unique_ptr &stm); +stm32_err_t stm32_readprot_memory(const stm32_unique_ptr &stm); +stm32_err_t stm32_runprot_memory(const stm32_unique_ptr &stm); +stm32_err_t stm32_crc_memory(const stm32_unique_ptr &stm, uint32_t address, uint32_t length, uint32_t *crc); +stm32_err_t stm32_crc_wrapper(const stm32_unique_ptr &stm, uint32_t address, uint32_t length, uint32_t *crc); uint32_t stm32_sw_crc(uint32_t crc, uint8_t *buf, unsigned int len); } // namespace shelly_dimmer From 01c4d3c225fca32bc62a39ec7126637262e8957d Mon Sep 17 00:00:00 2001 From: James Szalay Date: Wed, 11 May 2022 23:26:14 -0400 Subject: [PATCH 0287/3388] Use heat mode for heat. Move EXT HT to custom presets. (#3437) * Use heat mode for heat. Move EXT HT to custom presets. * Fix syntax error. --- esphome/components/bedjet/bedjet.cpp | 6 ++++-- esphome/components/bedjet/bedjet.h | 2 ++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/esphome/components/bedjet/bedjet.cpp b/esphome/components/bedjet/bedjet.cpp index 1a932da0c5..38ed6206a8 100644 --- a/esphome/components/bedjet/bedjet.cpp +++ b/esphome/components/bedjet/bedjet.cpp @@ -117,7 +117,7 @@ void Bedjet::control(const ClimateCall &call) { pkt = this->codec_->get_button_request(BTN_OFF); break; case climate::CLIMATE_MODE_HEAT: - pkt = this->codec_->get_button_request(BTN_EXTHT); + pkt = this->codec_->get_button_request(BTN_HEAT); break; case climate::CLIMATE_MODE_FAN_ONLY: pkt = this->codec_->get_button_request(BTN_COOL); @@ -137,7 +137,7 @@ void Bedjet::control(const ClimateCall &call) { } else { this->force_refresh_ = true; this->mode = mode; - // We're using (custom) preset for Turbo & M1-3 presets, so changing climate mode will clear those + // We're using (custom) preset for Turbo, EXT HT, & M1-3 presets, so changing climate mode will clear those this->custom_preset.reset(); this->preset.reset(); } @@ -186,6 +186,8 @@ void Bedjet::control(const ClimateCall &call) { pkt = this->codec_->get_button_request(BTN_M2); } else if (preset == "M3") { pkt = this->codec_->get_button_request(BTN_M3); + } else if (preset == "EXT HT") { + pkt = this->codec_->get_button_request(BTN_EXTHT); } else { ESP_LOGW(TAG, "Unsupported preset: %s", preset.c_str()); return; diff --git a/esphome/components/bedjet/bedjet.h b/esphome/components/bedjet/bedjet.h index b061d2b5ec..0565be6045 100644 --- a/esphome/components/bedjet/bedjet.h +++ b/esphome/components/bedjet/bedjet.h @@ -67,6 +67,8 @@ class Bedjet : public climate::Climate, public esphome::ble_client::BLEClientNod // We could fetch biodata from bedjet and set these names that way. // But then we have to invert the lookup in order to send the right preset. // For now, we can leave them as M1-3 to match the remote buttons. + // EXT HT added to match remote button. + "EXT HT", "M1", "M2", "M3", From bcb47c306c996340fd19a2e66cbca5985fe059e2 Mon Sep 17 00:00:00 2001 From: swifty99 Date: Thu, 12 May 2022 06:53:33 +0200 Subject: [PATCH 0288/3388] Tcs34725 automatic sampling settings for improved dynamics and accuracy (#3258) Co-authored-by: Daniel Cousens <413395+dcousens@users.noreply.github.com> Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/tcs34725/sensor.py | 3 +- esphome/components/tcs34725/tcs34725.cpp | 109 ++++++++++++++++++++--- esphome/components/tcs34725/tcs34725.h | 4 +- 3 files changed, 103 insertions(+), 13 deletions(-) diff --git a/esphome/components/tcs34725/sensor.py b/esphome/components/tcs34725/sensor.py index fcc56e395f..d47e9a34c8 100644 --- a/esphome/components/tcs34725/sensor.py +++ b/esphome/components/tcs34725/sensor.py @@ -31,6 +31,7 @@ TCS34725Component = tcs34725_ns.class_( TCS34725IntegrationTime = tcs34725_ns.enum("TCS34725IntegrationTime") TCS34725_INTEGRATION_TIMES = { + "auto": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_AUTO, "2.4ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_2_4MS, "24ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_24MS, "50ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_50MS, @@ -88,7 +89,7 @@ CONFIG_SCHEMA = ( cv.Optional(CONF_CLEAR_CHANNEL): color_channel_schema, cv.Optional(CONF_ILLUMINANCE): illuminance_schema, cv.Optional(CONF_COLOR_TEMPERATURE): color_temperature_schema, - cv.Optional(CONF_INTEGRATION_TIME, default="2.4ms"): cv.enum( + cv.Optional(CONF_INTEGRATION_TIME, default="auto"): cv.enum( TCS34725_INTEGRATION_TIMES, lower=True ), cv.Optional(CONF_GAIN, default="1X"): cv.enum(TCS34725_GAINS, upper=True), diff --git a/esphome/components/tcs34725/tcs34725.cpp b/esphome/components/tcs34725/tcs34725.cpp index 825f7da4cc..276bf65ebf 100644 --- a/esphome/components/tcs34725/tcs34725.cpp +++ b/esphome/components/tcs34725/tcs34725.cpp @@ -136,8 +136,14 @@ void TCS34725Component::calculate_temperature_and_lux_(uint16_t r, uint16_t g, u } /* Check for saturation and mark the sample as invalid if true */ if (c >= sat) { - ESP_LOGW(TAG, "Saturation too high, discarding sample with saturation %.1f and clear %d", sat, c); - return; + if (this->integration_time_auto_) { + ESP_LOGI(TAG, "Saturation too high, sample discarded, autogain ongoing"); + } else { + ESP_LOGW( + TAG, + "Saturation too high, sample with saturation %.1f and clear %d treat values carefully or use grey filter", + sat, c); + } } /* AMS RGB sensors have no IR channel, so the IR content must be */ @@ -149,8 +155,14 @@ void TCS34725Component::calculate_temperature_and_lux_(uint16_t r, uint16_t g, u g2 = g - ir; b2 = b - ir; + // discarding super low values? not recemmonded, and avoided by using auto gain. if (r2 == 0) { - return; + // legacy code + if (!this->integration_time_auto_) { + ESP_LOGW(TAG, + "No light detected on red channel, switch to auto gain or adjust timing, values will be unreliable"); + return; + } } // Lux Calculation (DN40 3.2) @@ -189,7 +201,7 @@ void TCS34725Component::update() { this->status_set_warning(); return; } - ESP_LOGV(TAG, "Raw values clear=%x red=%x green=%x blue=%x", raw_c, raw_r, raw_g, raw_b); + ESP_LOGV(TAG, "Raw values clear=%d red=%d green=%d blue=%d", raw_c, raw_r, raw_g, raw_b); float channel_c; float channel_r; @@ -220,20 +232,95 @@ void TCS34725Component::update() { calculate_temperature_and_lux_(raw_r, raw_g, raw_b, raw_c); } - if (this->illuminance_sensor_ != nullptr) - this->illuminance_sensor_->publish_state(this->illuminance_); + // do not publish values if auto gain finding ongoing, and oversaturated + // so: publish when: + // - not auto mode + // - clear not oversaturated + // - clear oversaturated but gain and timing cannot go lower + if (!this->integration_time_auto_ || raw_c < 65530 || (this->gain_reg_ == 0 && this->integration_time_ < 200)) { + if (this->illuminance_sensor_ != nullptr) + this->illuminance_sensor_->publish_state(this->illuminance_); - if (this->color_temperature_sensor_ != nullptr) - this->color_temperature_sensor_->publish_state(this->color_temperature_); + if (this->color_temperature_sensor_ != nullptr) + this->color_temperature_sensor_->publish_state(this->color_temperature_); + } - ESP_LOGD(TAG, "Got Red=%.1f%%,Green=%.1f%%,Blue=%.1f%%,Clear=%.1f%% Illuminance=%.1flx Color Temperature=%.1fK", + ESP_LOGD(TAG, + "Got Red=%.1f%%,Green=%.1f%%,Blue=%.1f%%,Clear=%.1f%% Illuminance=%.1flx Color " + "Temperature=%.1fK", channel_r, channel_g, channel_b, channel_c, this->illuminance_, this->color_temperature_); + if (this->integration_time_auto_) { + // change integration time an gain to achieve maximum resolution an dynamic range + // calculate optimal integration time to achieve 70% satuaration + float integration_time_ideal; + integration_time_ideal = 60 / ((float) raw_c / 655.35) * this->integration_time_; + + uint8_t gain_reg_val_new = this->gain_reg_; + // increase gain if less than 20% of white channel used and high integration time + // increase only if not already maximum + // do not use max gain, as ist will not get better + if (this->gain_reg_ < 3) { + if (((float) raw_c / 655.35 < 20.f) && (this->integration_time_ > 600.f)) { + gain_reg_val_new = this->gain_reg_ + 1; + // update integration time to new situation + integration_time_ideal = integration_time_ideal / 4; + } + } + + // decrease gain, if very high clear values and integration times alreadey low + if (this->gain_reg_ > 0) { + if (70 < ((float) raw_c / 655.35) && (this->integration_time_ < 200)) { + gain_reg_val_new = this->gain_reg_ - 1; + // update integration time to new situation + integration_time_ideal = integration_time_ideal * 4; + } + } + + // saturate integration times + float integration_time_next = integration_time_ideal; + if (integration_time_ideal > 2.4f * 256) { + integration_time_next = 2.4f * 256; + } + if (integration_time_ideal < 154) { + integration_time_next = 154; + } + + // calculate register value from timing + uint8_t regval_atime = (uint8_t)(256.f - integration_time_next / 2.4f); + ESP_LOGD(TAG, "Integration time: %.1fms, ideal: %.1fms regval_new %d Gain: %.f Clear channel raw: %d gain reg: %d", + this->integration_time_, integration_time_next, regval_atime, this->gain_, raw_c, this->gain_reg_); + + if (this->integration_reg_ != regval_atime || gain_reg_val_new != this->gain_reg_) { + this->integration_reg_ = regval_atime; + this->gain_reg_ = gain_reg_val_new; + set_gain((TCS34725Gain) gain_reg_val_new); + if (this->write_config_register_(TCS34725_REGISTER_ATIME, this->integration_reg_) != i2c::ERROR_OK || + this->write_config_register_(TCS34725_REGISTER_CONTROL, this->gain_reg_) != i2c::ERROR_OK) { + this->mark_failed(); + ESP_LOGW(TAG, "TCS34725I update timing failed!"); + } else { + this->integration_time_ = integration_time_next; + } + } + } this->status_clear_warning(); } void TCS34725Component::set_integration_time(TCS34725IntegrationTime integration_time) { - this->integration_reg_ = integration_time; - this->integration_time_ = (256.f - integration_time) * 2.4f; + // if an integration time is 0x100, this is auto start with 154ms as this gives best starting point + TCS34725IntegrationTime my_integration_time_regval; + + if (integration_time == TCS34725_INTEGRATION_TIME_AUTO) { + this->integration_time_auto_ = true; + this->integration_reg_ = TCS34725_INTEGRATION_TIME_154MS; + my_integration_time_regval = TCS34725_INTEGRATION_TIME_154MS; + } else { + this->integration_reg_ = integration_time; + my_integration_time_regval = integration_time; + this->integration_time_auto_ = false; + } + this->integration_time_ = (256.f - my_integration_time_regval) * 2.4f; + ESP_LOGI(TAG, "TCS34725I Integration time set to: %.1fms", this->integration_time_); } void TCS34725Component::set_gain(TCS34725Gain gain) { this->gain_reg_ = gain; diff --git a/esphome/components/tcs34725/tcs34725.h b/esphome/components/tcs34725/tcs34725.h index 04565d948e..23985e8221 100644 --- a/esphome/components/tcs34725/tcs34725.h +++ b/esphome/components/tcs34725/tcs34725.h @@ -26,6 +26,7 @@ enum TCS34725IntegrationTime { TCS34725_INTEGRATION_TIME_540MS = 0x1F, TCS34725_INTEGRATION_TIME_600MS = 0x06, TCS34725_INTEGRATION_TIME_614MS = 0x00, + TCS34725_INTEGRATION_TIME_AUTO = 0x100, }; enum TCS34725Gain { @@ -77,10 +78,11 @@ class TCS34725Component : public PollingComponent, public i2c::I2CDevice { float glass_attenuation_{1.0}; float illuminance_; float color_temperature_; + bool integration_time_auto_{true}; private: void calculate_temperature_and_lux_(uint16_t r, uint16_t g, uint16_t b, uint16_t c); - uint8_t integration_reg_{TCS34725_INTEGRATION_TIME_2_4MS}; + uint16_t integration_reg_; uint8_t gain_reg_{TCS34725_GAIN_1X}; }; From 1c873e003431562737d45271ab2fc5fe3409f5b8 Mon Sep 17 00:00:00 2001 From: Michael Davidson Date: Thu, 12 May 2022 14:54:45 +1000 Subject: [PATCH 0289/3388] Make custom_fan and custom_preset templatable as per documentation (#3330) --- esphome/components/climate/__init__.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/esphome/components/climate/__init__.py b/esphome/components/climate/__init__.py index 87b9a4b3e2..1de9aa3f3a 100644 --- a/esphome/components/climate/__init__.py +++ b/esphome/components/climate/__init__.py @@ -287,9 +287,11 @@ CLIMATE_CONTROL_ACTION_SCHEMA = cv.Schema( cv.Exclusive(CONF_FAN_MODE, "fan_mode"): cv.templatable( validate_climate_fan_mode ), - cv.Exclusive(CONF_CUSTOM_FAN_MODE, "fan_mode"): cv.string_strict, + cv.Exclusive(CONF_CUSTOM_FAN_MODE, "fan_mode"): cv.templatable( + cv.string_strict + ), cv.Exclusive(CONF_PRESET, "preset"): cv.templatable(validate_climate_preset), - cv.Exclusive(CONF_CUSTOM_PRESET, "preset"): cv.string_strict, + cv.Exclusive(CONF_CUSTOM_PRESET, "preset"): cv.templatable(cv.string_strict), cv.Optional(CONF_SWING_MODE): cv.templatable(validate_climate_swing_mode), } ) @@ -324,13 +326,17 @@ async def climate_control_to_code(config, action_id, template_arg, args): template_ = await cg.templatable(config[CONF_FAN_MODE], args, ClimateFanMode) cg.add(var.set_fan_mode(template_)) if CONF_CUSTOM_FAN_MODE in config: - template_ = await cg.templatable(config[CONF_CUSTOM_FAN_MODE], args, str) + template_ = await cg.templatable( + config[CONF_CUSTOM_FAN_MODE], args, cg.std_string + ) cg.add(var.set_custom_fan_mode(template_)) if CONF_PRESET in config: template_ = await cg.templatable(config[CONF_PRESET], args, ClimatePreset) cg.add(var.set_preset(template_)) if CONF_CUSTOM_PRESET in config: - template_ = await cg.templatable(config[CONF_CUSTOM_PRESET], args, str) + template_ = await cg.templatable( + config[CONF_CUSTOM_PRESET], args, cg.std_string + ) cg.add(var.set_custom_preset(template_)) if CONF_SWING_MODE in config: template_ = await cg.templatable( From 03d5a0ec1d1d251db9ae8871a80e6960591827fc Mon Sep 17 00:00:00 2001 From: Brian Kaufman Date: Wed, 11 May 2022 21:57:50 -0700 Subject: [PATCH 0290/3388] Update captive portal canHandle function (#3360) --- esphome/components/captive_portal/captive_portal.h | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/esphome/components/captive_portal/captive_portal.h b/esphome/components/captive_portal/captive_portal.h index 0e68bc9cef..c2aada171f 100644 --- a/esphome/components/captive_portal/captive_portal.h +++ b/esphome/components/captive_portal/captive_portal.h @@ -39,17 +39,7 @@ class CaptivePortal : public AsyncWebHandler, public Component { if (request->method() == HTTP_GET) { if (request->url() == "/") return true; - if (request->url() == "/stylesheet.css") - return true; - if (request->url() == "/wifi-strength-1.svg") - return true; - if (request->url() == "/wifi-strength-2.svg") - return true; - if (request->url() == "/wifi-strength-3.svg") - return true; - if (request->url() == "/wifi-strength-4.svg") - return true; - if (request->url() == "/lock.svg") + if (request->url() == "/config.json") return true; if (request->url() == "/wifisave") return true; From 63096ac2bcc03c48e7a076e8efaa8ad19411b828 Mon Sep 17 00:00:00 2001 From: Maurice Makaay Date: Wed, 11 May 2022 23:25:00 +0200 Subject: [PATCH 0291/3388] On epoch sync, restore local TZ (#3462) Co-authored-by: Maurice Makaay --- esphome/components/time/real_time_clock.cpp | 12 ++++++++++-- esphome/components/time/real_time_clock.h | 1 + 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/esphome/components/time/real_time_clock.cpp b/esphome/components/time/real_time_clock.cpp index 36c5f4161d..7b5f0aa49b 100644 --- a/esphome/components/time/real_time_clock.cpp +++ b/esphome/components/time/real_time_clock.cpp @@ -13,11 +13,11 @@ static const char *const TAG = "time"; RealTimeClock::RealTimeClock() = default; void RealTimeClock::call_setup() { - setenv("TZ", this->timezone_.c_str(), 1); - tzset(); + this->apply_timezone_(); PollingComponent::call_setup(); } void RealTimeClock::synchronize_epoch_(uint32_t epoch) { + // Update UTC epoch time. struct timeval timev { .tv_sec = static_cast(epoch), .tv_usec = 0, }; @@ -30,6 +30,9 @@ void RealTimeClock::synchronize_epoch_(uint32_t epoch) { ret = settimeofday(&timev, nullptr); } + // Move timezone back to local timezone. + this->apply_timezone_(); + if (ret != 0) { ESP_LOGW(TAG, "setimeofday() failed with code %d", ret); } @@ -41,6 +44,11 @@ void RealTimeClock::synchronize_epoch_(uint32_t epoch) { this->time_sync_callback_.call(); } +void RealTimeClock::apply_timezone_() { + setenv("TZ", this->timezone_.c_str(), 1); + tzset(); +} + size_t ESPTime::strftime(char *buffer, size_t buffer_len, const char *format) { struct tm c_tm = this->to_c_tm(); return ::strftime(buffer, buffer_len, format, &c_tm); diff --git a/esphome/components/time/real_time_clock.h b/esphome/components/time/real_time_clock.h index b22c6f04d7..7f4afee306 100644 --- a/esphome/components/time/real_time_clock.h +++ b/esphome/components/time/real_time_clock.h @@ -137,6 +137,7 @@ class RealTimeClock : public PollingComponent { void synchronize_epoch_(uint32_t epoch); std::string timezone_{}; + void apply_timezone_(); CallbackManager time_sync_callback_; }; From 40f622949e2b2d3b21ebb419324c70cf9605cc30 Mon Sep 17 00:00:00 2001 From: Niclas Larsson Date: Thu, 12 May 2022 00:26:51 +0200 Subject: [PATCH 0292/3388] Shelly dimmer: Use unique_ptr to handle the lifetime of stm32_t (#3400) Co-authored-by: Martin <25747549+martgras@users.noreply.github.com> Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- .../shelly_dimmer/shelly_dimmer.cpp | 9 +- .../components/shelly_dimmer/stm32flash.cpp | 119 +++++++++--------- esphome/components/shelly_dimmer/stm32flash.h | 28 +++-- 3 files changed, 79 insertions(+), 77 deletions(-) diff --git a/esphome/components/shelly_dimmer/shelly_dimmer.cpp b/esphome/components/shelly_dimmer/shelly_dimmer.cpp index 3b79d0bf57..32c556da5e 100644 --- a/esphome/components/shelly_dimmer/shelly_dimmer.cpp +++ b/esphome/components/shelly_dimmer/shelly_dimmer.cpp @@ -158,11 +158,8 @@ bool ShellyDimmer::upgrade_firmware_() { ESP_LOGW(TAG, "Starting STM32 firmware upgrade"); this->reset_dfu_boot_(); - // Could be constexpr in c++17 - static const auto CLOSE = [](stm32_t *stm32) { stm32_close(stm32); }; - // Cleanup with RAII - std::unique_ptr stm32{stm32_init(this, STREAM_SERIAL, 1), CLOSE}; + auto stm32 = stm32_init(this, STREAM_SERIAL, 1); if (!stm32) { ESP_LOGW(TAG, "Failed to initialize STM32"); @@ -170,7 +167,7 @@ bool ShellyDimmer::upgrade_firmware_() { } // Erase STM32 flash. - if (stm32_erase_memory(stm32.get(), 0, STM32_MASS_ERASE) != STM32_ERR_OK) { + if (stm32_erase_memory(stm32, 0, STM32_MASS_ERASE) != STM32_ERR_OK) { ESP_LOGW(TAG, "Failed to erase STM32 flash memory"); return false; } @@ -196,7 +193,7 @@ bool ShellyDimmer::upgrade_firmware_() { std::memcpy(buffer, p, BUFFER_SIZE); p += BUFFER_SIZE; - if (stm32_write_memory(stm32.get(), addr, buffer, len) != STM32_ERR_OK) { + if (stm32_write_memory(stm32, addr, buffer, len) != STM32_ERR_OK) { ESP_LOGW(TAG, "Failed to write to STM32 flash memory"); return false; } diff --git a/esphome/components/shelly_dimmer/stm32flash.cpp b/esphome/components/shelly_dimmer/stm32flash.cpp index 4c777776fb..e688f2de36 100644 --- a/esphome/components/shelly_dimmer/stm32flash.cpp +++ b/esphome/components/shelly_dimmer/stm32flash.cpp @@ -117,7 +117,7 @@ namespace shelly_dimmer { namespace { -int flash_addr_to_page_ceil(const stm32_t *stm, uint32_t addr) { +int flash_addr_to_page_ceil(const stm32_unique_ptr &stm, uint32_t addr) { if (!(addr >= stm->dev->fl_start && addr <= stm->dev->fl_end)) return 0; @@ -135,7 +135,7 @@ int flash_addr_to_page_ceil(const stm32_t *stm, uint32_t addr) { return addr ? page + 1 : page; } -stm32_err_t stm32_get_ack_timeout(const stm32_t *stm, uint32_t timeout) { +stm32_err_t stm32_get_ack_timeout(const stm32_unique_ptr &stm, uint32_t timeout) { auto *stream = stm->stream; uint8_t rxbyte; @@ -168,9 +168,9 @@ stm32_err_t stm32_get_ack_timeout(const stm32_t *stm, uint32_t timeout) { } while (true); } -stm32_err_t stm32_get_ack(const stm32_t *stm) { return stm32_get_ack_timeout(stm, 0); } +stm32_err_t stm32_get_ack(const stm32_unique_ptr &stm) { return stm32_get_ack_timeout(stm, 0); } -stm32_err_t stm32_send_command_timeout(const stm32_t *stm, const uint8_t cmd, const uint32_t timeout) { +stm32_err_t stm32_send_command_timeout(const stm32_unique_ptr &stm, const uint8_t cmd, const uint32_t timeout) { auto *const stream = stm->stream; static constexpr auto BUFFER_SIZE = 2; @@ -194,12 +194,12 @@ stm32_err_t stm32_send_command_timeout(const stm32_t *stm, const uint8_t cmd, co return STM32_ERR_UNKNOWN; } -stm32_err_t stm32_send_command(const stm32_t *stm, const uint8_t cmd) { +stm32_err_t stm32_send_command(const stm32_unique_ptr &stm, const uint8_t cmd) { return stm32_send_command_timeout(stm, cmd, 0); } /* if we have lost sync, send a wrong command and expect a NACK */ -stm32_err_t stm32_resync(const stm32_t *stm) { +stm32_err_t stm32_resync(const stm32_unique_ptr &stm) { auto *const stream = stm->stream; uint32_t t0 = millis(); auto t1 = t0; @@ -238,7 +238,7 @@ stm32_err_t stm32_resync(const stm32_t *stm) { * * len is value of the first byte in the frame. */ -stm32_err_t stm32_guess_len_cmd(const stm32_t *stm, const uint8_t cmd, uint8_t *const data, unsigned int len) { +stm32_err_t stm32_guess_len_cmd(const stm32_unique_ptr &stm, const uint8_t cmd, uint8_t *const data, unsigned int len) { auto *const stream = stm->stream; if (stm32_send_command(stm, cmd) != STM32_ERR_OK) @@ -286,7 +286,7 @@ stm32_err_t stm32_guess_len_cmd(const stm32_t *stm, const uint8_t cmd, uint8_t * * This function sends the init sequence and, in case of timeout, recovers * the interface. */ -stm32_err_t stm32_send_init_seq(const stm32_t *stm) { +stm32_err_t stm32_send_init_seq(const stm32_unique_ptr &stm) { auto *const stream = stm->stream; stream->write_array(&STM32_CMD_INIT, 1); @@ -320,7 +320,7 @@ stm32_err_t stm32_send_init_seq(const stm32_t *stm) { return STM32_ERR_UNKNOWN; } -stm32_err_t stm32_mass_erase(const stm32_t *stm) { +stm32_err_t stm32_mass_erase(const stm32_unique_ptr &stm) { auto *const stream = stm->stream; if (stm32_send_command(stm, stm->cmd->er) != STM32_ERR_OK) { @@ -364,7 +364,7 @@ template std::unique_ptr malloc_array_raii DELETOR}; } -stm32_err_t stm32_pages_erase(const stm32_t *stm, const uint32_t spage, const uint32_t pages) { +stm32_err_t stm32_pages_erase(const stm32_unique_ptr &stm, const uint32_t spage, const uint32_t pages) { auto *const stream = stm->stream; uint8_t cs = 0; int i = 0; @@ -474,6 +474,18 @@ template void populate_buffer_with_address(uint8_t (&buffer)[N], uint3 buffer[4] = static_cast(buffer[0] ^ buffer[1] ^ buffer[2] ^ buffer[3]); } +template stm32_unique_ptr make_stm32_with_deletor(T ptr) { + static const auto CLOSE = [](stm32_t *stm32) { + if (stm32) { + free(stm32->cmd); // NOLINT + } + free(stm32); // NOLINT + }; + + // Cleanup with RAII + return std::unique_ptr{ptr, CLOSE}; +} + } // Anonymous namespace } // namespace shelly_dimmer @@ -485,48 +497,44 @@ namespace shelly_dimmer { /* find newer command by higher code */ #define newer(prev, a) (((prev) == STM32_CMD_ERR) ? (a) : (((prev) > (a)) ? (prev) : (a))) -stm32_t *stm32_init(uart::UARTDevice *stream, const uint8_t flags, const char init) { +stm32_unique_ptr stm32_init(uart::UARTDevice *stream, const uint8_t flags, const char init) { uint8_t buf[257]; - // Could be constexpr in c++17 - static const auto CLOSE = [](stm32_t *stm32) { stm32_close(stm32); }; - - // Cleanup with RAII - std::unique_ptr stm{static_cast(calloc(sizeof(stm32_t), 1)), // NOLINT - CLOSE}; + auto stm = make_stm32_with_deletor(static_cast(calloc(sizeof(stm32_t), 1))); // NOLINT if (!stm) { - return nullptr; + return make_stm32_with_deletor(nullptr); } stm->stream = stream; stm->flags = flags; stm->cmd = static_cast(malloc(sizeof(stm32_cmd_t))); // NOLINT if (!stm->cmd) { - return nullptr; + return make_stm32_with_deletor(nullptr); } memset(stm->cmd, STM32_CMD_ERR, sizeof(stm32_cmd_t)); if ((stm->flags & STREAM_OPT_CMD_INIT) && init) { - if (stm32_send_init_seq(stm.get()) != STM32_ERR_OK) - return nullptr; // NOLINT + if (stm32_send_init_seq(stm) != STM32_ERR_OK) + return make_stm32_with_deletor(nullptr); } /* get the version and read protection status */ - if (stm32_send_command(stm.get(), STM32_CMD_GVR) != STM32_ERR_OK) { - return nullptr; // NOLINT + if (stm32_send_command(stm, STM32_CMD_GVR) != STM32_ERR_OK) { + return make_stm32_with_deletor(nullptr); } /* From AN, only UART bootloader returns 3 bytes */ { const auto len = (stm->flags & STREAM_OPT_GVR_ETX) ? 3 : 1; if (!stream->read_array(buf, len)) - return nullptr; // NOLINT + return make_stm32_with_deletor(nullptr); + stm->version = buf[0]; stm->option1 = (stm->flags & STREAM_OPT_GVR_ETX) ? buf[1] : 0; stm->option2 = (stm->flags & STREAM_OPT_GVR_ETX) ? buf[2] : 0; - if (stm32_get_ack(stm.get()) != STM32_ERR_OK) { - return nullptr; + if (stm32_get_ack(stm) != STM32_ERR_OK) { + return make_stm32_with_deletor(nullptr); } } @@ -544,8 +552,8 @@ stm32_t *stm32_init(uart::UARTDevice *stream, const uint8_t flags, const char in return STM32_CMD_GET_LENGTH; })(); - if (stm32_guess_len_cmd(stm.get(), STM32_CMD_GET, buf, len) != STM32_ERR_OK) - return nullptr; + if (stm32_guess_len_cmd(stm, STM32_CMD_GET, buf, len) != STM32_ERR_OK) + return make_stm32_with_deletor(nullptr); } const auto stop = buf[0] + 1; @@ -607,23 +615,23 @@ stm32_t *stm32_init(uart::UARTDevice *stream, const uint8_t flags, const char in } if (new_cmds) ESP_LOGD(TAG, ")"); - if (stm32_get_ack(stm.get()) != STM32_ERR_OK) { - return nullptr; + if (stm32_get_ack(stm) != STM32_ERR_OK) { + return make_stm32_with_deletor(nullptr); } if (stm->cmd->get == STM32_CMD_ERR || stm->cmd->gvr == STM32_CMD_ERR || stm->cmd->gid == STM32_CMD_ERR) { ESP_LOGD(TAG, "Error: bootloader did not returned correct information from GET command"); - return nullptr; + return make_stm32_with_deletor(nullptr); } /* get the device ID */ - if (stm32_guess_len_cmd(stm.get(), stm->cmd->gid, buf, 1) != STM32_ERR_OK) { - return nullptr; + if (stm32_guess_len_cmd(stm, stm->cmd->gid, buf, 1) != STM32_ERR_OK) { + return make_stm32_with_deletor(nullptr); } const auto returned = buf[0] + 1; if (returned < 2) { ESP_LOGD(TAG, "Only %d bytes sent in the PID, unknown/unsupported device", returned); - return nullptr; + return make_stm32_with_deletor(nullptr); } stm->pid = (buf[1] << 8) | buf[2]; if (returned > 2) { @@ -631,8 +639,8 @@ stm32_t *stm32_init(uart::UARTDevice *stream, const uint8_t flags, const char in for (auto i = 2; i <= returned; i++) ESP_LOGD(TAG, " %02x", buf[i]); } - if (stm32_get_ack(stm.get()) != STM32_ERR_OK) { - return nullptr; + if (stm32_get_ack(stm) != STM32_ERR_OK) { + return make_stm32_with_deletor(nullptr); } stm->dev = DEVICES; @@ -641,21 +649,14 @@ stm32_t *stm32_init(uart::UARTDevice *stream, const uint8_t flags, const char in if (!stm->dev->id) { ESP_LOGD(TAG, "Unknown/unsupported device (Device ID: 0x%03x)", stm->pid); - return nullptr; + return make_stm32_with_deletor(nullptr); } - // TODO: Would be much better if the unique_ptr was returned from this function - // Release ownership of unique_ptr - return stm.release(); // NOLINT + return stm; } -void stm32_close(stm32_t *stm) { - if (stm) - free(stm->cmd); // NOLINT - free(stm); // NOLINT -} - -stm32_err_t stm32_read_memory(const stm32_t *stm, const uint32_t address, uint8_t *data, const unsigned int len) { +stm32_err_t stm32_read_memory(const stm32_unique_ptr &stm, const uint32_t address, uint8_t *data, + const unsigned int len) { auto *const stream = stm->stream; if (!len) @@ -693,7 +694,8 @@ stm32_err_t stm32_read_memory(const stm32_t *stm, const uint32_t address, uint8_ return STM32_ERR_OK; } -stm32_err_t stm32_write_memory(const stm32_t *stm, uint32_t address, const uint8_t *data, const unsigned int len) { +stm32_err_t stm32_write_memory(const stm32_unique_ptr &stm, uint32_t address, const uint8_t *data, + const unsigned int len) { auto *const stream = stm->stream; if (!len) @@ -753,7 +755,7 @@ stm32_err_t stm32_write_memory(const stm32_t *stm, uint32_t address, const uint8 return STM32_ERR_OK; } -stm32_err_t stm32_wunprot_memory(const stm32_t *stm) { +stm32_err_t stm32_wunprot_memory(const stm32_unique_ptr &stm) { if (stm->cmd->uw == STM32_CMD_ERR) { ESP_LOGD(TAG, "Error: WRITE UNPROTECT command not implemented in bootloader."); return STM32_ERR_NO_CMD; @@ -766,7 +768,7 @@ stm32_err_t stm32_wunprot_memory(const stm32_t *stm) { []() { ESP_LOGD(TAG, "Error: Failed to WRITE UNPROTECT"); }); } -stm32_err_t stm32_wprot_memory(const stm32_t *stm) { +stm32_err_t stm32_wprot_memory(const stm32_unique_ptr &stm) { if (stm->cmd->wp == STM32_CMD_ERR) { ESP_LOGD(TAG, "Error: WRITE PROTECT command not implemented in bootloader."); return STM32_ERR_NO_CMD; @@ -779,7 +781,7 @@ stm32_err_t stm32_wprot_memory(const stm32_t *stm) { []() { ESP_LOGD(TAG, "Error: Failed to WRITE PROTECT"); }); } -stm32_err_t stm32_runprot_memory(const stm32_t *stm) { +stm32_err_t stm32_runprot_memory(const stm32_unique_ptr &stm) { if (stm->cmd->ur == STM32_CMD_ERR) { ESP_LOGD(TAG, "Error: READOUT UNPROTECT command not implemented in bootloader."); return STM32_ERR_NO_CMD; @@ -792,7 +794,7 @@ stm32_err_t stm32_runprot_memory(const stm32_t *stm) { []() { ESP_LOGD(TAG, "Error: Failed to READOUT UNPROTECT"); }); } -stm32_err_t stm32_readprot_memory(const stm32_t *stm) { +stm32_err_t stm32_readprot_memory(const stm32_unique_ptr &stm) { if (stm->cmd->rp == STM32_CMD_ERR) { ESP_LOGD(TAG, "Error: READOUT PROTECT command not implemented in bootloader."); return STM32_ERR_NO_CMD; @@ -805,7 +807,7 @@ stm32_err_t stm32_readprot_memory(const stm32_t *stm) { []() { ESP_LOGD(TAG, "Error: Failed to READOUT PROTECT"); }); } -stm32_err_t stm32_erase_memory(const stm32_t *stm, uint32_t spage, uint32_t pages) { +stm32_err_t stm32_erase_memory(const stm32_unique_ptr &stm, uint32_t spage, uint32_t pages) { if (!pages || spage > STM32_MAX_PAGES || ((pages != STM32_MASS_ERASE) && ((spage + pages) > STM32_MAX_PAGES))) return STM32_ERR_OK; @@ -847,7 +849,7 @@ stm32_err_t stm32_erase_memory(const stm32_t *stm, uint32_t spage, uint32_t page return STM32_ERR_OK; } -static stm32_err_t stm32_run_raw_code(const stm32_t *stm, uint32_t target_address, const uint8_t *code, +static stm32_err_t stm32_run_raw_code(const stm32_unique_ptr &stm, uint32_t target_address, const uint8_t *code, uint32_t code_size) { static constexpr uint32_t BUFFER_SIZE = 256; @@ -893,7 +895,7 @@ static stm32_err_t stm32_run_raw_code(const stm32_t *stm, uint32_t target_addres return stm32_go(stm, target_address); } -stm32_err_t stm32_go(const stm32_t *stm, const uint32_t address) { +stm32_err_t stm32_go(const stm32_unique_ptr &stm, const uint32_t address) { auto *const stream = stm->stream; if (stm->cmd->go == STM32_CMD_ERR) { @@ -916,7 +918,7 @@ stm32_err_t stm32_go(const stm32_t *stm, const uint32_t address) { return STM32_ERR_OK; } -stm32_err_t stm32_reset_device(const stm32_t *stm) { +stm32_err_t stm32_reset_device(const stm32_unique_ptr &stm) { const auto target_address = stm->dev->ram_start; if (stm->dev->flags & F_OBLL) { @@ -927,7 +929,8 @@ stm32_err_t stm32_reset_device(const stm32_t *stm) { } } -stm32_err_t stm32_crc_memory(const stm32_t *stm, const uint32_t address, const uint32_t length, uint32_t *const crc) { +stm32_err_t stm32_crc_memory(const stm32_unique_ptr &stm, const uint32_t address, const uint32_t length, + uint32_t *const crc) { static constexpr auto BUFFER_SIZE = 5; auto *const stream = stm->stream; @@ -1022,7 +1025,7 @@ uint32_t stm32_sw_crc(uint32_t crc, uint8_t *buf, unsigned int len) { return crc; } -stm32_err_t stm32_crc_wrapper(const stm32_t *stm, uint32_t address, uint32_t length, uint32_t *crc) { +stm32_err_t stm32_crc_wrapper(const stm32_unique_ptr &stm, uint32_t address, uint32_t length, uint32_t *crc) { static constexpr uint32_t CRC_INIT_VALUE = 0xFFFFFFFF; static constexpr uint32_t BUFFER_SIZE = 256; diff --git a/esphome/components/shelly_dimmer/stm32flash.h b/esphome/components/shelly_dimmer/stm32flash.h index c561375c38..d973b35222 100644 --- a/esphome/components/shelly_dimmer/stm32flash.h +++ b/esphome/components/shelly_dimmer/stm32flash.h @@ -23,6 +23,7 @@ #ifdef USE_SHD_FIRMWARE_DATA #include +#include #include "esphome/components/uart/uart.h" namespace esphome { @@ -108,19 +109,20 @@ struct VarlenCmd { uint8_t length; }; -stm32_t *stm32_init(uart::UARTDevice *stream, uint8_t flags, char init); -void stm32_close(stm32_t *stm); -stm32_err_t stm32_read_memory(const stm32_t *stm, uint32_t address, uint8_t *data, unsigned int len); -stm32_err_t stm32_write_memory(const stm32_t *stm, uint32_t address, const uint8_t *data, unsigned int len); -stm32_err_t stm32_wunprot_memory(const stm32_t *stm); -stm32_err_t stm32_wprot_memory(const stm32_t *stm); -stm32_err_t stm32_erase_memory(const stm32_t *stm, uint32_t spage, uint32_t pages); -stm32_err_t stm32_go(const stm32_t *stm, uint32_t address); -stm32_err_t stm32_reset_device(const stm32_t *stm); -stm32_err_t stm32_readprot_memory(const stm32_t *stm); -stm32_err_t stm32_runprot_memory(const stm32_t *stm); -stm32_err_t stm32_crc_memory(const stm32_t *stm, uint32_t address, uint32_t length, uint32_t *crc); -stm32_err_t stm32_crc_wrapper(const stm32_t *stm, uint32_t address, uint32_t length, uint32_t *crc); +using stm32_unique_ptr = std::unique_ptr; + +stm32_unique_ptr stm32_init(uart::UARTDevice *stream, uint8_t flags, char init); +stm32_err_t stm32_read_memory(const stm32_unique_ptr &stm, uint32_t address, uint8_t *data, unsigned int len); +stm32_err_t stm32_write_memory(const stm32_unique_ptr &stm, uint32_t address, const uint8_t *data, unsigned int len); +stm32_err_t stm32_wunprot_memory(const stm32_unique_ptr &stm); +stm32_err_t stm32_wprot_memory(const stm32_unique_ptr &stm); +stm32_err_t stm32_erase_memory(const stm32_unique_ptr &stm, uint32_t spage, uint32_t pages); +stm32_err_t stm32_go(const stm32_unique_ptr &stm, uint32_t address); +stm32_err_t stm32_reset_device(const stm32_unique_ptr &stm); +stm32_err_t stm32_readprot_memory(const stm32_unique_ptr &stm); +stm32_err_t stm32_runprot_memory(const stm32_unique_ptr &stm); +stm32_err_t stm32_crc_memory(const stm32_unique_ptr &stm, uint32_t address, uint32_t length, uint32_t *crc); +stm32_err_t stm32_crc_wrapper(const stm32_unique_ptr &stm, uint32_t address, uint32_t length, uint32_t *crc); uint32_t stm32_sw_crc(uint32_t crc, uint8_t *buf, unsigned int len); } // namespace shelly_dimmer From c1480029fb1cb94c2aa445d33247c4e49e18cf23 Mon Sep 17 00:00:00 2001 From: James Szalay Date: Wed, 11 May 2022 23:26:14 -0400 Subject: [PATCH 0293/3388] Use heat mode for heat. Move EXT HT to custom presets. (#3437) * Use heat mode for heat. Move EXT HT to custom presets. * Fix syntax error. --- esphome/components/bedjet/bedjet.cpp | 6 ++++-- esphome/components/bedjet/bedjet.h | 2 ++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/esphome/components/bedjet/bedjet.cpp b/esphome/components/bedjet/bedjet.cpp index 1a932da0c5..38ed6206a8 100644 --- a/esphome/components/bedjet/bedjet.cpp +++ b/esphome/components/bedjet/bedjet.cpp @@ -117,7 +117,7 @@ void Bedjet::control(const ClimateCall &call) { pkt = this->codec_->get_button_request(BTN_OFF); break; case climate::CLIMATE_MODE_HEAT: - pkt = this->codec_->get_button_request(BTN_EXTHT); + pkt = this->codec_->get_button_request(BTN_HEAT); break; case climate::CLIMATE_MODE_FAN_ONLY: pkt = this->codec_->get_button_request(BTN_COOL); @@ -137,7 +137,7 @@ void Bedjet::control(const ClimateCall &call) { } else { this->force_refresh_ = true; this->mode = mode; - // We're using (custom) preset for Turbo & M1-3 presets, so changing climate mode will clear those + // We're using (custom) preset for Turbo, EXT HT, & M1-3 presets, so changing climate mode will clear those this->custom_preset.reset(); this->preset.reset(); } @@ -186,6 +186,8 @@ void Bedjet::control(const ClimateCall &call) { pkt = this->codec_->get_button_request(BTN_M2); } else if (preset == "M3") { pkt = this->codec_->get_button_request(BTN_M3); + } else if (preset == "EXT HT") { + pkt = this->codec_->get_button_request(BTN_EXTHT); } else { ESP_LOGW(TAG, "Unsupported preset: %s", preset.c_str()); return; diff --git a/esphome/components/bedjet/bedjet.h b/esphome/components/bedjet/bedjet.h index b061d2b5ec..0565be6045 100644 --- a/esphome/components/bedjet/bedjet.h +++ b/esphome/components/bedjet/bedjet.h @@ -67,6 +67,8 @@ class Bedjet : public climate::Climate, public esphome::ble_client::BLEClientNod // We could fetch biodata from bedjet and set these names that way. // But then we have to invert the lookup in order to send the right preset. // For now, we can leave them as M1-3 to match the remote buttons. + // EXT HT added to match remote button. + "EXT HT", "M1", "M2", "M3", From e914828add0d6e41ac22c8d671184a73294560a8 Mon Sep 17 00:00:00 2001 From: Michael Davidson Date: Thu, 12 May 2022 14:54:45 +1000 Subject: [PATCH 0294/3388] Make custom_fan and custom_preset templatable as per documentation (#3330) --- esphome/components/climate/__init__.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/esphome/components/climate/__init__.py b/esphome/components/climate/__init__.py index 87b9a4b3e2..1de9aa3f3a 100644 --- a/esphome/components/climate/__init__.py +++ b/esphome/components/climate/__init__.py @@ -287,9 +287,11 @@ CLIMATE_CONTROL_ACTION_SCHEMA = cv.Schema( cv.Exclusive(CONF_FAN_MODE, "fan_mode"): cv.templatable( validate_climate_fan_mode ), - cv.Exclusive(CONF_CUSTOM_FAN_MODE, "fan_mode"): cv.string_strict, + cv.Exclusive(CONF_CUSTOM_FAN_MODE, "fan_mode"): cv.templatable( + cv.string_strict + ), cv.Exclusive(CONF_PRESET, "preset"): cv.templatable(validate_climate_preset), - cv.Exclusive(CONF_CUSTOM_PRESET, "preset"): cv.string_strict, + cv.Exclusive(CONF_CUSTOM_PRESET, "preset"): cv.templatable(cv.string_strict), cv.Optional(CONF_SWING_MODE): cv.templatable(validate_climate_swing_mode), } ) @@ -324,13 +326,17 @@ async def climate_control_to_code(config, action_id, template_arg, args): template_ = await cg.templatable(config[CONF_FAN_MODE], args, ClimateFanMode) cg.add(var.set_fan_mode(template_)) if CONF_CUSTOM_FAN_MODE in config: - template_ = await cg.templatable(config[CONF_CUSTOM_FAN_MODE], args, str) + template_ = await cg.templatable( + config[CONF_CUSTOM_FAN_MODE], args, cg.std_string + ) cg.add(var.set_custom_fan_mode(template_)) if CONF_PRESET in config: template_ = await cg.templatable(config[CONF_PRESET], args, ClimatePreset) cg.add(var.set_preset(template_)) if CONF_CUSTOM_PRESET in config: - template_ = await cg.templatable(config[CONF_CUSTOM_PRESET], args, str) + template_ = await cg.templatable( + config[CONF_CUSTOM_PRESET], args, cg.std_string + ) cg.add(var.set_custom_preset(template_)) if CONF_SWING_MODE in config: template_ = await cg.templatable( From 28883f711b6aa53db54ab16289de28baea890d15 Mon Sep 17 00:00:00 2001 From: Brian Kaufman Date: Wed, 11 May 2022 21:57:50 -0700 Subject: [PATCH 0295/3388] Update captive portal canHandle function (#3360) --- esphome/components/captive_portal/captive_portal.h | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/esphome/components/captive_portal/captive_portal.h b/esphome/components/captive_portal/captive_portal.h index 0e68bc9cef..c2aada171f 100644 --- a/esphome/components/captive_portal/captive_portal.h +++ b/esphome/components/captive_portal/captive_portal.h @@ -39,17 +39,7 @@ class CaptivePortal : public AsyncWebHandler, public Component { if (request->method() == HTTP_GET) { if (request->url() == "/") return true; - if (request->url() == "/stylesheet.css") - return true; - if (request->url() == "/wifi-strength-1.svg") - return true; - if (request->url() == "/wifi-strength-2.svg") - return true; - if (request->url() == "/wifi-strength-3.svg") - return true; - if (request->url() == "/wifi-strength-4.svg") - return true; - if (request->url() == "/lock.svg") + if (request->url() == "/config.json") return true; if (request->url() == "/wifisave") return true; From 603d0d0c7c1462166bcfeda0fe6cec87b779acf1 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 12 May 2022 17:00:14 +1200 Subject: [PATCH 0296/3388] Bump version to 2022.5.0b2 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index c254edf1ee..7717f709ec 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2022.5.0b1" +__version__ = "2022.5.0b2" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" From 39c6c2417ac2b3b20f4dbc2457aa380b330a7fb9 Mon Sep 17 00:00:00 2001 From: Dave T <17680170+davet2001@users.noreply.github.com> Date: Thu, 12 May 2022 11:18:51 +0100 Subject: [PATCH 0297/3388] Remove duplicate convert_to_8bit_color_ function. (#2469) Co-authored-by: Oxan van Leeuwen --- .../components/display/display_color_utils.h | 4 ++- .../components/ili9341/ili9341_display.cpp | 30 ++++--------------- esphome/components/ili9341/ili9341_display.h | 2 -- 3 files changed, 8 insertions(+), 28 deletions(-) diff --git a/esphome/components/display/display_color_utils.h b/esphome/components/display/display_color_utils.h index 202de912de..7f29586932 100644 --- a/esphome/components/display/display_color_utils.h +++ b/esphome/components/display/display_color_utils.h @@ -66,6 +66,9 @@ class ColorUtil { } return color_return; } + static inline Color rgb332_to_color(uint8_t rgb332_color) { + return to_color((uint32_t) rgb332_color, COLOR_ORDER_RGB, COLOR_BITNESS_332); + } static uint8_t color_to_332(Color color, ColorOrder color_order = ColorOrder::COLOR_ORDER_RGB) { uint16_t red_color, green_color, blue_color; @@ -100,7 +103,6 @@ class ColorUtil { } return 0; } - static uint32_t color_to_grayscale4(Color color) { uint32_t gs4 = esp_scale8(color.white, 15); return gs4; diff --git a/esphome/components/ili9341/ili9341_display.cpp b/esphome/components/ili9341/ili9341_display.cpp index a24f0bbb64..09524ba787 100644 --- a/esphome/components/ili9341/ili9341_display.cpp +++ b/esphome/components/ili9341/ili9341_display.cpp @@ -112,29 +112,9 @@ void ILI9341Display::display_() { this->y_high_ = 0; } -uint16_t ILI9341Display::convert_to_16bit_color_(uint8_t color_8bit) { - int r = color_8bit >> 5; - int g = (color_8bit >> 2) & 0x07; - int b = color_8bit & 0x03; - uint16_t color = (r * 0x04) << 11; - color |= (g * 0x09) << 5; - color |= (b * 0x0A); - - return color; -} - -uint8_t ILI9341Display::convert_to_8bit_color_(uint16_t color_16bit) { - // convert 16bit color to 8 bit buffer - uint8_t r = color_16bit >> 11; - uint8_t g = (color_16bit >> 5) & 0x3F; - uint8_t b = color_16bit & 0x1F; - - return ((b / 0x0A) | ((g / 0x09) << 2) | ((r / 0x04) << 5)); -} - void ILI9341Display::fill(Color color) { - auto color565 = display::ColorUtil::color_to_565(color); - memset(this->buffer_, convert_to_8bit_color_(color565), this->get_buffer_length_()); + uint8_t color332 = display::ColorUtil::color_to_332(color, display::ColorOrder::COLOR_ORDER_RGB); + memset(this->buffer_, color332, this->get_buffer_length_()); this->x_low_ = 0; this->y_low_ = 0; this->x_high_ = this->get_width_internal() - 1; @@ -181,8 +161,8 @@ void HOT ILI9341Display::draw_absolute_pixel_internal(int x, int y, Color color) this->y_high_ = (y > this->y_high_) ? y : this->y_high_; uint32_t pos = (y * width_) + x; - auto color565 = display::ColorUtil::color_to_565(color); - buffer_[pos] = convert_to_8bit_color_(color565); + uint8_t color332 = display::ColorUtil::color_to_332(color, display::ColorOrder::COLOR_ORDER_RGB); + buffer_[pos] = color332; } // should return the total size: return this->get_width_internal() * this->get_height_internal() * 2 // 16bit color @@ -247,7 +227,7 @@ uint32_t ILI9341Display::buffer_to_transfer_(uint32_t pos, uint32_t sz) { } for (uint32_t i = 0; i < sz; ++i) { - uint16_t color = convert_to_16bit_color_(*src++); + uint16_t color = display::ColorUtil::color_to_565(display::ColorUtil::rgb332_to_color(*src++)); *dst++ = (uint8_t)(color >> 8); *dst++ = (uint8_t) color; } diff --git a/esphome/components/ili9341/ili9341_display.h b/esphome/components/ili9341/ili9341_display.h index d8c90c9d33..eeff688f4f 100644 --- a/esphome/components/ili9341/ili9341_display.h +++ b/esphome/components/ili9341/ili9341_display.h @@ -51,8 +51,6 @@ class ILI9341Display : public PollingComponent, void reset_(); void fill_internal_(Color color); void display_(); - uint16_t convert_to_16bit_color_(uint8_t color_8bit); - uint8_t convert_to_8bit_color_(uint16_t color_16bit); ILI9341Model model_; int16_t width_{320}; ///< Display width as modified by current rotation From 2dc2aec954b6c5dbc65d5898bc65dcce2efd0e1a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 13 May 2022 13:44:24 +1200 Subject: [PATCH 0298/3388] Bump esptool from 3.3 to 3.3.1 (#3468) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index e62ef86765..dfe69cd33a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,7 +7,7 @@ tzlocal==4.2 # from time tzdata>=2021.1 # from time pyserial==3.5 platformio==5.2.5 # When updating platformio, also update Dockerfile -esptool==3.3 +esptool==3.3.1 click==8.1.3 esphome-dashboard==20220508.0 aioesphomeapi==10.8.2 From 7a03c7d56fd0b4075c6d3494e404fb4201452872 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 15 May 2022 19:46:36 +1200 Subject: [PATCH 0299/3388] Bump pylint from 2.13.8 to 2.13.9 (#3470) Bumps [pylint](https://github.com/PyCQA/pylint) from 2.13.8 to 2.13.9. - [Release notes](https://github.com/PyCQA/pylint/releases) - [Changelog](https://github.com/PyCQA/pylint/blob/main/ChangeLog) - [Commits](https://github.com/PyCQA/pylint/compare/v2.13.8...v2.13.9) --- updated-dependencies: - dependency-name: pylint dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] 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 4b5db8ce87..7c21979647 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,4 +1,4 @@ -pylint==2.13.8 +pylint==2.13.9 flake8==4.0.1 black==22.3.0 pyupgrade==2.32.1 From fea05e9d33f13a5435f035bcf10d0ea0ef23abc8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=5Bp=CA=B2=C9=B5s=5D?= Date: Sun, 15 May 2022 09:53:43 +0200 Subject: [PATCH 0300/3388] Increase JSON buffer size on overflow (#3475) --- esphome/components/json/json_util.cpp | 40 +++++++++++++++++---------- 1 file changed, 26 insertions(+), 14 deletions(-) diff --git a/esphome/components/json/json_util.cpp b/esphome/components/json/json_util.cpp index 2bd8112255..7e701af48b 100644 --- a/esphome/components/json/json_util.cpp +++ b/esphome/components/json/json_util.cpp @@ -26,21 +26,33 @@ std::string build_json(const json_build_t &f) { const size_t free_heap = heap_caps_get_largest_free_block(MALLOC_CAP_8BIT); #endif - const size_t request_size = std::min(free_heap, (size_t) 512); - - DynamicJsonDocument json_document(request_size); - if (json_document.capacity() == 0) { - ESP_LOGE(TAG, "Could not allocate memory for JSON document! Requested %u bytes, largest free heap block: %u bytes", - request_size, free_heap); - return "{}"; + size_t request_size = std::min(free_heap, (size_t) 512); + while (true) { + ESP_LOGV(TAG, "Attempting to allocate %u bytes for JSON serialization", request_size); + DynamicJsonDocument json_document(request_size); + if (json_document.capacity() == 0) { + ESP_LOGE(TAG, + "Could not allocate memory for JSON document! Requested %u bytes, largest free heap block: %u bytes", + request_size, free_heap); + return "{}"; + } + JsonObject root = json_document.to(); + f(root); + if (json_document.overflowed()) { + if (request_size == free_heap) { + ESP_LOGE(TAG, "Could not allocate memory for JSON document! Overflowed largest free heap block: %u bytes", + free_heap); + return "{}"; + } + request_size = std::min(request_size * 2, free_heap); + continue; + } + json_document.shrinkToFit(); + ESP_LOGV(TAG, "Size after shrink %u bytes", json_document.capacity()); + std::string output; + serializeJson(json_document, output); + return output; } - JsonObject root = json_document.to(); - f(root); - json_document.shrinkToFit(); - ESP_LOGV(TAG, "Size after shrink %u bytes", json_document.capacity()); - std::string output; - serializeJson(json_document, output); - return output; } void parse_json(const std::string &data, const json_parse_t &f) { From 0665acd1901f8ea1b644d89ab9933d032b288ba7 Mon Sep 17 00:00:00 2001 From: Maxim Ocheretianko Date: Sun, 15 May 2022 22:44:14 +0300 Subject: [PATCH 0301/3388] Tuya status gpio support (#3466) --- esphome/components/tuya/__init__.py | 6 +++++ esphome/components/tuya/tuya.cpp | 41 ++++++++++++++++++++++------- esphome/components/tuya/tuya.h | 7 +++-- tests/test4.yaml | 3 +++ 4 files changed, 45 insertions(+), 12 deletions(-) diff --git a/esphome/components/tuya/__init__.py b/esphome/components/tuya/__init__.py index 965893e012..2eaaa2a625 100644 --- a/esphome/components/tuya/__init__.py +++ b/esphome/components/tuya/__init__.py @@ -1,5 +1,6 @@ from esphome.components import time from esphome import automation +from esphome import pins import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import uart @@ -11,6 +12,7 @@ CONF_IGNORE_MCU_UPDATE_ON_DATAPOINTS = "ignore_mcu_update_on_datapoints" CONF_ON_DATAPOINT_UPDATE = "on_datapoint_update" CONF_DATAPOINT_TYPE = "datapoint_type" +CONF_STATUS_PIN = "status_pin" tuya_ns = cg.esphome_ns.namespace("tuya") Tuya = tuya_ns.class_("Tuya", cg.Component, uart.UARTDevice) @@ -88,6 +90,7 @@ CONFIG_SCHEMA = ( cv.Optional(CONF_IGNORE_MCU_UPDATE_ON_DATAPOINTS): cv.ensure_list( cv.uint8_t ), + cv.Optional(CONF_STATUS_PIN): pins.gpio_output_pin_schema, cv.Optional(CONF_ON_DATAPOINT_UPDATE): automation.validate_automation( { cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( @@ -114,6 +117,9 @@ async def to_code(config): if CONF_TIME_ID in config: time_ = await cg.get_variable(config[CONF_TIME_ID]) cg.add(var.set_time_id(time_)) + if CONF_STATUS_PIN in config: + status_pin_ = await cg.gpio_pin_expression(config[CONF_STATUS_PIN]) + cg.add(var.set_status_pin(status_pin_)) if CONF_IGNORE_MCU_UPDATE_ON_DATAPOINTS in config: for dp in config[CONF_IGNORE_MCU_UPDATE_ON_DATAPOINTS]: cg.add(var.add_ignore_mcu_update_on_datapoints(dp)) diff --git a/esphome/components/tuya/tuya.cpp b/esphome/components/tuya/tuya.cpp index 1fbca7796d..1b35121c57 100644 --- a/esphome/components/tuya/tuya.cpp +++ b/esphome/components/tuya/tuya.cpp @@ -3,6 +3,7 @@ #include "esphome/components/network/util.h" #include "esphome/core/helpers.h" #include "esphome/core/util.h" +#include "esphome/core/gpio.h" namespace esphome { namespace tuya { @@ -13,6 +14,9 @@ static const int RECEIVE_TIMEOUT = 300; void Tuya::setup() { this->set_interval("heartbeat", 15000, [this] { this->send_empty_command_(TuyaCommandType::HEARTBEAT); }); + if (this->status_pin_.has_value()) { + this->status_pin_.value()->digital_write(false); + } } void Tuya::loop() { @@ -49,9 +53,12 @@ void Tuya::dump_config() { ESP_LOGCONFIG(TAG, " Datapoint %u: unknown", info.id); } } - if ((this->gpio_status_ != -1) || (this->gpio_reset_ != -1)) { - ESP_LOGCONFIG(TAG, " GPIO Configuration: status: pin %d, reset: pin %d (not supported)", this->gpio_status_, - this->gpio_reset_); + if ((this->status_pin_reported_ != -1) || (this->reset_pin_reported_ != -1)) { + ESP_LOGCONFIG(TAG, " GPIO Configuration: status: pin %d, reset: pin %d (not supported)", + this->status_pin_reported_, this->reset_pin_reported_); + } + if (this->status_pin_.has_value()) { + LOG_PIN(" Status Pin: ", this->status_pin_.value()); } ESP_LOGCONFIG(TAG, " Product: '%s'", this->product_.c_str()); this->check_uart_settings(9600); @@ -164,16 +171,27 @@ void Tuya::handle_command_(uint8_t command, uint8_t version, const uint8_t *buff } case TuyaCommandType::CONF_QUERY: { if (len >= 2) { - this->gpio_status_ = buffer[0]; - this->gpio_reset_ = buffer[1]; + this->status_pin_reported_ = buffer[0]; + this->reset_pin_reported_ = buffer[1]; } if (this->init_state_ == TuyaInitState::INIT_CONF) { // If mcu returned status gpio, then we can omit sending wifi state - if (this->gpio_status_ != -1) { + if (this->status_pin_reported_ != -1) { this->init_state_ = TuyaInitState::INIT_DATAPOINT; this->send_empty_command_(TuyaCommandType::DATAPOINT_QUERY); + bool is_pin_equals = + this->status_pin_.has_value() && this->status_pin_.value()->get_pin() == this->status_pin_reported_; + // Configure status pin toggling (if reported and configured) or WIFI_STATE periodic send + if (is_pin_equals) { + ESP_LOGV(TAG, "Configured status pin %i", this->status_pin_reported_); + this->set_interval("wifi", 1000, [this] { this->set_status_pin_(); }); + } else { + ESP_LOGW(TAG, "Supplied status_pin does not equals the reported pin %i. TuyaMcu will work in limited mode.", + this->status_pin_reported_); + } } else { this->init_state_ = TuyaInitState::INIT_WIFI; + ESP_LOGV(TAG, "Configured WIFI_STATE periodic send"); this->set_interval("wifi", 1000, [this] { this->send_wifi_status_(); }); } } @@ -397,16 +415,19 @@ void Tuya::send_empty_command_(TuyaCommandType command) { send_command_(TuyaCommand{.cmd = command, .payload = std::vector{}}); } +void Tuya::set_status_pin_() { + bool is_network_ready = network::is_connected() && remote_is_connected(); + this->status_pin_.value()->digital_write(is_network_ready); +} + void Tuya::send_wifi_status_() { uint8_t status = 0x02; if (network::is_connected()) { status = 0x03; // Protocol version 3 also supports specifying when connected to "the cloud" - if (this->protocol_version_ >= 0x03) { - if (remote_is_connected()) { - status = 0x04; - } + if (this->protocol_version_ >= 0x03 && remote_is_connected()) { + status = 0x04; } } diff --git a/esphome/components/tuya/tuya.h b/esphome/components/tuya/tuya.h index 3828c49b48..3a267d75a7 100644 --- a/esphome/components/tuya/tuya.h +++ b/esphome/components/tuya/tuya.h @@ -79,6 +79,7 @@ class Tuya : public Component, public uart::UARTDevice { void set_raw_datapoint_value(uint8_t datapoint_id, const std::vector &value); void set_boolean_datapoint_value(uint8_t datapoint_id, bool value); void set_integer_datapoint_value(uint8_t datapoint_id, uint32_t value); + void set_status_pin(InternalGPIOPin *status_pin) { this->status_pin_ = status_pin; } void set_string_datapoint_value(uint8_t datapoint_id, const std::string &value); void set_enum_datapoint_value(uint8_t datapoint_id, uint8_t value); void set_bitmask_datapoint_value(uint8_t datapoint_id, uint32_t value, uint8_t length); @@ -115,6 +116,7 @@ class Tuya : public Component, public uart::UARTDevice { void set_string_datapoint_value_(uint8_t datapoint_id, const std::string &value, bool forced); void set_raw_datapoint_value_(uint8_t datapoint_id, const std::vector &value, bool forced); void send_datapoint_command_(uint8_t datapoint_id, TuyaDatapointType datapoint_type, std::vector data); + void set_status_pin_(); void send_wifi_status_(); #ifdef USE_TIME @@ -123,8 +125,9 @@ class Tuya : public Component, public uart::UARTDevice { #endif TuyaInitState init_state_ = TuyaInitState::INIT_HEARTBEAT; uint8_t protocol_version_ = -1; - int gpio_status_ = -1; - int gpio_reset_ = -1; + optional status_pin_{}; + int status_pin_reported_ = -1; + int reset_pin_reported_ = -1; uint32_t last_command_timestamp_ = 0; uint32_t last_rx_char_timestamp_ = 0; std::string product_ = ""; diff --git a/tests/test4.yaml b/tests/test4.yaml index 54412222b5..82bb9e2f85 100644 --- a/tests/test4.yaml +++ b/tests/test4.yaml @@ -57,6 +57,9 @@ time: tuya: time_id: sntp_time + status_pin: + number: 14 + inverted: true pipsolar: id: inverter0 From f62d5d3b9d150891a4f80ed9ea24b3a65e6c2981 Mon Sep 17 00:00:00 2001 From: Maxim Ocheretianko Date: Sun, 15 May 2022 22:49:40 +0300 Subject: [PATCH 0302/3388] Add Tuya select (#3469) --- CODEOWNERS | 1 + esphome/components/tuya/select/__init__.py | 47 +++++++++++++++++ .../components/tuya/select/tuya_select.cpp | 52 +++++++++++++++++++ esphome/components/tuya/select/tuya_select.h | 30 +++++++++++ esphome/const.py | 1 + tests/test4.yaml | 9 ++++ 6 files changed, 140 insertions(+) create mode 100644 esphome/components/tuya/select/__init__.py create mode 100644 esphome/components/tuya/select/tuya_select.cpp create mode 100644 esphome/components/tuya/select/tuya_select.h diff --git a/CODEOWNERS b/CODEOWNERS index e2b29547cb..be6e8be3f7 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -222,6 +222,7 @@ esphome/components/tsl2591/* @wjcarpenter esphome/components/tuya/binary_sensor/* @jesserockz esphome/components/tuya/climate/* @jesserockz esphome/components/tuya/number/* @frankiboy1 +esphome/components/tuya/select/* @bearpawmaxim esphome/components/tuya/sensor/* @jesserockz esphome/components/tuya/switch/* @jesserockz esphome/components/tuya/text_sensor/* @dentra diff --git a/esphome/components/tuya/select/__init__.py b/esphome/components/tuya/select/__init__.py new file mode 100644 index 0000000000..3d65eda301 --- /dev/null +++ b/esphome/components/tuya/select/__init__.py @@ -0,0 +1,47 @@ +from esphome.components import select +import esphome.config_validation as cv +import esphome.codegen as cg +from esphome.const import CONF_OPTIONS, CONF_OPTIMISTIC, CONF_ENUM_DATAPOINT +from .. import tuya_ns, CONF_TUYA_ID, Tuya + +DEPENDENCIES = ["tuya"] +CODEOWNERS = ["@bearpawmaxim"] + +TuyaSelect = tuya_ns.class_("TuyaSelect", select.Select, cg.Component) + + +def ensure_option_map(value): + cv.check_not_templatable(value) + option = cv.All(cv.int_range(0, 2**8 - 1)) + mapping = cv.All(cv.string_strict) + options_map_schema = cv.Schema({option: mapping}) + value = options_map_schema(value) + + all_values = list(value.keys()) + unique_values = set(value.keys()) + if len(all_values) != len(unique_values): + raise cv.Invalid("Mapping values must be unique.") + + return value + + +CONFIG_SCHEMA = select.SELECT_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(TuyaSelect), + cv.GenerateID(CONF_TUYA_ID): cv.use_id(Tuya), + cv.Required(CONF_ENUM_DATAPOINT): cv.uint8_t, + cv.Required(CONF_OPTIONS): ensure_option_map, + cv.Optional(CONF_OPTIMISTIC, default=False): cv.boolean, + } +).extend(cv.COMPONENT_SCHEMA) + + +async def to_code(config): + options_map = config[CONF_OPTIONS] + var = await select.new_select(config, options=list(options_map.values())) + await cg.register_component(var, config) + cg.add(var.set_select_mappings(list(options_map.keys()))) + parent = await cg.get_variable(config[CONF_TUYA_ID]) + cg.add(var.set_tuya_parent(parent)) + cg.add(var.set_select_id(config[CONF_ENUM_DATAPOINT])) + cg.add(var.set_optimistic(config[CONF_OPTIMISTIC])) diff --git a/esphome/components/tuya/select/tuya_select.cpp b/esphome/components/tuya/select/tuya_select.cpp new file mode 100644 index 0000000000..a4df0873b0 --- /dev/null +++ b/esphome/components/tuya/select/tuya_select.cpp @@ -0,0 +1,52 @@ +#include "esphome/core/log.h" +#include "tuya_select.h" + +namespace esphome { +namespace tuya { + +static const char *const TAG = "tuya.select"; + +void TuyaSelect::setup() { + this->parent_->register_listener(this->select_id_, [this](const TuyaDatapoint &datapoint) { + uint8_t enum_value = datapoint.value_enum; + ESP_LOGV(TAG, "MCU reported select %u value %u", this->select_id_, enum_value); + auto options = this->traits.get_options(); + auto mappings = this->mappings_; + auto it = std::find(mappings.cbegin(), mappings.cend(), enum_value); + if (it == mappings.end()) { + ESP_LOGW(TAG, "Invalid value %u", enum_value); + return; + } + size_t mapping_idx = std::distance(mappings.cbegin(), it); + auto value = this->at(mapping_idx); + this->publish_state(value.value()); + }); +} + +void TuyaSelect::control(const std::string &value) { + if (this->optimistic_) + this->publish_state(value); + + auto idx = this->index_of(value); + if (idx.has_value()) { + uint8_t mapping = this->mappings_.at(idx.value()); + ESP_LOGV(TAG, "Setting %u datapoint value to %u:%s", this->select_id_, mapping, value.c_str()); + this->parent_->set_enum_datapoint_value(this->select_id_, mapping); + return; + } + + ESP_LOGW(TAG, "Invalid value %s", value.c_str()); +} + +void TuyaSelect::dump_config() { + LOG_SELECT("", "Tuya Select", this); + ESP_LOGCONFIG(TAG, " Select has datapoint ID %u", this->select_id_); + ESP_LOGCONFIG(TAG, " Options are:"); + auto options = this->traits.get_options(); + for (auto i = 0; i < this->mappings_.size(); i++) { + ESP_LOGCONFIG(TAG, " %i: %s", this->mappings_.at(i), options.at(i).c_str()); + } +} + +} // namespace tuya +} // namespace esphome diff --git a/esphome/components/tuya/select/tuya_select.h b/esphome/components/tuya/select/tuya_select.h new file mode 100644 index 0000000000..ab233dc501 --- /dev/null +++ b/esphome/components/tuya/select/tuya_select.h @@ -0,0 +1,30 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/tuya/tuya.h" +#include "esphome/components/select/select.h" + +namespace esphome { +namespace tuya { + +class TuyaSelect : public select::Select, public Component { + public: + void setup() override; + void dump_config() override; + + void set_tuya_parent(Tuya *parent) { this->parent_ = parent; } + void set_optimistic(bool optimistic) { this->optimistic_ = optimistic; } + void set_select_id(uint8_t select_id) { this->select_id_ = select_id; } + void set_select_mappings(std::vector mappings) { this->mappings_ = std::move(mappings); } + + protected: + void control(const std::string &value) override; + + Tuya *parent_; + bool optimistic_ = false; + uint8_t select_id_; + std::vector mappings_; +}; + +} // namespace tuya +} // namespace esphome diff --git a/esphome/const.py b/esphome/const.py index 9a2e41d69f..c2aa53be70 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -198,6 +198,7 @@ CONF_ENABLE_TIME = "enable_time" CONF_ENERGY = "energy" CONF_ENTITY_CATEGORY = "entity_category" CONF_ENTITY_ID = "entity_id" +CONF_ENUM_DATAPOINT = "enum_datapoint" CONF_ESP8266_DISABLE_SSL_SUPPORT = "esp8266_disable_ssl_support" CONF_ESPHOME = "esphome" CONF_ETHERNET = "ethernet" diff --git a/tests/test4.yaml b/tests/test4.yaml index 82bb9e2f85..0e9f14e0d6 100644 --- a/tests/test4.yaml +++ b/tests/test4.yaml @@ -61,6 +61,15 @@ tuya: number: 14 inverted: true +select: + - platform: tuya + id: tuya_select + enum_datapoint: 42 + options: + 0: Internal + 1: Floor + 2: Both + pipsolar: id: inverter0 From 93e2506279239a3d09511bcfc0db44ee00fa3489 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 16 May 2022 13:05:20 +1200 Subject: [PATCH 0303/3388] Mark improv_serial and ESP-IDF usb based serial on c3/s2/s3 unsupported (#3477) --- esphome/components/improv_serial/__init__.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/esphome/components/improv_serial/__init__.py b/esphome/components/improv_serial/__init__.py index 21073a8ab3..67a0f7f4ed 100644 --- a/esphome/components/improv_serial/__init__.py +++ b/esphome/components/improv_serial/__init__.py @@ -1,6 +1,8 @@ -from esphome.const import CONF_BAUD_RATE, CONF_ID, CONF_LOGGER +from esphome.components.logger import USB_CDC, USB_SERIAL_JTAG +from esphome.const import CONF_BAUD_RATE, CONF_HARDWARE_UART, CONF_ID, CONF_LOGGER import esphome.codegen as cg import esphome.config_validation as cv +from esphome.core import CORE import esphome.final_validate as fv CODEOWNERS = ["@esphome/core"] @@ -17,14 +19,19 @@ CONFIG_SCHEMA = cv.Schema( ).extend(cv.COMPONENT_SCHEMA) -def validate_logger_baud_rate(config): +def validate_logger(config): logger_conf = fv.full_config.get()[CONF_LOGGER] if logger_conf[CONF_BAUD_RATE] == 0: raise cv.Invalid("improv_serial requires the logger baud_rate to be not 0") + if CORE.using_esp_idf: + if logger_conf[CONF_HARDWARE_UART] in [USB_SERIAL_JTAG, USB_CDC]: + raise cv.Invalid( + "improv_serial does not support the selected logger hardware_uart" + ) return config -FINAL_VALIDATE_SCHEMA = validate_logger_baud_rate +FINAL_VALIDATE_SCHEMA = validate_logger async def to_code(config): From 01222dbab75a376c2be07cdca0b4c50b98aa15fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=5Bp=CA=B2=C9=B5s=5D?= Date: Sun, 15 May 2022 09:53:43 +0200 Subject: [PATCH 0304/3388] Increase JSON buffer size on overflow (#3475) --- esphome/components/json/json_util.cpp | 40 +++++++++++++++++---------- 1 file changed, 26 insertions(+), 14 deletions(-) diff --git a/esphome/components/json/json_util.cpp b/esphome/components/json/json_util.cpp index 2bd8112255..7e701af48b 100644 --- a/esphome/components/json/json_util.cpp +++ b/esphome/components/json/json_util.cpp @@ -26,21 +26,33 @@ std::string build_json(const json_build_t &f) { const size_t free_heap = heap_caps_get_largest_free_block(MALLOC_CAP_8BIT); #endif - const size_t request_size = std::min(free_heap, (size_t) 512); - - DynamicJsonDocument json_document(request_size); - if (json_document.capacity() == 0) { - ESP_LOGE(TAG, "Could not allocate memory for JSON document! Requested %u bytes, largest free heap block: %u bytes", - request_size, free_heap); - return "{}"; + size_t request_size = std::min(free_heap, (size_t) 512); + while (true) { + ESP_LOGV(TAG, "Attempting to allocate %u bytes for JSON serialization", request_size); + DynamicJsonDocument json_document(request_size); + if (json_document.capacity() == 0) { + ESP_LOGE(TAG, + "Could not allocate memory for JSON document! Requested %u bytes, largest free heap block: %u bytes", + request_size, free_heap); + return "{}"; + } + JsonObject root = json_document.to(); + f(root); + if (json_document.overflowed()) { + if (request_size == free_heap) { + ESP_LOGE(TAG, "Could not allocate memory for JSON document! Overflowed largest free heap block: %u bytes", + free_heap); + return "{}"; + } + request_size = std::min(request_size * 2, free_heap); + continue; + } + json_document.shrinkToFit(); + ESP_LOGV(TAG, "Size after shrink %u bytes", json_document.capacity()); + std::string output; + serializeJson(json_document, output); + return output; } - JsonObject root = json_document.to(); - f(root); - json_document.shrinkToFit(); - ESP_LOGV(TAG, "Size after shrink %u bytes", json_document.capacity()); - std::string output; - serializeJson(json_document, output); - return output; } void parse_json(const std::string &data, const json_parse_t &f) { From a639690716d3a598025cbd6fc18a69415bf4dee0 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 16 May 2022 13:05:20 +1200 Subject: [PATCH 0305/3388] Mark improv_serial and ESP-IDF usb based serial on c3/s2/s3 unsupported (#3477) --- esphome/components/improv_serial/__init__.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/esphome/components/improv_serial/__init__.py b/esphome/components/improv_serial/__init__.py index 21073a8ab3..67a0f7f4ed 100644 --- a/esphome/components/improv_serial/__init__.py +++ b/esphome/components/improv_serial/__init__.py @@ -1,6 +1,8 @@ -from esphome.const import CONF_BAUD_RATE, CONF_ID, CONF_LOGGER +from esphome.components.logger import USB_CDC, USB_SERIAL_JTAG +from esphome.const import CONF_BAUD_RATE, CONF_HARDWARE_UART, CONF_ID, CONF_LOGGER import esphome.codegen as cg import esphome.config_validation as cv +from esphome.core import CORE import esphome.final_validate as fv CODEOWNERS = ["@esphome/core"] @@ -17,14 +19,19 @@ CONFIG_SCHEMA = cv.Schema( ).extend(cv.COMPONENT_SCHEMA) -def validate_logger_baud_rate(config): +def validate_logger(config): logger_conf = fv.full_config.get()[CONF_LOGGER] if logger_conf[CONF_BAUD_RATE] == 0: raise cv.Invalid("improv_serial requires the logger baud_rate to be not 0") + if CORE.using_esp_idf: + if logger_conf[CONF_HARDWARE_UART] in [USB_SERIAL_JTAG, USB_CDC]: + raise cv.Invalid( + "improv_serial does not support the selected logger hardware_uart" + ) return config -FINAL_VALIDATE_SCHEMA = validate_logger_baud_rate +FINAL_VALIDATE_SCHEMA = validate_logger async def to_code(config): From c707e646854f04bf9463ca050ae373570c36c0b3 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 16 May 2022 13:07:12 +1200 Subject: [PATCH 0306/3388] Bump version to 2022.5.0b3 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 7717f709ec..03d8c98712 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2022.5.0b2" +__version__ = "2022.5.0b3" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" From 6dabf24bf35681e98fe01950dd9c77bd28fe4c81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=5Bp=CA=B2=C9=B5s=5D?= Date: Mon, 16 May 2022 05:35:27 +0200 Subject: [PATCH 0307/3388] MQTT cover: send state even if position is available (#3473) --- esphome/components/mqtt/mqtt_cover.cpp | 28 +++++++++++--------------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/esphome/components/mqtt/mqtt_cover.cpp b/esphome/components/mqtt/mqtt_cover.cpp index e5525bc0f7..0718a24828 100644 --- a/esphome/components/mqtt/mqtt_cover.cpp +++ b/esphome/components/mqtt/mqtt_cover.cpp @@ -51,10 +51,9 @@ void MQTTCoverComponent::setup() { void MQTTCoverComponent::dump_config() { ESP_LOGCONFIG(TAG, "MQTT cover '%s':", this->cover_->get_name().c_str()); auto traits = this->cover_->get_traits(); - // no state topic for position - bool state_topic = !traits.get_supports_position(); - LOG_MQTT_COMPONENT(state_topic, true) - if (!state_topic) { + bool has_command_topic = traits.get_supports_position() || !traits.get_supports_tilt(); + LOG_MQTT_COMPONENT(true, has_command_topic) + if (traits.get_supports_position()) { ESP_LOGCONFIG(TAG, " Position State Topic: '%s'", this->get_position_state_topic().c_str()); ESP_LOGCONFIG(TAG, " Position Command Topic: '%s'", this->get_position_command_topic().c_str()); } @@ -72,7 +71,6 @@ void MQTTCoverComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConf root[MQTT_OPTIMISTIC] = true; } if (traits.get_supports_position()) { - config.state_topic = false; root[MQTT_POSITION_TOPIC] = this->get_position_state_topic(); root[MQTT_SET_POSITION_TOPIC] = this->get_position_command_topic(); } @@ -92,17 +90,7 @@ bool MQTTCoverComponent::send_initial_state() { return this->publish_state(); } bool MQTTCoverComponent::publish_state() { auto traits = this->cover_->get_traits(); bool success = true; - if (!traits.get_supports_position()) { - const char *state_s = "unknown"; - if (this->cover_->position == COVER_OPEN) { - state_s = "open"; - } else if (this->cover_->position == COVER_CLOSED) { - state_s = "closed"; - } - - if (!this->publish(this->get_state_topic_(), state_s)) - success = false; - } else { + if (traits.get_supports_position()) { std::string pos = value_accuracy_to_string(roundf(this->cover_->position * 100), 0); if (!this->publish(this->get_position_state_topic(), pos)) success = false; @@ -112,6 +100,14 @@ bool MQTTCoverComponent::publish_state() { if (!this->publish(this->get_tilt_state_topic(), pos)) success = false; } + const char *state_s = this->cover_->current_operation == COVER_OPERATION_OPENING ? "opening" + : this->cover_->current_operation == COVER_OPERATION_CLOSING ? "closing" + : this->cover_->position == COVER_CLOSED ? "closed" + : this->cover_->position == COVER_OPEN ? "open" + : traits.get_supports_position() ? "open" + : "unknown"; + if (!this->publish(this->get_state_topic_(), state_s)) + success = false; return success; } From 609a2ca5926b1213f242cac02a324821530612c6 Mon Sep 17 00:00:00 2001 From: Martin <25747549+martgras@users.noreply.github.com> Date: Tue, 17 May 2022 00:59:36 +0200 Subject: [PATCH 0308/3388] ESP32: Only save to NVS if data was changed (#3479) --- esphome/components/esp32/preferences.cpp | 33 +++++++++++++++++++----- 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/esphome/components/esp32/preferences.cpp b/esphome/components/esp32/preferences.cpp index 8c2b67a942..a78159825e 100644 --- a/esphome/components/esp32/preferences.cpp +++ b/esphome/components/esp32/preferences.cpp @@ -118,12 +118,17 @@ class ESP32Preferences : public ESPPreferences { // go through vector from back to front (makes erase easier/more efficient) for (ssize_t i = s_pending_save.size() - 1; i >= 0; i--) { const auto &save = s_pending_save[i]; - esp_err_t err = nvs_set_blob(nvs_handle, save.key.c_str(), save.data.data(), save.data.size()); - if (err != 0) { - ESP_LOGV(TAG, "nvs_set_blob('%s', len=%u) failed: %s", save.key.c_str(), save.data.size(), - esp_err_to_name(err)); - any_failed = true; - continue; + ESP_LOGVV(TAG, "Checking if NVS data %s has changed", save.key.c_str()); + if (is_changed(nvs_handle, save)) { + esp_err_t err = nvs_set_blob(nvs_handle, save.key.c_str(), save.data.data(), save.data.size()); + if (err != 0) { + ESP_LOGV(TAG, "nvs_set_blob('%s', len=%u) failed: %s", save.key.c_str(), save.data.size(), + esp_err_to_name(err)); + any_failed = true; + continue; + } + } else { + ESP_LOGD(TAG, "NVS data not changed skipping %s len=%u", save.key.c_str(), save.data.size()); } s_pending_save.erase(s_pending_save.begin() + i); } @@ -137,6 +142,22 @@ class ESP32Preferences : public ESPPreferences { return !any_failed; } + bool is_changed(const uint32_t nvs_handle, const NVSData &to_save) { + NVSData stored_data{}; + size_t actual_len; + esp_err_t err = nvs_get_blob(nvs_handle, to_save.key.c_str(), nullptr, &actual_len); + if (err != 0) { + ESP_LOGV(TAG, "nvs_get_blob('%s'): %s - the key might not be set yet", to_save.key.c_str(), esp_err_to_name(err)); + return true; + } + stored_data.data.reserve(actual_len); + err = nvs_get_blob(nvs_handle, to_save.key.c_str(), stored_data.data.data(), &actual_len); + if (err != 0) { + ESP_LOGV(TAG, "nvs_get_blob('%s') failed: %s", to_save.key.c_str(), esp_err_to_name(err)); + return true; + } + return to_save.data == stored_data.data; + } }; void setup_preferences() { From 9b6b9c1fa24b272c87232611126dbbbbcbac6a33 Mon Sep 17 00:00:00 2001 From: Samuel Sieb Date: Tue, 17 May 2022 01:15:02 -0700 Subject: [PATCH 0309/3388] Retry Tuya init commands (#3482) Co-authored-by: Samuel Sieb --- esphome/components/tuya/tuya.cpp | 28 +++++++++++++++++++++++----- esphome/components/tuya/tuya.h | 2 ++ 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/esphome/components/tuya/tuya.cpp b/esphome/components/tuya/tuya.cpp index 1b35121c57..f4744064e3 100644 --- a/esphome/components/tuya/tuya.cpp +++ b/esphome/components/tuya/tuya.cpp @@ -11,6 +11,7 @@ namespace tuya { static const char *const TAG = "tuya"; static const int COMMAND_DELAY = 10; static const int RECEIVE_TIMEOUT = 300; +static const int MAX_RETRIES = 5; void Tuya::setup() { this->set_interval("heartbeat", 15000, [this] { this->send_empty_command_(TuyaCommandType::HEARTBEAT); }); @@ -31,8 +32,12 @@ void Tuya::loop() { void Tuya::dump_config() { ESP_LOGCONFIG(TAG, "Tuya:"); if (this->init_state_ != TuyaInitState::INIT_DONE) { - ESP_LOGCONFIG(TAG, " Configuration will be reported when setup is complete. Current init_state: %u", - static_cast(this->init_state_)); + if (this->init_failed_) { + ESP_LOGCONFIG(TAG, " Initialization failed. Current init_state: %u", static_cast(this->init_state_)); + } else { + ESP_LOGCONFIG(TAG, " Configuration will be reported when setup is complete. Current init_state: %u", + static_cast(this->init_state_)); + } ESP_LOGCONFIG(TAG, " If no further output is received, confirm that this is a supported Tuya device."); return; } @@ -54,8 +59,8 @@ void Tuya::dump_config() { } } if ((this->status_pin_reported_ != -1) || (this->reset_pin_reported_ != -1)) { - ESP_LOGCONFIG(TAG, " GPIO Configuration: status: pin %d, reset: pin %d (not supported)", - this->status_pin_reported_, this->reset_pin_reported_); + ESP_LOGCONFIG(TAG, " GPIO Configuration: status: pin %d, reset: pin %d", this->status_pin_reported_, + this->reset_pin_reported_); } if (this->status_pin_.has_value()) { LOG_PIN(" Status Pin: ", this->status_pin_.value()); @@ -134,6 +139,8 @@ void Tuya::handle_command_(uint8_t command, uint8_t version, const uint8_t *buff if (this->expected_response_.has_value() && this->expected_response_ == command_type) { this->expected_response_.reset(); + this->command_queue_.erase(command_queue_.begin()); + this->init_retries_ = 0; } switch (command_type) { @@ -396,13 +403,24 @@ void Tuya::process_command_queue_() { if (this->expected_response_.has_value() && delay > RECEIVE_TIMEOUT) { this->expected_response_.reset(); + if (init_state_ != TuyaInitState::INIT_DONE) { + if (++this->init_retries_ >= MAX_RETRIES) { + this->init_failed_ = true; + ESP_LOGE(TAG, "Initialization failed at init_state %u", static_cast(this->init_state_)); + this->command_queue_.erase(command_queue_.begin()); + this->init_retries_ = 0; + } + } else { + this->command_queue_.erase(command_queue_.begin()); + } } // Left check of delay since last command in case there's ever a command sent by calling send_raw_command_ directly if (delay > COMMAND_DELAY && !this->command_queue_.empty() && this->rx_message_.empty() && !this->expected_response_.has_value()) { this->send_raw_command_(command_queue_.front()); - this->command_queue_.erase(command_queue_.begin()); + if (!this->expected_response_.has_value()) + this->command_queue_.erase(command_queue_.begin()); } } diff --git a/esphome/components/tuya/tuya.h b/esphome/components/tuya/tuya.h index 3a267d75a7..1f21b09c0c 100644 --- a/esphome/components/tuya/tuya.h +++ b/esphome/components/tuya/tuya.h @@ -124,6 +124,8 @@ class Tuya : public Component, public uart::UARTDevice { optional time_id_{}; #endif TuyaInitState init_state_ = TuyaInitState::INIT_HEARTBEAT; + bool init_failed_{false}; + int init_retries_{0}; uint8_t protocol_version_ = -1; optional status_pin_{}; int status_pin_reported_ = -1; From 17b8bd83161fa9e2fddd1bbb91cdd7dcddc591aa Mon Sep 17 00:00:00 2001 From: Martin <25747549+martgras@users.noreply.github.com> Date: Tue, 17 May 2022 00:59:36 +0200 Subject: [PATCH 0310/3388] ESP32: Only save to NVS if data was changed (#3479) --- esphome/components/esp32/preferences.cpp | 33 +++++++++++++++++++----- 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/esphome/components/esp32/preferences.cpp b/esphome/components/esp32/preferences.cpp index 8c2b67a942..a78159825e 100644 --- a/esphome/components/esp32/preferences.cpp +++ b/esphome/components/esp32/preferences.cpp @@ -118,12 +118,17 @@ class ESP32Preferences : public ESPPreferences { // go through vector from back to front (makes erase easier/more efficient) for (ssize_t i = s_pending_save.size() - 1; i >= 0; i--) { const auto &save = s_pending_save[i]; - esp_err_t err = nvs_set_blob(nvs_handle, save.key.c_str(), save.data.data(), save.data.size()); - if (err != 0) { - ESP_LOGV(TAG, "nvs_set_blob('%s', len=%u) failed: %s", save.key.c_str(), save.data.size(), - esp_err_to_name(err)); - any_failed = true; - continue; + ESP_LOGVV(TAG, "Checking if NVS data %s has changed", save.key.c_str()); + if (is_changed(nvs_handle, save)) { + esp_err_t err = nvs_set_blob(nvs_handle, save.key.c_str(), save.data.data(), save.data.size()); + if (err != 0) { + ESP_LOGV(TAG, "nvs_set_blob('%s', len=%u) failed: %s", save.key.c_str(), save.data.size(), + esp_err_to_name(err)); + any_failed = true; + continue; + } + } else { + ESP_LOGD(TAG, "NVS data not changed skipping %s len=%u", save.key.c_str(), save.data.size()); } s_pending_save.erase(s_pending_save.begin() + i); } @@ -137,6 +142,22 @@ class ESP32Preferences : public ESPPreferences { return !any_failed; } + bool is_changed(const uint32_t nvs_handle, const NVSData &to_save) { + NVSData stored_data{}; + size_t actual_len; + esp_err_t err = nvs_get_blob(nvs_handle, to_save.key.c_str(), nullptr, &actual_len); + if (err != 0) { + ESP_LOGV(TAG, "nvs_get_blob('%s'): %s - the key might not be set yet", to_save.key.c_str(), esp_err_to_name(err)); + return true; + } + stored_data.data.reserve(actual_len); + err = nvs_get_blob(nvs_handle, to_save.key.c_str(), stored_data.data.data(), &actual_len); + if (err != 0) { + ESP_LOGV(TAG, "nvs_get_blob('%s') failed: %s", to_save.key.c_str(), esp_err_to_name(err)); + return true; + } + return to_save.data == stored_data.data; + } }; void setup_preferences() { From 6f49f5465b61581d5f357c891e3c4aa134fec477 Mon Sep 17 00:00:00 2001 From: Samuel Sieb Date: Tue, 17 May 2022 01:15:02 -0700 Subject: [PATCH 0311/3388] Retry Tuya init commands (#3482) Co-authored-by: Samuel Sieb --- esphome/components/tuya/tuya.cpp | 30 ++++++++++++++++++++++++------ esphome/components/tuya/tuya.h | 2 ++ 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/esphome/components/tuya/tuya.cpp b/esphome/components/tuya/tuya.cpp index 1fbca7796d..78e9d9e568 100644 --- a/esphome/components/tuya/tuya.cpp +++ b/esphome/components/tuya/tuya.cpp @@ -1,7 +1,7 @@ #include "tuya.h" -#include "esphome/core/log.h" #include "esphome/components/network/util.h" #include "esphome/core/helpers.h" +#include "esphome/core/log.h" #include "esphome/core/util.h" namespace esphome { @@ -10,6 +10,7 @@ namespace tuya { static const char *const TAG = "tuya"; static const int COMMAND_DELAY = 10; static const int RECEIVE_TIMEOUT = 300; +static const int MAX_RETRIES = 5; void Tuya::setup() { this->set_interval("heartbeat", 15000, [this] { this->send_empty_command_(TuyaCommandType::HEARTBEAT); }); @@ -27,8 +28,12 @@ void Tuya::loop() { void Tuya::dump_config() { ESP_LOGCONFIG(TAG, "Tuya:"); if (this->init_state_ != TuyaInitState::INIT_DONE) { - ESP_LOGCONFIG(TAG, " Configuration will be reported when setup is complete. Current init_state: %u", - static_cast(this->init_state_)); + if (this->init_failed_) { + ESP_LOGCONFIG(TAG, " Initialization failed. Current init_state: %u", static_cast(this->init_state_)); + } else { + ESP_LOGCONFIG(TAG, " Configuration will be reported when setup is complete. Current init_state: %u", + static_cast(this->init_state_)); + } ESP_LOGCONFIG(TAG, " If no further output is received, confirm that this is a supported Tuya device."); return; } @@ -127,6 +132,8 @@ void Tuya::handle_command_(uint8_t command, uint8_t version, const uint8_t *buff if (this->expected_response_.has_value() && this->expected_response_ == command_type) { this->expected_response_.reset(); + this->command_queue_.erase(command_queue_.begin()); + this->init_retries_ = 0; } switch (command_type) { @@ -332,8 +339,8 @@ void Tuya::handle_datapoints_(const uint8_t *buffer, size_t len) { } void Tuya::send_raw_command_(TuyaCommand command) { - uint8_t len_hi = (uint8_t)(command.payload.size() >> 8); - uint8_t len_lo = (uint8_t)(command.payload.size() & 0xFF); + uint8_t len_hi = (uint8_t) (command.payload.size() >> 8); + uint8_t len_lo = (uint8_t) (command.payload.size() & 0xFF); uint8_t version = 0; this->last_command_timestamp_ = millis(); @@ -378,13 +385,24 @@ void Tuya::process_command_queue_() { if (this->expected_response_.has_value() && delay > RECEIVE_TIMEOUT) { this->expected_response_.reset(); + if (init_state_ != TuyaInitState::INIT_DONE) { + if (++this->init_retries_ >= MAX_RETRIES) { + this->init_failed_ = true; + ESP_LOGE(TAG, "Initialization failed at init_state %u", static_cast(this->init_state_)); + this->command_queue_.erase(command_queue_.begin()); + this->init_retries_ = 0; + } + } else { + this->command_queue_.erase(command_queue_.begin()); + } } // Left check of delay since last command in case there's ever a command sent by calling send_raw_command_ directly if (delay > COMMAND_DELAY && !this->command_queue_.empty() && this->rx_message_.empty() && !this->expected_response_.has_value()) { this->send_raw_command_(command_queue_.front()); - this->command_queue_.erase(command_queue_.begin()); + if (!this->expected_response_.has_value()) + this->command_queue_.erase(command_queue_.begin()); } } diff --git a/esphome/components/tuya/tuya.h b/esphome/components/tuya/tuya.h index 3828c49b48..cdff523f90 100644 --- a/esphome/components/tuya/tuya.h +++ b/esphome/components/tuya/tuya.h @@ -122,6 +122,8 @@ class Tuya : public Component, public uart::UARTDevice { optional time_id_{}; #endif TuyaInitState init_state_ = TuyaInitState::INIT_HEARTBEAT; + bool init_failed_{false}; + int init_retries_{0}; uint8_t protocol_version_ = -1; int gpio_status_ = -1; int gpio_reset_ = -1; From 72fcf2cbe1f58113eca966bd62969c20a391f8ab Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 17 May 2022 23:23:37 +1200 Subject: [PATCH 0312/3388] Bump version to 2022.5.0b4 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 03d8c98712..3e5630c470 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2022.5.0b3" +__version__ = "2022.5.0b4" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" From 282d9e138caf4cdbd8769703b29ff3c04ea71a46 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 17 May 2022 23:31:55 +1200 Subject: [PATCH 0313/3388] Revert adding spaces --- esphome/components/tuya/tuya.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/tuya/tuya.cpp b/esphome/components/tuya/tuya.cpp index 78e9d9e568..f8379a93f2 100644 --- a/esphome/components/tuya/tuya.cpp +++ b/esphome/components/tuya/tuya.cpp @@ -339,8 +339,8 @@ void Tuya::handle_datapoints_(const uint8_t *buffer, size_t len) { } void Tuya::send_raw_command_(TuyaCommand command) { - uint8_t len_hi = (uint8_t) (command.payload.size() >> 8); - uint8_t len_lo = (uint8_t) (command.payload.size() & 0xFF); + uint8_t len_hi = (uint8_t)(command.payload.size() >> 8); + uint8_t len_lo = (uint8_t)(command.payload.size() & 0xFF); uint8_t version = 0; this->last_command_timestamp_ = millis(); From ae2f6ad4d12d2f6959218e490c7525ac4497c724 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 18 May 2022 16:30:20 +1200 Subject: [PATCH 0314/3388] Bump version to 2022.5.0 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 3e5630c470..913f0fd0dc 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2022.5.0b4" +__version__ = "2022.5.0" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" From c000e1d6dda3f0b07b2df46b7440a2ba79d33c19 Mon Sep 17 00:00:00 2001 From: Dave T <17680170+davet2001@users.noreply.github.com> Date: Wed, 18 May 2022 23:23:00 +0100 Subject: [PATCH 0315/3388] Ili9341 8bit indexed mode pt1 (#2490) --- .../components/display/display_color_utils.h | 47 +++++++++++++++++++ esphome/components/ili9341/display.py | 18 +++++++ .../components/ili9341/ili9341_display.cpp | 17 +++++-- esphome/components/ili9341/ili9341_display.h | 10 ++++ esphome/const.py | 1 + 5 files changed, 90 insertions(+), 3 deletions(-) diff --git a/esphome/components/display/display_color_utils.h b/esphome/components/display/display_color_utils.h index 7f29586932..bf6d5445f1 100644 --- a/esphome/components/display/display_color_utils.h +++ b/esphome/components/display/display_color_utils.h @@ -107,6 +107,53 @@ class ColorUtil { uint32_t gs4 = esp_scale8(color.white, 15); return gs4; } + /*** + * Converts a Color value to an 8bit index using a 24bit 888 palette. + * Uses euclidiean distance to calculate the linear distance between + * two points in an RGB cube, then iterates through the full palette + * returning the closest match. + * @param[in] color The target color. + * @param[in] palette The 256*3 byte RGB palette. + * @return The 8 bit index of the closest color (e.g. for display buffer). + */ + // static uint8_t color_to_index8_palette888(Color color, uint8_t *palette) { + static uint8_t color_to_index8_palette888(Color color, const uint8_t *palette) { + uint8_t closest_index = 0; + uint32_t minimum_dist2 = UINT32_MAX; // Smallest distance^2 to the target + // so far + // int8_t(*plt)[][3] = palette; + int16_t tgt_r = color.r; + int16_t tgt_g = color.g; + int16_t tgt_b = color.b; + uint16_t x, y, z; + // Loop through each row of the palette + for (uint16_t i = 0; i < 256; i++) { + // Get the pallet rgb color + int16_t plt_r = (int16_t) palette[i * 3 + 0]; + int16_t plt_g = (int16_t) palette[i * 3 + 1]; + int16_t plt_b = (int16_t) palette[i * 3 + 2]; + // Calculate euclidian distance (linear distance in rgb cube). + x = (uint32_t) std::abs(tgt_r - plt_r); + y = (uint32_t) std::abs(tgt_g - plt_g); + z = (uint32_t) std::abs(tgt_b - plt_b); + uint32_t dist2 = x * x + y * y + z * z; + if (dist2 < minimum_dist2) { + minimum_dist2 = dist2; + closest_index = (uint8_t) i; + } + } + return closest_index; + } + /*** + * Converts an 8bit palette index (e.g. from a display buffer) to a color. + * @param[in] index The index to look up. + * @param[in] palette The 256*3 byte RGB palette. + * @return The RGBW Color object looked up by the palette. + */ + static Color index8_to_color_palette888(uint8_t index, const uint8_t *palette) { + Color color = Color(palette[index * 3 + 0], palette[index * 3 + 1], palette[index * 3 + 2], 0); + return color; + } }; } // namespace display } // namespace esphome diff --git a/esphome/components/ili9341/display.py b/esphome/components/ili9341/display.py index 157e8212bd..6b18196ef1 100644 --- a/esphome/components/ili9341/display.py +++ b/esphome/components/ili9341/display.py @@ -3,13 +3,16 @@ import esphome.config_validation as cv from esphome import pins from esphome.components import display, spi from esphome.const import ( + CONF_COLOR_PALETTE, CONF_DC_PIN, CONF_ID, CONF_LAMBDA, CONF_MODEL, CONF_PAGES, + CONF_RAW_DATA_ID, CONF_RESET_PIN, ) +from esphome.core import HexInt DEPENDENCIES = ["spi"] @@ -23,6 +26,7 @@ ILI9341M5Stack = ili9341_ns.class_("ILI9341M5Stack", ili9341) ILI9341TFT24 = ili9341_ns.class_("ILI9341TFT24", ili9341) ILI9341Model = ili9341_ns.enum("ILI9341Model") +ILI9341ColorMode = ili9341_ns.enum("ILI9341ColorMode") MODELS = { "M5STACK": ILI9341Model.M5STACK, @@ -31,6 +35,8 @@ MODELS = { ILI9341_MODEL = cv.enum(MODELS, upper=True, space="_") +COLOR_PALETTE = cv.one_of("NONE", "GRAYSCALE") + CONFIG_SCHEMA = cv.All( display.FULL_DISPLAY_SCHEMA.extend( { @@ -39,6 +45,8 @@ CONFIG_SCHEMA = cv.All( cv.Required(CONF_DC_PIN): pins.gpio_output_pin_schema, cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema, cv.Optional(CONF_LED_PIN): pins.gpio_output_pin_schema, + cv.Optional(CONF_COLOR_PALETTE, default="NONE"): COLOR_PALETTE, + cv.GenerateID(CONF_RAW_DATA_ID): cv.declare_id(cg.uint8), } ) .extend(cv.polling_component_schema("1s")) @@ -73,3 +81,13 @@ async def to_code(config): if CONF_LED_PIN in config: led_pin = await cg.gpio_pin_expression(config[CONF_LED_PIN]) cg.add(var.set_led_pin(led_pin)) + + if config[CONF_COLOR_PALETTE] == "GRAYSCALE": + cg.add(var.set_buffer_color_mode(ILI9341ColorMode.BITS_8_INDEXED)) + rhs = [] + for x in range(256): + rhs.extend([HexInt(x), HexInt(x), HexInt(x)]) + prog_arr = cg.progmem_array(config[CONF_RAW_DATA_ID], rhs) + cg.add(var.set_palette(prog_arr)) + else: + pass diff --git a/esphome/components/ili9341/ili9341_display.cpp b/esphome/components/ili9341/ili9341_display.cpp index 09524ba787..0ad5446d9a 100644 --- a/esphome/components/ili9341/ili9341_display.cpp +++ b/esphome/components/ili9341/ili9341_display.cpp @@ -161,8 +161,13 @@ void HOT ILI9341Display::draw_absolute_pixel_internal(int x, int y, Color color) this->y_high_ = (y > this->y_high_) ? y : this->y_high_; uint32_t pos = (y * width_) + x; - uint8_t color332 = display::ColorUtil::color_to_332(color, display::ColorOrder::COLOR_ORDER_RGB); - buffer_[pos] = color332; + if (this->buffer_color_mode_ == BITS_8) { + uint8_t color332 = display::ColorUtil::color_to_332(color, display::ColorOrder::COLOR_ORDER_RGB); + buffer_[pos] = color332; + } else { // if (this->buffer_color_mode_ == BITS_8_INDEXED) { + uint8_t index = display::ColorUtil::color_to_index8_palette888(color, this->palette_); + buffer_[pos] = index; + } } // should return the total size: return this->get_width_internal() * this->get_height_internal() * 2 // 16bit color @@ -227,7 +232,13 @@ uint32_t ILI9341Display::buffer_to_transfer_(uint32_t pos, uint32_t sz) { } for (uint32_t i = 0; i < sz; ++i) { - uint16_t color = display::ColorUtil::color_to_565(display::ColorUtil::rgb332_to_color(*src++)); + uint16_t color; + if (this->buffer_color_mode_ == BITS_8) { + color = display::ColorUtil::color_to_565(display::ColorUtil::rgb332_to_color(*src++)); + } else { // if (this->buffer_color_mode == BITS_8_INDEXED) { + Color col = display::ColorUtil::index8_to_color_palette888(*src++, this->palette_); + color = display::ColorUtil::color_to_565(col); + } *dst++ = (uint8_t)(color >> 8); *dst++ = (uint8_t) color; } diff --git a/esphome/components/ili9341/ili9341_display.h b/esphome/components/ili9341/ili9341_display.h index eeff688f4f..6014dbf9a6 100644 --- a/esphome/components/ili9341/ili9341_display.h +++ b/esphome/components/ili9341/ili9341_display.h @@ -14,6 +14,11 @@ enum ILI9341Model { TFT_24, }; +enum ILI9341ColorMode { + BITS_8, + BITS_8_INDEXED, +}; + class ILI9341Display : public PollingComponent, public display::DisplayBuffer, public spi::SPIDevicereset_pin_ = reset; } void set_led_pin(GPIOPin *led) { this->led_pin_ = led; } void set_model(ILI9341Model model) { this->model_ = model; } + void set_palette(const uint8_t *palette) { this->palette_ = palette; } + void set_buffer_color_mode(ILI9341ColorMode color_mode) { this->buffer_color_mode_ = color_mode; } void command(uint8_t value); void data(uint8_t value); @@ -59,6 +66,9 @@ class ILI9341Display : public PollingComponent, uint16_t y_low_{0}; uint16_t x_high_{0}; uint16_t y_high_{0}; + const uint8_t *palette_; + + ILI9341ColorMode buffer_color_mode_{BITS_8}; uint32_t get_buffer_length_(); int get_width_internal() override; diff --git a/esphome/const.py b/esphome/const.py index c2aa53be70..b73d7e33bd 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -105,6 +105,7 @@ CONF_COLOR_BRIGHTNESS = "color_brightness" CONF_COLOR_CORRECT = "color_correct" CONF_COLOR_INTERLOCK = "color_interlock" CONF_COLOR_MODE = "color_mode" +CONF_COLOR_PALETTE = "color_palette" CONF_COLOR_TEMPERATURE = "color_temperature" CONF_COLORS = "colors" CONF_COMMAND = "command" From 78821056617a9acb35755b2f7a3bdf22965f5c2c Mon Sep 17 00:00:00 2001 From: user897943 Date: Wed, 18 May 2022 23:25:42 +0100 Subject: [PATCH 0316/3388] Update bedjet_const.h to remove blank spaces before speed steps, fixes Unknown Error when using climate.set_fan_mode in HA (#3476) --- esphome/components/bedjet/bedjet_const.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/bedjet/bedjet_const.h b/esphome/components/bedjet/bedjet_const.h index e6bfa45d3a..ae10ca1885 100644 --- a/esphome/components/bedjet/bedjet_const.h +++ b/esphome/components/bedjet/bedjet_const.h @@ -66,8 +66,8 @@ enum BedjetCommand : uint8_t { #define BEDJET_FAN_STEP_NAMES_ \ { \ - " 5%", " 10%", " 15%", " 20%", " 25%", " 30%", " 35%", " 40%", " 45%", " 50%", " 55%", " 60%", " 65%", " 70%", \ - " 75%", " 80%", " 85%", " 90%", " 95%", "100%" \ + "5%", "10%", "15%", "20%", "25%", "30%", "35%", "40%", "45%", "50%", "55%", "60%", "65%", "70%", "75%", "80%", \ + "85%", "90%", "95%", "100%" \ } static const char *const BEDJET_FAN_STEP_NAMES[20] = BEDJET_FAN_STEP_NAMES_; From 9c78049359711006cabf94bf02b2c1a46549777f Mon Sep 17 00:00:00 2001 From: myml Date: Thu, 19 May 2022 08:23:50 +0800 Subject: [PATCH 0317/3388] feat: esp32-camera add stream event (#3285) --- esphome/components/esp32_camera/__init__.py | 37 ++++++++++++++++++- .../components/esp32_camera/esp32_camera.cpp | 16 +++++++- .../components/esp32_camera/esp32_camera.h | 23 ++++++++++++ 3 files changed, 73 insertions(+), 3 deletions(-) diff --git a/esphome/components/esp32_camera/__init__.py b/esphome/components/esp32_camera/__init__.py index 912e705766..753b6ed9da 100644 --- a/esphome/components/esp32_camera/__init__.py +++ b/esphome/components/esp32_camera/__init__.py @@ -1,5 +1,6 @@ import esphome.codegen as cg import esphome.config_validation as cv +from esphome import automation from esphome import pins from esphome.const import ( CONF_FREQUENCY, @@ -12,6 +13,7 @@ from esphome.const import ( CONF_RESOLUTION, CONF_BRIGHTNESS, CONF_CONTRAST, + CONF_TRIGGER_ID, ) from esphome.core import CORE from esphome.components.esp32 import add_idf_sdkconfig_option @@ -23,7 +25,14 @@ AUTO_LOAD = ["psram"] esp32_camera_ns = cg.esphome_ns.namespace("esp32_camera") ESP32Camera = esp32_camera_ns.class_("ESP32Camera", cg.PollingComponent, cg.EntityBase) - +ESP32CameraStreamStartTrigger = esp32_camera_ns.class_( + "ESP32CameraStreamStartTrigger", + automation.Trigger.template(), +) +ESP32CameraStreamStopTrigger = esp32_camera_ns.class_( + "ESP32CameraStreamStopTrigger", + automation.Trigger.template(), +) ESP32CameraFrameSize = esp32_camera_ns.enum("ESP32CameraFrameSize") FRAME_SIZES = { "160X120": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_160X120, @@ -111,6 +120,10 @@ CONF_TEST_PATTERN = "test_pattern" CONF_MAX_FRAMERATE = "max_framerate" CONF_IDLE_FRAMERATE = "idle_framerate" +# stream trigger +CONF_ON_STREAM_START = "on_stream_start" +CONF_ON_STREAM_STOP = "on_stream_stop" + camera_range_param = cv.int_range(min=-2, max=2) CONFIG_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend( @@ -178,6 +191,20 @@ CONFIG_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend( cv.Optional(CONF_IDLE_FRAMERATE, default="0.1 fps"): cv.All( cv.framerate, cv.Range(min=0, max=1) ), + cv.Optional(CONF_ON_STREAM_START): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( + ESP32CameraStreamStartTrigger + ), + } + ), + cv.Optional(CONF_ON_STREAM_STOP): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( + ESP32CameraStreamStopTrigger + ), + } + ), } ).extend(cv.COMPONENT_SCHEMA) @@ -238,3 +265,11 @@ async def to_code(config): if CORE.using_esp_idf: cg.add_library("espressif/esp32-camera", "1.0.0") add_idf_sdkconfig_option("CONFIG_RTCIO_SUPPORT_RTC_GPIO_DESC", True) + + for conf in config.get(CONF_ON_STREAM_START, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [], conf) + + for conf in config.get(CONF_ON_STREAM_STOP, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [], conf) diff --git a/esphome/components/esp32_camera/esp32_camera.cpp b/esphome/components/esp32_camera/esp32_camera.cpp index 851926b083..65b316dc8d 100644 --- a/esphome/components/esp32_camera/esp32_camera.cpp +++ b/esphome/components/esp32_camera/esp32_camera.cpp @@ -282,8 +282,20 @@ void ESP32Camera::set_idle_update_interval(uint32_t idle_update_interval) { void ESP32Camera::add_image_callback(std::function)> &&f) { this->new_image_callback_.add(std::move(f)); } -void ESP32Camera::start_stream(CameraRequester requester) { this->stream_requesters_ |= (1U << requester); } -void ESP32Camera::stop_stream(CameraRequester requester) { this->stream_requesters_ &= ~(1U << requester); } +void ESP32Camera::add_stream_start_callback(std::function &&callback) { + this->stream_start_callback_.add(std::move(callback)); +} +void ESP32Camera::add_stream_stop_callback(std::function &&callback) { + this->stream_stop_callback_.add(std::move(callback)); +} +void ESP32Camera::start_stream(CameraRequester requester) { + this->stream_start_callback_.call(); + this->stream_requesters_ |= (1U << requester); +} +void ESP32Camera::stop_stream(CameraRequester requester) { + this->stream_stop_callback_.call(); + this->stream_requesters_ &= ~(1U << requester); +} void ESP32Camera::request_image(CameraRequester requester) { this->single_requesters_ |= (1U << requester); } void ESP32Camera::update_camera_parameters() { sensor_t *s = esp_camera_sensor_get(); diff --git a/esphome/components/esp32_camera/esp32_camera.h b/esphome/components/esp32_camera/esp32_camera.h index 743b5bde5f..8bf73a0fa6 100644 --- a/esphome/components/esp32_camera/esp32_camera.h +++ b/esphome/components/esp32_camera/esp32_camera.h @@ -2,6 +2,7 @@ #ifdef USE_ESP32 +#include "esphome/core/automation.h" #include "esphome/core/component.h" #include "esphome/core/entity_base.h" #include "esphome/core/helpers.h" @@ -145,6 +146,9 @@ class ESP32Camera : public Component, public EntityBase { void request_image(CameraRequester requester); void update_camera_parameters(); + void add_stream_start_callback(std::function &&callback); + void add_stream_stop_callback(std::function &&callback); + protected: /* internal methods */ uint32_t hash_base() override; @@ -187,6 +191,8 @@ class ESP32Camera : public Component, public EntityBase { QueueHandle_t framebuffer_get_queue_; QueueHandle_t framebuffer_return_queue_; CallbackManager)> new_image_callback_; + CallbackManager stream_start_callback_{}; + CallbackManager stream_stop_callback_{}; uint32_t last_idle_request_{0}; uint32_t last_update_{0}; @@ -195,6 +201,23 @@ class ESP32Camera : public Component, public EntityBase { // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) extern ESP32Camera *global_esp32_camera; +class ESP32CameraStreamStartTrigger : public Trigger<> { + public: + explicit ESP32CameraStreamStartTrigger(ESP32Camera *parent) { + parent->add_stream_start_callback([this]() { this->trigger(); }); + } + + protected: +}; +class ESP32CameraStreamStopTrigger : public Trigger<> { + public: + explicit ESP32CameraStreamStopTrigger(ESP32Camera *parent) { + parent->add_stream_stop_callback([this]() { this->trigger(); }); + } + + protected: +}; + } // namespace esp32_camera } // namespace esphome From 0ed7db979be7057cd844033b37fe6f7079d16ed7 Mon Sep 17 00:00:00 2001 From: Martin <25747549+martgras@users.noreply.github.com> Date: Thu, 19 May 2022 02:47:33 +0200 Subject: [PATCH 0318/3388] Add support for SGP41 (#3382) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- CODEOWNERS | 1 + .../sgp40/sensirion_voc_algorithm.cpp | 628 ------------------ .../sgp40/sensirion_voc_algorithm.h | 147 ---- esphome/components/sgp40/sensor.py | 68 +- esphome/components/sgp40/sgp40.cpp | 274 -------- esphome/components/sgp40/sgp40.h | 93 --- esphome/components/sgp4x/__init__.py | 0 esphome/components/sgp4x/sensor.py | 144 ++++ esphome/components/sgp4x/sgp4x.cpp | 343 ++++++++++ esphome/components/sgp4x/sgp4x.h | 142 ++++ platformio.ini | 2 + tests/test2.yaml | 23 +- 12 files changed, 655 insertions(+), 1210 deletions(-) delete mode 100644 esphome/components/sgp40/sensirion_voc_algorithm.cpp delete mode 100644 esphome/components/sgp40/sensirion_voc_algorithm.h delete mode 100644 esphome/components/sgp40/sgp40.cpp delete mode 100644 esphome/components/sgp40/sgp40.h create mode 100644 esphome/components/sgp4x/__init__.py create mode 100644 esphome/components/sgp4x/sensor.py create mode 100644 esphome/components/sgp4x/sgp4x.cpp create mode 100644 esphome/components/sgp4x/sgp4x.h diff --git a/CODEOWNERS b/CODEOWNERS index be6e8be3f7..3e82a372ce 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -178,6 +178,7 @@ esphome/components/sen5x/* @martgras esphome/components/sensirion_common/* @martgras esphome/components/sensor/* @esphome/core esphome/components/sgp40/* @SenexCrenshaw +esphome/components/sgp4x/* @SenexCrenshaw @martgras esphome/components/shelly_dimmer/* @edge90 @rnauber esphome/components/sht4x/* @sjtrny esphome/components/shutdown/* @esphome/core @jsuanet diff --git a/esphome/components/sgp40/sensirion_voc_algorithm.cpp b/esphome/components/sgp40/sensirion_voc_algorithm.cpp deleted file mode 100644 index d76b776641..0000000000 --- a/esphome/components/sgp40/sensirion_voc_algorithm.cpp +++ /dev/null @@ -1,628 +0,0 @@ - -#include "sensirion_voc_algorithm.h" - -namespace esphome { -namespace sgp40 { - -/* The VOC code were originally created by - * https://github.com/Sensirion/embedded-sgp - * The fixed point arithmetic parts of this code were originally created by - * https://github.com/PetteriAimonen/libfixmath - */ - -/*!< the maximum value of fix16_t */ -#define FIX16_MAXIMUM 0x7FFFFFFF -/*!< the minimum value of fix16_t */ -static const uint32_t FIX16_MINIMUM = 0x80000000; -/*!< the value used to indicate overflows when FIXMATH_NO_OVERFLOW is not - * specified */ -static const uint32_t FIX16_OVERFLOW = 0x80000000; -/*!< fix16_t value of 1 */ -const uint32_t FIX16_ONE = 0x00010000; - -inline fix16_t fix16_from_int(int32_t a) { return a * FIX16_ONE; } - -inline int32_t fix16_cast_to_int(fix16_t a) { return (a >> 16); } - -/*! Multiplies the two given fix16_t's and returns the result. */ -static fix16_t fix16_mul(fix16_t in_arg0, fix16_t in_arg1); - -/*! Divides the first given fix16_t by the second and returns the result. */ -static fix16_t fix16_div(fix16_t a, fix16_t b); - -/*! Returns the square root of the given fix16_t. */ -static fix16_t fix16_sqrt(fix16_t in_value); - -/*! Returns the exponent (e^) of the given fix16_t. */ -static fix16_t fix16_exp(fix16_t in_value); - -static fix16_t fix16_mul(fix16_t in_arg0, fix16_t in_arg1) { - // Each argument is divided to 16-bit parts. - // AB - // * CD - // ----------- - // BD 16 * 16 -> 32 bit products - // CB - // AD - // AC - // |----| 64 bit product - int32_t a = (in_arg0 >> 16), c = (in_arg1 >> 16); - uint32_t b = (in_arg0 & 0xFFFF), d = (in_arg1 & 0xFFFF); - - int32_t ac = a * c; - int32_t ad_cb = a * d + c * b; - uint32_t bd = b * d; - - int32_t product_hi = ac + (ad_cb >> 16); // NOLINT - - // Handle carry from lower 32 bits to upper part of result. - uint32_t ad_cb_temp = ad_cb << 16; // NOLINT - uint32_t product_lo = bd + ad_cb_temp; - if (product_lo < bd) - product_hi++; - -#ifndef FIXMATH_NO_OVERFLOW - // The upper 17 bits should all be the same (the sign). - if (product_hi >> 31 != product_hi >> 15) - return FIX16_OVERFLOW; -#endif - -#ifdef FIXMATH_NO_ROUNDING - return (product_hi << 16) | (product_lo >> 16); -#else - // Subtracting 0x8000 (= 0.5) and then using signed right shift - // achieves proper rounding to result-1, except in the corner - // case of negative numbers and lowest word = 0x8000. - // To handle that, we also have to subtract 1 for negative numbers. - uint32_t product_lo_tmp = product_lo; - product_lo -= 0x8000; - product_lo -= (uint32_t) product_hi >> 31; - if (product_lo > product_lo_tmp) - product_hi--; - - // Discard the lowest 16 bits. Note that this is not exactly the same - // as dividing by 0x10000. For example if product = -1, result will - // also be -1 and not 0. This is compensated by adding +1 to the result - // and compensating this in turn in the rounding above. - fix16_t result = (product_hi << 16) | (product_lo >> 16); // NOLINT - result += 1; - return result; -#endif -} - -static fix16_t fix16_div(fix16_t a, fix16_t b) { - // This uses the basic binary restoring division algorithm. - // It appears to be faster to do the whole division manually than - // trying to compose a 64-bit divide out of 32-bit divisions on - // platforms without hardware divide. - - if (b == 0) - return FIX16_MINIMUM; - - uint32_t remainder = (a >= 0) ? a : (-a); - uint32_t divider = (b >= 0) ? b : (-b); - - uint32_t quotient = 0; - uint32_t bit = 0x10000; - - /* The algorithm requires D >= R */ - while (divider < remainder) { - divider <<= 1; - bit <<= 1; - } - -#ifndef FIXMATH_NO_OVERFLOW - if (!bit) - return FIX16_OVERFLOW; -#endif - - if (divider & 0x80000000) { - // Perform one step manually to avoid overflows later. - // We know that divider's bottom bit is 0 here. - if (remainder >= divider) { - quotient |= bit; - remainder -= divider; - } - divider >>= 1; - bit >>= 1; - } - - /* Main division loop */ - while (bit && remainder) { - if (remainder >= divider) { - quotient |= bit; - remainder -= divider; - } - - remainder <<= 1; - bit >>= 1; - } - -#ifndef FIXMATH_NO_ROUNDING - if (remainder >= divider) { - quotient++; - } -#endif - - fix16_t result = quotient; - - /* Figure out the sign of result */ - if ((a ^ b) & 0x80000000) { -#ifndef FIXMATH_NO_OVERFLOW - if (result == FIX16_MINIMUM) // NOLINT(clang-diagnostic-sign-compare) - return FIX16_OVERFLOW; -#endif - - result = -result; - } - - return result; -} - -static fix16_t fix16_sqrt(fix16_t in_value) { - // It is assumed that x is not negative - - uint32_t num = in_value; - uint32_t result = 0; - uint32_t bit; - uint8_t n; - - bit = (uint32_t) 1 << 30; - while (bit > num) - bit >>= 2; - - // The main part is executed twice, in order to avoid - // using 64 bit values in computations. - for (n = 0; n < 2; n++) { - // First we get the top 24 bits of the answer. - while (bit) { - if (num >= result + bit) { - num -= result + bit; - result = (result >> 1) + bit; - } else { - result = (result >> 1); - } - bit >>= 2; - } - - if (n == 0) { - // Then process it again to get the lowest 8 bits. - if (num > 65535) { - // The remainder 'num' is too large to be shifted left - // by 16, so we have to add 1 to result manually and - // adjust 'num' accordingly. - // num = a - (result + 0.5)^2 - // = num + result^2 - (result + 0.5)^2 - // = num - result - 0.5 - num -= result; - num = (num << 16) - 0x8000; - result = (result << 16) + 0x8000; - } else { - num <<= 16; - result <<= 16; - } - - bit = 1 << 14; - } - } - -#ifndef FIXMATH_NO_ROUNDING - // Finally, if next bit would have been 1, round the result upwards. - if (num > result) { - result++; - } -#endif - - return (fix16_t) result; -} - -static fix16_t fix16_exp(fix16_t in_value) { - // Function to approximate exp(); optimized more for code size than speed - - // exp(x) for x = +/- {1, 1/8, 1/64, 1/512} - fix16_t x = in_value; - static const uint8_t NUM_EXP_VALUES = 4; - static const fix16_t EXP_POS_VALUES[4] = {F16(2.7182818), F16(1.1331485), F16(1.0157477), F16(1.0019550)}; - static const fix16_t EXP_NEG_VALUES[4] = {F16(0.3678794), F16(0.8824969), F16(0.9844964), F16(0.9980488)}; - const fix16_t *exp_values; - - fix16_t res, arg; - uint16_t i; - - if (x >= F16(10.3972)) - return FIX16_MAXIMUM; - if (x <= F16(-11.7835)) - return 0; - - if (x < 0) { - x = -x; - exp_values = EXP_NEG_VALUES; - } else { - exp_values = EXP_POS_VALUES; - } - - res = FIX16_ONE; - arg = FIX16_ONE; - for (i = 0; i < NUM_EXP_VALUES; i++) { - while (x >= arg) { - res = fix16_mul(res, exp_values[i]); - x -= arg; - } - arg >>= 3; - } - return res; -} - -static void voc_algorithm_init_instances(VocAlgorithmParams *params); -static void voc_algorithm_mean_variance_estimator_init(VocAlgorithmParams *params); -static void voc_algorithm_mean_variance_estimator_init_instances(VocAlgorithmParams *params); -static void voc_algorithm_mean_variance_estimator_set_parameters(VocAlgorithmParams *params, fix16_t std_initial, - fix16_t tau_mean_variance_hours, - fix16_t gating_max_duration_minutes); -static void voc_algorithm_mean_variance_estimator_set_states(VocAlgorithmParams *params, fix16_t mean, fix16_t std, - fix16_t uptime_gamma); -static fix16_t voc_algorithm_mean_variance_estimator_get_std(VocAlgorithmParams *params); -static fix16_t voc_algorithm_mean_variance_estimator_get_mean(VocAlgorithmParams *params); -static void voc_algorithm_mean_variance_estimator_calculate_gamma(VocAlgorithmParams *params, - fix16_t voc_index_from_prior); -static void voc_algorithm_mean_variance_estimator_process(VocAlgorithmParams *params, fix16_t sraw, - fix16_t voc_index_from_prior); -static void voc_algorithm_mean_variance_estimator_sigmoid_init(VocAlgorithmParams *params); -static void voc_algorithm_mean_variance_estimator_sigmoid_set_parameters(VocAlgorithmParams *params, fix16_t l, - fix16_t x0, fix16_t k); -static fix16_t voc_algorithm_mean_variance_estimator_sigmoid_process(VocAlgorithmParams *params, fix16_t sample); -static void voc_algorithm_mox_model_init(VocAlgorithmParams *params); -static void voc_algorithm_mox_model_set_parameters(VocAlgorithmParams *params, fix16_t sraw_std, fix16_t sraw_mean); -static fix16_t voc_algorithm_mox_model_process(VocAlgorithmParams *params, fix16_t sraw); -static void voc_algorithm_sigmoid_scaled_init(VocAlgorithmParams *params); -static void voc_algorithm_sigmoid_scaled_set_parameters(VocAlgorithmParams *params, fix16_t offset); -static fix16_t voc_algorithm_sigmoid_scaled_process(VocAlgorithmParams *params, fix16_t sample); -static void voc_algorithm_adaptive_lowpass_init(VocAlgorithmParams *params); -static void voc_algorithm_adaptive_lowpass_set_parameters(VocAlgorithmParams *params); -static fix16_t voc_algorithm_adaptive_lowpass_process(VocAlgorithmParams *params, fix16_t sample); - -void voc_algorithm_init(VocAlgorithmParams *params) { - params->mVoc_Index_Offset = F16(VOC_ALGORITHM_VOC_INDEX_OFFSET_DEFAULT); - params->mTau_Mean_Variance_Hours = F16(VOC_ALGORITHM_TAU_MEAN_VARIANCE_HOURS); - params->mGating_Max_Duration_Minutes = F16(VOC_ALGORITHM_GATING_MAX_DURATION_MINUTES); - params->mSraw_Std_Initial = F16(VOC_ALGORITHM_SRAW_STD_INITIAL); - params->mUptime = F16(0.); - params->mSraw = F16(0.); - params->mVoc_Index = 0; - voc_algorithm_init_instances(params); -} - -static void voc_algorithm_init_instances(VocAlgorithmParams *params) { - voc_algorithm_mean_variance_estimator_init(params); - voc_algorithm_mean_variance_estimator_set_parameters( - params, params->mSraw_Std_Initial, params->mTau_Mean_Variance_Hours, params->mGating_Max_Duration_Minutes); - voc_algorithm_mox_model_init(params); - voc_algorithm_mox_model_set_parameters(params, voc_algorithm_mean_variance_estimator_get_std(params), - voc_algorithm_mean_variance_estimator_get_mean(params)); - voc_algorithm_sigmoid_scaled_init(params); - voc_algorithm_sigmoid_scaled_set_parameters(params, params->mVoc_Index_Offset); - voc_algorithm_adaptive_lowpass_init(params); - voc_algorithm_adaptive_lowpass_set_parameters(params); -} - -void voc_algorithm_get_states(VocAlgorithmParams *params, int32_t *state0, int32_t *state1) { - *state0 = voc_algorithm_mean_variance_estimator_get_mean(params); - *state1 = voc_algorithm_mean_variance_estimator_get_std(params); -} - -void voc_algorithm_set_states(VocAlgorithmParams *params, int32_t state0, int32_t state1) { - voc_algorithm_mean_variance_estimator_set_states(params, state0, state1, F16(VOC_ALGORITHM_PERSISTENCE_UPTIME_GAMMA)); - params->mSraw = state0; -} - -void voc_algorithm_set_tuning_parameters(VocAlgorithmParams *params, int32_t voc_index_offset, - int32_t learning_time_hours, int32_t gating_max_duration_minutes, - int32_t std_initial) { - params->mVoc_Index_Offset = (fix16_from_int(voc_index_offset)); - params->mTau_Mean_Variance_Hours = (fix16_from_int(learning_time_hours)); - params->mGating_Max_Duration_Minutes = (fix16_from_int(gating_max_duration_minutes)); - params->mSraw_Std_Initial = (fix16_from_int(std_initial)); - voc_algorithm_init_instances(params); -} - -void voc_algorithm_process(VocAlgorithmParams *params, int32_t sraw, int32_t *voc_index) { - if ((params->mUptime <= F16(VOC_ALGORITHM_INITIAL_BLACKOUT))) { - params->mUptime = (params->mUptime + F16(VOC_ALGORITHM_SAMPLING_INTERVAL)); - } else { - if (((sraw > 0) && (sraw < 65000))) { - if ((sraw < 20001)) { - sraw = 20001; - } else if ((sraw > 52767)) { - sraw = 52767; - } - params->mSraw = (fix16_from_int((sraw - 20000))); - } - params->mVoc_Index = voc_algorithm_mox_model_process(params, params->mSraw); - params->mVoc_Index = voc_algorithm_sigmoid_scaled_process(params, params->mVoc_Index); - params->mVoc_Index = voc_algorithm_adaptive_lowpass_process(params, params->mVoc_Index); - if ((params->mVoc_Index < F16(0.5))) { - params->mVoc_Index = F16(0.5); - } - if ((params->mSraw > F16(0.))) { - voc_algorithm_mean_variance_estimator_process(params, params->mSraw, params->mVoc_Index); - voc_algorithm_mox_model_set_parameters(params, voc_algorithm_mean_variance_estimator_get_std(params), - voc_algorithm_mean_variance_estimator_get_mean(params)); - } - } - *voc_index = (fix16_cast_to_int((params->mVoc_Index + F16(0.5)))); -} - -static void voc_algorithm_mean_variance_estimator_init(VocAlgorithmParams *params) { - voc_algorithm_mean_variance_estimator_set_parameters(params, F16(0.), F16(0.), F16(0.)); - voc_algorithm_mean_variance_estimator_init_instances(params); -} - -static void voc_algorithm_mean_variance_estimator_init_instances(VocAlgorithmParams *params) { - voc_algorithm_mean_variance_estimator_sigmoid_init(params); -} - -static void voc_algorithm_mean_variance_estimator_set_parameters(VocAlgorithmParams *params, fix16_t std_initial, - fix16_t tau_mean_variance_hours, - fix16_t gating_max_duration_minutes) { - params->m_Mean_Variance_Estimator_Gating_Max_Duration_Minutes = gating_max_duration_minutes; - params->m_Mean_Variance_Estimator_Initialized = false; - params->m_Mean_Variance_Estimator_Mean = F16(0.); - params->m_Mean_Variance_Estimator_Sraw_Offset = F16(0.); - params->m_Mean_Variance_Estimator_Std = std_initial; - params->m_Mean_Variance_Estimator_Gamma = - (fix16_div(F16((VOC_ALGORITHM_MEAN_VARIANCE_ESTIMATOR_GAMMA_SCALING * (VOC_ALGORITHM_SAMPLING_INTERVAL / 3600.))), - (tau_mean_variance_hours + F16((VOC_ALGORITHM_SAMPLING_INTERVAL / 3600.))))); - params->m_Mean_Variance_Estimator_Gamma_Initial_Mean = - F16(((VOC_ALGORITHM_MEAN_VARIANCE_ESTIMATOR_GAMMA_SCALING * VOC_ALGORITHM_SAMPLING_INTERVAL) / - (VOC_ALGORITHM_TAU_INITIAL_MEAN + VOC_ALGORITHM_SAMPLING_INTERVAL))); - params->m_Mean_Variance_Estimator_Gamma_Initial_Variance = - F16(((VOC_ALGORITHM_MEAN_VARIANCE_ESTIMATOR_GAMMA_SCALING * VOC_ALGORITHM_SAMPLING_INTERVAL) / - (VOC_ALGORITHM_TAU_INITIAL_VARIANCE + VOC_ALGORITHM_SAMPLING_INTERVAL))); - params->m_Mean_Variance_Estimator_Gamma_Mean = F16(0.); - params->m_Mean_Variance_Estimator_Gamma_Variance = F16(0.); - params->m_Mean_Variance_Estimator_Uptime_Gamma = F16(0.); - params->m_Mean_Variance_Estimator_Uptime_Gating = F16(0.); - params->m_Mean_Variance_Estimator_Gating_Duration_Minutes = F16(0.); -} - -static void voc_algorithm_mean_variance_estimator_set_states(VocAlgorithmParams *params, fix16_t mean, fix16_t std, - fix16_t uptime_gamma) { - params->m_Mean_Variance_Estimator_Mean = mean; - params->m_Mean_Variance_Estimator_Std = std; - params->m_Mean_Variance_Estimator_Uptime_Gamma = uptime_gamma; - params->m_Mean_Variance_Estimator_Initialized = true; -} - -static fix16_t voc_algorithm_mean_variance_estimator_get_std(VocAlgorithmParams *params) { - return params->m_Mean_Variance_Estimator_Std; -} - -static fix16_t voc_algorithm_mean_variance_estimator_get_mean(VocAlgorithmParams *params) { - return (params->m_Mean_Variance_Estimator_Mean + params->m_Mean_Variance_Estimator_Sraw_Offset); -} - -static void voc_algorithm_mean_variance_estimator_calculate_gamma(VocAlgorithmParams *params, - fix16_t voc_index_from_prior) { - fix16_t uptime_limit; - fix16_t sigmoid_gamma_mean; - fix16_t gamma_mean; - fix16_t gating_threshold_mean; - fix16_t sigmoid_gating_mean; - fix16_t sigmoid_gamma_variance; - fix16_t gamma_variance; - fix16_t gating_threshold_variance; - fix16_t sigmoid_gating_variance; - - uptime_limit = F16((VOC_ALGORITHM_MEAN_VARIANCE_ESTIMATOR_FI_X16_MAX - VOC_ALGORITHM_SAMPLING_INTERVAL)); - if ((params->m_Mean_Variance_Estimator_Uptime_Gamma < uptime_limit)) { - params->m_Mean_Variance_Estimator_Uptime_Gamma = - (params->m_Mean_Variance_Estimator_Uptime_Gamma + F16(VOC_ALGORITHM_SAMPLING_INTERVAL)); - } - if ((params->m_Mean_Variance_Estimator_Uptime_Gating < uptime_limit)) { - params->m_Mean_Variance_Estimator_Uptime_Gating = - (params->m_Mean_Variance_Estimator_Uptime_Gating + F16(VOC_ALGORITHM_SAMPLING_INTERVAL)); - } - voc_algorithm_mean_variance_estimator_sigmoid_set_parameters(params, F16(1.), F16(VOC_ALGORITHM_INIT_DURATION_MEAN), - F16(VOC_ALGORITHM_INIT_TRANSITION_MEAN)); - sigmoid_gamma_mean = - voc_algorithm_mean_variance_estimator_sigmoid_process(params, params->m_Mean_Variance_Estimator_Uptime_Gamma); - gamma_mean = - (params->m_Mean_Variance_Estimator_Gamma + - (fix16_mul((params->m_Mean_Variance_Estimator_Gamma_Initial_Mean - params->m_Mean_Variance_Estimator_Gamma), - sigmoid_gamma_mean))); - gating_threshold_mean = (F16(VOC_ALGORITHM_GATING_THRESHOLD) + - (fix16_mul(F16((VOC_ALGORITHM_GATING_THRESHOLD_INITIAL - VOC_ALGORITHM_GATING_THRESHOLD)), - voc_algorithm_mean_variance_estimator_sigmoid_process( - params, params->m_Mean_Variance_Estimator_Uptime_Gating)))); - voc_algorithm_mean_variance_estimator_sigmoid_set_parameters(params, F16(1.), gating_threshold_mean, - F16(VOC_ALGORITHM_GATING_THRESHOLD_TRANSITION)); - sigmoid_gating_mean = voc_algorithm_mean_variance_estimator_sigmoid_process(params, voc_index_from_prior); - params->m_Mean_Variance_Estimator_Gamma_Mean = (fix16_mul(sigmoid_gating_mean, gamma_mean)); - voc_algorithm_mean_variance_estimator_sigmoid_set_parameters( - params, F16(1.), F16(VOC_ALGORITHM_INIT_DURATION_VARIANCE), F16(VOC_ALGORITHM_INIT_TRANSITION_VARIANCE)); - sigmoid_gamma_variance = - voc_algorithm_mean_variance_estimator_sigmoid_process(params, params->m_Mean_Variance_Estimator_Uptime_Gamma); - gamma_variance = - (params->m_Mean_Variance_Estimator_Gamma + - (fix16_mul((params->m_Mean_Variance_Estimator_Gamma_Initial_Variance - params->m_Mean_Variance_Estimator_Gamma), - (sigmoid_gamma_variance - sigmoid_gamma_mean)))); - gating_threshold_variance = - (F16(VOC_ALGORITHM_GATING_THRESHOLD) + - (fix16_mul(F16((VOC_ALGORITHM_GATING_THRESHOLD_INITIAL - VOC_ALGORITHM_GATING_THRESHOLD)), - voc_algorithm_mean_variance_estimator_sigmoid_process( - params, params->m_Mean_Variance_Estimator_Uptime_Gating)))); - voc_algorithm_mean_variance_estimator_sigmoid_set_parameters(params, F16(1.), gating_threshold_variance, - F16(VOC_ALGORITHM_GATING_THRESHOLD_TRANSITION)); - sigmoid_gating_variance = voc_algorithm_mean_variance_estimator_sigmoid_process(params, voc_index_from_prior); - params->m_Mean_Variance_Estimator_Gamma_Variance = (fix16_mul(sigmoid_gating_variance, gamma_variance)); - params->m_Mean_Variance_Estimator_Gating_Duration_Minutes = - (params->m_Mean_Variance_Estimator_Gating_Duration_Minutes + - (fix16_mul(F16((VOC_ALGORITHM_SAMPLING_INTERVAL / 60.)), - ((fix16_mul((F16(1.) - sigmoid_gating_mean), F16((1. + VOC_ALGORITHM_GATING_MAX_RATIO)))) - - F16(VOC_ALGORITHM_GATING_MAX_RATIO))))); - if ((params->m_Mean_Variance_Estimator_Gating_Duration_Minutes < F16(0.))) { - params->m_Mean_Variance_Estimator_Gating_Duration_Minutes = F16(0.); - } - if ((params->m_Mean_Variance_Estimator_Gating_Duration_Minutes > - params->m_Mean_Variance_Estimator_Gating_Max_Duration_Minutes)) { - params->m_Mean_Variance_Estimator_Uptime_Gating = F16(0.); - } -} - -static void voc_algorithm_mean_variance_estimator_process(VocAlgorithmParams *params, fix16_t sraw, - fix16_t voc_index_from_prior) { - fix16_t delta_sgp; - fix16_t c; - fix16_t additional_scaling; - - if ((!params->m_Mean_Variance_Estimator_Initialized)) { - params->m_Mean_Variance_Estimator_Initialized = true; - params->m_Mean_Variance_Estimator_Sraw_Offset = sraw; - params->m_Mean_Variance_Estimator_Mean = F16(0.); - } else { - if (((params->m_Mean_Variance_Estimator_Mean >= F16(100.)) || - (params->m_Mean_Variance_Estimator_Mean <= F16(-100.)))) { - params->m_Mean_Variance_Estimator_Sraw_Offset = - (params->m_Mean_Variance_Estimator_Sraw_Offset + params->m_Mean_Variance_Estimator_Mean); - params->m_Mean_Variance_Estimator_Mean = F16(0.); - } - sraw = (sraw - params->m_Mean_Variance_Estimator_Sraw_Offset); - voc_algorithm_mean_variance_estimator_calculate_gamma(params, voc_index_from_prior); - delta_sgp = (fix16_div((sraw - params->m_Mean_Variance_Estimator_Mean), - F16(VOC_ALGORITHM_MEAN_VARIANCE_ESTIMATOR_GAMMA_SCALING))); - if ((delta_sgp < F16(0.))) { - c = (params->m_Mean_Variance_Estimator_Std - delta_sgp); - } else { - c = (params->m_Mean_Variance_Estimator_Std + delta_sgp); - } - additional_scaling = F16(1.); - if ((c > F16(1440.))) { - additional_scaling = F16(4.); - } - params->m_Mean_Variance_Estimator_Std = (fix16_mul( - fix16_sqrt((fix16_mul(additional_scaling, (F16(VOC_ALGORITHM_MEAN_VARIANCE_ESTIMATOR_GAMMA_SCALING) - - params->m_Mean_Variance_Estimator_Gamma_Variance)))), - fix16_sqrt(((fix16_mul(params->m_Mean_Variance_Estimator_Std, - (fix16_div(params->m_Mean_Variance_Estimator_Std, - (fix16_mul(F16(VOC_ALGORITHM_MEAN_VARIANCE_ESTIMATOR_GAMMA_SCALING), - additional_scaling)))))) + - (fix16_mul((fix16_div((fix16_mul(params->m_Mean_Variance_Estimator_Gamma_Variance, delta_sgp)), - additional_scaling)), - delta_sgp)))))); - params->m_Mean_Variance_Estimator_Mean = - (params->m_Mean_Variance_Estimator_Mean + (fix16_mul(params->m_Mean_Variance_Estimator_Gamma_Mean, delta_sgp))); - } -} - -static void voc_algorithm_mean_variance_estimator_sigmoid_init(VocAlgorithmParams *params) { - voc_algorithm_mean_variance_estimator_sigmoid_set_parameters(params, F16(0.), F16(0.), F16(0.)); -} - -static void voc_algorithm_mean_variance_estimator_sigmoid_set_parameters(VocAlgorithmParams *params, fix16_t l, - fix16_t x0, fix16_t k) { - params->m_Mean_Variance_Estimator_Sigmoid_L = l; - params->m_Mean_Variance_Estimator_Sigmoid_K = k; - params->m_Mean_Variance_Estimator_Sigmoid_X0 = x0; -} - -static fix16_t voc_algorithm_mean_variance_estimator_sigmoid_process(VocAlgorithmParams *params, fix16_t sample) { - fix16_t x; - - x = (fix16_mul(params->m_Mean_Variance_Estimator_Sigmoid_K, (sample - params->m_Mean_Variance_Estimator_Sigmoid_X0))); - if ((x < F16(-50.))) { - return params->m_Mean_Variance_Estimator_Sigmoid_L; - } else if ((x > F16(50.))) { - return F16(0.); - } else { - return (fix16_div(params->m_Mean_Variance_Estimator_Sigmoid_L, (F16(1.) + fix16_exp(x)))); - } -} - -static void voc_algorithm_mox_model_init(VocAlgorithmParams *params) { - voc_algorithm_mox_model_set_parameters(params, F16(1.), F16(0.)); -} - -static void voc_algorithm_mox_model_set_parameters(VocAlgorithmParams *params, fix16_t sraw_std, fix16_t sraw_mean) { - params->m_Mox_Model_Sraw_Std = sraw_std; - params->m_Mox_Model_Sraw_Mean = sraw_mean; -} - -static fix16_t voc_algorithm_mox_model_process(VocAlgorithmParams *params, fix16_t sraw) { - return (fix16_mul((fix16_div((sraw - params->m_Mox_Model_Sraw_Mean), - (-(params->m_Mox_Model_Sraw_Std + F16(VOC_ALGORITHM_SRAW_STD_BONUS))))), - F16(VOC_ALGORITHM_VOC_INDEX_GAIN))); -} - -static void voc_algorithm_sigmoid_scaled_init(VocAlgorithmParams *params) { - voc_algorithm_sigmoid_scaled_set_parameters(params, F16(0.)); -} - -static void voc_algorithm_sigmoid_scaled_set_parameters(VocAlgorithmParams *params, fix16_t offset) { - params->m_Sigmoid_Scaled_Offset = offset; -} - -static fix16_t voc_algorithm_sigmoid_scaled_process(VocAlgorithmParams *params, fix16_t sample) { - fix16_t x; - fix16_t shift; - - x = (fix16_mul(F16(VOC_ALGORITHM_SIGMOID_K), (sample - F16(VOC_ALGORITHM_SIGMOID_X0)))); - if ((x < F16(-50.))) { - return F16(VOC_ALGORITHM_SIGMOID_L); - } else if ((x > F16(50.))) { - return F16(0.); - } else { - if ((sample >= F16(0.))) { - shift = - (fix16_div((F16(VOC_ALGORITHM_SIGMOID_L) - (fix16_mul(F16(5.), params->m_Sigmoid_Scaled_Offset))), F16(4.))); - return ((fix16_div((F16(VOC_ALGORITHM_SIGMOID_L) + shift), (F16(1.) + fix16_exp(x)))) - shift); - } else { - return (fix16_mul((fix16_div(params->m_Sigmoid_Scaled_Offset, F16(VOC_ALGORITHM_VOC_INDEX_OFFSET_DEFAULT))), - (fix16_div(F16(VOC_ALGORITHM_SIGMOID_L), (F16(1.) + fix16_exp(x)))))); - } - } -} - -static void voc_algorithm_adaptive_lowpass_init(VocAlgorithmParams *params) { - voc_algorithm_adaptive_lowpass_set_parameters(params); -} - -static void voc_algorithm_adaptive_lowpass_set_parameters(VocAlgorithmParams *params) { - params->m_Adaptive_Lowpass_A1 = - F16((VOC_ALGORITHM_SAMPLING_INTERVAL / (VOC_ALGORITHM_LP_TAU_FAST + VOC_ALGORITHM_SAMPLING_INTERVAL))); - params->m_Adaptive_Lowpass_A2 = - F16((VOC_ALGORITHM_SAMPLING_INTERVAL / (VOC_ALGORITHM_LP_TAU_SLOW + VOC_ALGORITHM_SAMPLING_INTERVAL))); - params->m_Adaptive_Lowpass_Initialized = false; -} - -static fix16_t voc_algorithm_adaptive_lowpass_process(VocAlgorithmParams *params, fix16_t sample) { - fix16_t abs_delta; - fix16_t f1; - fix16_t tau_a; - fix16_t a3; - - if ((!params->m_Adaptive_Lowpass_Initialized)) { - params->m_Adaptive_Lowpass_X1 = sample; - params->m_Adaptive_Lowpass_X2 = sample; - params->m_Adaptive_Lowpass_X3 = sample; - params->m_Adaptive_Lowpass_Initialized = true; - } - params->m_Adaptive_Lowpass_X1 = - ((fix16_mul((F16(1.) - params->m_Adaptive_Lowpass_A1), params->m_Adaptive_Lowpass_X1)) + - (fix16_mul(params->m_Adaptive_Lowpass_A1, sample))); - params->m_Adaptive_Lowpass_X2 = - ((fix16_mul((F16(1.) - params->m_Adaptive_Lowpass_A2), params->m_Adaptive_Lowpass_X2)) + - (fix16_mul(params->m_Adaptive_Lowpass_A2, sample))); - abs_delta = (params->m_Adaptive_Lowpass_X1 - params->m_Adaptive_Lowpass_X2); - if ((abs_delta < F16(0.))) { - abs_delta = (-abs_delta); - } - f1 = fix16_exp((fix16_mul(F16(VOC_ALGORITHM_LP_ALPHA), abs_delta))); - tau_a = - ((fix16_mul(F16((VOC_ALGORITHM_LP_TAU_SLOW - VOC_ALGORITHM_LP_TAU_FAST)), f1)) + F16(VOC_ALGORITHM_LP_TAU_FAST)); - a3 = (fix16_div(F16(VOC_ALGORITHM_SAMPLING_INTERVAL), (F16(VOC_ALGORITHM_SAMPLING_INTERVAL) + tau_a))); - params->m_Adaptive_Lowpass_X3 = - ((fix16_mul((F16(1.) - a3), params->m_Adaptive_Lowpass_X3)) + (fix16_mul(a3, sample))); - return params->m_Adaptive_Lowpass_X3; -} -} // namespace sgp40 -} // namespace esphome diff --git a/esphome/components/sgp40/sensirion_voc_algorithm.h b/esphome/components/sgp40/sensirion_voc_algorithm.h deleted file mode 100644 index adef6b29e8..0000000000 --- a/esphome/components/sgp40/sensirion_voc_algorithm.h +++ /dev/null @@ -1,147 +0,0 @@ -#pragma once -#include -namespace esphome { -namespace sgp40 { - -/* The VOC code were originally created by - * https://github.com/Sensirion/embedded-sgp - * The fixed point arithmetic parts of this code were originally created by - * https://github.com/PetteriAimonen/libfixmath - */ - -using fix16_t = int32_t; - -#define F16(x) ((fix16_t)(((x) >= 0) ? ((x) *65536.0 + 0.5) : ((x) *65536.0 - 0.5))) - -static const float VOC_ALGORITHM_SAMPLING_INTERVAL(1.); -static const float VOC_ALGORITHM_INITIAL_BLACKOUT(45.); -static const float VOC_ALGORITHM_VOC_INDEX_GAIN(230.); -static const float VOC_ALGORITHM_SRAW_STD_INITIAL(50.); -static const float VOC_ALGORITHM_SRAW_STD_BONUS(220.); -static const float VOC_ALGORITHM_TAU_MEAN_VARIANCE_HOURS(12.); -static const float VOC_ALGORITHM_TAU_INITIAL_MEAN(20.); -static const float VOC_ALGORITHM_INIT_DURATION_MEAN((3600. * 0.75)); -static const float VOC_ALGORITHM_INIT_TRANSITION_MEAN(0.01); -static const float VOC_ALGORITHM_TAU_INITIAL_VARIANCE(2500.); -static const float VOC_ALGORITHM_INIT_DURATION_VARIANCE((3600. * 1.45)); -static const float VOC_ALGORITHM_INIT_TRANSITION_VARIANCE(0.01); -static const float VOC_ALGORITHM_GATING_THRESHOLD(340.); -static const float VOC_ALGORITHM_GATING_THRESHOLD_INITIAL(510.); -static const float VOC_ALGORITHM_GATING_THRESHOLD_TRANSITION(0.09); -static const float VOC_ALGORITHM_GATING_MAX_DURATION_MINUTES((60. * 3.)); -static const float VOC_ALGORITHM_GATING_MAX_RATIO(0.3); -static const float VOC_ALGORITHM_SIGMOID_L(500.); -static const float VOC_ALGORITHM_SIGMOID_K(-0.0065); -static const float VOC_ALGORITHM_SIGMOID_X0(213.); -static const float VOC_ALGORITHM_VOC_INDEX_OFFSET_DEFAULT(100.); -static const float VOC_ALGORITHM_LP_TAU_FAST(20.0); -static const float VOC_ALGORITHM_LP_TAU_SLOW(500.0); -static const float VOC_ALGORITHM_LP_ALPHA(-0.2); -static const float VOC_ALGORITHM_PERSISTENCE_UPTIME_GAMMA((3. * 3600.)); -static const float VOC_ALGORITHM_MEAN_VARIANCE_ESTIMATOR_GAMMA_SCALING(64.); -static const float VOC_ALGORITHM_MEAN_VARIANCE_ESTIMATOR_FI_X16_MAX(32767.); - -/** - * Struct to hold all the states of the VOC algorithm. - */ -struct VocAlgorithmParams { - fix16_t mVoc_Index_Offset; - fix16_t mTau_Mean_Variance_Hours; - fix16_t mGating_Max_Duration_Minutes; - fix16_t mSraw_Std_Initial; - fix16_t mUptime; - fix16_t mSraw; - fix16_t mVoc_Index; - fix16_t m_Mean_Variance_Estimator_Gating_Max_Duration_Minutes; - bool m_Mean_Variance_Estimator_Initialized; - fix16_t m_Mean_Variance_Estimator_Mean; - fix16_t m_Mean_Variance_Estimator_Sraw_Offset; - fix16_t m_Mean_Variance_Estimator_Std; - fix16_t m_Mean_Variance_Estimator_Gamma; - fix16_t m_Mean_Variance_Estimator_Gamma_Initial_Mean; - fix16_t m_Mean_Variance_Estimator_Gamma_Initial_Variance; - fix16_t m_Mean_Variance_Estimator_Gamma_Mean; - fix16_t m_Mean_Variance_Estimator_Gamma_Variance; - fix16_t m_Mean_Variance_Estimator_Uptime_Gamma; - fix16_t m_Mean_Variance_Estimator_Uptime_Gating; - fix16_t m_Mean_Variance_Estimator_Gating_Duration_Minutes; - fix16_t m_Mean_Variance_Estimator_Sigmoid_L; - fix16_t m_Mean_Variance_Estimator_Sigmoid_K; - fix16_t m_Mean_Variance_Estimator_Sigmoid_X0; - fix16_t m_Mox_Model_Sraw_Std; - fix16_t m_Mox_Model_Sraw_Mean; - fix16_t m_Sigmoid_Scaled_Offset; - fix16_t m_Adaptive_Lowpass_A1; - fix16_t m_Adaptive_Lowpass_A2; - bool m_Adaptive_Lowpass_Initialized; - fix16_t m_Adaptive_Lowpass_X1; - fix16_t m_Adaptive_Lowpass_X2; - fix16_t m_Adaptive_Lowpass_X3; -}; - -/** - * Initialize the VOC algorithm parameters. Call this once at the beginning or - * whenever the sensor stopped measurements. - * @param params Pointer to the VocAlgorithmParams struct - */ -void voc_algorithm_init(VocAlgorithmParams *params); - -/** - * Get current algorithm states. Retrieved values can be used in - * voc_algorithm_set_states() to resume operation after a short interruption, - * skipping initial learning phase. This feature can only be used after at least - * 3 hours of continuous operation. - * @param params Pointer to the VocAlgorithmParams struct - * @param state0 State0 to be stored - * @param state1 State1 to be stored - */ -void voc_algorithm_get_states(VocAlgorithmParams *params, int32_t *state0, int32_t *state1); - -/** - * Set previously retrieved algorithm states to resume operation after a short - * interruption, skipping initial learning phase. This feature should not be - * used after inerruptions of more than 10 minutes. Call this once after - * voc_algorithm_init() and the optional voc_algorithm_set_tuning_parameters(), if - * desired. Otherwise, the algorithm will start with initial learning phase. - * @param params Pointer to the VocAlgorithmParams struct - * @param state0 State0 to be restored - * @param state1 State1 to be restored - */ -void voc_algorithm_set_states(VocAlgorithmParams *params, int32_t state0, int32_t state1); - -/** - * Set parameters to customize the VOC algorithm. Call this once after - * voc_algorithm_init(), if desired. Otherwise, the default values will be used. - * - * @param params Pointer to the VocAlgorithmParams struct - * @param voc_index_offset VOC index representing typical (average) - * conditions. Range 1..250, default 100 - * @param learning_time_hours Time constant of long-term estimator. - * Past events will be forgotten after about - * twice the learning time. - * Range 1..72 [hours], default 12 [hours] - * @param gating_max_duration_minutes Maximum duration of gating (freeze of - * estimator during high VOC index signal). - * 0 (no gating) or range 1..720 [minutes], - * default 180 [minutes] - * @param std_initial Initial estimate for standard deviation. - * Lower value boosts events during initial - * learning period, but may result in larger - * device-to-device variations. - * Range 10..500, default 50 - */ -void voc_algorithm_set_tuning_parameters(VocAlgorithmParams *params, int32_t voc_index_offset, - int32_t learning_time_hours, int32_t gating_max_duration_minutes, - int32_t std_initial); - -/** - * Calculate the VOC index value from the raw sensor value. - * - * @param params Pointer to the VocAlgorithmParams struct - * @param sraw Raw value from the SGP40 sensor - * @param voc_index Calculated VOC index value from the raw sensor value. Zero - * during initial blackout period and 1..500 afterwards - */ -void voc_algorithm_process(VocAlgorithmParams *params, int32_t sraw, int32_t *voc_index); -} // namespace sgp40 -} // namespace esphome diff --git a/esphome/components/sgp40/sensor.py b/esphome/components/sgp40/sensor.py index ee267d6062..cb4231c168 100644 --- a/esphome/components/sgp40/sensor.py +++ b/esphome/components/sgp40/sensor.py @@ -1,70 +1,8 @@ -import esphome.codegen as cg import esphome.config_validation as cv -from esphome.components import i2c, sensor, sensirion_common - -from esphome.const import ( - CONF_STORE_BASELINE, - CONF_TEMPERATURE_SOURCE, - ICON_RADIATOR, - DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS, - STATE_CLASS_MEASUREMENT, -) - -DEPENDENCIES = ["i2c"] -AUTO_LOAD = ["sensirion_common"] CODEOWNERS = ["@SenexCrenshaw"] -sgp40_ns = cg.esphome_ns.namespace("sgp40") -SGP40Component = sgp40_ns.class_( - "SGP40Component", - sensor.Sensor, - cg.PollingComponent, - sensirion_common.SensirionI2CDevice, +CONFIG_SCHEMA = CONFIG_SCHEMA = cv.invalid( + "SGP40 is deprecated.\nPlease use the SGP4x platform instead.\nSGP4x supports both SPG40 and SGP41.\n" + " See https://esphome.io/components/sensor/sgp4x.html" ) - -CONF_COMPENSATION = "compensation" -CONF_HUMIDITY_SOURCE = "humidity_source" -CONF_VOC_BASELINE = "voc_baseline" - -CONFIG_SCHEMA = ( - sensor.sensor_schema( - SGP40Component, - icon=ICON_RADIATOR, - accuracy_decimals=0, - device_class=DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS, - state_class=STATE_CLASS_MEASUREMENT, - ) - .extend( - { - cv.Optional(CONF_STORE_BASELINE, default=True): cv.boolean, - cv.Optional(CONF_VOC_BASELINE): cv.hex_uint16_t, - cv.Optional(CONF_COMPENSATION): cv.Schema( - { - cv.Required(CONF_HUMIDITY_SOURCE): cv.use_id(sensor.Sensor), - cv.Required(CONF_TEMPERATURE_SOURCE): cv.use_id(sensor.Sensor), - }, - ), - } - ) - .extend(cv.polling_component_schema("60s")) - .extend(i2c.i2c_device_schema(0x59)) -) - - -async def to_code(config): - var = await sensor.new_sensor(config) - await cg.register_component(var, config) - await i2c.register_i2c_device(var, config) - - if CONF_COMPENSATION in config: - compensation_config = config[CONF_COMPENSATION] - sens = await cg.get_variable(compensation_config[CONF_HUMIDITY_SOURCE]) - cg.add(var.set_humidity_sensor(sens)) - sens = await cg.get_variable(compensation_config[CONF_TEMPERATURE_SOURCE]) - cg.add(var.set_temperature_sensor(sens)) - - cg.add(var.set_store_baseline(config[CONF_STORE_BASELINE])) - - if CONF_VOC_BASELINE in config: - cg.add(var.set_voc_baseline(CONF_VOC_BASELINE)) diff --git a/esphome/components/sgp40/sgp40.cpp b/esphome/components/sgp40/sgp40.cpp deleted file mode 100644 index 9d78572b50..0000000000 --- a/esphome/components/sgp40/sgp40.cpp +++ /dev/null @@ -1,274 +0,0 @@ -#include "sgp40.h" -#include "esphome/core/log.h" -#include "esphome/core/hal.h" -#include - -namespace esphome { -namespace sgp40 { - -static const char *const TAG = "sgp40"; - -void SGP40Component::setup() { - ESP_LOGCONFIG(TAG, "Setting up SGP40..."); - - // Serial Number identification - if (!this->write_command(SGP40_CMD_GET_SERIAL_ID)) { - this->error_code_ = COMMUNICATION_FAILED; - this->mark_failed(); - return; - } - uint16_t raw_serial_number[3]; - - if (!this->read_data(raw_serial_number, 3)) { - this->mark_failed(); - return; - } - this->serial_number_ = (uint64_t(raw_serial_number[0]) << 24) | (uint64_t(raw_serial_number[1]) << 16) | - (uint64_t(raw_serial_number[2])); - ESP_LOGD(TAG, "Serial Number: %" PRIu64, this->serial_number_); - - // Featureset identification for future use - if (!this->write_command(SGP40_CMD_GET_FEATURESET)) { - ESP_LOGD(TAG, "raw_featureset write_command_ failed"); - this->mark_failed(); - return; - } - uint16_t raw_featureset; - if (!this->read_data(raw_featureset)) { - ESP_LOGD(TAG, "raw_featureset read_data_ failed"); - this->mark_failed(); - return; - } - - this->featureset_ = raw_featureset; - if ((this->featureset_ & 0x1FF) != SGP40_FEATURESET) { - ESP_LOGD(TAG, "Product feature set failed 0x%0X , expecting 0x%0X", uint16_t(this->featureset_ & 0x1FF), - SGP40_FEATURESET); - this->mark_failed(); - return; - } - - ESP_LOGD(TAG, "Product version: 0x%0X", uint16_t(this->featureset_ & 0x1FF)); - - voc_algorithm_init(&this->voc_algorithm_params_); - - if (this->store_baseline_) { - // Hash with compilation time - // This ensures the baseline storage is cleared after OTA - uint32_t hash = fnv1_hash(App.get_compilation_time()); - this->pref_ = global_preferences->make_preference(hash, true); - - if (this->pref_.load(&this->baselines_storage_)) { - this->state0_ = this->baselines_storage_.state0; - this->state1_ = this->baselines_storage_.state1; - ESP_LOGI(TAG, "Loaded VOC baseline state0: 0x%04X, state1: 0x%04X", this->baselines_storage_.state0, - baselines_storage_.state1); - } - - // Initialize storage timestamp - this->seconds_since_last_store_ = 0; - - if (this->baselines_storage_.state0 > 0 && this->baselines_storage_.state1 > 0) { - ESP_LOGI(TAG, "Setting VOC baseline from save state0: 0x%04X, state1: 0x%04X", this->baselines_storage_.state0, - baselines_storage_.state1); - voc_algorithm_set_states(&this->voc_algorithm_params_, this->baselines_storage_.state0, - this->baselines_storage_.state1); - } - } - - this->self_test_(); - - /* The official spec for this sensor at https://docs.rs-online.com/1956/A700000007055193.pdf - indicates this sensor should be driven at 1Hz. Comments from the developers at: - https://github.com/Sensirion/embedded-sgp/issues/136 indicate the algorithm should be a bit - resilient to slight timing variations so the software timer should be accurate enough for - this. - - This block starts sampling from the sensor at 1Hz, and is done seperately from the call - to the update method. This seperation is to support getting accurate measurements but - limit the amount of communication done over wifi for power consumption or to keep the - number of records reported from being overwhelming. - */ - ESP_LOGD(TAG, "Component requires sampling of 1Hz, setting up background sampler"); - this->set_interval(1000, [this]() { this->update_voc_index(); }); -} - -void SGP40Component::self_test_() { - ESP_LOGD(TAG, "Self-test started"); - if (!this->write_command(SGP40_CMD_SELF_TEST)) { - this->error_code_ = COMMUNICATION_FAILED; - ESP_LOGD(TAG, "Self-test communication failed"); - this->mark_failed(); - } - - this->set_timeout(250, [this]() { - uint16_t reply; - if (!this->read_data(reply)) { - ESP_LOGD(TAG, "Self-test read_data_ failed"); - this->mark_failed(); - return; - } - - if (reply == 0xD400) { - this->self_test_complete_ = true; - ESP_LOGD(TAG, "Self-test completed"); - return; - } - - ESP_LOGD(TAG, "Self-test failed"); - this->mark_failed(); - }); -} - -/** - * @brief Combined the measured gasses, temperature, and humidity - * to calculate the VOC Index - * - * @param temperature The measured temperature in degrees C - * @param humidity The measured relative humidity in % rH - * @return int32_t The VOC Index - */ -int32_t SGP40Component::measure_voc_index_() { - int32_t voc_index; - - uint16_t sraw = measure_raw_(); - - if (sraw == UINT16_MAX) - return UINT16_MAX; - - this->status_clear_warning(); - - voc_algorithm_process(&voc_algorithm_params_, sraw, &voc_index); - - // Store baselines after defined interval or if the difference between current and stored baseline becomes too - // much - if (this->store_baseline_ && this->seconds_since_last_store_ > SHORTEST_BASELINE_STORE_INTERVAL) { - voc_algorithm_get_states(&voc_algorithm_params_, &this->state0_, &this->state1_); - if ((uint32_t) abs(this->baselines_storage_.state0 - this->state0_) > MAXIMUM_STORAGE_DIFF || - (uint32_t) abs(this->baselines_storage_.state1 - this->state1_) > MAXIMUM_STORAGE_DIFF) { - this->seconds_since_last_store_ = 0; - this->baselines_storage_.state0 = this->state0_; - this->baselines_storage_.state1 = this->state1_; - - if (this->pref_.save(&this->baselines_storage_)) { - ESP_LOGI(TAG, "Stored VOC baseline state0: 0x%04X ,state1: 0x%04X", this->baselines_storage_.state0, - baselines_storage_.state1); - } else { - ESP_LOGW(TAG, "Could not store VOC baselines"); - } - } - } - - return voc_index; -} - -/** - * @brief Return the raw gas measurement - * - * @param temperature The measured temperature in degrees C - * @param humidity The measured relative humidity in % rH - * @return uint16_t The current raw gas measurement - */ -uint16_t SGP40Component::measure_raw_() { - float humidity = NAN; - - if (!this->self_test_complete_) { - ESP_LOGD(TAG, "Self-test not yet complete"); - return UINT16_MAX; - } - - if (this->humidity_sensor_ != nullptr) { - humidity = this->humidity_sensor_->state; - } - if (std::isnan(humidity) || humidity < 0.0f || humidity > 100.0f) { - humidity = 50; - } - - float temperature = NAN; - if (this->temperature_sensor_ != nullptr) { - temperature = float(this->temperature_sensor_->state); - } - if (std::isnan(temperature) || temperature < -40.0f || temperature > 85.0f) { - temperature = 25; - } - - uint16_t data[2]; - uint16_t rhticks = llround((uint16_t)((humidity * 65535) / 100)); - uint16_t tempticks = (uint16_t)(((temperature + 45) * 65535) / 175); - // first paramater is the relative humidity ticks - data[0] = rhticks; - // second paramater is the temperature ticks - data[1] = tempticks; - - if (!this->write_command(SGP40_CMD_MEASURE_RAW, data, 2)) { - this->status_set_warning(); - ESP_LOGD(TAG, "write error (%d)", this->last_error_); - return false; - } - delay(30); - - uint16_t raw_data; - if (!this->read_data(raw_data)) { - this->status_set_warning(); - ESP_LOGD(TAG, "read_data_ error"); - return UINT16_MAX; - } - return raw_data; -} - -void SGP40Component::update_voc_index() { - this->seconds_since_last_store_ += 1; - - this->voc_index_ = this->measure_voc_index_(); - if (this->samples_read_ < this->samples_to_stabalize_) { - this->samples_read_++; - ESP_LOGD(TAG, "Sensor has not collected enough samples yet. (%d/%d) VOC index is: %u", this->samples_read_, - this->samples_to_stabalize_, this->voc_index_); - return; - } -} - -void SGP40Component::update() { - if (this->samples_read_ < this->samples_to_stabalize_) { - return; - } - - if (this->voc_index_ != UINT16_MAX) { - this->status_clear_warning(); - this->publish_state(this->voc_index_); - } else { - this->status_set_warning(); - } -} - -void SGP40Component::dump_config() { - ESP_LOGCONFIG(TAG, "SGP40:"); - LOG_I2C_DEVICE(this); - ESP_LOGCONFIG(TAG, " store_baseline: %d", this->store_baseline_); - - if (this->is_failed()) { - switch (this->error_code_) { - case COMMUNICATION_FAILED: - ESP_LOGW(TAG, "Communication failed! Is the sensor connected?"); - break; - default: - ESP_LOGW(TAG, "Unknown setup error!"); - break; - } - } else { - ESP_LOGCONFIG(TAG, " Serial number: %" PRIu64, this->serial_number_); - ESP_LOGCONFIG(TAG, " Minimum Samples: %f", VOC_ALGORITHM_INITIAL_BLACKOUT); - } - LOG_UPDATE_INTERVAL(this); - - if (this->humidity_sensor_ != nullptr && this->temperature_sensor_ != nullptr) { - ESP_LOGCONFIG(TAG, " Compensation:"); - LOG_SENSOR(" ", "Temperature Source:", this->temperature_sensor_); - LOG_SENSOR(" ", "Humidity Source:", this->humidity_sensor_); - } else { - ESP_LOGCONFIG(TAG, " Compensation: No source configured"); - } -} - -} // namespace sgp40 -} // namespace esphome diff --git a/esphome/components/sgp40/sgp40.h b/esphome/components/sgp40/sgp40.h deleted file mode 100644 index c5b7d2dfa0..0000000000 --- a/esphome/components/sgp40/sgp40.h +++ /dev/null @@ -1,93 +0,0 @@ -#pragma once - -#include "esphome/core/component.h" -#include "esphome/components/sensor/sensor.h" -#include "esphome/components/sensirion_common/i2c_sensirion.h" -#include "esphome/core/application.h" -#include "esphome/core/preferences.h" -#include "sensirion_voc_algorithm.h" - -#include - -namespace esphome { -namespace sgp40 { - -struct SGP40Baselines { - int32_t state0; - int32_t state1; -} PACKED; // NOLINT - -// commands and constants -static const uint8_t SGP40_FEATURESET = 0x0020; ///< The required set for this library -static const uint8_t SGP40_CRC8_POLYNOMIAL = 0x31; ///< Seed for SGP40's CRC polynomial -static const uint8_t SGP40_CRC8_INIT = 0xFF; ///< Init value for CRC -static const uint8_t SGP40_WORD_LEN = 2; ///< 2 bytes per word - -// Commands - -static const uint16_t SGP40_CMD_GET_SERIAL_ID = 0x3682; -static const uint16_t SGP40_CMD_GET_FEATURESET = 0x202f; -static const uint16_t SGP40_CMD_SELF_TEST = 0x280e; -static const uint16_t SGP40_CMD_MEASURE_RAW = 0x260F; - -// Shortest time interval of 3H for storing baseline values. -// Prevents wear of the flash because of too many write operations -const uint32_t SHORTEST_BASELINE_STORE_INTERVAL = 10800; - -// Store anyway if the baseline difference exceeds the max storage diff value -const uint32_t MAXIMUM_STORAGE_DIFF = 50; - -class SGP40Component; - -/// This class implements support for the Sensirion sgp40 i2c GAS (VOC) sensors. -class SGP40Component : public PollingComponent, public sensor::Sensor, public sensirion_common::SensirionI2CDevice { - public: - void set_humidity_sensor(sensor::Sensor *humidity) { humidity_sensor_ = humidity; } - void set_temperature_sensor(sensor::Sensor *temperature) { temperature_sensor_ = temperature; } - - void setup() override; - void update() override; - void update_voc_index(); - void dump_config() override; - float get_setup_priority() const override { return setup_priority::DATA; } - void set_store_baseline(bool store_baseline) { store_baseline_ = store_baseline; } - - protected: - /// Input sensor for humidity and temperature compensation. - sensor::Sensor *humidity_sensor_{nullptr}; - sensor::Sensor *temperature_sensor_{nullptr}; - int16_t sensirion_init_sensors_(); - int16_t sgp40_probe_(); - uint64_t serial_number_; - uint16_t featureset_; - int32_t measure_voc_index_(); - uint8_t generate_crc_(const uint8_t *data, uint8_t datalen); - uint16_t measure_raw_(); - ESPPreferenceObject pref_; - uint32_t seconds_since_last_store_; - SGP40Baselines baselines_storage_; - VocAlgorithmParams voc_algorithm_params_; - bool self_test_complete_; - bool store_baseline_; - int32_t state0_; - int32_t state1_; - int32_t voc_index_ = 0; - uint8_t samples_read_ = 0; - uint8_t samples_to_stabalize_ = static_cast(VOC_ALGORITHM_INITIAL_BLACKOUT) * 2; - - /** - * @brief Request the sensor to perform a self-test, returning the result - * - * @return true: success false:failure - */ - void self_test_(); - enum ErrorCode { - COMMUNICATION_FAILED, - MEASUREMENT_INIT_FAILED, - INVALID_ID, - UNSUPPORTED_ID, - UNKNOWN - } error_code_{UNKNOWN}; -}; -} // namespace sgp40 -} // namespace esphome diff --git a/esphome/components/sgp4x/__init__.py b/esphome/components/sgp4x/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esphome/components/sgp4x/sensor.py b/esphome/components/sgp4x/sensor.py new file mode 100644 index 0000000000..4855d7f066 --- /dev/null +++ b/esphome/components/sgp4x/sensor.py @@ -0,0 +1,144 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import i2c, sensor, sensirion_common +from esphome.const import ( + CONF_ID, + CONF_STORE_BASELINE, + CONF_TEMPERATURE_SOURCE, + ICON_RADIATOR, + DEVICE_CLASS_NITROUS_OXIDE, + DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS, + STATE_CLASS_MEASUREMENT, +) + +DEPENDENCIES = ["i2c"] +AUTO_LOAD = ["sensirion_common"] +CODEOWNERS = ["@SenexCrenshaw", "@martgras"] + +sgp4x_ns = cg.esphome_ns.namespace("sgp4x") +SGP4xComponent = sgp4x_ns.class_( + "SGP4xComponent", + sensor.Sensor, + cg.PollingComponent, + sensirion_common.SensirionI2CDevice, +) + +CONF_ALGORITHM_TUNING = "algorithm_tuning" +CONF_COMPENSATION = "compensation" +CONF_GAIN_FACTOR = "gain_factor" +CONF_GATING_MAX_DURATION_MINUTES = "gating_max_duration_minutes" +CONF_HUMIDITY_SOURCE = "humidity_source" +CONF_INDEX_OFFSET = "index_offset" +CONF_LEARNING_TIME_GAIN_HOURS = "learning_time_gain_hours" +CONF_LEARNING_TIME_OFFSET_HOURS = "learning_time_offset_hours" +CONF_NOX = "nox" +CONF_STD_INITIAL = "std_initial" +CONF_VOC = "voc" +CONF_VOC_BASELINE = "voc_baseline" + + +def validate_sensors(config): + if CONF_VOC not in config and CONF_NOX not in config: + raise cv.Invalid( + f"At least one sensor is required. Define {CONF_VOC} and/or {CONF_NOX}" + ) + return config + + +GAS_SENSOR = cv.Schema( + { + cv.Optional(CONF_ALGORITHM_TUNING): cv.Schema( + { + cv.Optional(CONF_INDEX_OFFSET, default=100): cv.int_, + cv.Optional(CONF_LEARNING_TIME_OFFSET_HOURS, default=12): cv.int_, + cv.Optional(CONF_LEARNING_TIME_GAIN_HOURS, default=12): cv.int_, + cv.Optional(CONF_GATING_MAX_DURATION_MINUTES, default=720): cv.int_, + cv.Optional(CONF_STD_INITIAL, default=50): cv.int_, + cv.Optional(CONF_GAIN_FACTOR, default=230): cv.int_, + } + ) + } +) + +CONFIG_SCHEMA = cv.All( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(SGP4xComponent), + cv.Optional(CONF_VOC): sensor.sensor_schema( + icon=ICON_RADIATOR, + accuracy_decimals=0, + device_class=DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS, + state_class=STATE_CLASS_MEASUREMENT, + ).extend(GAS_SENSOR), + cv.Optional(CONF_NOX): sensor.sensor_schema( + icon=ICON_RADIATOR, + accuracy_decimals=0, + device_class=DEVICE_CLASS_NITROUS_OXIDE, + state_class=STATE_CLASS_MEASUREMENT, + ).extend(GAS_SENSOR), + cv.Optional(CONF_STORE_BASELINE, default=True): cv.boolean, + cv.Optional(CONF_VOC_BASELINE): cv.hex_uint16_t, + cv.Optional(CONF_COMPENSATION): cv.Schema( + { + cv.Required(CONF_HUMIDITY_SOURCE): cv.use_id(sensor.Sensor), + cv.Required(CONF_TEMPERATURE_SOURCE): cv.use_id(sensor.Sensor), + }, + ), + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x59)), + validate_sensors, +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await i2c.register_i2c_device(var, config) + + if CONF_COMPENSATION in config: + compensation_config = config[CONF_COMPENSATION] + sens = await cg.get_variable(compensation_config[CONF_HUMIDITY_SOURCE]) + cg.add(var.set_humidity_sensor(sens)) + sens = await cg.get_variable(compensation_config[CONF_TEMPERATURE_SOURCE]) + cg.add(var.set_temperature_sensor(sens)) + + cg.add(var.set_store_baseline(config[CONF_STORE_BASELINE])) + + if CONF_VOC_BASELINE in config: + cg.add(var.set_voc_baseline(CONF_VOC_BASELINE)) + + if CONF_VOC in config: + sens = await sensor.new_sensor(config[CONF_VOC]) + cg.add(var.set_voc_sensor(sens)) + if CONF_ALGORITHM_TUNING in config[CONF_VOC]: + cfg = config[CONF_VOC][CONF_ALGORITHM_TUNING] + cg.add( + var.set_voc_algorithm_tuning( + cfg[CONF_INDEX_OFFSET], + cfg[CONF_LEARNING_TIME_OFFSET_HOURS], + cfg[CONF_LEARNING_TIME_GAIN_HOURS], + cfg[CONF_GATING_MAX_DURATION_MINUTES], + cfg[CONF_STD_INITIAL], + cfg[CONF_GAIN_FACTOR], + ) + ) + + if CONF_NOX in config: + sens = await sensor.new_sensor(config[CONF_NOX]) + cg.add(var.set_nox_sensor(sens)) + if CONF_ALGORITHM_TUNING in config[CONF_NOX]: + cfg = config[CONF_NOX][CONF_ALGORITHM_TUNING] + cg.add( + var.set_nox_algorithm_tuning( + cfg[CONF_INDEX_OFFSET], + cfg[CONF_LEARNING_TIME_OFFSET_HOURS], + cfg[CONF_LEARNING_TIME_GAIN_HOURS], + cfg[CONF_GATING_MAX_DURATION_MINUTES], + cfg[CONF_GAIN_FACTOR], + ) + ) + cg.add_library( + None, None, "https://github.com/Sensirion/arduino-gas-index-algorithm.git" + ) diff --git a/esphome/components/sgp4x/sgp4x.cpp b/esphome/components/sgp4x/sgp4x.cpp new file mode 100644 index 0000000000..a6f57e0342 --- /dev/null +++ b/esphome/components/sgp4x/sgp4x.cpp @@ -0,0 +1,343 @@ +#include "sgp4x.h" +#include "esphome/core/log.h" +#include "esphome/core/hal.h" +#include + +namespace esphome { +namespace sgp4x { + +static const char *const TAG = "sgp4x"; + +void SGP4xComponent::setup() { + ESP_LOGCONFIG(TAG, "Setting up SGP4x..."); + + // Serial Number identification + uint16_t raw_serial_number[3]; + if (!this->get_register(SGP4X_CMD_GET_SERIAL_ID, raw_serial_number, 3, 1)) { + ESP_LOGE(TAG, "Failed to read serial number"); + this->error_code_ = SERIAL_NUMBER_IDENTIFICATION_FAILED; + this->mark_failed(); + return; + } + this->serial_number_ = (uint64_t(raw_serial_number[0]) << 24) | (uint64_t(raw_serial_number[1]) << 16) | + (uint64_t(raw_serial_number[2])); + ESP_LOGD(TAG, "Serial Number: %" PRIu64, this->serial_number_); + + // Featureset identification for future use + uint16_t raw_featureset; + if (!this->get_register(SGP4X_CMD_GET_FEATURESET, raw_featureset, 1)) { + ESP_LOGD(TAG, "raw_featureset write_command_ failed"); + this->mark_failed(); + return; + } + this->featureset_ = raw_featureset; + if ((this->featureset_ & 0x1FF) == SGP40_FEATURESET) { + sgp_type_ = SGP40; + self_test_time_ = SPG40_SELFTEST_TIME; + measure_time_ = SGP40_MEASURE_TIME; + if (this->nox_sensor_) { + ESP_LOGE(TAG, "Measuring NOx requires a SGP41 sensor but a SGP40 sensor is detected"); + // disable the sensor + this->nox_sensor_->set_disabled_by_default(true); + // make sure it's not visiable in HA + this->nox_sensor_->set_internal(true); + this->nox_sensor_->state = NAN; + // remove pointer to sensor + this->nox_sensor_ = nullptr; + } + } else { + if ((this->featureset_ & 0x1FF) == SGP41_FEATURESET) { + sgp_type_ = SGP41; + self_test_time_ = SPG41_SELFTEST_TIME; + measure_time_ = SGP41_MEASURE_TIME; + } else { + ESP_LOGD(TAG, "Product feature set failed 0x%0X , expecting 0x%0X", uint16_t(this->featureset_ & 0x1FF), + SGP40_FEATURESET); + this->mark_failed(); + return; + } + } + + ESP_LOGD(TAG, "Product version: 0x%0X", uint16_t(this->featureset_ & 0x1FF)); + + if (this->store_baseline_) { + // Hash with compilation time + // This ensures the baseline storage is cleared after OTA + uint32_t hash = fnv1_hash(App.get_compilation_time()); + this->pref_ = global_preferences->make_preference(hash, true); + + if (this->pref_.load(&this->voc_baselines_storage_)) { + this->voc_state0_ = this->voc_baselines_storage_.state0; + this->voc_state1_ = this->voc_baselines_storage_.state1; + ESP_LOGI(TAG, "Loaded VOC baseline state0: 0x%04X, state1: 0x%04X", this->voc_baselines_storage_.state0, + voc_baselines_storage_.state1); + } + + // Initialize storage timestamp + this->seconds_since_last_store_ = 0; + + if (this->voc_baselines_storage_.state0 > 0 && this->voc_baselines_storage_.state1 > 0) { + ESP_LOGI(TAG, "Setting VOC baseline from save state0: 0x%04X, state1: 0x%04X", + this->voc_baselines_storage_.state0, voc_baselines_storage_.state1); + voc_algorithm_.set_states(this->voc_baselines_storage_.state0, this->voc_baselines_storage_.state1); + } + } + if (this->voc_sensor_ && this->voc_tuning_params_.has_value()) { + voc_algorithm_.set_tuning_parameters( + voc_tuning_params_.value().index_offset, voc_tuning_params_.value().learning_time_offset_hours, + voc_tuning_params_.value().learning_time_gain_hours, voc_tuning_params_.value().gating_max_duration_minutes, + voc_tuning_params_.value().std_initial, voc_tuning_params_.value().gain_factor); + } + + if (this->nox_sensor_ && this->nox_tuning_params_.has_value()) { + nox_algorithm_.set_tuning_parameters( + nox_tuning_params_.value().index_offset, nox_tuning_params_.value().learning_time_offset_hours, + nox_tuning_params_.value().learning_time_gain_hours, nox_tuning_params_.value().gating_max_duration_minutes, + nox_tuning_params_.value().std_initial, nox_tuning_params_.value().gain_factor); + } + + this->self_test_(); + + /* The official spec for this sensor at + https://sensirion.com/media/documents/296373BB/6203C5DF/Sensirion_Gas_Sensors_Datasheet_SGP40.pdf indicates this + sensor should be driven at 1Hz. Comments from the developers at: + https://github.com/Sensirion/embedded-sgp/issues/136 indicate the algorithm should be a bit resilient to slight + timing variations so the software timer should be accurate enough for this. + + This block starts sampling from the sensor at 1Hz, and is done seperately from the call + to the update method. This seperation is to support getting accurate measurements but + limit the amount of communication done over wifi for power consumption or to keep the + number of records reported from being overwhelming. + */ + ESP_LOGD(TAG, "Component requires sampling of 1Hz, setting up background sampler"); + this->set_interval(1000, [this]() { this->update_gas_indices(); }); +} + +void SGP4xComponent::self_test_() { + ESP_LOGD(TAG, "Self-test started"); + if (!this->write_command(SGP4X_CMD_SELF_TEST)) { + this->error_code_ = COMMUNICATION_FAILED; + ESP_LOGD(TAG, "Self-test communication failed"); + this->mark_failed(); + } + + this->set_timeout(self_test_time_, [this]() { + uint16_t reply; + if (!this->read_data(reply)) { + this->error_code_ = SELF_TEST_FAILED; + ESP_LOGD(TAG, "Self-test read_data_ failed"); + this->mark_failed(); + return; + } + + if (reply == 0xD400) { + this->self_test_complete_ = true; + ESP_LOGD(TAG, "Self-test completed"); + return; + } else { + this->error_code_ = SELF_TEST_FAILED; + ESP_LOGD(TAG, "Self-test failed 0x%X", reply); + return; + } + + ESP_LOGD(TAG, "Self-test failed 0x%X", reply); + this->mark_failed(); + }); +} + +/** + * @brief Combined the measured gasses, temperature, and humidity + * to calculate the VOC Index + * + * @param temperature The measured temperature in degrees C + * @param humidity The measured relative humidity in % rH + * @return int32_t The VOC Index + */ +bool SGP4xComponent::measure_gas_indices_(int32_t &voc, int32_t &nox) { + uint16_t voc_sraw; + uint16_t nox_sraw; + if (!measure_raw_(voc_sraw, nox_sraw)) + return false; + + this->status_clear_warning(); + + voc = voc_algorithm_.process(voc_sraw); + if (nox_sensor_) { + nox = nox_algorithm_.process(nox_sraw); + } + ESP_LOGV(TAG, "VOC = %d, NOx = %d", voc, nox); + // Store baselines after defined interval or if the difference between current and stored baseline becomes too + // much + if (this->store_baseline_ && this->seconds_since_last_store_ > SHORTEST_BASELINE_STORE_INTERVAL) { + voc_algorithm_.get_states(this->voc_state0_, this->voc_state1_); + if ((uint32_t) abs(this->voc_baselines_storage_.state0 - this->voc_state0_) > MAXIMUM_STORAGE_DIFF || + (uint32_t) abs(this->voc_baselines_storage_.state1 - this->voc_state1_) > MAXIMUM_STORAGE_DIFF) { + this->seconds_since_last_store_ = 0; + this->voc_baselines_storage_.state0 = this->voc_state0_; + this->voc_baselines_storage_.state1 = this->voc_state1_; + + if (this->pref_.save(&this->voc_baselines_storage_)) { + ESP_LOGI(TAG, "Stored VOC baseline state0: 0x%04X ,state1: 0x%04X", this->voc_baselines_storage_.state0, + voc_baselines_storage_.state1); + } else { + ESP_LOGW(TAG, "Could not store VOC baselines"); + } + } + } + + return true; +} +/** + * @brief Return the raw gas measurement + * + * @param temperature The measured temperature in degrees C + * @param humidity The measured relative humidity in % rH + * @return uint16_t The current raw gas measurement + */ +bool SGP4xComponent::measure_raw_(uint16_t &voc_raw, uint16_t &nox_raw) { + float humidity = NAN; + static uint32_t nox_conditioning_start = millis(); + + if (!this->self_test_complete_) { + ESP_LOGD(TAG, "Self-test not yet complete"); + return false; + } + if (this->humidity_sensor_ != nullptr) { + humidity = this->humidity_sensor_->state; + } + if (std::isnan(humidity) || humidity < 0.0f || humidity > 100.0f) { + humidity = 50; + } + + float temperature = NAN; + if (this->temperature_sensor_ != nullptr) { + temperature = float(this->temperature_sensor_->state); + } + if (std::isnan(temperature) || temperature < -40.0f || temperature > 85.0f) { + temperature = 25; + } + + uint16_t command; + uint16_t data[2]; + size_t response_words; + // Use SGP40 measure command if we don't care about NOx + if (nox_sensor_ == nullptr) { + command = SGP40_CMD_MEASURE_RAW; + response_words = 1; + } else { + // SGP41 sensor must use NOx conditioning command for the first 10 seconds + if (millis() - nox_conditioning_start < 10000) { + command = SGP41_CMD_NOX_CONDITIONING; + response_words = 1; + } else { + command = SGP41_CMD_MEASURE_RAW; + response_words = 2; + } + } + uint16_t rhticks = llround((uint16_t)((humidity * 65535) / 100)); + uint16_t tempticks = (uint16_t)(((temperature + 45) * 65535) / 175); + // first paramater are the relative humidity ticks + data[0] = rhticks; + // secomd paramater are the temperature ticks + data[1] = tempticks; + + if (!this->write_command(command, data, 2)) { + this->status_set_warning(); + ESP_LOGD(TAG, "write error (%d)", this->last_error_); + return false; + } + delay(measure_time_); + uint16_t raw_data[2]; + raw_data[1] = 0; + if (!this->read_data(raw_data, response_words)) { + this->status_set_warning(); + ESP_LOGD(TAG, "read error (%d)", this->last_error_); + return false; + } + voc_raw = raw_data[0]; + nox_raw = raw_data[1]; // either 0 or the measured NOx ticks + return true; +} + +void SGP4xComponent::update_gas_indices() { + if (!this->self_test_complete_) + return; + + this->seconds_since_last_store_ += 1; + if (!this->measure_gas_indices_(this->voc_index_, this->nox_index_)) { + // Set values to UINT16_MAX to indicate failure + this->voc_index_ = this->nox_index_ = UINT16_MAX; + ESP_LOGE(TAG, "measure gas indices failed"); + return; + } + if (this->samples_read_ < this->samples_to_stabilize_) { + this->samples_read_++; + ESP_LOGD(TAG, "Sensor has not collected enough samples yet. (%d/%d) VOC index is: %u", this->samples_read_, + this->samples_to_stabilize_, this->voc_index_); + return; + } +} + +void SGP4xComponent::update() { + if (this->samples_read_ < this->samples_to_stabilize_) { + return; + } + if (this->voc_sensor_) { + if (this->voc_index_ != UINT16_MAX) { + this->status_clear_warning(); + this->voc_sensor_->publish_state(this->voc_index_); + } else { + this->status_set_warning(); + } + } + if (this->nox_sensor_) { + if (this->nox_index_ != UINT16_MAX) { + this->status_clear_warning(); + this->nox_sensor_->publish_state(this->nox_index_); + } else { + this->status_set_warning(); + } + } +} + +void SGP4xComponent::dump_config() { + ESP_LOGCONFIG(TAG, "SGP4x:"); + LOG_I2C_DEVICE(this); + ESP_LOGCONFIG(TAG, " store_baseline: %d", this->store_baseline_); + + if (this->is_failed()) { + switch (this->error_code_) { + case COMMUNICATION_FAILED: + ESP_LOGW(TAG, "Communication failed! Is the sensor connected?"); + break; + case SERIAL_NUMBER_IDENTIFICATION_FAILED: + ESP_LOGW(TAG, "Get Serial number failed."); + break; + case SELF_TEST_FAILED: + ESP_LOGW(TAG, "Self test failed."); + break; + + default: + ESP_LOGW(TAG, "Unknown setup error!"); + break; + } + } else { + ESP_LOGCONFIG(TAG, " Type: %s", sgp_type_ == SGP41 ? "SGP41" : "SPG40"); + ESP_LOGCONFIG(TAG, " Serial number: %" PRIu64, this->serial_number_); + ESP_LOGCONFIG(TAG, " Minimum Samples: %f", GasIndexAlgorithm_INITIAL_BLACKOUT); + } + LOG_UPDATE_INTERVAL(this); + + if (this->humidity_sensor_ != nullptr && this->temperature_sensor_ != nullptr) { + ESP_LOGCONFIG(TAG, " Compensation:"); + LOG_SENSOR(" ", "Temperature Source:", this->temperature_sensor_); + LOG_SENSOR(" ", "Humidity Source:", this->humidity_sensor_); + } else { + ESP_LOGCONFIG(TAG, " Compensation: No source configured"); + } + LOG_SENSOR(" ", "VOC", this->voc_sensor_); + LOG_SENSOR(" ", "NOx", this->nox_sensor_); +} + +} // namespace sgp4x +} // namespace esphome diff --git a/esphome/components/sgp4x/sgp4x.h b/esphome/components/sgp4x/sgp4x.h new file mode 100644 index 0000000000..3060972fc3 --- /dev/null +++ b/esphome/components/sgp4x/sgp4x.h @@ -0,0 +1,142 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/sensirion_common/i2c_sensirion.h" +#include "esphome/core/application.h" +#include "esphome/core/preferences.h" +#include +#include + +#include + +namespace esphome { +namespace sgp4x { + +struct SGP4xBaselines { + int32_t state0; + int32_t state1; +} PACKED; // NOLINT + +enum SgpType { SGP40, SGP41 }; + +struct GasTuning { + uint16_t index_offset; + uint16_t learning_time_offset_hours; + uint16_t learning_time_gain_hours; + uint16_t gating_max_duration_minutes; + uint16_t std_initial; + uint16_t gain_factor; +}; + +// commands and constants +static const uint8_t SGP40_FEATURESET = 0x0020; // can measure VOC +static const uint8_t SGP41_FEATURESET = 0x0040; // can measure VOC and NOX +// Commands +static const uint16_t SGP4X_CMD_GET_SERIAL_ID = 0x3682; +static const uint16_t SGP4X_CMD_GET_FEATURESET = 0x202f; +static const uint16_t SGP4X_CMD_SELF_TEST = 0x280e; +static const uint16_t SGP40_CMD_MEASURE_RAW = 0x260F; +static const uint16_t SGP41_CMD_MEASURE_RAW = 0x2619; +static const uint16_t SGP41_CMD_NOX_CONDITIONING = 0x2612; +static const uint8_t SGP41_SUBCMD_NOX_CONDITIONING = 0x12; + +// Shortest time interval of 3H for storing baseline values. +// Prevents wear of the flash because of too many write operations +const uint32_t SHORTEST_BASELINE_STORE_INTERVAL = 10800; +static const uint16_t SPG40_SELFTEST_TIME = 250; // 250 ms for self test +static const uint16_t SPG41_SELFTEST_TIME = 320; // 320 ms for self test +static const uint16_t SGP40_MEASURE_TIME = 30; +static const uint16_t SGP41_MEASURE_TIME = 55; +// Store anyway if the baseline difference exceeds the max storage diff value +const uint32_t MAXIMUM_STORAGE_DIFF = 50; + +class SGP4xComponent; + +/// This class implements support for the Sensirion sgp4x i2c GAS (VOC) sensors. +class SGP4xComponent : public PollingComponent, public sensor::Sensor, public sensirion_common::SensirionI2CDevice { + enum ErrorCode { + COMMUNICATION_FAILED, + MEASUREMENT_INIT_FAILED, + INVALID_ID, + UNSUPPORTED_ID, + SERIAL_NUMBER_IDENTIFICATION_FAILED, + SELF_TEST_FAILED, + UNKNOWN + } error_code_{UNKNOWN}; + + public: + // SGP4xComponent() {}; + void set_humidity_sensor(sensor::Sensor *humidity) { humidity_sensor_ = humidity; } + void set_temperature_sensor(sensor::Sensor *temperature) { temperature_sensor_ = temperature; } + + void setup() override; + void update() override; + void update_gas_indices(); + void dump_config() override; + float get_setup_priority() const override { return setup_priority::DATA; } + void set_store_baseline(bool store_baseline) { store_baseline_ = store_baseline; } + void set_voc_sensor(sensor::Sensor *voc_sensor) { voc_sensor_ = voc_sensor; } + void set_nox_sensor(sensor::Sensor *nox_sensor) { nox_sensor_ = nox_sensor; } + void set_voc_algorithm_tuning(uint16_t index_offset, uint16_t learning_time_offset_hours, + uint16_t learning_time_gain_hours, uint16_t gating_max_duration_minutes, + uint16_t std_initial, uint16_t gain_factor) { + voc_tuning_params_.value().index_offset = index_offset; + voc_tuning_params_.value().learning_time_offset_hours = learning_time_offset_hours; + voc_tuning_params_.value().learning_time_gain_hours = learning_time_gain_hours; + voc_tuning_params_.value().gating_max_duration_minutes = gating_max_duration_minutes; + voc_tuning_params_.value().std_initial = std_initial; + voc_tuning_params_.value().gain_factor = gain_factor; + } + void set_nox_algorithm_tuning(uint16_t index_offset, uint16_t learning_time_offset_hours, + uint16_t learning_time_gain_hours, uint16_t gating_max_duration_minutes, + uint16_t gain_factor) { + nox_tuning_params_.value().index_offset = index_offset; + nox_tuning_params_.value().learning_time_offset_hours = learning_time_offset_hours; + nox_tuning_params_.value().learning_time_gain_hours = learning_time_gain_hours; + nox_tuning_params_.value().gating_max_duration_minutes = gating_max_duration_minutes; + nox_tuning_params_.value().std_initial = 50; + nox_tuning_params_.value().gain_factor = gain_factor; + } + + protected: + void self_test_(); + + /// Input sensor for humidity and temperature compensation. + sensor::Sensor *humidity_sensor_{nullptr}; + sensor::Sensor *temperature_sensor_{nullptr}; + int16_t sensirion_init_sensors_(); + + bool measure_gas_indices_(int32_t &voc, int32_t &nox); + bool measure_raw_(uint16_t &voc_raw, uint16_t &nox_raw); + + SgpType sgp_type_{SGP40}; + uint64_t serial_number_; + uint16_t featureset_; + + bool self_test_complete_; + uint16_t self_test_time_; + + sensor::Sensor *voc_sensor_{nullptr}; + VOCGasIndexAlgorithm voc_algorithm_; + optional voc_tuning_params_; + int32_t voc_state0_; + int32_t voc_state1_; + int32_t voc_index_ = 0; + + sensor::Sensor *nox_sensor_{nullptr}; + int32_t nox_index_ = 0; + NOxGasIndexAlgorithm nox_algorithm_; + optional nox_tuning_params_; + + uint16_t measure_time_; + uint8_t samples_read_ = 0; + uint8_t samples_to_stabilize_ = static_cast(GasIndexAlgorithm_INITIAL_BLACKOUT) * 2; + + bool store_baseline_; + ESPPreferenceObject pref_; + uint32_t seconds_since_last_store_; + SGP4xBaselines voc_baselines_storage_; +}; +} // namespace sgp4x +} // namespace esphome diff --git a/platformio.ini b/platformio.ini index bc2cddb9f7..82cf6eeb9a 100644 --- a/platformio.ini +++ b/platformio.ini @@ -39,6 +39,8 @@ lib_deps = bblanchon/ArduinoJson@6.18.5 ; json wjtje/qr-code-generator-library@1.7.0 ; qr_code functionpointer/arduino-MLX90393@1.0.0 ; mlx90393 + ; This is using the repository until a new release is published to PlatformIO + https://github.com/Sensirion/arduino-gas-index-algorithm.git ; Sensirion Gas Index Algorithm Arduino Library build_flags = -DESPHOME_LOG_LEVEL=ESPHOME_LOG_LEVEL_VERY_VERBOSE src_filter = diff --git a/tests/test2.yaml b/tests/test2.yaml index a7a9ef9661..f88486524f 100644 --- a/tests/test2.yaml +++ b/tests/test2.yaml @@ -281,10 +281,27 @@ sensor: window_correction_factor: 1.0 address: 0x53 update_interval: 60s - - platform: sgp40 - name: 'Workshop VOC' + - platform: sgp4x + voc: + name: "VOC Index" + id: sgp40_voc_index + algorithm_tuning: + index_offset: 100 + learning_time_offset_hours: 12 + learning_time_gain_hours: 12 + gating_max_duration_minutes: 180 + std_initial: 50 + gain_factor: 230 + nox: + name: "NOx" + algorithm_tuning: + index_offset: 100 + learning_time_offset_hours: 12 + learning_time_gain_hours: 12 + gating_max_duration_minutes: 180 + std_initial: 50 + gain_factor: 230 update_interval: 5s - store_baseline: 'true' - platform: mcp3008 update_interval: 5s mcp3008_id: 'mcp3008_hub' From 4f52d43347c6b87529238d737473bed974b5705e Mon Sep 17 00:00:00 2001 From: gazoodle <47351872+gazoodle@users.noreply.github.com> Date: Thu, 19 May 2022 01:49:12 +0100 Subject: [PATCH 0319/3388] add support user-defined modbus functions (#3461) --- esphome/components/modbus/modbus.cpp | 67 ++++++++++++++++++---------- 1 file changed, 44 insertions(+), 23 deletions(-) diff --git a/esphome/components/modbus/modbus.cpp b/esphome/components/modbus/modbus.cpp index 60ce50097c..19b5e8019e 100644 --- a/esphome/components/modbus/modbus.cpp +++ b/esphome/components/modbus/modbus.cpp @@ -68,33 +68,54 @@ bool Modbus::parse_modbus_byte_(uint8_t byte) { uint8_t data_len = raw[2]; uint8_t data_offset = 3; - // the response for write command mirrors the requests and data startes at offset 2 instead of 3 for read commands - if (function_code == 0x5 || function_code == 0x06 || function_code == 0xF || function_code == 0x10) { - data_offset = 2; - data_len = 4; - } - // Error ( msb indicates error ) - // response format: Byte[0] = device address, Byte[1] function code | 0x80 , Byte[2] excpetion code, Byte[3-4] crc - if ((function_code & 0x80) == 0x80) { - data_offset = 2; - data_len = 1; - } + // Per https://modbus.org/docs/Modbus_Application_Protocol_V1_1b3.pdf Ch 5 User-Defined function codes + if (((function_code >= 65) && (function_code <= 72)) || ((function_code >= 100) && (function_code <= 110))) { + // Handle user-defined function, since we don't know how big this ought to be, + // ideally we should delegate the entire length detection to whatever handler is + // installed, but wait, there is the CRC, and if we get a hit there is a good + // chance that this is a complete message ... admittedly there is a small chance is + // isn't but that is quite small given the purpose of the CRC in the first place + data_len = at; + data_offset = 1; - // Byte data_offset..data_offset+data_len-1: Data - if (at < data_offset + data_len) - return true; + uint16_t computed_crc = crc16(raw, data_offset + data_len); + uint16_t remote_crc = uint16_t(raw[data_offset + data_len]) | (uint16_t(raw[data_offset + data_len + 1]) << 8); - // Byte 3+data_len: CRC_LO (over all bytes) - if (at == data_offset + data_len) - return true; + if (computed_crc != remote_crc) + return true; - // Byte data_offset+len+1: CRC_HI (over all bytes) - uint16_t computed_crc = crc16(raw, data_offset + data_len); - uint16_t remote_crc = uint16_t(raw[data_offset + data_len]) | (uint16_t(raw[data_offset + data_len + 1]) << 8); - if (computed_crc != remote_crc) { - ESP_LOGW(TAG, "Modbus CRC Check failed! %02X!=%02X", computed_crc, remote_crc); - return false; + ESP_LOGD(TAG, "Modbus user-defined function %02X found", function_code); + + } else { + // the response for write command mirrors the requests and data startes at offset 2 instead of 3 for read commands + if (function_code == 0x5 || function_code == 0x06 || function_code == 0xF || function_code == 0x10) { + data_offset = 2; + data_len = 4; + } + + // Error ( msb indicates error ) + // response format: Byte[0] = device address, Byte[1] function code | 0x80 , Byte[2] excpetion code, Byte[3-4] crc + if ((function_code & 0x80) == 0x80) { + data_offset = 2; + data_len = 1; + } + + // Byte data_offset..data_offset+data_len-1: Data + if (at < data_offset + data_len) + return true; + + // Byte 3+data_len: CRC_LO (over all bytes) + if (at == data_offset + data_len) + return true; + + // Byte data_offset+len+1: CRC_HI (over all bytes) + uint16_t computed_crc = crc16(raw, data_offset + data_len); + uint16_t remote_crc = uint16_t(raw[data_offset + data_len]) | (uint16_t(raw[data_offset + data_len + 1]) << 8); + if (computed_crc != remote_crc) { + ESP_LOGW(TAG, "Modbus CRC Check failed! %02X!=%02X", computed_crc, remote_crc); + return false; + } } std::vector data(this->rx_buffer_.begin() + data_offset, this->rx_buffer_.begin() + data_offset + data_len); bool found = false; From f0c890f160dc1f073b351d39525f0b377be2055f Mon Sep 17 00:00:00 2001 From: Keilin Bickar Date: Wed, 18 May 2022 20:50:44 -0400 Subject: [PATCH 0320/3388] Remove deprecated fan speeds (#3397) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/api/api_connection.cpp | 12 ----- esphome/components/fan/fan.cpp | 17 ------- esphome/components/fan/fan.h | 7 --- esphome/components/fan/fan_helpers.cpp | 23 ---------- esphome/components/fan/fan_helpers.h | 20 --------- .../components/hbridge/fan/hbridge_fan.cpp | 1 - esphome/components/mqtt/mqtt_fan.cpp | 45 +------------------ esphome/components/speed/fan/speed_fan.cpp | 1 - esphome/components/tuya/fan/tuya_fan.cpp | 1 - esphome/components/web_server/web_server.cpp | 24 ---------- 10 files changed, 2 insertions(+), 149 deletions(-) delete mode 100644 esphome/components/fan/fan_helpers.cpp delete mode 100644 esphome/components/fan/fan_helpers.h diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 81f2465b74..4f399d95d0 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -12,9 +12,6 @@ #ifdef USE_HOMEASSISTANT_TIME #include "esphome/components/homeassistant/time/homeassistant_time.h" #endif -#ifdef USE_FAN -#include "esphome/components/fan/fan_helpers.h" -#endif namespace esphome { namespace api { @@ -253,9 +250,6 @@ void APIConnection::cover_command(const CoverCommandRequest &msg) { #endif #ifdef USE_FAN -// Shut-up about usage of deprecated speed_level_to_enum/speed_enum_to_level functions for a bit. -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wdeprecated-declarations" bool APIConnection::send_fan_state(fan::Fan *fan) { if (!this->state_subscription_) return false; @@ -268,7 +262,6 @@ bool APIConnection::send_fan_state(fan::Fan *fan) { resp.oscillating = fan->oscillating; if (traits.supports_speed()) { resp.speed_level = fan->speed; - resp.speed = static_cast(fan::speed_level_to_enum(fan->speed, traits.supported_speed_count())); } if (traits.supports_direction()) resp.direction = static_cast(fan->direction); @@ -295,8 +288,6 @@ void APIConnection::fan_command(const FanCommandRequest &msg) { if (fan == nullptr) return; - auto traits = fan->get_traits(); - auto call = fan->make_call(); if (msg.has_state) call.set_state(msg.state); @@ -305,14 +296,11 @@ void APIConnection::fan_command(const FanCommandRequest &msg) { if (msg.has_speed_level) { // Prefer level call.set_speed(msg.speed_level); - } else if (msg.has_speed) { - call.set_speed(fan::speed_enum_to_level(static_cast(msg.speed), traits.supported_speed_count())); } if (msg.has_direction) call.set_direction(static_cast(msg.direction)); call.perform(); } -#pragma GCC diagnostic pop #endif #ifdef USE_LIGHT diff --git a/esphome/components/fan/fan.cpp b/esphome/components/fan/fan.cpp index 5f9660f6d6..f7c4ab2e11 100644 --- a/esphome/components/fan/fan.cpp +++ b/esphome/components/fan/fan.cpp @@ -1,5 +1,4 @@ #include "fan.h" -#include "fan_helpers.h" #include "esphome/core/log.h" namespace esphome { @@ -61,22 +60,6 @@ void FanCall::validate_() { } } -// This whole method is deprecated, don't warn about usage of deprecated methods inside of it. -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wdeprecated-declarations" -FanCall &FanCall::set_speed(const char *legacy_speed) { - const auto supported_speed_count = this->parent_.get_traits().supported_speed_count(); - if (strcasecmp(legacy_speed, "low") == 0) { - this->set_speed(fan::speed_enum_to_level(FAN_SPEED_LOW, supported_speed_count)); - } else if (strcasecmp(legacy_speed, "medium") == 0) { - this->set_speed(fan::speed_enum_to_level(FAN_SPEED_MEDIUM, supported_speed_count)); - } else if (strcasecmp(legacy_speed, "high") == 0) { - this->set_speed(fan::speed_enum_to_level(FAN_SPEED_HIGH, supported_speed_count)); - } - return *this; -} -#pragma GCC diagnostic pop - FanCall FanRestoreState::to_call(Fan &fan) { auto call = fan.make_call(); call.set_state(this->state); diff --git a/esphome/components/fan/fan.h b/esphome/components/fan/fan.h index cafb5843d1..ef2ecd0f3f 100644 --- a/esphome/components/fan/fan.h +++ b/esphome/components/fan/fan.h @@ -16,13 +16,6 @@ namespace fan { (obj)->dump_traits_(TAG, prefix); \ } -/// Simple enum to represent the speed of a fan. - DEPRECATED - Will be deleted soon -enum ESPDEPRECATED("FanSpeed is deprecated.", "2021.9") FanSpeed { - FAN_SPEED_LOW = 0, ///< The fan is running on low speed. - FAN_SPEED_MEDIUM = 1, ///< The fan is running on medium speed. - FAN_SPEED_HIGH = 2 ///< The fan is running on high/full speed. -}; - /// Simple enum to represent the direction of a fan. enum class FanDirection { FORWARD = 0, REVERSE = 1 }; diff --git a/esphome/components/fan/fan_helpers.cpp b/esphome/components/fan/fan_helpers.cpp deleted file mode 100644 index 34883617e6..0000000000 --- a/esphome/components/fan/fan_helpers.cpp +++ /dev/null @@ -1,23 +0,0 @@ -#include -#include "fan_helpers.h" - -namespace esphome { -namespace fan { - -// This whole file is deprecated, don't warn about usage of deprecated types in here. -#pragma GCC diagnostic ignored "-Wdeprecated-declarations" - -FanSpeed speed_level_to_enum(int speed_level, int supported_speed_levels) { - const auto speed_ratio = static_cast(speed_level) / (supported_speed_levels + 1); - const auto legacy_level = clamp(static_cast(ceilf(speed_ratio * 3)), 1, 3); - return static_cast(legacy_level - 1); -} - -int speed_enum_to_level(FanSpeed speed, int supported_speed_levels) { - const auto enum_level = static_cast(speed) + 1; - const auto speed_level = roundf(enum_level / 3.0f * supported_speed_levels); - return static_cast(speed_level); -} - -} // namespace fan -} // namespace esphome diff --git a/esphome/components/fan/fan_helpers.h b/esphome/components/fan/fan_helpers.h deleted file mode 100644 index 8e8e3859bd..0000000000 --- a/esphome/components/fan/fan_helpers.h +++ /dev/null @@ -1,20 +0,0 @@ -#pragma once - -#include "fan.h" - -namespace esphome { -namespace fan { - -// Shut-up about usage of deprecated FanSpeed for a bit. -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wdeprecated-declarations" - -ESPDEPRECATED("FanSpeed and speed_level_to_enum() are deprecated.", "2021.9") -FanSpeed speed_level_to_enum(int speed_level, int supported_speed_levels); -ESPDEPRECATED("FanSpeed and speed_enum_to_level() are deprecated.", "2021.9") -int speed_enum_to_level(FanSpeed speed, int supported_speed_levels); - -#pragma GCC diagnostic pop - -} // namespace fan -} // namespace esphome diff --git a/esphome/components/hbridge/fan/hbridge_fan.cpp b/esphome/components/hbridge/fan/hbridge_fan.cpp index 52d2b3d8b7..44cf5ae049 100644 --- a/esphome/components/hbridge/fan/hbridge_fan.cpp +++ b/esphome/components/hbridge/fan/hbridge_fan.cpp @@ -1,5 +1,4 @@ #include "hbridge_fan.h" -#include "esphome/components/fan/fan_helpers.h" #include "esphome/core/log.h" namespace esphome { diff --git a/esphome/components/mqtt/mqtt_fan.cpp b/esphome/components/mqtt/mqtt_fan.cpp index e4d867843c..6433ead6b2 100644 --- a/esphome/components/mqtt/mqtt_fan.cpp +++ b/esphome/components/mqtt/mqtt_fan.cpp @@ -5,7 +5,6 @@ #ifdef USE_MQTT #ifdef USE_FAN -#include "esphome/components/fan/fan_helpers.h" namespace esphome { namespace mqtt { @@ -88,17 +87,6 @@ void MQTTFanComponent::setup() { }); } - if (this->state_->get_traits().supports_speed()) { - this->subscribe(this->get_speed_command_topic(), [this](const std::string &topic, const std::string &payload) { -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wdeprecated-declarations" - this->state_->make_call() - .set_speed(payload.c_str()) // NOLINT(clang-diagnostic-deprecated-declarations) - .perform(); -#pragma GCC diagnostic pop - }); - } - auto f = std::bind(&MQTTFanComponent::publish_state, this); this->state_->add_on_state_callback([this, f]() { this->defer("send", f); }); } @@ -113,8 +101,6 @@ void MQTTFanComponent::dump_config() { if (this->state_->get_traits().supports_speed()) { ESP_LOGCONFIG(TAG, " Speed Level State Topic: '%s'", this->get_speed_level_state_topic().c_str()); ESP_LOGCONFIG(TAG, " Speed Level Command Topic: '%s'", this->get_speed_level_command_topic().c_str()); - ESP_LOGCONFIG(TAG, " Speed State Topic: '%s'", this->get_speed_state_topic().c_str()); - ESP_LOGCONFIG(TAG, " Speed Command Topic: '%s'", this->get_speed_command_topic().c_str()); } } @@ -126,10 +112,8 @@ void MQTTFanComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig root[MQTT_OSCILLATION_STATE_TOPIC] = this->get_oscillation_state_topic(); } if (this->state_->get_traits().supports_speed()) { - root["speed_level_command_topic"] = this->get_speed_level_command_topic(); - root["speed_level_state_topic"] = this->get_speed_level_state_topic(); - root[MQTT_SPEED_COMMAND_TOPIC] = this->get_speed_command_topic(); - root[MQTT_SPEED_STATE_TOPIC] = this->get_speed_state_topic(); + root[MQTT_PERCENTAGE_COMMAND_TOPIC] = this->get_speed_level_command_topic(); + root[MQTT_PERCENTAGE_STATE_TOPIC] = this->get_speed_level_state_topic(); } } bool MQTTFanComponent::publish_state() { @@ -148,31 +132,6 @@ bool MQTTFanComponent::publish_state() { bool success = this->publish(this->get_speed_level_state_topic(), payload); failed = failed || !success; } - if (traits.supports_speed()) { - const char *payload; -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wdeprecated-declarations" - // NOLINTNEXTLINE(clang-diagnostic-deprecated-declarations) - switch (fan::speed_level_to_enum(this->state_->speed, traits.supported_speed_count())) { - case FAN_SPEED_LOW: { // NOLINT(clang-diagnostic-deprecated-declarations) - payload = "low"; - break; - } - case FAN_SPEED_MEDIUM: { // NOLINT(clang-diagnostic-deprecated-declarations) - payload = "medium"; - break; - } - default: - case FAN_SPEED_HIGH: { // NOLINT(clang-diagnostic-deprecated-declarations) - payload = "high"; - break; - } - } -#pragma GCC diagnostic pop - bool success = this->publish(this->get_speed_state_topic(), payload); - failed = failed || !success; - } - return !failed; } diff --git a/esphome/components/speed/fan/speed_fan.cpp b/esphome/components/speed/fan/speed_fan.cpp index 9ed201982a..3a65f2c365 100644 --- a/esphome/components/speed/fan/speed_fan.cpp +++ b/esphome/components/speed/fan/speed_fan.cpp @@ -1,5 +1,4 @@ #include "speed_fan.h" -#include "esphome/components/fan/fan_helpers.h" #include "esphome/core/log.h" namespace esphome { diff --git a/esphome/components/tuya/fan/tuya_fan.cpp b/esphome/components/tuya/fan/tuya_fan.cpp index 019b504deb..813aee4aa0 100644 --- a/esphome/components/tuya/fan/tuya_fan.cpp +++ b/esphome/components/tuya/fan/tuya_fan.cpp @@ -1,5 +1,4 @@ #include "esphome/core/log.h" -#include "esphome/components/fan/fan_helpers.h" #include "tuya_fan.h" namespace esphome { diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index 6822ce9953..18374d606b 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -21,10 +21,6 @@ #include "esphome/components/logger/logger.h" #endif -#ifdef USE_FAN -#include "esphome/components/fan/fan_helpers.h" -#endif - #ifdef USE_CLIMATE #include "esphome/components/climate/climate.h" #endif @@ -482,22 +478,6 @@ std::string WebServer::fan_json(fan::Fan *obj, JsonDetail start_config) { if (traits.supports_speed()) { root["speed_level"] = obj->speed; root["speed_count"] = traits.supported_speed_count(); - -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wdeprecated-declarations" - // NOLINTNEXTLINE(clang-diagnostic-deprecated-declarations) - switch (fan::speed_level_to_enum(obj->speed, traits.supported_speed_count())) { - case fan::FAN_SPEED_LOW: // NOLINT(clang-diagnostic-deprecated-declarations) - root["speed"] = "low"; - break; - case fan::FAN_SPEED_MEDIUM: // NOLINT(clang-diagnostic-deprecated-declarations) - root["speed"] = "medium"; - break; - case fan::FAN_SPEED_HIGH: // NOLINT(clang-diagnostic-deprecated-declarations) - root["speed"] = "high"; - break; - } -#pragma GCC diagnostic pop } if (obj->get_traits().supports_oscillation()) root["oscillation"] = obj->oscillating; @@ -518,10 +498,6 @@ void WebServer::handle_fan_request(AsyncWebServerRequest *request, const UrlMatc auto call = obj->turn_on(); if (request->hasParam("speed")) { String speed = request->getParam("speed")->value(); -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wdeprecated-declarations" - call.set_speed(speed.c_str()); // NOLINT(clang-diagnostic-deprecated-declarations) -#pragma GCC diagnostic pop } if (request->hasParam("speed_level")) { String speed_level = request->getParam("speed_level")->value(); From dda1ddcb26b9ca891ca5de668673a92bcf87ca22 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 19 May 2022 16:23:40 +1200 Subject: [PATCH 0321/3388] Add missing import to bedjet (#3490) --- esphome/components/bedjet/bedjet.h | 1 + 1 file changed, 1 insertion(+) diff --git a/esphome/components/bedjet/bedjet.h b/esphome/components/bedjet/bedjet.h index 0565be6045..750a20594f 100644 --- a/esphome/components/bedjet/bedjet.h +++ b/esphome/components/bedjet/bedjet.h @@ -4,6 +4,7 @@ #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" #include "esphome/components/climate/climate.h" #include "esphome/core/component.h" +#include "esphome/core/defines.h" #include "esphome/core/hal.h" #include "bedjet_base.h" From d9d2edeb080297c3f4b0e0d82eee161222466692 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 19 May 2022 21:21:42 +1200 Subject: [PATCH 0322/3388] Fix compile issues on windows (#3491) --- esphome/components/sonoff_d1/sonoff_d1.cpp | 3 +-- esphome/components/tuya/text_sensor/tuya_text_sensor.cpp | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/esphome/components/sonoff_d1/sonoff_d1.cpp b/esphome/components/sonoff_d1/sonoff_d1.cpp index b4bcbc6760..d07f9229b6 100644 --- a/esphome/components/sonoff_d1/sonoff_d1.cpp +++ b/esphome/components/sonoff_d1/sonoff_d1.cpp @@ -41,7 +41,6 @@ * O FF FF FF FF FF FF FF FF - Not used * M 6C - CRC over bytes 2 to F (Addition) \*********************************************************************************************/ -#include #include "sonoff_d1.h" namespace esphome { @@ -263,7 +262,7 @@ void SonoffD1Output::write_state(light::LightState *state) { state->current_values_as_brightness(&brightness); // Convert ESPHome's brightness (0-1) to the device's internal brightness (0-100) - const uint8_t calculated_brightness = std::round(brightness * 100); + const uint8_t calculated_brightness = (uint8_t) roundf(brightness * 100); if (calculated_brightness == 0) { // if(binary) ESP_LOGD(TAG, "current_values_as_binary() returns true for zero brightness"); diff --git a/esphome/components/tuya/text_sensor/tuya_text_sensor.cpp b/esphome/components/tuya/text_sensor/tuya_text_sensor.cpp index 0b51ba90c4..602595e89d 100644 --- a/esphome/components/tuya/text_sensor/tuya_text_sensor.cpp +++ b/esphome/components/tuya/text_sensor/tuya_text_sensor.cpp @@ -20,7 +20,7 @@ void TuyaTextSensor::setup() { break; } default: - ESP_LOGW(TAG, "Unsupported data type for tuya text sensor %u: %#02hhX", datapoint.id, datapoint.type); + ESP_LOGW(TAG, "Unsupported data type for tuya text sensor %u: %#02hhX", datapoint.id, (uint8_t) datapoint.type); break; } }); From 7092f7663e5d129690f5a6603c002778bb2f444d Mon Sep 17 00:00:00 2001 From: Sergey Dudanov Date: Mon, 23 May 2022 11:51:45 +0300 Subject: [PATCH 0323/3388] midea: New power_toggle action. Auto-use remote transmitter. (#3496) --- esphome/components/midea/ac_automations.h | 5 ++++ esphome/components/midea/air_conditioner.h | 1 + esphome/components/midea/climate.py | 13 +++++++++- tests/test1.yaml | 28 +++++++++++++++------- 4 files changed, 38 insertions(+), 9 deletions(-) diff --git a/esphome/components/midea/ac_automations.h b/esphome/components/midea/ac_automations.h index d4ed2e7168..5084fd1eec 100644 --- a/esphome/components/midea/ac_automations.h +++ b/esphome/components/midea/ac_automations.h @@ -56,6 +56,11 @@ template class PowerOffAction : public MideaActionBase { void play(Ts... x) override { this->parent_->do_power_off(); } }; +template class PowerToggleAction : public MideaActionBase { + public: + void play(Ts... x) override { this->parent_->do_power_toggle(); } +}; + } // namespace ac } // namespace midea } // namespace esphome diff --git a/esphome/components/midea/air_conditioner.h b/esphome/components/midea/air_conditioner.h index a6023b78bb..d809aa78f6 100644 --- a/esphome/components/midea/air_conditioner.h +++ b/esphome/components/midea/air_conditioner.h @@ -39,6 +39,7 @@ class AirConditioner : public ApplianceBase, void do_beeper_off() { this->set_beeper_feedback(false); } void do_power_on() { this->base_.setPowerState(true); } void do_power_off() { this->base_.setPowerState(false); } + void do_power_toggle() { this->base_.setPowerState(this->mode == ClimateMode::CLIMATE_MODE_OFF); } void set_supported_modes(const std::set &modes) { this->supported_modes_ = modes; } void set_supported_swing_modes(const std::set &modes) { this->supported_swing_modes_ = modes; } void set_supported_presets(const std::set &presets) { this->supported_presets_ = presets; } diff --git a/esphome/components/midea/climate.py b/esphome/components/midea/climate.py index 46c0019efa..80b1461576 100644 --- a/esphome/components/midea/climate.py +++ b/esphome/components/midea/climate.py @@ -113,7 +113,7 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_PERIOD, default="1s"): cv.time_period, cv.Optional(CONF_TIMEOUT, default="2s"): cv.time_period, cv.Optional(CONF_NUM_ATTEMPTS, default=3): cv.int_range(min=1, max=5), - cv.Optional(CONF_TRANSMITTER_ID): cv.use_id( + cv.OnlyWith(CONF_TRANSMITTER_ID, "remote_transmitter"): cv.use_id( remote_transmitter.RemoteTransmitterComponent ), cv.Optional(CONF_BEEPER, default=False): cv.boolean, @@ -163,6 +163,7 @@ BeeperOnAction = midea_ac_ns.class_("BeeperOnAction", automation.Action) BeeperOffAction = midea_ac_ns.class_("BeeperOffAction", automation.Action) PowerOnAction = midea_ac_ns.class_("PowerOnAction", automation.Action) PowerOffAction = midea_ac_ns.class_("PowerOffAction", automation.Action) +PowerToggleAction = midea_ac_ns.class_("PowerToggleAction", automation.Action) MIDEA_ACTION_BASE_SCHEMA = cv.Schema( { @@ -249,6 +250,16 @@ async def power_off_to_code(var, config, args): pass +# Power Toggle action +@register_action( + "power_toggle", + PowerToggleAction, + cv.Schema({}), +) +async def power_inv_to_code(var, config, args): + pass + + async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) diff --git a/tests/test1.yaml b/tests/test1.yaml index 7bb1fbe954..52aa03c371 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -1901,14 +1901,6 @@ script: preset: SLEEP switch: - - platform: template - name: MIDEA_AC_TOGGLE_LIGHT - turn_on_action: - midea_ac.display_toggle: - - platform: template - name: MIDEA_AC_SWING_STEP - turn_on_action: - midea_ac.swing_step: - platform: template name: MIDEA_AC_BEEPER_CONTROL optimistic: true @@ -2834,3 +2826,23 @@ button: id: scd40 - scd4x.factory_reset: id: scd40 + - platform: template + name: Midea Display Toggle + on_press: + midea_ac.display_toggle: + - platform: template + name: Midea Swing Step + on_press: + midea_ac.swing_step: + - platform: template + name: Midea Power On + on_press: + midea_ac.power_on: + - platform: template + name: Midea Power Off + on_press: + midea_ac.power_off: + - platform: template + name: Midea Power Inverse + on_press: + midea_ac.power_toggle: From a8ceeaa7b0616b132091f2fcd1d02c137238e4d8 Mon Sep 17 00:00:00 2001 From: Martin <25747549+martgras@users.noreply.github.com> Date: Mon, 23 May 2022 10:56:26 +0200 Subject: [PATCH 0324/3388] esp32: fix NVS (#3497) --- esphome/components/esp32/preferences.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/esp32/preferences.cpp b/esphome/components/esp32/preferences.cpp index a78159825e..aa03c5acc7 100644 --- a/esphome/components/esp32/preferences.cpp +++ b/esphome/components/esp32/preferences.cpp @@ -156,7 +156,7 @@ class ESP32Preferences : public ESPPreferences { ESP_LOGV(TAG, "nvs_get_blob('%s') failed: %s", to_save.key.c_str(), esp_err_to_name(err)); return true; } - return to_save.data == stored_data.data; + return to_save.data != stored_data.data; } }; From 9dc804ee27426ee3550855347e30b4ebd2fcd2d9 Mon Sep 17 00:00:00 2001 From: joseph douce Date: Tue, 24 May 2022 01:52:54 +0100 Subject: [PATCH 0325/3388] Output a true RMS voltage % (#3494) --- esphome/components/ac_dimmer/ac_dimmer.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/esphome/components/ac_dimmer/ac_dimmer.cpp b/esphome/components/ac_dimmer/ac_dimmer.cpp index e9af828a9d..1d0cd8d0ab 100644 --- a/esphome/components/ac_dimmer/ac_dimmer.cpp +++ b/esphome/components/ac_dimmer/ac_dimmer.cpp @@ -121,7 +121,11 @@ void IRAM_ATTR HOT AcDimmerDataStore::gpio_intr() { // calculate time until enable in µs: (1.0-value)*cycle_time, but with integer arithmetic // also take into account min_power auto min_us = this->cycle_time_us * this->min_power / 1000; - this->enable_time_us = std::max((uint32_t) 1, ((65535 - this->value) * (this->cycle_time_us - min_us)) / 65535); + // calculate required value to provide a true RMS voltage output + this->enable_time_us = + std::max((uint32_t) 1, (uint32_t)((65535 - (acos(1 - (2 * this->value / 65535.0)) / 3.14159 * 65535)) * + (this->cycle_time_us - min_us)) / + 65535); if (this->method == DIM_METHOD_LEADING_PULSE) { // Minimum pulse time should be enough for the triac to trigger when it is close to the ZC zone // this is for brightness near 99% From cd35ead890a934d474093e6c9791c5551e08ce04 Mon Sep 17 00:00:00 2001 From: Wumpf Date: Tue, 24 May 2022 03:00:06 +0200 Subject: [PATCH 0326/3388] [scd4x] Fix not passing arguments to templatable value for perform_forced_calibration (#3495) --- esphome/components/scd4x/automation.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/scd4x/automation.h b/esphome/components/scd4x/automation.h index 21ecb2ea4c..dc43e9eb56 100644 --- a/esphome/components/scd4x/automation.h +++ b/esphome/components/scd4x/automation.h @@ -11,7 +11,7 @@ template class PerformForcedCalibrationAction : public Actionvalue_.has_value()) { - this->parent_->perform_forced_calibration(value_.value()); + this->parent_->perform_forced_calibration(this->value_.value(x...)); } } From 6617d576a7aaa4f0d4319781f83df87f7c94224b Mon Sep 17 00:00:00 2001 From: user897943 Date: Wed, 18 May 2022 23:25:42 +0100 Subject: [PATCH 0327/3388] Update bedjet_const.h to remove blank spaces before speed steps, fixes Unknown Error when using climate.set_fan_mode in HA (#3476) --- esphome/components/bedjet/bedjet_const.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/bedjet/bedjet_const.h b/esphome/components/bedjet/bedjet_const.h index e6bfa45d3a..ae10ca1885 100644 --- a/esphome/components/bedjet/bedjet_const.h +++ b/esphome/components/bedjet/bedjet_const.h @@ -66,8 +66,8 @@ enum BedjetCommand : uint8_t { #define BEDJET_FAN_STEP_NAMES_ \ { \ - " 5%", " 10%", " 15%", " 20%", " 25%", " 30%", " 35%", " 40%", " 45%", " 50%", " 55%", " 60%", " 65%", " 70%", \ - " 75%", " 80%", " 85%", " 90%", " 95%", "100%" \ + "5%", "10%", "15%", "20%", "25%", "30%", "35%", "40%", "45%", "50%", "55%", "60%", "65%", "70%", "75%", "80%", \ + "85%", "90%", "95%", "100%" \ } static const char *const BEDJET_FAN_STEP_NAMES[20] = BEDJET_FAN_STEP_NAMES_; From b66af9fb4df4e093a6bd23672621301bb1662548 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 19 May 2022 16:23:40 +1200 Subject: [PATCH 0328/3388] Add missing import to bedjet (#3490) --- esphome/components/bedjet/bedjet.h | 1 + 1 file changed, 1 insertion(+) diff --git a/esphome/components/bedjet/bedjet.h b/esphome/components/bedjet/bedjet.h index 0565be6045..750a20594f 100644 --- a/esphome/components/bedjet/bedjet.h +++ b/esphome/components/bedjet/bedjet.h @@ -4,6 +4,7 @@ #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" #include "esphome/components/climate/climate.h" #include "esphome/core/component.h" +#include "esphome/core/defines.h" #include "esphome/core/hal.h" #include "bedjet_base.h" From fb0fec1f25fc24d8a7130cab60e99de3567d6b43 Mon Sep 17 00:00:00 2001 From: Martin <25747549+martgras@users.noreply.github.com> Date: Mon, 23 May 2022 10:56:26 +0200 Subject: [PATCH 0329/3388] esp32: fix NVS (#3497) --- esphome/components/esp32/preferences.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/esp32/preferences.cpp b/esphome/components/esp32/preferences.cpp index a78159825e..aa03c5acc7 100644 --- a/esphome/components/esp32/preferences.cpp +++ b/esphome/components/esp32/preferences.cpp @@ -156,7 +156,7 @@ class ESP32Preferences : public ESPPreferences { ESP_LOGV(TAG, "nvs_get_blob('%s') failed: %s", to_save.key.c_str(), esp_err_to_name(err)); return true; } - return to_save.data == stored_data.data; + return to_save.data != stored_data.data; } }; From f3f6e54818342e8a19328b5f069ac49a0451a312 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 24 May 2022 21:56:18 +1200 Subject: [PATCH 0330/3388] Bump version to 2022.5.1 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 913f0fd0dc..3911a5fa4c 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2022.5.0" +__version__ = "2022.5.1" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" From adb7aa69509d2389144433e36a0cf965c47776c0 Mon Sep 17 00:00:00 2001 From: Michael Davidson Date: Wed, 25 May 2022 13:44:26 +1000 Subject: [PATCH 0331/3388] Thermostat preset with modes (#3298) * Rework HOME/AWAY support to being driven via a map of ClimatePreset/ThermostatClimateTargetTempConfig This opens up to theoretically being able to support other presets (ECO, SLEEP, etc) * Add support for additional presets Configuration takes the form; ``` climate: platform: preset ... preset: [eco | away | boost | comfort | home | sleep | activity]: default_target_temperature_low: 20 default_target_temperature_high: 24 ``` These will be available in the Home Assistant UI and, like the existing Home/Away config will reset the temperature in line with these defaults when selected. The existing away_config/home_config is still respected (although preset->home/preset->away will be applied after them and override them if both styles are specified) * Add support for specifying MODE, FAN_MODE and SWING_MODE on a preset When switching presets these will implicitly flow through to the controller. However calls to climate.control which specify any of these will take precedence even when changing the mode (think of the preset version as the default for that preset) * Add `preset_change` mode trigger When defined this trigger will fire when the preset for the thermostat has been changed. The intent of this is similar to `auto_mode` - it's not intended to be used to control the preset's state (eg. communicate with the physical thermostat) but instead might be used to update a visual indicator, for instance. * Apply lint, clang-format, and clang-tidy fixes * Additional clang-format fixes * Wrap log related strings in LOG_STR_ARG * Add support for custom presets This also changes the configuration syntax to; ```yaml preset: # Standard preset - name: [eco | away | boost | comfort | home | sleep | activity] default_target_temperature_low: 20 ... # Custom preset - name: My custom preset default_target_temperature_low: 18 ``` For the end user there is no difference between a custom and built in preset. For developers custom presets are set via `climate.control` `custom_preset` property instead of the `preset` * Lint/clang-format/clang-tidy fixes * Additional lint/clang-format/clang-tidy fixes * Clang-tidy changes * Sort imports * Improve configuration validation for presets - Unify temperature validation across default, away, and preset configuration - Validate modes for presets have the required actions * Trigger a refresh after changing internals of the thermostat * Apply formatting fixes * Validate mode, fan_mode, and swing_mode on presets * Add preset temperature validation against visual min/max configuration * Apply code formatting fixes * Fix preset temperature validation --- esphome/components/thermostat/climate.py | 235 ++++++++++++++++-- .../thermostat/thermostat_climate.cpp | 191 ++++++++++---- .../thermostat/thermostat_climate.h | 42 +++- 3 files changed, 373 insertions(+), 95 deletions(-) diff --git a/esphome/components/thermostat/climate.py b/esphome/components/thermostat/climate.py index 20565e811c..5e26e6d6de 100644 --- a/esphome/components/thermostat/climate.py +++ b/esphome/components/thermostat/climate.py @@ -14,6 +14,7 @@ from esphome.const import ( CONF_DEFAULT_TARGET_TEMPERATURE_LOW, CONF_DRY_ACTION, CONF_DRY_MODE, + CONF_FAN_MODE, CONF_FAN_MODE_ON_ACTION, CONF_FAN_MODE_OFF_ACTION, CONF_FAN_MODE_AUTO_ACTION, @@ -37,6 +38,7 @@ from esphome.const import ( CONF_IDLE_ACTION, CONF_MAX_COOLING_RUN_TIME, CONF_MAX_HEATING_RUN_TIME, + CONF_MAX_TEMPERATURE, CONF_MIN_COOLING_OFF_TIME, CONF_MIN_COOLING_RUN_TIME, CONF_MIN_FAN_MODE_SWITCHING_TIME, @@ -45,7 +47,11 @@ from esphome.const import ( CONF_MIN_HEATING_OFF_TIME, CONF_MIN_HEATING_RUN_TIME, CONF_MIN_IDLE_TIME, + CONF_MIN_TEMPERATURE, + CONF_NAME, + CONF_MODE, CONF_OFF_MODE, + CONF_PRESET, CONF_SENSOR, CONF_SET_POINT_MINIMUM_DIFFERENTIAL, CONF_STARTUP_DELAY, @@ -55,11 +61,15 @@ from esphome.const import ( CONF_SUPPLEMENTAL_HEATING_DELTA, CONF_SWING_BOTH_ACTION, CONF_SWING_HORIZONTAL_ACTION, + CONF_SWING_MODE, CONF_SWING_OFF_ACTION, CONF_SWING_VERTICAL_ACTION, CONF_TARGET_TEMPERATURE_CHANGE_ACTION, + CONF_VISUAL, ) +CONF_PRESET_CHANGE = "preset_change" + CODEOWNERS = ["@kbx81"] climate_ns = cg.esphome_ns.namespace("climate") @@ -82,6 +92,38 @@ CLIMATE_MODES = { } validate_climate_mode = cv.enum(CLIMATE_MODES, upper=True) +ClimatePreset = climate_ns.enum("ClimatePreset") + +PRESET_CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.declare_id(ThermostatClimateTargetTempConfig), + cv.Required(CONF_NAME): cv.string_strict, + cv.Optional(CONF_MODE): validate_climate_mode, + cv.Optional(CONF_DEFAULT_TARGET_TEMPERATURE_HIGH): cv.temperature, + cv.Optional(CONF_DEFAULT_TARGET_TEMPERATURE_LOW): cv.temperature, + cv.Optional(CONF_FAN_MODE): cv.templatable(climate.validate_climate_fan_mode), + cv.Optional(CONF_SWING_MODE): cv.templatable( + climate.validate_climate_swing_mode + ), + } +) + + +def validate_temperature_preset(preset, root_config, name, requirements): + # verify temperature settings for the provided preset / default / away configuration + for config_temp, req_actions in requirements.items(): + for req_action in req_actions: + # verify corresponding default target temperature exists when a given climate action exists + if config_temp not in preset and req_action in root_config: + raise cv.Invalid( + f"{config_temp} must be defined in {name} config when using {req_action}" + ) + # if a given climate action is NOT defined, it should not have a default target temperature + if config_temp in preset and req_action not in root_config: + raise cv.Invalid( + f"{config_temp} is defined in {name} config with no {req_action}" + ) + def validate_thermostat(config): # verify corresponding action(s) exist(s) for any defined climate mode or action @@ -235,33 +277,22 @@ def validate_thermostat(config): CONF_DEFAULT_TARGET_TEMPERATURE_LOW: [CONF_HEAT_ACTION], } - for config_temp, req_actions in requirements.items(): - for req_action in req_actions: - # verify corresponding default target temperature exists when a given climate action exists - if config_temp not in config and req_action in config: - raise cv.Invalid( - f"{config_temp} must be defined when using {req_action}" - ) - # if a given climate action is NOT defined, it should not have a default target temperature - if config_temp in config and req_action not in config: - raise cv.Invalid(f"{config_temp} is defined with no {req_action}") + # Validate temperature requirements for default configuraation + validate_temperature_preset(config, config, "default", requirements) + # Validate temperature requirements for away configuration if CONF_AWAY_CONFIG in config: away = config[CONF_AWAY_CONFIG] - for config_temp, req_actions in requirements.items(): - for req_action in req_actions: - # verify corresponding default target temperature exists when a given climate action exists - if config_temp not in away and req_action in config: - raise cv.Invalid( - f"{config_temp} must be defined in away configuration when using {req_action}" - ) - # if a given climate action is NOT defined, it should not have a default target temperature - if config_temp in away and req_action not in config: - raise cv.Invalid( - f"{config_temp} is defined in away configuration with no {req_action}" - ) + validate_temperature_preset(away, config, "away", requirements) - # verify default climate mode is valid given above configuration + # Validate temperature requirements for presets + if CONF_PRESET in config: + for preset_config in config[CONF_PRESET]: + validate_temperature_preset( + preset_config, config, preset_config[CONF_NAME], requirements + ) + + # Verify default climate mode is valid given above configuration default_mode = config[CONF_DEFAULT_MODE] requirements = { "HEAT_COOL": [CONF_COOL_ACTION, CONF_HEAT_ACTION], @@ -270,13 +301,108 @@ def validate_thermostat(config): "DRY": [CONF_DRY_ACTION], "FAN_ONLY": [CONF_FAN_ONLY_ACTION], "AUTO": [CONF_COOL_ACTION, CONF_HEAT_ACTION], - }.get(default_mode, []) - for req in requirements: + "OFF": [], + } + actions_for_default_mode = requirements.get(default_mode, []) + for req in actions_for_default_mode: if req not in config: raise cv.Invalid( f"{CONF_DEFAULT_MODE} is set to {default_mode} but {req} is not present in the configuration" ) + # Verify that the modes for presets are valid given the configuration + if CONF_PRESET in config: + # Preset temperature vs Visual temperature validation + + # Default visual configuration from climate_traits.h + visual_min_temperature = 10.0 + visual_max_temperature = 30.0 + if CONF_VISUAL in config: + visual_config = config[CONF_VISUAL] + + if CONF_MIN_TEMPERATURE in visual_config: + visual_min_temperature = visual_config[CONF_MIN_TEMPERATURE] + + if CONF_MAX_TEMPERATURE in visual_config: + visual_max_temperature = visual_config[CONF_MAX_TEMPERATURE] + + for preset_config in config[CONF_PRESET]: + if CONF_DEFAULT_TARGET_TEMPERATURE_LOW in preset_config: + preset_min_temperature = preset_config[ + CONF_DEFAULT_TARGET_TEMPERATURE_LOW + ] + if preset_min_temperature < visual_min_temperature: + raise cv.Invalid( + f"{CONF_DEFAULT_TARGET_TEMPERATURE_LOW} for {preset_config[CONF_NAME]} is set to {preset_min_temperature} which is less than the visual minimum temperature of {visual_min_temperature}" + ) + + if CONF_DEFAULT_TARGET_TEMPERATURE_HIGH in preset_config: + preset_max_temperature = preset_config[ + CONF_DEFAULT_TARGET_TEMPERATURE_HIGH + ] + if preset_max_temperature > visual_max_temperature: + raise cv.Invalid( + f"{CONF_DEFAULT_TARGET_TEMPERATURE_HIGH} for {preset_config[CONF_NAME]} is set to {preset_max_temperature} which is more than the visual maximum temperature of {visual_max_temperature}" + ) + + # Mode validation + for preset_config in config[CONF_PRESET]: + if CONF_MODE not in preset_config: + continue + + mode = preset_config[CONF_MODE] + + for req in requirements[mode]: + if req not in config: + raise cv.Invalid( + f"{CONF_MODE} is set to {mode} for {preset_config[CONF_NAME]} but {req} is not present in the configuration" + ) + + # Fan mode requirements + requirements = { + "ON": [CONF_FAN_MODE_ON_ACTION], + "OFF": [CONF_FAN_MODE_OFF_ACTION], + "AUTO": [CONF_FAN_MODE_AUTO_ACTION], + "LOW": [CONF_FAN_MODE_LOW_ACTION], + "MEDIUM": [CONF_FAN_MODE_MEDIUM_ACTION], + "HIGH": [CONF_FAN_MODE_HIGH_ACTION], + "MIDDLE": [CONF_FAN_MODE_MIDDLE_ACTION], + "FOCUS": [CONF_FAN_MODE_FOCUS_ACTION], + "DIFFUSE": [CONF_FAN_MODE_DIFFUSE_ACTION], + } + + for preset_config in config[CONF_PRESET]: + if CONF_FAN_MODE not in preset_config: + continue + + fan_mode = preset_config[CONF_FAN_MODE] + + for req in requirements[fan_mode]: + if req not in config: + raise cv.Invalid( + f"{CONF_FAN_MODE} is set to {fan_mode} for {preset_config[CONF_NAME]} but {req} is not present in the configuration" + ) + + # Swing mode requirements + requirements = { + "OFF": [CONF_SWING_OFF_ACTION], + "BOTH": [CONF_SWING_BOTH_ACTION], + "VERTICAL": [CONF_SWING_VERTICAL_ACTION], + "HORIZONTAL": [CONF_SWING_HORIZONTAL_ACTION], + } + + for preset_config in config[CONF_PRESET]: + if CONF_SWING_MODE not in preset_config: + continue + + swing_mode = preset_config[CONF_SWING_MODE] + + for req in requirements[swing_mode]: + if req not in config: + raise cv.Invalid( + f"{CONF_SWING_MODE} is set to {swing_mode} for {preset_config[CONF_NAME]} but {req} is not present in the configuration" + ) + if config[CONF_FAN_WITH_COOLING] is True and CONF_FAN_ONLY_ACTION not in config: raise cv.Invalid( f"{CONF_FAN_ONLY_ACTION} must be defined to use {CONF_FAN_WITH_COOLING}" @@ -415,6 +541,10 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_DEFAULT_TARGET_TEMPERATURE_LOW): cv.temperature, } ), + cv.Optional(CONF_PRESET): cv.ensure_list(PRESET_CONFIG_SCHEMA), + cv.Optional(CONF_PRESET_CHANGE): automation.validate_automation( + single=True + ), } ).extend(cv.COMPONENT_SCHEMA), cv.has_at_least_one_key( @@ -531,7 +661,7 @@ async def to_code(config): cg.add(var.set_supports_fan_with_heating(config[CONF_FAN_WITH_HEATING])) cg.add(var.set_use_startup_delay(config[CONF_STARTUP_DELAY])) - cg.add(var.set_normal_config(normal_config)) + cg.add(var.set_preset_config(ClimatePreset.CLIMATE_PRESET_HOME, normal_config)) await automation.build_automation( var.get_idle_action_trigger(), [], config[CONF_IDLE_ACTION] @@ -694,4 +824,55 @@ async def to_code(config): away_config = ThermostatClimateTargetTempConfig( away[CONF_DEFAULT_TARGET_TEMPERATURE_LOW] ) - cg.add(var.set_away_config(away_config)) + cg.add(var.set_preset_config(ClimatePreset.CLIMATE_PRESET_AWAY, away_config)) + + if CONF_PRESET in config: + for preset_config in config[CONF_PRESET]: + + name = preset_config[CONF_NAME] + standard_preset = None + if name.upper() in climate.CLIMATE_PRESETS: + standard_preset = climate.CLIMATE_PRESETS[name.upper()] + + if two_points_available is True: + preset_target_config = ThermostatClimateTargetTempConfig( + preset_config[CONF_DEFAULT_TARGET_TEMPERATURE_LOW], + preset_config[CONF_DEFAULT_TARGET_TEMPERATURE_HIGH], + ) + elif CONF_DEFAULT_TARGET_TEMPERATURE_HIGH in preset_config: + preset_target_config = ThermostatClimateTargetTempConfig( + preset_config[CONF_DEFAULT_TARGET_TEMPERATURE_HIGH] + ) + elif CONF_DEFAULT_TARGET_TEMPERATURE_LOW in preset_config: + preset_target_config = ThermostatClimateTargetTempConfig( + preset_config[CONF_DEFAULT_TARGET_TEMPERATURE_LOW] + ) + + preset_target_variable = cg.new_variable( + preset_config[CONF_ID], preset_target_config + ) + + if CONF_MODE in preset_config: + cg.add(preset_target_variable.set_mode(preset_config[CONF_MODE])) + + if CONF_FAN_MODE in preset_config: + cg.add( + preset_target_variable.set_fan_mode(preset_config[CONF_FAN_MODE]) + ) + + if CONF_SWING_MODE in preset_config: + cg.add( + preset_target_variable.set_swing_mode( + preset_config[CONF_SWING_MODE] + ) + ) + + if standard_preset is not None: + cg.add(var.set_preset_config(standard_preset, preset_target_variable)) + else: + cg.add(var.set_custom_preset_config(name, preset_target_variable)) + + if CONF_PRESET_CHANGE in config: + await automation.build_automation( + var.get_preset_change_trigger(), [], config[CONF_PRESET_CHANGE] + ) diff --git a/esphome/components/thermostat/thermostat_climate.cpp b/esphome/components/thermostat/thermostat_climate.cpp index 760525e2cd..dc4e1e437e 100644 --- a/esphome/components/thermostat/thermostat_climate.cpp +++ b/esphome/components/thermostat/thermostat_climate.cpp @@ -32,7 +32,7 @@ void ThermostatClimate::setup() { } else { // restore from defaults, change_away handles temps for us this->mode = this->default_mode_; - this->change_away_(false); + this->change_preset_(climate::CLIMATE_PRESET_HOME); } // refresh the climate action based on the restored settings, we'll publish_state() later this->switch_to_action_(this->compute_action_(), false); @@ -162,11 +162,20 @@ void ThermostatClimate::control(const climate::ClimateCall &call) { if (call.get_preset().has_value()) { // setup_complete_ blocks modifying/resetting the temps immediately after boot if (this->setup_complete_) { - this->change_away_(*call.get_preset() == climate::CLIMATE_PRESET_AWAY); + this->change_preset_(*call.get_preset()); } else { this->preset = *call.get_preset(); } } + if (call.get_custom_preset().has_value()) { + // setup_complete_ blocks modifying/resetting the temps immediately after boot + if (this->setup_complete_) { + this->change_custom_preset_(*call.get_custom_preset()); + } else { + this->custom_preset = *call.get_custom_preset(); + } + } + if (call.get_mode().has_value()) this->mode = *call.get_mode(); if (call.get_fan_mode().has_value()) @@ -236,8 +245,12 @@ climate::ClimateTraits ThermostatClimate::traits() { if (supports_swing_mode_vertical_) traits.add_supported_swing_mode(climate::CLIMATE_SWING_VERTICAL); - if (supports_away_) - traits.set_supported_presets({climate::CLIMATE_PRESET_HOME, climate::CLIMATE_PRESET_AWAY}); + for (auto &it : this->preset_config_) { + traits.add_supported_preset(it.first); + } + for (auto &it : this->custom_preset_config_) { + traits.add_supported_custom_preset(it.first); + } traits.set_supports_two_point_target_temperature(this->supports_two_points_); traits.set_supports_action(true); @@ -910,30 +923,112 @@ bool ThermostatClimate::supplemental_heating_required_() { (this->supplemental_action_ == climate::CLIMATE_ACTION_HEATING)); } -void ThermostatClimate::change_away_(bool away) { - if (!away) { +void ThermostatClimate::dump_preset_config_(const std::string &preset, + const ThermostatClimateTargetTempConfig &config) { + const auto *preset_name = preset.c_str(); + + if (this->supports_heat_) { if (this->supports_two_points_) { - this->target_temperature_low = this->normal_config_.default_temperature_low; - this->target_temperature_high = this->normal_config_.default_temperature_high; - } else - this->target_temperature = this->normal_config_.default_temperature; - } else { - if (this->supports_two_points_) { - this->target_temperature_low = this->away_config_.default_temperature_low; - this->target_temperature_high = this->away_config_.default_temperature_high; - } else - this->target_temperature = this->away_config_.default_temperature; + ESP_LOGCONFIG(TAG, " %s Default Target Temperature Low: %.1f°C", preset_name, + config.default_temperature_low); + } else { + ESP_LOGCONFIG(TAG, " %s Default Target Temperature Low: %.1f°C", preset_name, config.default_temperature); + } + } + if ((this->supports_cool_) || (this->supports_fan_only_)) { + if (this->supports_two_points_) { + ESP_LOGCONFIG(TAG, " %s Default Target Temperature High: %.1f°C", preset_name, + config.default_temperature_high); + } else { + ESP_LOGCONFIG(TAG, " %s Default Target Temperature High: %.1f°C", preset_name, config.default_temperature); + } + } + + if (config.mode_.has_value()) { + ESP_LOGCONFIG(TAG, " %s Default Mode: %s", preset_name, + LOG_STR_ARG(climate::climate_mode_to_string(*config.mode_))); + } + if (config.fan_mode_.has_value()) { + ESP_LOGCONFIG(TAG, " %s Default Fan Mode: %s", preset_name, + LOG_STR_ARG(climate::climate_fan_mode_to_string(*config.fan_mode_))); + } + if (config.swing_mode_.has_value()) { + ESP_LOGCONFIG(TAG, " %s Default Swing Mode: %s", preset_name, + LOG_STR_ARG(climate::climate_swing_mode_to_string(*config.swing_mode_))); } - this->preset = away ? climate::CLIMATE_PRESET_AWAY : climate::CLIMATE_PRESET_HOME; } -void ThermostatClimate::set_normal_config(const ThermostatClimateTargetTempConfig &normal_config) { - this->normal_config_ = normal_config; +void ThermostatClimate::change_preset_(climate::ClimatePreset preset) { + auto config = this->preset_config_.find(preset); + + if (config != this->preset_config_.end()) { + ESP_LOGI(TAG, "Switching to preset %s", LOG_STR_ARG(climate::climate_preset_to_string(preset))); + this->change_preset_internal_(config->second); + + this->custom_preset.reset(); + this->preset = preset; + } else { + ESP_LOGE(TAG, "Preset %s is not configured, ignoring.", LOG_STR_ARG(climate::climate_preset_to_string(preset))); + } } -void ThermostatClimate::set_away_config(const ThermostatClimateTargetTempConfig &away_config) { - this->supports_away_ = true; - this->away_config_ = away_config; +void ThermostatClimate::change_custom_preset_(const std::string &custom_preset) { + auto config = this->custom_preset_config_.find(custom_preset); + + if (config != this->custom_preset_config_.end()) { + ESP_LOGI(TAG, "Switching to custom preset %s", custom_preset.c_str()); + this->change_preset_internal_(config->second); + + this->preset.reset(); + this->custom_preset = custom_preset; + } else { + ESP_LOGE(TAG, "Custom Preset %s is not configured, ignoring.", custom_preset.c_str()); + } +} + +void ThermostatClimate::change_preset_internal_(const ThermostatClimateTargetTempConfig &config) { + if (this->supports_two_points_) { + this->target_temperature_low = config.default_temperature_low; + this->target_temperature_high = config.default_temperature_high; + } else { + this->target_temperature = config.default_temperature; + } + + // Note: The mode, fan_mode, and swing_mode can all be defined on the preset but if the climate.control call + // also specifies them then the control's version will override these for that call + if (config.mode_.has_value()) { + this->mode = *config.mode_; + ESP_LOGV(TAG, "Setting mode to %s", LOG_STR_ARG(climate::climate_mode_to_string(*config.mode_))); + } + + if (config.fan_mode_.has_value()) { + this->fan_mode = *config.fan_mode_; + ESP_LOGV(TAG, "Setting fan mode to %s", LOG_STR_ARG(climate::climate_fan_mode_to_string(*config.fan_mode_))); + } + + if (config.swing_mode_.has_value()) { + ESP_LOGV(TAG, "Setting swing mode to %s", LOG_STR_ARG(climate::climate_swing_mode_to_string(*config.swing_mode_))); + this->swing_mode = *config.swing_mode_; + } + + // Fire any preset changed trigger if defined + if (this->preset != preset) { + Trigger<> *trig = this->preset_change_trigger_; + assert(trig != nullptr); + trig->trigger(); + } + + this->refresh(); +} + +void ThermostatClimate::set_preset_config(climate::ClimatePreset preset, + const ThermostatClimateTargetTempConfig &config) { + this->preset_config_[preset] = config; +} + +void ThermostatClimate::set_custom_preset_config(const std::string &name, + const ThermostatClimateTargetTempConfig &config) { + this->custom_preset_config_[name] = config; } ThermostatClimate::ThermostatClimate() @@ -963,7 +1058,8 @@ ThermostatClimate::ThermostatClimate() swing_mode_off_trigger_(new Trigger<>()), swing_mode_horizontal_trigger_(new Trigger<>()), swing_mode_vertical_trigger_(new Trigger<>()), - temperature_change_trigger_(new Trigger<>()) {} + temperature_change_trigger_(new Trigger<>()), + preset_change_trigger_(new Trigger<>()) {} void ThermostatClimate::set_default_mode(climate::ClimateMode default_mode) { this->default_mode_ = default_mode; } void ThermostatClimate::set_set_point_minimum_differential(float differential) { @@ -1112,23 +1208,11 @@ Trigger<> *ThermostatClimate::get_swing_mode_off_trigger() const { return this-> Trigger<> *ThermostatClimate::get_swing_mode_horizontal_trigger() const { return this->swing_mode_horizontal_trigger_; } Trigger<> *ThermostatClimate::get_swing_mode_vertical_trigger() const { return this->swing_mode_vertical_trigger_; } Trigger<> *ThermostatClimate::get_temperature_change_trigger() const { return this->temperature_change_trigger_; } +Trigger<> *ThermostatClimate::get_preset_change_trigger() const { return this->preset_change_trigger_; } void ThermostatClimate::dump_config() { LOG_CLIMATE("", "Thermostat", this); - if (this->supports_heat_) { - if (this->supports_two_points_) { - ESP_LOGCONFIG(TAG, " Default Target Temperature Low: %.1f°C", this->normal_config_.default_temperature_low); - } else { - ESP_LOGCONFIG(TAG, " Default Target Temperature Low: %.1f°C", this->normal_config_.default_temperature); - } - } - if ((this->supports_cool_) || (this->supports_fan_only_ && this->supports_fan_only_cooling_)) { - if (this->supports_two_points_) { - ESP_LOGCONFIG(TAG, " Default Target Temperature High: %.1f°C", this->normal_config_.default_temperature_high); - } else { - ESP_LOGCONFIG(TAG, " Default Target Temperature High: %.1f°C", this->normal_config_.default_temperature); - } - } + if (this->supports_two_points_) ESP_LOGCONFIG(TAG, " Minimum Set Point Differential: %.1f°C", this->set_point_minimum_differential_); ESP_LOGCONFIG(TAG, " Start-up Delay Enabled: %s", YESNO(this->use_startup_delay_)); @@ -1194,24 +1278,21 @@ void ThermostatClimate::dump_config() { ESP_LOGCONFIG(TAG, " Supports SWING MODE HORIZONTAL: %s", YESNO(this->supports_swing_mode_horizontal_)); ESP_LOGCONFIG(TAG, " Supports SWING MODE VERTICAL: %s", YESNO(this->supports_swing_mode_vertical_)); ESP_LOGCONFIG(TAG, " Supports TWO SET POINTS: %s", YESNO(this->supports_two_points_)); - ESP_LOGCONFIG(TAG, " Supports AWAY mode: %s", YESNO(this->supports_away_)); - if (this->supports_away_) { - if (this->supports_heat_) { - if (this->supports_two_points_) { - ESP_LOGCONFIG(TAG, " Away Default Target Temperature Low: %.1f°C", - this->away_config_.default_temperature_low); - } else { - ESP_LOGCONFIG(TAG, " Away Default Target Temperature Low: %.1f°C", this->away_config_.default_temperature); - } - } - if ((this->supports_cool_) || (this->supports_fan_only_)) { - if (this->supports_two_points_) { - ESP_LOGCONFIG(TAG, " Away Default Target Temperature High: %.1f°C", - this->away_config_.default_temperature_high); - } else { - ESP_LOGCONFIG(TAG, " Away Default Target Temperature High: %.1f°C", this->away_config_.default_temperature); - } - } + + ESP_LOGCONFIG(TAG, " Supported PRESETS: "); + for (auto &it : this->preset_config_) { + const auto *preset_name = LOG_STR_ARG(climate::climate_preset_to_string(it.first)); + + ESP_LOGCONFIG(TAG, " Supports %s: %s", preset_name, YESNO(true)); + this->dump_preset_config_(preset_name, it.second); + } + + ESP_LOGCONFIG(TAG, " Supported CUSTOM PRESETS: "); + for (auto &it : this->custom_preset_config_) { + const auto *preset_name = it.first.c_str(); + + ESP_LOGCONFIG(TAG, " Supports %s: %s", preset_name, YESNO(true)); + this->dump_preset_config_(preset_name, it.second); } } diff --git a/esphome/components/thermostat/thermostat_climate.h b/esphome/components/thermostat/thermostat_climate.h index 8d3e926752..c9231370ba 100644 --- a/esphome/components/thermostat/thermostat_climate.h +++ b/esphome/components/thermostat/thermostat_climate.h @@ -4,6 +4,7 @@ #include "esphome/core/automation.h" #include "esphome/components/climate/climate.h" #include "esphome/components/sensor/sensor.h" +#include namespace esphome { namespace thermostat { @@ -34,6 +35,10 @@ struct ThermostatClimateTargetTempConfig { ThermostatClimateTargetTempConfig(float default_temperature); ThermostatClimateTargetTempConfig(float default_temperature_low, float default_temperature_high); + void set_fan_mode(climate::ClimateFanMode fan_mode) { this->fan_mode_ = fan_mode; } + void set_swing_mode(climate::ClimateSwingMode swing_mode) { this->swing_mode_ = swing_mode; } + void set_mode(climate::ClimateMode mode) { this->mode_ = mode; } + float default_temperature{NAN}; float default_temperature_low{NAN}; float default_temperature_high{NAN}; @@ -41,6 +46,9 @@ struct ThermostatClimateTargetTempConfig { float cool_overrun_{NAN}; float heat_deadband_{NAN}; float heat_overrun_{NAN}; + optional fan_mode_{}; + optional swing_mode_{}; + optional mode_{}; }; class ThermostatClimate : public climate::Climate, public Component { @@ -94,8 +102,8 @@ class ThermostatClimate : public climate::Climate, public Component { void set_supports_swing_mode_vertical(bool supports_swing_mode_vertical); void set_supports_two_points(bool supports_two_points); - void set_normal_config(const ThermostatClimateTargetTempConfig &normal_config); - void set_away_config(const ThermostatClimateTargetTempConfig &away_config); + void set_preset_config(climate::ClimatePreset preset, const ThermostatClimateTargetTempConfig &config); + void set_custom_preset_config(const std::string &name, const ThermostatClimateTargetTempConfig &config); Trigger<> *get_cool_action_trigger() const; Trigger<> *get_supplemental_cool_action_trigger() const; @@ -124,6 +132,7 @@ class ThermostatClimate : public climate::Climate, public Component { Trigger<> *get_swing_mode_off_trigger() const; Trigger<> *get_swing_mode_vertical_trigger() const; Trigger<> *get_temperature_change_trigger() const; + Trigger<> *get_preset_change_trigger() const; /// Get current hysteresis values float cool_deadband(); float cool_overrun(); @@ -149,8 +158,14 @@ class ThermostatClimate : public climate::Climate, public Component { /// Override control to change settings of the climate device. void control(const climate::ClimateCall &call) override; - /// Change the away setting, will reset target temperatures to defaults. - void change_away_(bool away); + /// Change to a provided preset setting; will reset temperature, mode, fan, and swing modes accordingly + void change_preset_(climate::ClimatePreset preset); + /// Change to a provided custom preset setting; will reset temperature, mode, fan, and swing modes accordingly + void change_custom_preset_(const std::string &custom_preset); + + /// Applies the temperature, mode, fan, and swing modes of the provded config. + /// This is agnostic of custom vs built in preset + void change_preset_internal_(const ThermostatClimateTargetTempConfig &config); /// Return the traits of this controller. climate::ClimateTraits traits() override; @@ -210,6 +225,8 @@ class ThermostatClimate : public climate::Climate, public Component { bool supplemental_cooling_required_(); bool supplemental_heating_required_(); + void dump_preset_config_(const std::string &preset_name, const ThermostatClimateTargetTempConfig &config); + /// The sensor used for getting the current temperature sensor::Sensor *sensor_{nullptr}; @@ -267,11 +284,6 @@ class ThermostatClimate : public climate::Climate, public Component { /// A false value means that the controller has no such support. bool supports_two_points_{false}; - /// Whether the controller supports an "away" mode - /// - /// A false value means that the controller has no such mode. - bool supports_away_{false}; - /// Flags indicating if maximum allowable run time was exceeded bool cooling_max_runtime_exceeded_{false}; bool heating_max_runtime_exceeded_{false}; @@ -368,6 +380,9 @@ class ThermostatClimate : public climate::Climate, public Component { /// The trigger to call when the target temperature(s) change(es). Trigger<> *temperature_change_trigger_{nullptr}; + /// The triggr to call when the preset mode changes + Trigger<> *preset_change_trigger_{nullptr}; + /// A reference to the trigger that was previously active. /// /// This is so that the previous trigger can be stopped before enabling a new one @@ -409,10 +424,6 @@ class ThermostatClimate : public climate::Climate, public Component { /// Minimum allowable duration in seconds for action timers const uint8_t min_timer_duration_{1}; - /// Temperature data for normal/home and away modes - ThermostatClimateTargetTempConfig normal_config_{}; - ThermostatClimateTargetTempConfig away_config_{}; - /// Climate action timers std::vector timer_{ {"cool_run", false, 0, std::bind(&ThermostatClimate::cooling_max_run_time_timer_callback_, this)}, @@ -425,6 +436,11 @@ class ThermostatClimate : public climate::Climate, public Component { {"heat_off", false, 0, std::bind(&ThermostatClimate::heating_off_timer_callback_, this)}, {"heat_on", false, 0, std::bind(&ThermostatClimate::heating_on_timer_callback_, this)}, {"idle_on", false, 0, std::bind(&ThermostatClimate::idle_on_timer_callback_, this)}}; + + /// The set of standard preset configurations this thermostat supports (Eg. AWAY, ECO, etc) + std::map preset_config_{}; + /// The set of custom preset configurations this thermostat supports (eg. "My Custom Preset") + std::map custom_preset_config_{}; }; } // namespace thermostat From d2cefbf22495b3c62956a455c6bbc63daf40c96b Mon Sep 17 00:00:00 2001 From: Jan Grewe Date: Mon, 30 May 2022 21:29:57 +0200 Subject: [PATCH 0332/3388] Allow Prometheus component to export internal components (#3508) Co-authored-by: Jan Grewe --- esphome/components/prometheus/__init__.py | 8 +++++++- .../components/prometheus/prometheus_handler.cpp | 14 +++++++------- esphome/components/prometheus/prometheus_handler.h | 8 ++++++++ 3 files changed, 22 insertions(+), 8 deletions(-) diff --git a/esphome/components/prometheus/__init__.py b/esphome/components/prometheus/__init__.py index 45345f06e8..e7c0459251 100644 --- a/esphome/components/prometheus/__init__.py +++ b/esphome/components/prometheus/__init__.py @@ -1,6 +1,9 @@ import esphome.config_validation as cv import esphome.codegen as cg -from esphome.const import CONF_ID +from esphome.const import ( + CONF_ID, + CONF_INCLUDE_INTERNAL, +) from esphome.components.web_server_base import CONF_WEB_SERVER_BASE_ID from esphome.components import web_server_base @@ -15,6 +18,7 @@ CONFIG_SCHEMA = cv.Schema( cv.GenerateID(CONF_WEB_SERVER_BASE_ID): cv.use_id( web_server_base.WebServerBase ), + cv.Optional(CONF_INCLUDE_INTERNAL, default=False): cv.boolean, }, cv.only_with_arduino, ).extend(cv.COMPONENT_SCHEMA) @@ -27,3 +31,5 @@ async def to_code(config): var = cg.new_Pvariable(config[CONF_ID], paren) await cg.register_component(var, config) + + cg.add(var.set_include_internal(config[CONF_INCLUDE_INTERNAL])) diff --git a/esphome/components/prometheus/prometheus_handler.cpp b/esphome/components/prometheus/prometheus_handler.cpp index e4dd6b9043..a52347ba57 100644 --- a/esphome/components/prometheus/prometheus_handler.cpp +++ b/esphome/components/prometheus/prometheus_handler.cpp @@ -61,7 +61,7 @@ void PrometheusHandler::sensor_type_(AsyncResponseStream *stream) { stream->print(F("#TYPE esphome_sensor_failed GAUGE\n")); } void PrometheusHandler::sensor_row_(AsyncResponseStream *stream, sensor::Sensor *obj) { - if (obj->is_internal()) + if (obj->is_internal() && !this->include_internal_) return; if (!std::isnan(obj->state)) { // We have a valid value, output this value @@ -98,7 +98,7 @@ void PrometheusHandler::binary_sensor_type_(AsyncResponseStream *stream) { stream->print(F("#TYPE esphome_binary_sensor_failed GAUGE\n")); } void PrometheusHandler::binary_sensor_row_(AsyncResponseStream *stream, binary_sensor::BinarySensor *obj) { - if (obj->is_internal()) + if (obj->is_internal() && !this->include_internal_) return; if (obj->has_state()) { // We have a valid value, output this value @@ -134,7 +134,7 @@ void PrometheusHandler::fan_type_(AsyncResponseStream *stream) { stream->print(F("#TYPE esphome_fan_oscillation GAUGE\n")); } void PrometheusHandler::fan_row_(AsyncResponseStream *stream, fan::Fan *obj) { - if (obj->is_internal()) + if (obj->is_internal() && !this->include_internal_) return; stream->print(F("esphome_fan_failed{id=\"")); stream->print(obj->get_object_id().c_str()); @@ -179,7 +179,7 @@ void PrometheusHandler::light_type_(AsyncResponseStream *stream) { stream->print(F("#TYPE esphome_light_effect_active GAUGE\n")); } void PrometheusHandler::light_row_(AsyncResponseStream *stream, light::LightState *obj) { - if (obj->is_internal()) + if (obj->is_internal() && !this->include_internal_) return; // State stream->print(F("esphome_light_state{id=\"")); @@ -255,7 +255,7 @@ void PrometheusHandler::cover_type_(AsyncResponseStream *stream) { stream->print(F("#TYPE esphome_cover_failed GAUGE\n")); } void PrometheusHandler::cover_row_(AsyncResponseStream *stream, cover::Cover *obj) { - if (obj->is_internal()) + if (obj->is_internal() && !this->include_internal_) return; if (!std::isnan(obj->position)) { // We have a valid value, output this value @@ -298,7 +298,7 @@ void PrometheusHandler::switch_type_(AsyncResponseStream *stream) { stream->print(F("#TYPE esphome_switch_failed GAUGE\n")); } void PrometheusHandler::switch_row_(AsyncResponseStream *stream, switch_::Switch *obj) { - if (obj->is_internal()) + if (obj->is_internal() && !this->include_internal_) return; stream->print(F("esphome_switch_failed{id=\"")); stream->print(obj->get_object_id().c_str()); @@ -322,7 +322,7 @@ void PrometheusHandler::lock_type_(AsyncResponseStream *stream) { stream->print(F("#TYPE esphome_lock_failed GAUGE\n")); } void PrometheusHandler::lock_row_(AsyncResponseStream *stream, lock::Lock *obj) { - if (obj->is_internal()) + if (obj->is_internal() && !this->include_internal_) return; stream->print(F("esphome_lock_failed{id=\"")); stream->print(obj->get_object_id().c_str()); diff --git a/esphome/components/prometheus/prometheus_handler.h b/esphome/components/prometheus/prometheus_handler.h index 5c8d51c60f..b378e46ea3 100644 --- a/esphome/components/prometheus/prometheus_handler.h +++ b/esphome/components/prometheus/prometheus_handler.h @@ -13,6 +13,13 @@ class PrometheusHandler : public AsyncWebHandler, public Component { public: PrometheusHandler(web_server_base::WebServerBase *base) : base_(base) {} + /** Determine whether internal components should be exported as metrics. + * Defaults to false. + * + * @param include_internal Whether internal components should be exported. + */ + void set_include_internal(bool include_internal) { include_internal_ = include_internal; } + bool canHandle(AsyncWebServerRequest *request) override { if (request->method() == HTTP_GET) { if (request->url() == "/metrics") @@ -84,6 +91,7 @@ class PrometheusHandler : public AsyncWebHandler, public Component { #endif web_server_base::WebServerBase *base_; + bool include_internal_{false}; }; } // namespace prometheus From 708672ec7e18745ca76be2afffb05ca320986568 Mon Sep 17 00:00:00 2001 From: Joe Date: Mon, 30 May 2022 23:45:01 -0400 Subject: [PATCH 0333/3388] [BedJet] Add configurable heating strategy (#3519) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/bedjet/bedjet.cpp | 28 ++++++++++++++++++++++-- esphome/components/bedjet/bedjet.h | 10 +++++++-- esphome/components/bedjet/bedjet_const.h | 8 +++++++ esphome/components/bedjet/climate.py | 10 +++++++++ tests/test1.yaml | 1 + 5 files changed, 53 insertions(+), 4 deletions(-) diff --git a/esphome/components/bedjet/bedjet.cpp b/esphome/components/bedjet/bedjet.cpp index 38ed6206a8..493685448c 100644 --- a/esphome/components/bedjet/bedjet.cpp +++ b/esphome/components/bedjet/bedjet.cpp @@ -36,6 +36,14 @@ static uint8_t bedjet_fan_speed_to_step(const std::string &fan_step_percent) { return -1; } +static BedjetButton heat_button(BedjetHeatMode mode) { + BedjetButton btn = BTN_HEAT; + if (mode == HEAT_MODE_EXTENDED) { + btn = BTN_EXTHT; + } + return btn; +} + void Bedjet::upgrade_firmware() { auto *pkt = this->codec_->get_button_request(MAGIC_UPDATE); auto status = this->write_bedjet_packet_(pkt); @@ -117,7 +125,7 @@ void Bedjet::control(const ClimateCall &call) { pkt = this->codec_->get_button_request(BTN_OFF); break; case climate::CLIMATE_MODE_HEAT: - pkt = this->codec_->get_button_request(BTN_HEAT); + pkt = this->codec_->get_button_request(heat_button(this->heating_mode_)); break; case climate::CLIMATE_MODE_FAN_ONLY: pkt = this->codec_->get_button_request(BTN_COOL); @@ -186,6 +194,8 @@ void Bedjet::control(const ClimateCall &call) { pkt = this->codec_->get_button_request(BTN_M2); } else if (preset == "M3") { pkt = this->codec_->get_button_request(BTN_M3); + } else if (preset == "LTD HT") { + pkt = this->codec_->get_button_request(BTN_HEAT); } else if (preset == "EXT HT") { pkt = this->codec_->get_button_request(BTN_EXTHT); } else { @@ -557,11 +567,25 @@ bool Bedjet::update_status_() { break; case MODE_HEAT: + this->mode = climate::CLIMATE_MODE_HEAT; + this->action = climate::CLIMATE_ACTION_HEATING; + this->preset.reset(); + if (this->heating_mode_ == HEAT_MODE_EXTENDED) { + this->set_custom_preset_("LTD HT"); + } else { + this->custom_preset.reset(); + } + break; + case MODE_EXTHT: this->mode = climate::CLIMATE_MODE_HEAT; this->action = climate::CLIMATE_ACTION_HEATING; - this->custom_preset.reset(); this->preset.reset(); + if (this->heating_mode_ == HEAT_MODE_EXTENDED) { + this->custom_preset.reset(); + } else { + this->set_custom_preset_("EXT HT"); + } break; case MODE_COOL: diff --git a/esphome/components/bedjet/bedjet.h b/esphome/components/bedjet/bedjet.h index 750a20594f..5d66f6f252 100644 --- a/esphome/components/bedjet/bedjet.h +++ b/esphome/components/bedjet/bedjet.h @@ -40,6 +40,8 @@ class Bedjet : public climate::Climate, public esphome::ble_client::BLEClientNod void set_time_id(time::RealTimeClock *time_id) { this->time_id_ = time_id; } #endif void set_status_timeout(uint32_t timeout) { this->timeout_ = timeout; } + /** Sets the default strategy to use for climate::CLIMATE_MODE_HEAT. */ + void set_heating_mode(BedjetHeatMode mode) { this->heating_mode_ = mode; } /** Attempts to check for and apply firmware updates. */ void upgrade_firmware(); @@ -68,12 +70,15 @@ class Bedjet : public climate::Climate, public esphome::ble_client::BLEClientNod // We could fetch biodata from bedjet and set these names that way. // But then we have to invert the lookup in order to send the right preset. // For now, we can leave them as M1-3 to match the remote buttons. - // EXT HT added to match remote button. - "EXT HT", "M1", "M2", "M3", }); + if (this->heating_mode_ == HEAT_MODE_EXTENDED) { + traits.add_supported_custom_preset("LTD HT"); + } else { + traits.add_supported_custom_preset("EXT HT"); + } traits.set_visual_min_temperature(19.0); traits.set_visual_max_temperature(43.0); traits.set_visual_temperature_step(1.0); @@ -90,6 +95,7 @@ class Bedjet : public climate::Climate, public esphome::ble_client::BLEClientNod #endif uint32_t timeout_{DEFAULT_STATUS_TIMEOUT}; + BedjetHeatMode heating_mode_ = HEAT_MODE_HEAT; static const uint32_t MIN_NOTIFY_THROTTLE = 5000; static const uint32_t NOTIFY_WARN_THRESHOLD = 300000; diff --git a/esphome/components/bedjet/bedjet_const.h b/esphome/components/bedjet/bedjet_const.h index ae10ca1885..16f73717c6 100644 --- a/esphome/components/bedjet/bedjet_const.h +++ b/esphome/components/bedjet/bedjet_const.h @@ -24,6 +24,14 @@ enum BedjetMode : uint8_t { MODE_WAIT = 6, }; +/** Optional heating strategies to use for climate::CLIMATE_MODE_HEAT. */ +enum BedjetHeatMode { + /// HVACMode.HEAT is handled using BTN_HEAT (default) + HEAT_MODE_HEAT, + /// HVACMode.HEAT is handled using BTN_EXTHT + HEAT_MODE_EXTENDED, +}; + enum BedjetButton : uint8_t { /// Turn BedJet off BTN_OFF = 0x1, diff --git a/esphome/components/bedjet/climate.py b/esphome/components/bedjet/climate.py index 49353934f6..d718ba9969 100644 --- a/esphome/components/bedjet/climate.py +++ b/esphome/components/bedjet/climate.py @@ -2,6 +2,7 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import climate, ble_client, time from esphome.const import ( + CONF_HEAT_MODE, CONF_ID, CONF_RECEIVE_TIMEOUT, CONF_TIME_ID, @@ -14,11 +15,19 @@ bedjet_ns = cg.esphome_ns.namespace("bedjet") Bedjet = bedjet_ns.class_( "Bedjet", climate.Climate, ble_client.BLEClientNode, cg.PollingComponent ) +BedjetHeatMode = bedjet_ns.enum("BedjetHeatMode") +BEDJET_HEAT_MODES = { + "heat": BedjetHeatMode.HEAT_MODE_HEAT, + "extended": BedjetHeatMode.HEAT_MODE_EXTENDED, +} CONFIG_SCHEMA = ( climate.CLIMATE_SCHEMA.extend( { cv.GenerateID(): cv.declare_id(Bedjet), + cv.Optional(CONF_HEAT_MODE, default="heat"): cv.enum( + BEDJET_HEAT_MODES, lower=True + ), cv.Optional(CONF_TIME_ID): cv.use_id(time.RealTimeClock), cv.Optional( CONF_RECEIVE_TIMEOUT, default="0s" @@ -35,6 +44,7 @@ async def to_code(config): await cg.register_component(var, config) await climate.register_climate(var, config) await ble_client.register_ble_node(var, config) + cg.add(var.set_heating_mode(config[CONF_HEAT_MODE])) if CONF_TIME_ID in config: time_ = await cg.get_variable(config[CONF_TIME_ID]) cg.add(var.set_time_id(time_)) diff --git a/tests/test1.yaml b/tests/test1.yaml index 52aa03c371..1b8ed7e370 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -1886,6 +1886,7 @@ climate: - platform: bedjet name: My Bedjet ble_client_id: my_bedjet_ble_client + heat_mode: extended script: - id: climate_custom From 5aa42e5e66a5a65528e3dd6218e17b59970dfc82 Mon Sep 17 00:00:00 2001 From: jimtng <2554958+jimtng@users.noreply.github.com> Date: Tue, 31 May 2022 14:45:18 +1000 Subject: [PATCH 0334/3388] Add variable substitutions for !include (#3510) --- esphome/components/substitutions/__init__.py | 33 +++++++------- esphome/yaml_util.py | 44 ++++++++++++++++++- .../fixtures/yaml_util/includes/included.yaml | 2 + .../fixtures/yaml_util/includes/list.yaml | 2 + .../fixtures/yaml_util/includes/scalar.yaml | 1 + .../fixtures/yaml_util/includetest.yaml | 17 +++++++ tests/unit_tests/test_yaml_util.py | 13 ++++++ 7 files changed, 95 insertions(+), 17 deletions(-) create mode 100644 tests/unit_tests/fixtures/yaml_util/includes/included.yaml create mode 100644 tests/unit_tests/fixtures/yaml_util/includes/list.yaml create mode 100644 tests/unit_tests/fixtures/yaml_util/includes/scalar.yaml create mode 100644 tests/unit_tests/fixtures/yaml_util/includetest.yaml create mode 100644 tests/unit_tests/test_yaml_util.py diff --git a/esphome/components/substitutions/__init__.py b/esphome/components/substitutions/__init__.py index 6188b14b35..5a3da1abbe 100644 --- a/esphome/components/substitutions/__init__.py +++ b/esphome/components/substitutions/__init__.py @@ -48,7 +48,7 @@ VARIABLE_PROG = re.compile( ) -def _expand_substitutions(substitutions, value, path): +def _expand_substitutions(substitutions, value, path, ignore_missing): if "$" not in value: return value @@ -66,13 +66,14 @@ def _expand_substitutions(substitutions, value, path): if name.startswith("{") and name.endswith("}"): name = name[1:-1] if name not in substitutions: - _LOGGER.warning( - "Found '%s' (see %s) which looks like a substitution, but '%s' was " - "not declared", - orig_value, - "->".join(str(x) for x in path), - name, - ) + if not ignore_missing: + _LOGGER.warning( + "Found '%s' (see %s) which looks like a substitution, but '%s' was " + "not declared", + orig_value, + "->".join(str(x) for x in path), + name, + ) i = j continue @@ -92,37 +93,37 @@ def _expand_substitutions(substitutions, value, path): return value -def _substitute_item(substitutions, item, path): +def _substitute_item(substitutions, item, path, ignore_missing): if isinstance(item, list): for i, it in enumerate(item): - sub = _substitute_item(substitutions, it, path + [i]) + sub = _substitute_item(substitutions, it, path + [i], ignore_missing) if sub is not None: item[i] = sub elif isinstance(item, dict): replace_keys = [] for k, v in item.items(): if path or k != CONF_SUBSTITUTIONS: - sub = _substitute_item(substitutions, k, path + [k]) + sub = _substitute_item(substitutions, k, path + [k], ignore_missing) if sub is not None: replace_keys.append((k, sub)) - sub = _substitute_item(substitutions, v, path + [k]) + sub = _substitute_item(substitutions, v, path + [k], ignore_missing) if sub is not None: item[k] = sub for old, new in replace_keys: item[new] = merge_config(item.get(old), item.get(new)) del item[old] elif isinstance(item, str): - sub = _expand_substitutions(substitutions, item, path) + sub = _expand_substitutions(substitutions, item, path, ignore_missing) if sub != item: return sub elif isinstance(item, core.Lambda): - sub = _expand_substitutions(substitutions, item.value, path) + sub = _expand_substitutions(substitutions, item.value, path, ignore_missing) if sub != item: item.value = sub return None -def do_substitution_pass(config, command_line_substitutions): +def do_substitution_pass(config, command_line_substitutions, ignore_missing=False): if CONF_SUBSTITUTIONS not in config and not command_line_substitutions: return @@ -151,4 +152,4 @@ def do_substitution_pass(config, command_line_substitutions): config[CONF_SUBSTITUTIONS] = substitutions # Move substitutions to the first place to replace substitutions in them correctly config.move_to_end(CONF_SUBSTITUTIONS, False) - _substitute_item(substitutions, config, []) + _substitute_item(substitutions, config, [], ignore_missing) diff --git a/esphome/yaml_util.py b/esphome/yaml_util.py index 57009be57e..75aec0edc8 100644 --- a/esphome/yaml_util.py +++ b/esphome/yaml_util.py @@ -251,7 +251,49 @@ class ESPHomeLoader(yaml.SafeLoader): # pylint: disable=too-many-ancestors @_add_data_ref def construct_include(self, node): - return _load_yaml_internal(self._rel_path(node.value)) + def extract_file_vars(node): + fields = self.construct_yaml_map(node) + file = fields.get("file") + if file is None: + raise yaml.MarkedYAMLError("Must include 'file'", node.start_mark) + vars = fields.get("vars") + if vars: + vars = {k: str(v) for k, v in vars.items()} + return file, vars + + def substitute_vars(config, vars): + from esphome.const import CONF_SUBSTITUTIONS + from esphome.components import substitutions + + org_subs = None + result = config + if not isinstance(config, dict): + # when the included yaml contains a list or a scalar + # wrap it into an OrderedDict because do_substitution_pass expects it + result = OrderedDict([("yaml", config)]) + elif CONF_SUBSTITUTIONS in result: + org_subs = result.pop(CONF_SUBSTITUTIONS) + + result[CONF_SUBSTITUTIONS] = vars + # Ignore missing vars that refer to the top level substitutions + substitutions.do_substitution_pass(result, None, ignore_missing=True) + result.pop(CONF_SUBSTITUTIONS) + + if not isinstance(config, dict): + result = result["yaml"] # unwrap the result + elif org_subs: + result[CONF_SUBSTITUTIONS] = org_subs + return result + + if isinstance(node, yaml.nodes.MappingNode): + file, vars = extract_file_vars(node) + else: + file, vars = node.value, None + + result = _load_yaml_internal(self._rel_path(file)) + if vars: + result = substitute_vars(result, vars) + return result @_add_data_ref def construct_include_dir_list(self, node): diff --git a/tests/unit_tests/fixtures/yaml_util/includes/included.yaml b/tests/unit_tests/fixtures/yaml_util/includes/included.yaml new file mode 100644 index 0000000000..e9fca324a3 --- /dev/null +++ b/tests/unit_tests/fixtures/yaml_util/includes/included.yaml @@ -0,0 +1,2 @@ +--- +ssid: ${name} diff --git a/tests/unit_tests/fixtures/yaml_util/includes/list.yaml b/tests/unit_tests/fixtures/yaml_util/includes/list.yaml new file mode 100644 index 0000000000..2fb3838631 --- /dev/null +++ b/tests/unit_tests/fixtures/yaml_util/includes/list.yaml @@ -0,0 +1,2 @@ +--- +- ${var1} diff --git a/tests/unit_tests/fixtures/yaml_util/includes/scalar.yaml b/tests/unit_tests/fixtures/yaml_util/includes/scalar.yaml new file mode 100644 index 0000000000..ddd2156b5e --- /dev/null +++ b/tests/unit_tests/fixtures/yaml_util/includes/scalar.yaml @@ -0,0 +1 @@ +${var1} diff --git a/tests/unit_tests/fixtures/yaml_util/includetest.yaml b/tests/unit_tests/fixtures/yaml_util/includetest.yaml new file mode 100644 index 0000000000..959283df60 --- /dev/null +++ b/tests/unit_tests/fixtures/yaml_util/includetest.yaml @@ -0,0 +1,17 @@ +--- +substitutions: + name: original + +wifi: !include + file: includes/included.yaml + vars: + name: my_custom_ssid + +esphome: + # should be substituted as 'original', not overwritten by vars in the !include above + name: ${name} + name_add_mac_suffix: true + platform: esp8266 + board: !include { file: includes/scalar.yaml, vars: { var1: nodemcu } } + + libraries: !include { file: includes/list.yaml, vars: { var1: Wire } } diff --git a/tests/unit_tests/test_yaml_util.py b/tests/unit_tests/test_yaml_util.py new file mode 100644 index 0000000000..8ee991f5b3 --- /dev/null +++ b/tests/unit_tests/test_yaml_util.py @@ -0,0 +1,13 @@ +from esphome import yaml_util +from esphome.components import substitutions + + +def test_include_with_vars(fixture_path): + yaml_file = fixture_path / "yaml_util" / "includetest.yaml" + + actual = yaml_util.load_yaml(yaml_file) + substitutions.do_substitution_pass(actual, None) + assert actual["esphome"]["name"] == "original" + assert actual["esphome"]["libraries"][0] == "Wire" + assert actual["esphome"]["board"] == "nodemcu" + assert actual["wifi"]["ssid"] == "my_custom_ssid" From a922efeafa9c46bd64fb9de7e88b05ea92de3d2c Mon Sep 17 00:00:00 2001 From: Wolfgang Tremmel Date: Tue, 31 May 2022 06:49:18 +0200 Subject: [PATCH 0335/3388] Change rain intensity sensor string (#3511) --- esphome/components/hydreon_rgxx/sensor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/hydreon_rgxx/sensor.py b/esphome/components/hydreon_rgxx/sensor.py index 409500305a..c604f8d3c1 100644 --- a/esphome/components/hydreon_rgxx/sensor.py +++ b/esphome/components/hydreon_rgxx/sensor.py @@ -37,7 +37,7 @@ SUPPORTED_SENSORS = { PROTOCOL_NAMES = { CONF_MOISTURE: "R", CONF_ACC: "Acc", - CONF_R_INT: "Rint", + CONF_R_INT: "RInt", CONF_EVENT_ACC: "EventAcc", CONF_TOTAL_ACC: "TotalAcc", } From 6221f6d47d9522e62c1894bccb83c8cbc2bbb7f0 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 2 Jun 2022 17:00:17 +1200 Subject: [PATCH 0336/3388] Implement Media Player and I2S Media player (#3487) --- CODEOWNERS | 2 + esphome/components/api/api.proto | 62 +++- esphome/components/api/api_connection.cpp | 46 +++ esphome/components/api/api_connection.h | 5 + esphome/components/api/api_pb2.cpp | 278 ++++++++++++++++++ esphome/components/api/api_pb2.h | 67 +++++ esphome/components/api/api_pb2_service.cpp | 42 +++ esphome/components/api/api_pb2_service.h | 15 + esphome/components/api/api_server.cpp | 9 + esphome/components/api/api_server.h | 3 + esphome/components/api/list_entities.cpp | 6 + esphome/components/api/list_entities.h | 3 + esphome/components/api/subscribe_state.cpp | 5 + esphome/components/api/subscribe_state.h | 3 + esphome/components/i2s_audio/__init__.py | 0 .../i2s_audio/i2s_audio_media_player.cpp | 132 +++++++++ .../i2s_audio/i2s_audio_media_player.h | 63 ++++ esphome/components/i2s_audio/media_player.py | 94 ++++++ esphome/components/media_player/__init__.py | 64 ++++ .../components/media_player/media_player.cpp | 115 ++++++++ .../components/media_player/media_player.h | 91 ++++++ esphome/core/application.h | 19 ++ esphome/core/component_iterator.cpp | 18 ++ esphome/core/component_iterator.h | 6 + esphome/core/controller.cpp | 6 + esphome/core/controller.h | 6 + esphome/core/defines.h | 1 + platformio.ini | 2 + tests/test4.yaml | 9 + 29 files changed, 1170 insertions(+), 2 deletions(-) create mode 100644 esphome/components/i2s_audio/__init__.py create mode 100644 esphome/components/i2s_audio/i2s_audio_media_player.cpp create mode 100644 esphome/components/i2s_audio/i2s_audio_media_player.h create mode 100644 esphome/components/i2s_audio/media_player.py create mode 100644 esphome/components/media_player/__init__.py create mode 100644 esphome/components/media_player/media_player.cpp create mode 100644 esphome/components/media_player/media_player.h diff --git a/CODEOWNERS b/CODEOWNERS index 3e82a372ce..d5ce5e6920 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -88,6 +88,7 @@ esphome/components/honeywellabp/* @RubyBailey esphome/components/hrxl_maxsonar_wr/* @netmikey esphome/components/hydreon_rgxx/* @functionpointer esphome/components/i2c/* @esphome/core +esphome/components/i2s_audio/* @jesserockz esphome/components/improv_serial/* @esphome/core esphome/components/ina260/* @MrEditor97 esphome/components/inkbird_ibsth1_mini/* @fkirill @@ -119,6 +120,7 @@ esphome/components/mcp47a1/* @jesserockz esphome/components/mcp9808/* @k7hpn esphome/components/md5/* @esphome/core esphome/components/mdns/* @esphome/core +esphome/components/media_player/* @jesserockz esphome/components/midea/* @dudanov esphome/components/midea_ir/* @dudanov esphome/components/mitsubishi/* @RubyBailey diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index bd39893825..3e9a62f3d8 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -42,6 +42,7 @@ service APIConnection { rpc select_command (SelectCommandRequest) returns (void) {} rpc button_command (ButtonCommandRequest) returns (void) {} rpc lock_command (LockCommandRequest) returns (void) {} + rpc media_player_command (MediaPlayerCommandRequest) returns (void) {} } @@ -991,7 +992,7 @@ message ListEntitiesLockResponse { bool supports_open = 9; bool requires_code = 10; - # Not yet implemented: + // Not yet implemented: string code_format = 11; } message LockStateResponse { @@ -1010,7 +1011,7 @@ message LockCommandRequest { fixed32 key = 1; LockCommand command = 2; - # Not yet implemented: + // Not yet implemented: bool has_code = 3; string code = 4; } @@ -1040,3 +1041,60 @@ message ButtonCommandRequest { fixed32 key = 1; } +// ==================== MEDIA PLAYER ==================== +enum MediaPlayerState { + MEDIA_PLAYER_STATE_NONE = 0; + MEDIA_PLAYER_STATE_IDLE = 1; + MEDIA_PLAYER_STATE_PLAYING = 2; + MEDIA_PLAYER_STATE_PAUSED = 3; +} +enum MediaPlayerCommand { + MEDIA_PLAYER_COMMAND_PLAY = 0; + MEDIA_PLAYER_COMMAND_PAUSE = 1; + MEDIA_PLAYER_COMMAND_STOP = 2; + MEDIA_PLAYER_COMMAND_MUTE = 3; + MEDIA_PLAYER_COMMAND_UNMUTE = 4; +} +message ListEntitiesMediaPlayerResponse { + option (id) = 63; + option (source) = SOURCE_SERVER; + option (ifdef) = "USE_MEDIA_PLAYER"; + + string object_id = 1; + fixed32 key = 2; + string name = 3; + string unique_id = 4; + + string icon = 5; + bool disabled_by_default = 6; + EntityCategory entity_category = 7; + + bool supports_pause = 8; +} +message MediaPlayerStateResponse { + option (id) = 64; + option (source) = SOURCE_SERVER; + option (ifdef) = "USE_MEDIA_PLAYER"; + option (no_delay) = true; + fixed32 key = 1; + MediaPlayerState state = 2; + float volume = 3; + bool muted = 4; +} +message MediaPlayerCommandRequest { + option (id) = 65; + option (source) = SOURCE_CLIENT; + option (ifdef) = "USE_MEDIA_PLAYER"; + option (no_delay) = true; + + fixed32 key = 1; + + bool has_command = 2; + MediaPlayerCommand command = 3; + + bool has_volume = 4; + float volume = 5; + + bool has_media_url = 6; + string media_url = 7; +} diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 4f399d95d0..9028034c90 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -733,6 +733,52 @@ void APIConnection::lock_command(const LockCommandRequest &msg) { } #endif +#ifdef USE_MEDIA_PLAYER +bool APIConnection::send_media_player_state(media_player::MediaPlayer *media_player) { + if (!this->state_subscription_) + return false; + + MediaPlayerStateResponse resp{}; + resp.key = media_player->get_object_id_hash(); + resp.state = static_cast(media_player->state); + resp.volume = media_player->volume; + resp.muted = media_player->is_muted(); + return this->send_media_player_state_response(resp); +} +bool APIConnection::send_media_player_info(media_player::MediaPlayer *media_player) { + ListEntitiesMediaPlayerResponse msg; + msg.key = media_player->get_object_id_hash(); + msg.object_id = media_player->get_object_id(); + msg.name = media_player->get_name(); + msg.unique_id = get_default_unique_id("media_player", media_player); + msg.icon = media_player->get_icon(); + msg.disabled_by_default = media_player->is_disabled_by_default(); + msg.entity_category = static_cast(media_player->get_entity_category()); + + auto traits = media_player->get_traits(); + msg.supports_pause = traits.get_supports_pause(); + + return this->send_list_entities_media_player_response(msg); +} +void APIConnection::media_player_command(const MediaPlayerCommandRequest &msg) { + media_player::MediaPlayer *media_player = App.get_media_player_by_key(msg.key); + if (media_player == nullptr) + return; + + auto call = media_player->make_call(); + if (msg.has_command) { + call.set_command(static_cast(msg.command)); + } + if (msg.has_volume) { + call.set_volume(msg.volume); + } + if (msg.has_media_url) { + call.set_media_url(msg.media_url); + } + call.perform(); +} +#endif + #ifdef USE_ESP32_CAMERA void APIConnection::send_camera_state(std::shared_ptr image) { if (!this->state_subscription_) diff --git a/esphome/components/api/api_connection.h b/esphome/components/api/api_connection.h index 10f0becc54..0787d2f7eb 100644 --- a/esphome/components/api/api_connection.h +++ b/esphome/components/api/api_connection.h @@ -82,6 +82,11 @@ class APIConnection : public APIServerConnection { bool send_lock_state(lock::Lock *a_lock, lock::LockState state); bool send_lock_info(lock::Lock *a_lock); void lock_command(const LockCommandRequest &msg) override; +#endif +#ifdef USE_MEDIA_PLAYER + bool send_media_player_state(media_player::MediaPlayer *media_player); + bool send_media_player_info(media_player::MediaPlayer *media_player); + void media_player_command(const MediaPlayerCommandRequest &msg) override; #endif bool send_log_message(int level, const char *tag, const char *line); void send_homeassistant_service_call(const HomeassistantServiceResponse &call) { diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index 5a78587473..70f909c07a 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -308,6 +308,36 @@ template<> const char *proto_enum_to_string(enums::LockComma return "UNKNOWN"; } } +template<> const char *proto_enum_to_string(enums::MediaPlayerState value) { + switch (value) { + case enums::MEDIA_PLAYER_STATE_NONE: + return "MEDIA_PLAYER_STATE_NONE"; + case enums::MEDIA_PLAYER_STATE_IDLE: + return "MEDIA_PLAYER_STATE_IDLE"; + case enums::MEDIA_PLAYER_STATE_PLAYING: + return "MEDIA_PLAYER_STATE_PLAYING"; + case enums::MEDIA_PLAYER_STATE_PAUSED: + return "MEDIA_PLAYER_STATE_PAUSED"; + default: + return "UNKNOWN"; + } +} +template<> const char *proto_enum_to_string(enums::MediaPlayerCommand value) { + switch (value) { + case enums::MEDIA_PLAYER_COMMAND_PLAY: + return "MEDIA_PLAYER_COMMAND_PLAY"; + case enums::MEDIA_PLAYER_COMMAND_PAUSE: + return "MEDIA_PLAYER_COMMAND_PAUSE"; + case enums::MEDIA_PLAYER_COMMAND_STOP: + return "MEDIA_PLAYER_COMMAND_STOP"; + case enums::MEDIA_PLAYER_COMMAND_MUTE: + return "MEDIA_PLAYER_COMMAND_MUTE"; + case enums::MEDIA_PLAYER_COMMAND_UNMUTE: + return "MEDIA_PLAYER_COMMAND_UNMUTE"; + default: + return "UNKNOWN"; + } +} bool HelloRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { case 1: { @@ -4574,6 +4604,254 @@ void ButtonCommandRequest::dump_to(std::string &out) const { out.append("}"); } #endif +bool ListEntitiesMediaPlayerResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { + switch (field_id) { + case 6: { + this->disabled_by_default = value.as_bool(); + return true; + } + case 7: { + this->entity_category = value.as_enum(); + return true; + } + case 8: { + this->supports_pause = value.as_bool(); + return true; + } + default: + return false; + } +} +bool ListEntitiesMediaPlayerResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { + switch (field_id) { + case 1: { + this->object_id = value.as_string(); + return true; + } + case 3: { + this->name = value.as_string(); + return true; + } + case 4: { + this->unique_id = value.as_string(); + return true; + } + case 5: { + this->icon = value.as_string(); + return true; + } + default: + return false; + } +} +bool ListEntitiesMediaPlayerResponse::decode_32bit(uint32_t field_id, Proto32Bit value) { + switch (field_id) { + case 2: { + this->key = value.as_fixed32(); + return true; + } + default: + return false; + } +} +void ListEntitiesMediaPlayerResponse::encode(ProtoWriteBuffer buffer) const { + buffer.encode_string(1, this->object_id); + buffer.encode_fixed32(2, this->key); + buffer.encode_string(3, this->name); + buffer.encode_string(4, this->unique_id); + buffer.encode_string(5, this->icon); + buffer.encode_bool(6, this->disabled_by_default); + buffer.encode_enum(7, this->entity_category); + buffer.encode_bool(8, this->supports_pause); +} +#ifdef HAS_PROTO_MESSAGE_DUMP +void ListEntitiesMediaPlayerResponse::dump_to(std::string &out) const { + __attribute__((unused)) char buffer[64]; + out.append("ListEntitiesMediaPlayerResponse {\n"); + out.append(" object_id: "); + out.append("'").append(this->object_id).append("'"); + out.append("\n"); + + out.append(" key: "); + sprintf(buffer, "%u", this->key); + out.append(buffer); + out.append("\n"); + + out.append(" name: "); + out.append("'").append(this->name).append("'"); + out.append("\n"); + + out.append(" unique_id: "); + out.append("'").append(this->unique_id).append("'"); + out.append("\n"); + + out.append(" icon: "); + out.append("'").append(this->icon).append("'"); + out.append("\n"); + + out.append(" disabled_by_default: "); + out.append(YESNO(this->disabled_by_default)); + out.append("\n"); + + out.append(" entity_category: "); + out.append(proto_enum_to_string(this->entity_category)); + out.append("\n"); + + out.append(" supports_pause: "); + out.append(YESNO(this->supports_pause)); + out.append("\n"); + out.append("}"); +} +#endif +bool MediaPlayerStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { + switch (field_id) { + case 2: { + this->state = value.as_enum(); + return true; + } + case 4: { + this->muted = value.as_bool(); + return true; + } + default: + return false; + } +} +bool MediaPlayerStateResponse::decode_32bit(uint32_t field_id, Proto32Bit value) { + switch (field_id) { + case 1: { + this->key = value.as_fixed32(); + return true; + } + case 3: { + this->volume = value.as_float(); + return true; + } + default: + return false; + } +} +void MediaPlayerStateResponse::encode(ProtoWriteBuffer buffer) const { + buffer.encode_fixed32(1, this->key); + buffer.encode_enum(2, this->state); + buffer.encode_float(3, this->volume); + buffer.encode_bool(4, this->muted); +} +#ifdef HAS_PROTO_MESSAGE_DUMP +void MediaPlayerStateResponse::dump_to(std::string &out) const { + __attribute__((unused)) char buffer[64]; + out.append("MediaPlayerStateResponse {\n"); + out.append(" key: "); + sprintf(buffer, "%u", this->key); + out.append(buffer); + out.append("\n"); + + out.append(" state: "); + out.append(proto_enum_to_string(this->state)); + out.append("\n"); + + out.append(" volume: "); + sprintf(buffer, "%g", this->volume); + out.append(buffer); + out.append("\n"); + + out.append(" muted: "); + out.append(YESNO(this->muted)); + out.append("\n"); + out.append("}"); +} +#endif +bool MediaPlayerCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { + switch (field_id) { + case 2: { + this->has_command = value.as_bool(); + return true; + } + case 3: { + this->command = value.as_enum(); + return true; + } + case 4: { + this->has_volume = value.as_bool(); + return true; + } + case 6: { + this->has_media_url = value.as_bool(); + return true; + } + default: + return false; + } +} +bool MediaPlayerCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { + switch (field_id) { + case 7: { + this->media_url = value.as_string(); + return true; + } + default: + return false; + } +} +bool MediaPlayerCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) { + switch (field_id) { + case 1: { + this->key = value.as_fixed32(); + return true; + } + case 5: { + this->volume = value.as_float(); + return true; + } + default: + return false; + } +} +void MediaPlayerCommandRequest::encode(ProtoWriteBuffer buffer) const { + buffer.encode_fixed32(1, this->key); + buffer.encode_bool(2, this->has_command); + buffer.encode_enum(3, this->command); + buffer.encode_bool(4, this->has_volume); + buffer.encode_float(5, this->volume); + buffer.encode_bool(6, this->has_media_url); + buffer.encode_string(7, this->media_url); +} +#ifdef HAS_PROTO_MESSAGE_DUMP +void MediaPlayerCommandRequest::dump_to(std::string &out) const { + __attribute__((unused)) char buffer[64]; + out.append("MediaPlayerCommandRequest {\n"); + out.append(" key: "); + sprintf(buffer, "%u", this->key); + out.append(buffer); + out.append("\n"); + + out.append(" has_command: "); + out.append(YESNO(this->has_command)); + out.append("\n"); + + out.append(" command: "); + out.append(proto_enum_to_string(this->command)); + out.append("\n"); + + out.append(" has_volume: "); + out.append(YESNO(this->has_volume)); + out.append("\n"); + + out.append(" volume: "); + sprintf(buffer, "%g", this->volume); + out.append(buffer); + out.append("\n"); + + out.append(" has_media_url: "); + out.append(YESNO(this->has_media_url)); + out.append("\n"); + + out.append(" media_url: "); + out.append("'").append(this->media_url).append("'"); + out.append("\n"); + out.append("}"); +} +#endif } // namespace api } // namespace esphome diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index 28c0a7ce88..ec1cdc35ac 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -141,6 +141,19 @@ enum LockCommand : uint32_t { LOCK_LOCK = 1, LOCK_OPEN = 2, }; +enum MediaPlayerState : uint32_t { + MEDIA_PLAYER_STATE_NONE = 0, + MEDIA_PLAYER_STATE_IDLE = 1, + MEDIA_PLAYER_STATE_PLAYING = 2, + MEDIA_PLAYER_STATE_PAUSED = 3, +}; +enum MediaPlayerCommand : uint32_t { + MEDIA_PLAYER_COMMAND_PLAY = 0, + MEDIA_PLAYER_COMMAND_PAUSE = 1, + MEDIA_PLAYER_COMMAND_STOP = 2, + MEDIA_PLAYER_COMMAND_MUTE = 3, + MEDIA_PLAYER_COMMAND_UNMUTE = 4, +}; } // namespace enums @@ -1146,6 +1159,60 @@ class ButtonCommandRequest : public ProtoMessage { protected: bool decode_32bit(uint32_t field_id, Proto32Bit value) override; }; +class ListEntitiesMediaPlayerResponse : public ProtoMessage { + public: + std::string object_id{}; + uint32_t key{0}; + std::string name{}; + std::string unique_id{}; + std::string icon{}; + bool disabled_by_default{false}; + enums::EntityCategory entity_category{}; + bool supports_pause{false}; + void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP + void dump_to(std::string &out) const override; +#endif + + protected: + bool decode_32bit(uint32_t field_id, Proto32Bit value) override; + bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; + bool decode_varint(uint32_t field_id, ProtoVarInt value) override; +}; +class MediaPlayerStateResponse : public ProtoMessage { + public: + uint32_t key{0}; + enums::MediaPlayerState state{}; + float volume{0.0f}; + bool muted{false}; + void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP + void dump_to(std::string &out) const override; +#endif + + protected: + bool decode_32bit(uint32_t field_id, Proto32Bit value) override; + bool decode_varint(uint32_t field_id, ProtoVarInt value) override; +}; +class MediaPlayerCommandRequest : public ProtoMessage { + public: + uint32_t key{0}; + bool has_command{false}; + enums::MediaPlayerCommand command{}; + bool has_volume{false}; + float volume{0.0f}; + bool has_media_url{false}; + std::string media_url{}; + void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP + void dump_to(std::string &out) const override; +#endif + + protected: + bool decode_32bit(uint32_t field_id, Proto32Bit value) override; + bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; + bool decode_varint(uint32_t field_id, ProtoVarInt value) override; +}; } // namespace api } // namespace esphome diff --git a/esphome/components/api/api_pb2_service.cpp b/esphome/components/api/api_pb2_service.cpp index d981a3bf4e..bd146cb54d 100644 --- a/esphome/components/api/api_pb2_service.cpp +++ b/esphome/components/api/api_pb2_service.cpp @@ -310,6 +310,24 @@ bool APIServerConnectionBase::send_list_entities_button_response(const ListEntit #endif #ifdef USE_BUTTON #endif +#ifdef USE_MEDIA_PLAYER +bool APIServerConnectionBase::send_list_entities_media_player_response(const ListEntitiesMediaPlayerResponse &msg) { +#ifdef HAS_PROTO_MESSAGE_DUMP + ESP_LOGVV(TAG, "send_list_entities_media_player_response: %s", msg.dump().c_str()); +#endif + return this->send_message_(msg, 63); +} +#endif +#ifdef USE_MEDIA_PLAYER +bool APIServerConnectionBase::send_media_player_state_response(const MediaPlayerStateResponse &msg) { +#ifdef HAS_PROTO_MESSAGE_DUMP + ESP_LOGVV(TAG, "send_media_player_state_response: %s", msg.dump().c_str()); +#endif + return this->send_message_(msg, 64); +} +#endif +#ifdef USE_MEDIA_PLAYER +#endif bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) { switch (msg_type) { case 1: { @@ -563,6 +581,17 @@ bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, ESP_LOGVV(TAG, "on_button_command_request: %s", msg.dump().c_str()); #endif this->on_button_command_request(msg); +#endif + break; + } + case 65: { +#ifdef USE_MEDIA_PLAYER + MediaPlayerCommandRequest msg; + msg.decode(msg_data, msg_size); +#ifdef HAS_PROTO_MESSAGE_DUMP + ESP_LOGVV(TAG, "on_media_player_command_request: %s", msg.dump().c_str()); +#endif + this->on_media_player_command_request(msg); #endif break; } @@ -813,6 +842,19 @@ void APIServerConnection::on_lock_command_request(const LockCommandRequest &msg) this->lock_command(msg); } #endif +#ifdef USE_MEDIA_PLAYER +void APIServerConnection::on_media_player_command_request(const MediaPlayerCommandRequest &msg) { + if (!this->is_connection_setup()) { + this->on_no_setup_connection(); + return; + } + if (!this->is_authenticated()) { + this->on_unauthenticated_access(); + return; + } + this->media_player_command(msg); +} +#endif } // namespace api } // namespace esphome diff --git a/esphome/components/api/api_pb2_service.h b/esphome/components/api/api_pb2_service.h index 5aaf831c91..28ad3fbd15 100644 --- a/esphome/components/api/api_pb2_service.h +++ b/esphome/components/api/api_pb2_service.h @@ -144,6 +144,15 @@ class APIServerConnectionBase : public ProtoService { #endif #ifdef USE_BUTTON virtual void on_button_command_request(const ButtonCommandRequest &value){}; +#endif +#ifdef USE_MEDIA_PLAYER + bool send_list_entities_media_player_response(const ListEntitiesMediaPlayerResponse &msg); +#endif +#ifdef USE_MEDIA_PLAYER + bool send_media_player_state_response(const MediaPlayerStateResponse &msg); +#endif +#ifdef USE_MEDIA_PLAYER + virtual void on_media_player_command_request(const MediaPlayerCommandRequest &value){}; #endif protected: bool read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override; @@ -192,6 +201,9 @@ class APIServerConnection : public APIServerConnectionBase { #endif #ifdef USE_LOCK virtual void lock_command(const LockCommandRequest &msg) = 0; +#endif +#ifdef USE_MEDIA_PLAYER + virtual void media_player_command(const MediaPlayerCommandRequest &msg) = 0; #endif protected: void on_hello_request(const HelloRequest &msg) override; @@ -236,6 +248,9 @@ class APIServerConnection : public APIServerConnectionBase { #ifdef USE_LOCK void on_lock_command_request(const LockCommandRequest &msg) override; #endif +#ifdef USE_MEDIA_PLAYER + void on_media_player_command_request(const MediaPlayerCommandRequest &msg) override; +#endif }; } // namespace api diff --git a/esphome/components/api/api_server.cpp b/esphome/components/api/api_server.cpp index 1f2800f298..8375a82313 100644 --- a/esphome/components/api/api_server.cpp +++ b/esphome/components/api/api_server.cpp @@ -272,6 +272,15 @@ void APIServer::on_lock_update(lock::Lock *obj) { } #endif +#ifdef USE_MEDIA_PLAYER +void APIServer::on_media_player_update(media_player::MediaPlayer *obj) { + if (obj->is_internal()) + return; + for (auto &c : this->clients_) + c->send_media_player_state(obj); +} +#endif + float APIServer::get_setup_priority() const { return setup_priority::AFTER_WIFI; } void APIServer::set_port(uint16_t port) { this->port_ = port; } APIServer *global_api_server = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) diff --git a/esphome/components/api/api_server.h b/esphome/components/api/api_server.h index f03a83fc7b..6997e23cac 100644 --- a/esphome/components/api/api_server.h +++ b/esphome/components/api/api_server.h @@ -68,6 +68,9 @@ class APIServer : public Component, public Controller { #endif #ifdef USE_LOCK void on_lock_update(lock::Lock *obj) override; +#endif +#ifdef USE_MEDIA_PLAYER + void on_media_player_update(media_player::MediaPlayer *obj) override; #endif void send_homeassistant_service_call(const HomeassistantServiceResponse &call); void register_user_service(UserServiceDescriptor *descriptor) { this->user_services_.push_back(descriptor); } diff --git a/esphome/components/api/list_entities.cpp b/esphome/components/api/list_entities.cpp index 9f55fda617..85d4cd61ef 100644 --- a/esphome/components/api/list_entities.cpp +++ b/esphome/components/api/list_entities.cpp @@ -64,5 +64,11 @@ bool ListEntitiesIterator::on_number(number::Number *number) { return this->clie bool ListEntitiesIterator::on_select(select::Select *select) { return this->client_->send_select_info(select); } #endif +#ifdef USE_MEDIA_PLAYER +bool ListEntitiesIterator::on_media_player(media_player::MediaPlayer *media_player) { + return this->client_->send_media_player_info(media_player); +} +#endif + } // namespace api } // namespace esphome diff --git a/esphome/components/api/list_entities.h b/esphome/components/api/list_entities.h index 51c343eb03..4fbaa509a2 100644 --- a/esphome/components/api/list_entities.h +++ b/esphome/components/api/list_entities.h @@ -51,6 +51,9 @@ class ListEntitiesIterator : public ComponentIterator { #endif #ifdef USE_LOCK bool on_lock(lock::Lock *a_lock) override; +#endif +#ifdef USE_MEDIA_PLAYER + bool on_media_player(media_player::MediaPlayer *media_player) override; #endif bool on_end() override; diff --git a/esphome/components/api/subscribe_state.cpp b/esphome/components/api/subscribe_state.cpp index ba277502c8..1d1ba0245e 100644 --- a/esphome/components/api/subscribe_state.cpp +++ b/esphome/components/api/subscribe_state.cpp @@ -50,6 +50,11 @@ bool InitialStateIterator::on_select(select::Select *select) { #ifdef USE_LOCK bool InitialStateIterator::on_lock(lock::Lock *a_lock) { return this->client_->send_lock_state(a_lock, a_lock->state); } #endif +#ifdef USE_MEDIA_PLAYER +bool InitialStateIterator::on_media_player(media_player::MediaPlayer *media_player) { + return this->client_->send_media_player_state(media_player); +} +#endif InitialStateIterator::InitialStateIterator(APIConnection *client) : client_(client) {} } // namespace api diff --git a/esphome/components/api/subscribe_state.h b/esphome/components/api/subscribe_state.h index 515e1a2d07..7a7ba697c0 100644 --- a/esphome/components/api/subscribe_state.h +++ b/esphome/components/api/subscribe_state.h @@ -48,6 +48,9 @@ class InitialStateIterator : public ComponentIterator { #endif #ifdef USE_LOCK bool on_lock(lock::Lock *a_lock) override; +#endif +#ifdef USE_MEDIA_PLAYER + bool on_media_player(media_player::MediaPlayer *media_player) override; #endif protected: APIConnection *client_; diff --git a/esphome/components/i2s_audio/__init__.py b/esphome/components/i2s_audio/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esphome/components/i2s_audio/i2s_audio_media_player.cpp b/esphome/components/i2s_audio/i2s_audio_media_player.cpp new file mode 100644 index 0000000000..0ab3237aeb --- /dev/null +++ b/esphome/components/i2s_audio/i2s_audio_media_player.cpp @@ -0,0 +1,132 @@ +#include "i2s_audio_media_player.h" + +#ifdef USE_ESP32_FRAMEWORK_ARDUINO + +#include "esphome/core/log.h" + +namespace esphome { +namespace i2s_audio { + +static const char *const TAG = "audio"; + +void I2SAudioMediaPlayer::control(const media_player::MediaPlayerCall &call) { + if (call.get_media_url().has_value()) { + if (this->audio_->isRunning()) + this->audio_->stopSong(); + this->high_freq_.start(); + this->audio_->connecttohost(call.get_media_url().value().c_str()); + this->state = media_player::MEDIA_PLAYER_STATE_PLAYING; + } + if (call.get_volume().has_value()) { + this->volume = call.get_volume().value(); + this->set_volume_(volume); + this->unmute_(); + } + if (call.get_command().has_value()) { + switch (call.get_command().value()) { + case media_player::MEDIA_PLAYER_COMMAND_PLAY: + if (!this->audio_->isRunning()) + this->audio_->pauseResume(); + this->state = media_player::MEDIA_PLAYER_STATE_PLAYING; + break; + case media_player::MEDIA_PLAYER_COMMAND_PAUSE: + if (this->audio_->isRunning()) + this->audio_->pauseResume(); + this->state = media_player::MEDIA_PLAYER_STATE_PAUSED; + break; + case media_player::MEDIA_PLAYER_COMMAND_STOP: + this->stop_(); + break; + case media_player::MEDIA_PLAYER_COMMAND_MUTE: + this->mute_(); + break; + case media_player::MEDIA_PLAYER_COMMAND_UNMUTE: + this->unmute_(); + break; + } + } + this->publish_state(); +} + +void I2SAudioMediaPlayer::mute_() { + if (this->mute_pin_ != nullptr) { + this->mute_pin_->digital_write(true); + } else { + this->set_volume_(0.0f, false); + } + this->muted_ = true; +} +void I2SAudioMediaPlayer::unmute_() { + if (this->mute_pin_ != nullptr) { + this->mute_pin_->digital_write(false); + } else { + this->set_volume_(this->volume, false); + } + this->muted_ = false; +} +void I2SAudioMediaPlayer::set_volume_(float volume, bool publish) { + this->audio_->setVolume(remap(volume, 0.0f, 1.0f, 0, 21)); + if (publish) + this->volume = volume; +} + +void I2SAudioMediaPlayer::stop_() { + if (this->audio_->isRunning()) + this->audio_->stopSong(); + this->high_freq_.stop(); + this->state = media_player::MEDIA_PLAYER_STATE_IDLE; +} + +void I2SAudioMediaPlayer::setup() { + ESP_LOGCONFIG(TAG, "Setting up Audio..."); + if (this->internal_dac_mode_ != I2S_DAC_CHANNEL_DISABLE) { + this->audio_ = make_unique