From 534ce11d54437d1a8426be2392743ae712321ab0 Mon Sep 17 00:00:00 2001 From: razorback16 Date: Wed, 13 Oct 2021 09:45:41 -0700 Subject: [PATCH 001/142] TCS34725 BugFix and GA factor (#2445) - Fixed endianness bug on tcs34725 data read - Fixed lux adjustments based on gain, integration time and GA factor - Added glass attenuation factor to allow using this sensor behind semi transparent glass Co-authored-by: Razorback16 --- esphome/components/tcs34725/sensor.py | 19 ++- esphome/components/tcs34725/tcs34725.cpp | 180 +++++++++++++++++++---- esphome/components/tcs34725/tcs34725.h | 27 +++- tests/test3.yaml | 2 +- 4 files changed, 197 insertions(+), 31 deletions(-) diff --git a/esphome/components/tcs34725/sensor.py b/esphome/components/tcs34725/sensor.py index 6c74c86faf..fcc56e395f 100644 --- a/esphome/components/tcs34725/sensor.py +++ b/esphome/components/tcs34725/sensor.py @@ -6,6 +6,7 @@ from esphome.const import ( CONF_GAIN, CONF_ID, CONF_ILLUMINANCE, + CONF_GLASS_ATTENUATION_FACTOR, CONF_INTEGRATION_TIME, DEVICE_CLASS_ILLUMINANCE, ICON_LIGHTBULB, @@ -34,8 +35,20 @@ TCS34725_INTEGRATION_TIMES = { "24ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_24MS, "50ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_50MS, "101ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_101MS, + "120ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_120MS, "154ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_154MS, - "700ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_700MS, + "180ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_180MS, + "199ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_199MS, + "240ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_240MS, + "300ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_300MS, + "360ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_360MS, + "401ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_401MS, + "420ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_420MS, + "480ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_480MS, + "499ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_499MS, + "540ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_540MS, + "600ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_600MS, + "614ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_614MS, } TCS34725Gain = tcs34725_ns.enum("TCS34725Gain") @@ -79,6 +92,9 @@ CONFIG_SCHEMA = ( TCS34725_INTEGRATION_TIMES, lower=True ), cv.Optional(CONF_GAIN, default="1X"): cv.enum(TCS34725_GAINS, upper=True), + cv.Optional(CONF_GLASS_ATTENUATION_FACTOR, default=1.0): cv.float_range( + min=1.0 + ), } ) .extend(cv.polling_component_schema("60s")) @@ -93,6 +109,7 @@ async def to_code(config): cg.add(var.set_integration_time(config[CONF_INTEGRATION_TIME])) cg.add(var.set_gain(config[CONF_GAIN])) + cg.add(var.set_glass_attenuation_factor(config[CONF_GLASS_ATTENUATION_FACTOR])) if CONF_RED_CHANNEL in config: sens = await sensor.new_sensor(config[CONF_RED_CHANNEL]) diff --git a/esphome/components/tcs34725/tcs34725.cpp b/esphome/components/tcs34725/tcs34725.cpp index 564d3dcda7..f7ffe2a97d 100644 --- a/esphome/components/tcs34725/tcs34725.cpp +++ b/esphome/components/tcs34725/tcs34725.cpp @@ -26,10 +26,8 @@ void TCS34725Component::setup() { return; } - uint8_t integration_reg = this->integration_time_; - uint8_t gain_reg = this->gain_; - if (!this->write_byte(TCS34725_REGISTER_ATIME, integration_reg) || - !this->write_byte(TCS34725_REGISTER_CONTROL, gain_reg)) { + if (!this->write_byte(TCS34725_REGISTER_ATIME, this->integration_reg_) || + !this->write_byte(TCS34725_REGISTER_CONTROL, this->gain_reg_)) { this->mark_failed(); return; } @@ -61,6 +59,114 @@ void TCS34725Component::dump_config() { LOG_SENSOR(" ", "Color Temperature", this->color_temperature_sensor_); } float TCS34725Component::get_setup_priority() const { return setup_priority::DATA; } + +/*! + * @brief Converts the raw R/G/B values to color temperature in degrees + * Kelvin using the algorithm described in DN40 from Taos (now AMS). + * @param r + * Red value + * @param g + * Green value + * @param b + * Blue value + * @param c + * Clear channel value + * @return Color temperature in degrees Kelvin + */ +void TCS34725Component::calculate_temperature_and_lux_(uint16_t r, uint16_t g, uint16_t b, uint16_t c) { + float r2, g2, b2; /* RGB values minus IR component */ + float sat; /* Digital saturation level */ + float ir; /* Inferred IR content */ + + this->illuminance_ = 0; // Assign 0 value before calculation + this->color_temperature_ = 0; + + const float ga = this->glass_attenuation_; // Glass Attenuation Factor + static const float DF = 310.f; // Device Factor + static const float R_COEF = 0.136f; // + static const float G_COEF = 1.f; // used in lux computation + static const float B_COEF = -0.444f; // + static const float CT_COEF = 3810.f; // Color Temperature Coefficient + static const float CT_OFFSET = 1391.f; // Color Temperatuer Offset + + if (c == 0) { + return; + } + + /* Analog/Digital saturation: + * + * (a) As light becomes brighter, the clear channel will tend to + * saturate first since R+G+B is approximately equal to C. + * (b) The TCS34725 accumulates 1024 counts per 2.4ms of integration + * time, up to a maximum values of 65535. This means analog + * saturation can occur up to an integration time of 153.6ms + * (64*2.4ms=153.6ms). + * (c) If the integration time is > 153.6ms, digital saturation will + * occur before analog saturation. Digital saturation occurs when + * the count reaches 65535. + */ + if ((256 - this->integration_reg_) > 63) { + /* Track digital saturation */ + sat = 65535.f; + } else { + /* Track analog saturation */ + sat = 1024.f * (256.f - this->integration_reg_); + } + + /* Ripple rejection: + * + * (a) An integration time of 50ms or multiples of 50ms are required to + * reject both 50Hz and 60Hz ripple. + * (b) If an integration time faster than 50ms is required, you may need + * to average a number of samples over a 50ms period to reject ripple + * from fluorescent and incandescent light sources. + * + * Ripple saturation notes: + * + * (a) If there is ripple in the received signal, the value read from C + * will be less than the max, but still have some effects of being + * saturated. This means that you can be below the 'sat' value, but + * still be saturating. At integration times >150ms this can be + * ignored, but <= 150ms you should calculate the 75% saturation + * level to avoid this problem. + */ + if (this->integration_time_ < 150) { + /* Adjust sat to 75% to avoid analog saturation if atime < 153.6ms */ + sat -= sat / 4.f; + } + + /* Check for saturation and mark the sample as invalid if true */ + if (c >= sat) { + return; + } + + /* AMS RGB sensors have no IR channel, so the IR content must be */ + /* calculated indirectly. */ + ir = ((r + g + b) > c) ? (r + g + b - c) / 2 : 0; + + /* Remove the IR component from the raw RGB values */ + r2 = r - ir; + g2 = g - ir; + b2 = b - ir; + + if (r2 == 0) { + return; + } + + // Lux Calculation (DN40 3.2) + + float g1 = R_COEF * r2 + G_COEF * g2 + B_COEF * b2; + float cpl = (this->integration_time_ * this->gain_) / (ga * DF); + this->illuminance_ = g1 / cpl; + + // Color Temperature Calculation (DN40) + /* A simple method of measuring color temp is to use the ratio of blue */ + /* to red light, taking IR cancellation into account. */ + this->color_temperature_ = (CT_COEF * b2) / /** Color temp coefficient. */ + r2 + + CT_OFFSET; /** Color temp offset. */ +} + void TCS34725Component::update() { uint16_t raw_c; uint16_t raw_r; @@ -74,6 +180,12 @@ void TCS34725Component::update() { return; } + // May need to fix endianness as the data read over I2C is big-endian, but most ESP platforms are little-endian + raw_c = i2c::i2ctohs(raw_c); + raw_r = i2c::i2ctohs(raw_r); + raw_g = i2c::i2ctohs(raw_g); + raw_b = i2c::i2ctohs(raw_b); + const float channel_c = raw_c / 655.35f; const float channel_r = raw_r / 655.35f; const float channel_g = raw_g / 655.35f; @@ -87,38 +199,54 @@ void TCS34725Component::update() { if (this->blue_sensor_ != nullptr) this->blue_sensor_->publish_state(channel_b); - // Formulae taken from Adafruit TCS35725 library - float illuminance = (-0.32466f * channel_r) + (1.57837f * channel_g) + (-0.73191f * channel_b); + if (this->illuminance_sensor_ || this->color_temperature_sensor_) { + calculate_temperature_and_lux_(raw_r, raw_g, raw_b, raw_c); + } + if (this->illuminance_sensor_ != nullptr) - this->illuminance_sensor_->publish_state(illuminance); + this->illuminance_sensor_->publish_state(this->illuminance_); - // Color temperature - // 1. Convert RGB to XYZ color space - const float x = (-0.14282f * raw_r) + (1.54924f * raw_g) + (-0.95641f * raw_b); - const float y = (-0.32466f * raw_r) + (1.57837f * raw_g) + (-0.73191f * raw_b); - const float z = (-0.68202f * raw_r) + (0.77073f * raw_g) + (0.56332f * raw_b); - - // 2. Calculate chromacity coordinates - const float xc = (x) / (x + y + z); - const float yc = (y) / (x + y + z); - - // 3. Use McCamy's formula to determine the color temperature - const float n = (xc - 0.3320f) / (0.1858f - yc); - - // 4. final color temperature in Kelvin. - const float color_temperature = (449.0f * powf(n, 3.0f)) + (3525.0f * powf(n, 2.0f)) + (6823.3f * n) + 5520.33f; if (this->color_temperature_sensor_ != nullptr) - this->color_temperature_sensor_->publish_state(color_temperature); + this->color_temperature_sensor_->publish_state(this->color_temperature_); ESP_LOGD(TAG, "Got R=%.1f%%,G=%.1f%%,B=%.1f%%,C=%.1f%% Illuminance=%.1flx Color Temperature=%.1fK", channel_r, - channel_g, channel_b, channel_c, illuminance, color_temperature); + channel_g, channel_b, channel_c, this->illuminance_, this->color_temperature_); this->status_clear_warning(); } void TCS34725Component::set_integration_time(TCS34725IntegrationTime integration_time) { - this->integration_time_ = integration_time; + this->integration_reg_ = integration_time; + this->integration_time_ = (256.f - integration_time) * 2.4f; +} +void TCS34725Component::set_gain(TCS34725Gain gain) { + this->gain_reg_ = gain; + switch (gain) { + case TCS34725Gain::TCS34725_GAIN_1X: + this->gain_ = 1.f; + break; + case TCS34725Gain::TCS34725_GAIN_4X: + this->gain_ = 4.f; + break; + case TCS34725Gain::TCS34725_GAIN_16X: + this->gain_ = 16.f; + break; + case TCS34725Gain::TCS34725_GAIN_60X: + this->gain_ = 60.f; + break; + default: + this->gain_ = 1.f; + break; + } +} + +void TCS34725Component::set_glass_attenuation_factor(float ga) { + // The Glass Attenuation (FA) factor used to compensate for lower light + // levels at the device due to the possible presence of glass. The GA is + // the inverse of the glass transmissivity (T), so GA = 1/T. A transmissivity + // of 50% gives GA = 1 / 0.50 = 2. If no glass is present, use GA = 1. + // See Application Note: DN40-Rev 1.0 + this->glass_attenuation_ = ga; } -void TCS34725Component::set_gain(TCS34725Gain gain) { this->gain_ = gain; } } // namespace tcs34725 } // namespace esphome diff --git a/esphome/components/tcs34725/tcs34725.h b/esphome/components/tcs34725/tcs34725.h index b914db0eb0..47ed2959c6 100644 --- a/esphome/components/tcs34725/tcs34725.h +++ b/esphome/components/tcs34725/tcs34725.h @@ -12,8 +12,20 @@ enum TCS34725IntegrationTime { TCS34725_INTEGRATION_TIME_24MS = 0xF6, TCS34725_INTEGRATION_TIME_50MS = 0xEB, TCS34725_INTEGRATION_TIME_101MS = 0xD5, + TCS34725_INTEGRATION_TIME_120MS = 0xCE, TCS34725_INTEGRATION_TIME_154MS = 0xC0, - TCS34725_INTEGRATION_TIME_700MS = 0x00, + TCS34725_INTEGRATION_TIME_180MS = 0xB5, + TCS34725_INTEGRATION_TIME_199MS = 0xAD, + TCS34725_INTEGRATION_TIME_240MS = 0x9C, + TCS34725_INTEGRATION_TIME_300MS = 0x83, + TCS34725_INTEGRATION_TIME_360MS = 0x6A, + TCS34725_INTEGRATION_TIME_401MS = 0x59, + TCS34725_INTEGRATION_TIME_420MS = 0x51, + TCS34725_INTEGRATION_TIME_480MS = 0x38, + TCS34725_INTEGRATION_TIME_499MS = 0x30, + TCS34725_INTEGRATION_TIME_540MS = 0x1F, + TCS34725_INTEGRATION_TIME_600MS = 0x06, + TCS34725_INTEGRATION_TIME_614MS = 0x00, }; enum TCS34725Gain { @@ -27,6 +39,7 @@ class TCS34725Component : public PollingComponent, public i2c::I2CDevice { public: void set_integration_time(TCS34725IntegrationTime integration_time); void set_gain(TCS34725Gain gain); + void set_glass_attenuation_factor(float ga); void set_clear_sensor(sensor::Sensor *clear_sensor) { clear_sensor_ = clear_sensor; } void set_red_sensor(sensor::Sensor *red_sensor) { red_sensor_ = red_sensor; } @@ -49,8 +62,16 @@ class TCS34725Component : public PollingComponent, public i2c::I2CDevice { sensor::Sensor *blue_sensor_{nullptr}; sensor::Sensor *illuminance_sensor_{nullptr}; sensor::Sensor *color_temperature_sensor_{nullptr}; - TCS34725IntegrationTime integration_time_{TCS34725_INTEGRATION_TIME_2_4MS}; - TCS34725Gain gain_{TCS34725_GAIN_1X}; + float integration_time_{2.4}; + float gain_{1.0}; + float glass_attenuation_{1.0}; + float illuminance_; + float color_temperature_; + + 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}; + uint8_t gain_reg_{TCS34725_GAIN_1X}; }; } // namespace tcs34725 diff --git a/tests/test3.yaml b/tests/test3.yaml index 73e314c94c..4c76967842 100644 --- a/tests/test3.yaml +++ b/tests/test3.yaml @@ -403,7 +403,7 @@ sensor: name: Illuminance color_temperature: name: Color Temperature - integration_time: 700ms + integration_time: 614ms gain: 60x - platform: custom lambda: |- From 859e5083925edc541d4c20509f9adfe7497b3231 Mon Sep 17 00:00:00 2001 From: Carlos Garcia Saura Date: Wed, 13 Oct 2021 18:50:27 +0200 Subject: [PATCH 002/142] change millis() to micros() in feed_wdt for 3ms check (#2492) --- esphome/core/application.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/core/application.cpp b/esphome/core/application.cpp index a4d61f819c..f67fc826cf 100644 --- a/esphome/core/application.cpp +++ b/esphome/core/application.cpp @@ -109,8 +109,8 @@ void Application::loop() { void IRAM_ATTR HOT Application::feed_wdt() { static uint32_t last_feed = 0; - uint32_t now = millis(); - if (now - last_feed > 3) { + uint32_t now = micros(); + if (now - last_feed > 3000) { arch_feed_wdt(); last_feed = now; #ifdef USE_STATUS_LED From e06b6d7140ade52fa956de83b9a2944141a945aa Mon Sep 17 00:00:00 2001 From: Maurice Makaay Date: Wed, 13 Oct 2021 21:22:57 +0200 Subject: [PATCH 003/142] Add ESP32 IDF as a test env for PRs (#2494) Co-authored-by: Maurice Makaay --- .github/PULL_REQUEST_TEMPLATE.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index aa90ef365f..25411c19f5 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -16,6 +16,7 @@ Quick description and explanation of changes ## Test Environment - [ ] ESP32 +- [ ] ESP32 IDF - [ ] ESP8266 ## Example entry for `config.yaml`: From 05388d2dfcb6f2b49e13cefb2dae04f478d9f1d4 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Wed, 13 Oct 2021 21:53:00 +0200 Subject: [PATCH 004/142] 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 867fecd157f45f591c11ae7e40c646f1111695d3 Mon Sep 17 00:00:00 2001 From: Paul Monigatti Date: Thu, 14 Oct 2021 08:59:52 +1300 Subject: [PATCH 005/142] 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 6bbb5e9b56bb71353aac609cea7fe325cd9b4c60 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Wed, 13 Oct 2021 22:21:43 +0200 Subject: [PATCH 006/142] 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 07b309e65d38bf420b89975aee56675314ab7a1d Mon Sep 17 00:00:00 2001 From: Paul Monigatti Date: Thu, 14 Oct 2021 20:58:35 +1300 Subject: [PATCH 007/142] 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 7e482901d93d56ee517690309da87aa75933023b Mon Sep 17 00:00:00 2001 From: Dmitriy Lopatko Date: Thu, 14 Oct 2021 10:00:53 +0200 Subject: [PATCH 008/142] 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 4896f870f0d6755bcdd570a47f0323b9e3640645 Mon Sep 17 00:00:00 2001 From: Paul Monigatti Date: Thu, 14 Oct 2021 21:04:50 +1300 Subject: [PATCH 009/142] 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 882302450956b3070a13cd7bb701dba91b59f132 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 010/142] 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 63d6b610b82f77b88d42dc0865c1b7ae32709967 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Thu, 14 Oct 2021 11:25:10 +0200 Subject: [PATCH 011/142] 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 1308236429d1564ed7cb14ee38c5d904f0c22916 Mon Sep 17 00:00:00 2001 From: Paul Monigatti Date: Thu, 14 Oct 2021 23:31:52 +1300 Subject: [PATCH 012/142] 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 7178f10bdaa793401ff2c2fc376e7b7d12fd91bc Mon Sep 17 00:00:00 2001 From: Keith Burzinski Date: Fri, 15 Oct 2021 02:26:26 -0500 Subject: [PATCH 013/142] 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 6beb9e568a3108a27a980478e7b5388e0ae4cad7 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 014/142] 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 dc15d1c8ecea36a21cf648322385a42c9f5fe703 Mon Sep 17 00:00:00 2001 From: Dmitriy Lopatko Date: Fri, 15 Oct 2021 16:52:03 +0200 Subject: [PATCH 015/142] use no hold master mode for si7021/htu21d (#2528) --- esphome/components/htu21d/htu21d.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/htu21d/htu21d.cpp b/esphome/components/htu21d/htu21d.cpp index b53284ae3f..a38ec73019 100644 --- a/esphome/components/htu21d/htu21d.cpp +++ b/esphome/components/htu21d/htu21d.cpp @@ -9,8 +9,8 @@ static const char *const TAG = "htu21d"; static const uint8_t HTU21D_ADDRESS = 0x40; static const uint8_t HTU21D_REGISTER_RESET = 0xFE; -static const uint8_t HTU21D_REGISTER_TEMPERATURE = 0xE3; -static const uint8_t HTU21D_REGISTER_HUMIDITY = 0xE5; +static const uint8_t HTU21D_REGISTER_TEMPERATURE = 0xF3; +static const uint8_t HTU21D_REGISTER_HUMIDITY = 0xF5; static const uint8_t HTU21D_REGISTER_STATUS = 0xE7; void HTU21DComponent::setup() { From 935992bcb32bad838aa90d7eab03dcd6f749d9fb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 15 Oct 2021 19:38:09 +0200 Subject: [PATCH 016/142] Bump pyyaml from 5.4.1 to 6.0 (#2521) Bumps [pyyaml](https://github.com/yaml/pyyaml) from 5.4.1 to 6.0. - [Release notes](https://github.com/yaml/pyyaml/releases) - [Changelog](https://github.com/yaml/pyyaml/blob/master/CHANGES) - [Commits](https://github.com/yaml/pyyaml/compare/5.4.1...6.0) --- updated-dependencies: - dependency-name: pyyaml dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] 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 23a00d3755..b21bed87a6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ voluptuous==0.12.2 -PyYAML==5.4.1 +PyYAML==6.0 paho-mqtt==1.5.1 colorama==0.4.4 tornado==6.1 From 85d2f24447dc79029b715d6082add1935d53b1ca Mon Sep 17 00:00:00 2001 From: Carlos Garcia Saura Date: Fri, 15 Oct 2021 19:51:42 +0200 Subject: [PATCH 017/142] Clarify statement at the cmd wizard tool, for new users (#2519) * Clarify next steps for the install wizard * Update wizard.py * Link to relevant section of guide * Formatting Co-authored-by: Oxan van Leeuwen --- esphome/wizard.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/esphome/wizard.py b/esphome/wizard.py index 5c35fac73a..6c87b66453 100644 --- a/esphome/wizard.py +++ b/esphome/wizard.py @@ -367,10 +367,9 @@ def wizard(path): ) safe_print() safe_print("Next steps:") + safe_print(" > Follow the rest of the getting started guide:") safe_print( - ' > Check your Home Assistant "integrations" screen. If all goes well, you ' - "should see your ESP being discovered automatically." + " > https://esphome.io/guides/getting_started_command_line.html#adding-some-features" ) - safe_print(" > Then follow the rest of the getting started guide:") - safe_print(" > https://esphome.io/guides/getting_started_command_line.html") + safe_print(" > to learn how to customize ESPHome and install it to your device.") return 0 From 884b7201de5256cd5692d1c992654d429866f53a Mon Sep 17 00:00:00 2001 From: Tom Matheussen Date: Fri, 15 Oct 2021 20:46:58 +0200 Subject: [PATCH 018/142] Continue ethernet setup if hostname fails (#2430) --- 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 d55db0a7d8..00f68df2b4 100644 --- a/esphome/components/ethernet/ethernet_component.cpp +++ b/esphome/components/ethernet/ethernet_component.cpp @@ -168,7 +168,9 @@ void EthernetComponent::start_connect_() { esp_err_t err; err = tcpip_adapter_set_hostname(TCPIP_ADAPTER_IF_ETH, App.get_name().c_str()); - ESPHL_ERROR_CHECK(err, "ETH set hostname error"); + if (err != ERR_OK) { + ESP_LOGW(TAG, "tcpip_adapter_set_hostname failed: %s", esp_err_to_name(err)); + } tcpip_adapter_ip_info_t info; if (this->manual_ip_.has_value()) { From 653a3d5d11a42e93158e7b65058806197713cc97 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 15 Oct 2021 22:05:04 +0200 Subject: [PATCH 019/142] Bump aioesphomeapi from 9.1.5 to 10.0.0 (#2508) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Oxan van Leeuwen --- esphome/components/api/client.py | 1 - requirements.txt | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/esphome/components/api/client.py b/esphome/components/api/client.py index 4a3944d33e..b2920f239b 100644 --- a/esphome/components/api/client.py +++ b/esphome/components/api/client.py @@ -23,7 +23,6 @@ async def async_run_logs(config, address): _LOGGER.info("Starting log output from %s using esphome API", address) zc = zeroconf.Zeroconf() cli = APIClient( - asyncio.get_event_loop(), address, port, password, diff --git a/requirements.txt b/requirements.txt index b21bed87a6..afc75efcc7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,7 +10,7 @@ platformio==5.2.1 esptool==3.1 click==8.0.3 esphome-dashboard==20211011.1 -aioesphomeapi==9.1.5 +aioesphomeapi==10.0.0 # esp-idf requires this, but doesn't bundle it by default # https://github.com/espressif/esp-idf/blob/220590d599e134d7a5e7f1e683cc4550349ffbf8/requirements.txt#L24 From c82d5d63e37cb2eda25f5316160cfbebd0a06381 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Fri, 15 Oct 2021 22:05:11 +0200 Subject: [PATCH 020/142] Move TemplatableValue helper class to automation.h (#2511) --- .../components/api/homeassistant_service.h | 16 +++++- esphome/core/automation.h | 50 +++++++++++++--- esphome/core/helpers.h | 57 ------------------- 3 files changed, 58 insertions(+), 65 deletions(-) diff --git a/esphome/components/api/homeassistant_service.h b/esphome/components/api/homeassistant_service.h index 8a72765195..26269bcae4 100644 --- a/esphome/components/api/homeassistant_service.h +++ b/esphome/components/api/homeassistant_service.h @@ -8,6 +8,18 @@ namespace esphome { namespace api { +template class TemplatableStringValue : public TemplatableValue { + public: + TemplatableStringValue() : TemplatableValue() {} + + template::value, int> = 0> + TemplatableStringValue(F value) : TemplatableValue(value) {} + + template::value, int> = 0> + TemplatableStringValue(F f) + : TemplatableValue([f](X... x) -> std::string { return to_string(f(x...)); }) {} +}; + template class TemplatableKeyValuePair { public: template TemplatableKeyValuePair(std::string key, T value) : key(std::move(key)), value(value) {} @@ -19,7 +31,8 @@ template class HomeAssistantServiceCallAction : public Action void set_service(T service) { this->service_ = service; } + template void add_data(std::string key, T value) { this->data_.push_back(TemplatableKeyValuePair(key, value)); } @@ -58,6 +71,7 @@ template class HomeAssistantServiceCallAction : public Action service_{}; std::vector> data_; std::vector> data_template_; std::vector> variables_; diff --git a/esphome/core/automation.h b/esphome/core/automation.h index 6d79480f0f..e5460bef34 100644 --- a/esphome/core/automation.h +++ b/esphome/core/automation.h @@ -17,14 +17,50 @@ namespace esphome { #define TEMPLATABLE_VALUE(type, name) TEMPLATABLE_VALUE_(type, name) -#define TEMPLATABLE_STRING_VALUE_(name) \ - protected: \ - TemplatableStringValue name##_{}; \ -\ - public: \ - template void set_##name(V name) { this->name##_ = name; } +template class TemplatableValue { + public: + TemplatableValue() : type_(EMPTY) {} -#define TEMPLATABLE_STRING_VALUE(name) TEMPLATABLE_STRING_VALUE_(name) + template::value, int> = 0> + TemplatableValue(F value) : type_(VALUE), value_(value) {} + + template::value, int> = 0> + TemplatableValue(F f) : type_(LAMBDA), f_(f) {} + + bool has_value() { return this->type_ != EMPTY; } + + T value(X... x) { + if (this->type_ == LAMBDA) { + return this->f_(x...); + } + // return value also when empty + return this->value_; + } + + optional optional_value(X... x) { + if (!this->has_value()) { + return {}; + } + return this->value(x...); + } + + T value_or(X... x, T default_value) { + if (!this->has_value()) { + return default_value; + } + return this->value(x...); + } + + protected: + enum { + EMPTY, + VALUE, + LAMBDA, + } type_; + + T value_{}; + std::function f_{}; +}; /** Base class for all automation conditions. * diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index 61cc9a9e4a..905cb76170 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -255,63 +255,6 @@ struct is_callable // NOLINT static constexpr auto value = decltype(test(nullptr))::value; // NOLINT }; -template class TemplatableValue { - public: - TemplatableValue() : type_(EMPTY) {} - - template::value, int> = 0> - TemplatableValue(F value) : type_(VALUE), value_(value) {} - - template::value, int> = 0> - TemplatableValue(F f) : type_(LAMBDA), f_(f) {} - - bool has_value() { return this->type_ != EMPTY; } - - T value(X... x) { - if (this->type_ == LAMBDA) { - return this->f_(x...); - } - // return value also when empty - return this->value_; - } - - optional optional_value(X... x) { - if (!this->has_value()) { - return {}; - } - return this->value(x...); - } - - T value_or(X... x, T default_value) { - if (!this->has_value()) { - return default_value; - } - return this->value(x...); - } - - protected: - enum { - EMPTY, - VALUE, - LAMBDA, - } type_; - - T value_{}; - std::function f_{}; -}; - -template class TemplatableStringValue : public TemplatableValue { - public: - TemplatableStringValue() : TemplatableValue() {} - - template::value, int> = 0> - TemplatableStringValue(F value) : TemplatableValue(value) {} - - template::value, int> = 0> - TemplatableStringValue(F f) - : TemplatableValue([f](X... x) -> std::string { return to_string(f(x...)); }) {} -}; - void delay_microseconds_accurate(uint32_t usec); template class Deduplicator { From 384f8d97d8d6ef665c72d635ce1e04c108fa2cff Mon Sep 17 00:00:00 2001 From: Maurice Makaay Date: Fri, 15 Oct 2021 22:06:32 +0200 Subject: [PATCH 021/142] 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 7cca67390201f391198f063baf5cd897dd99adac Mon Sep 17 00:00:00 2001 From: Carlos Garcia Saura Date: Fri, 15 Oct 2021 22:06:49 +0200 Subject: [PATCH 022/142] [esp-idf fix] increase FreeRTOS ticker loop from 100Hz to 1kHz (#2527) Co-authored-by: Otto Winter --- esphome/components/esp32/__init__.py | 2 ++ sdkconfig.defaults | 1 + 2 files changed, 3 insertions(+) diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index 09eabe1fa7..68653d68ff 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -302,6 +302,8 @@ async def to_code(config): ) add_idf_sdkconfig_option("CONFIG_COMPILER_OPTIMIZATION_DEFAULT", False) add_idf_sdkconfig_option("CONFIG_COMPILER_OPTIMIZATION_SIZE", True) + # Increase freertos tick speed from 100Hz to 1kHz so that delay() resolution is 1ms + add_idf_sdkconfig_option("CONFIG_FREERTOS_HZ", 1000) cg.add_platformio_option("board_build.partitions", "partitions.csv") diff --git a/sdkconfig.defaults b/sdkconfig.defaults index 6b2d6f8f2e..26db4705b8 100644 --- a/sdkconfig.defaults +++ b/sdkconfig.defaults @@ -8,6 +8,7 @@ CONFIG_COMPILER_OPTIMIZATION_SIZE=y CONFIG_PARTITION_TABLE_CUSTOM=y #CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" CONFIG_PARTITION_TABLE_SINGLE_APP=n +CONFIG_FREERTOS_HZ=1000 # esp32_ble CONFIG_BT_ENABLED=y From 94d518a41871f941944eeaf66ceed4d497666fc2 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Fri, 15 Oct 2021 22:07:05 +0200 Subject: [PATCH 023/142] 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 68653d68ff..8a4b8b478a 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) @@ -325,7 +311,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 65d2b37496577b35d6b2a73931731f6d3a8bca83 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Sun, 17 Oct 2021 08:53:49 +0200 Subject: [PATCH 024/142] 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 0991ab3543d67c5d7dfbe64bcf4aedd44cb5430f 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 025/142] 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 12fce7a08d615b3bd52f62a6452d34e247c31636 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sun, 17 Oct 2021 00:26:59 -0700 Subject: [PATCH 026/142] 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 afc75efcc7..6615d4bd3d 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==10.0.0 # esp-idf requires this, but doesn't bundle it by default From 5425e45851169ee58edc175b12a31417317dcba0 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Sun, 17 Oct 2021 21:01:51 +0200 Subject: [PATCH 027/142] 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 644ce2a26c8a161a3005471d5faae0dd0cd925b6 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 028/142] 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 8a4b8b478a..d36471f0d4 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 6b9c084162496aacf5896edfa0b9867593017d6b 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 029/142] 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 ced11bc707a8aaecdd2d2d6df22d64372e0d3ba8 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Mon, 18 Oct 2021 02:36:18 +0200 Subject: [PATCH 030/142] 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 d36471f0d4..db24b9aa29 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 03cfd78c591b660db15ae649bb530e60adf2797c 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 031/142] 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 6615d4bd3d..23642adf9a 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==10.0.0 # esp-idf requires this, but doesn't bundle it by default From 5b5ead872b677bc21e5eb62ac660d388b05894d6 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Tue, 19 Oct 2021 12:56:49 +0200 Subject: [PATCH 032/142] 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 e2a812fa4b43489bfc3813c1982a8ac50784a2c6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 19 Oct 2021 20:28:48 +0200 Subject: [PATCH 033/142] Bump pytest-asyncio from 0.15.1 to 0.16.0 (#2547) 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 8ebcf24d4d..e40d51f4bf 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -8,6 +8,6 @@ pre-commit pytest==6.2.5 pytest-cov==3.0.0 pytest-mock==3.6.1 -pytest-asyncio==0.15.1 +pytest-asyncio==0.16.0 asyncmock==0.4.2 hypothesis==5.49.0 From f5441a87e3f308c76fef424551bc9fea27f6d8ad 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 034/142] 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 cb48394e8a776beb8f873cd70a2c0e3c9b28dcd3 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 035/142] 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 23642adf9a..08f3e8e4e5 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==10.0.0 # esp-idf requires this, but doesn't bundle it by default From 8b11e5aeb1ea4072a76412cb9da3fd2345159766 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 036/142] 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 bcc77c73e1fb42ab0b3bbbf2ad704dbe98457ab1 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 037/142] 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 08f3e8e4e5..a32cb1e123 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==10.0.0 # esp-idf requires this, but doesn't bundle it by default From e79f7ce29016a68df3cc2b685dd8ef4dbf3081d0 Mon Sep 17 00:00:00 2001 From: Carlos Garcia Saura Date: Wed, 20 Oct 2021 19:44:51 +0200 Subject: [PATCH 038/142] [ESP32] ADC auto-range setting (#2541) --- esphome/components/adc/adc_sensor.cpp | 160 ++++++++++++++++---------- esphome/components/adc/adc_sensor.h | 4 + esphome/components/adc/sensor.py | 6 +- 3 files changed, 110 insertions(+), 60 deletions(-) diff --git a/esphome/components/adc/adc_sensor.cpp b/esphome/components/adc/adc_sensor.cpp index c8f8b0e0f6..0c24c615f3 100644 --- a/esphome/components/adc/adc_sensor.cpp +++ b/esphome/components/adc/adc_sensor.cpp @@ -1,5 +1,6 @@ #include "adc_sensor.h" #include "esphome/core/log.h" +#include "esphome/core/helpers.h" #ifdef USE_ESP8266 #ifdef USE_ADC_SENSOR_VCC @@ -16,8 +17,6 @@ namespace adc { static const char *const TAG = "adc"; #ifdef USE_ESP32 -void ADCSensor::set_attenuation(adc_atten_t attenuation) { this->attenuation_ = attenuation; } - inline adc1_channel_t gpio_to_adc1(uint8_t pin) { #if CONFIG_IDF_TARGET_ESP32 switch (pin) { @@ -57,6 +56,8 @@ inline adc1_channel_t gpio_to_adc1(uint8_t pin) { } #endif } +void ADCSensor::set_attenuation(adc_atten_t attenuation) { this->attenuation_ = attenuation; } +void ADCSensor::set_autorange(bool autorange) { this->autorange_ = autorange; } #endif void ADCSensor::setup() { @@ -66,6 +67,8 @@ void ADCSensor::setup() { #endif #ifdef USE_ESP32 + if (this->autorange_) + this->attenuation_ = ADC_ATTEN_DB_11; adc1_config_channel_atten(gpio_to_adc1(pin_->get_pin()), attenuation_); adc1_config_width(ADC_WIDTH_BIT_12); #if !CONFIG_IDF_TARGET_ESP32C3 && !CONFIG_IDF_TARGET_ESP32H2 @@ -84,22 +87,25 @@ void ADCSensor::dump_config() { #endif #ifdef USE_ESP32 LOG_PIN(" Pin: ", pin_); - switch (this->attenuation_) { - case ADC_ATTEN_DB_0: - ESP_LOGCONFIG(TAG, " Attenuation: 0db (max 1.1V)"); - break; - case ADC_ATTEN_DB_2_5: - ESP_LOGCONFIG(TAG, " Attenuation: 2.5db (max 1.5V)"); - break; - case ADC_ATTEN_DB_6: - ESP_LOGCONFIG(TAG, " Attenuation: 6db (max 2.2V)"); - break; - case ADC_ATTEN_DB_11: - ESP_LOGCONFIG(TAG, " Attenuation: 11db (max 3.9V)"); - break; - default: // This is to satisfy the unused ADC_ATTEN_MAX - break; - } + if (autorange_) + ESP_LOGCONFIG(TAG, " Attenuation: auto"); + else + switch (this->attenuation_) { + case ADC_ATTEN_DB_0: + ESP_LOGCONFIG(TAG, " Attenuation: 0db (max 1.1V)"); + break; + case ADC_ATTEN_DB_2_5: + ESP_LOGCONFIG(TAG, " Attenuation: 2.5db (max 1.5V)"); + break; + case ADC_ATTEN_DB_6: + ESP_LOGCONFIG(TAG, " Attenuation: 6db (max 2.2V)"); + break; + case ADC_ATTEN_DB_11: + ESP_LOGCONFIG(TAG, " Attenuation: 11db (max 3.9V)"); + break; + default: // This is to satisfy the unused ADC_ATTEN_MAX + break; + } #endif LOG_UPDATE_INTERVAL(this); } @@ -109,56 +115,92 @@ void ADCSensor::update() { ESP_LOGD(TAG, "'%s': Got voltage=%.2fV", this->get_name().c_str(), value_v); this->publish_state(value_v); } -float ADCSensor::sample() { +uint16_t ADCSensor::read_raw_() { #ifdef USE_ESP32 - int raw = adc1_get_raw(gpio_to_adc1(pin_->get_pin())); - float value_v = raw / 4095.0f; -#if CONFIG_IDF_TARGET_ESP32 - switch (this->attenuation_) { - case ADC_ATTEN_DB_0: - value_v *= 1.1; - break; - case ADC_ATTEN_DB_2_5: - value_v *= 1.5; - break; - case ADC_ATTEN_DB_6: - value_v *= 2.2; - break; - case ADC_ATTEN_DB_11: - value_v *= 3.9; - break; - default: // This is to satisfy the unused ADC_ATTEN_MAX - break; - } -#elif CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32H2 - switch (this->attenuation_) { - case ADC_ATTEN_DB_0: - value_v *= 0.84; - break; - case ADC_ATTEN_DB_2_5: - value_v *= 1.13; - break; - case ADC_ATTEN_DB_6: - value_v *= 1.56; - break; - case ADC_ATTEN_DB_11: - value_v *= 3.0; - break; - default: // This is to satisfy the unused ADC_ATTEN_MAX - break; - } -#endif - return value_v; + return adc1_get_raw(gpio_to_adc1(pin_->get_pin())); #endif #ifdef USE_ESP8266 #ifdef USE_ADC_SENSOR_VCC - return ESP.getVcc() / 1024.0f; // NOLINT(readability-static-accessed-through-instance) + return ESP.getVcc(); // NOLINT(readability-static-accessed-through-instance) #else - return analogRead(this->pin_->get_pin()) / 1024.0f; // NOLINT + return analogRead(this->pin_->get_pin()); // NOLINT #endif #endif } +uint32_t ADCSensor::raw_to_microvolts_(uint16_t raw) { +#ifdef USE_ESP32 +#if CONFIG_IDF_TARGET_ESP32 + switch (this->attenuation_) { + case ADC_ATTEN_DB_0: + return raw * 269; // 1e6 * 1.1 / 4095 + case ADC_ATTEN_DB_2_5: + return raw * 366; // 1e6 * 1.5 / 4095 + case ADC_ATTEN_DB_6: + return raw * 537; // 1e6 * 2.2 / 4095 + case ADC_ATTEN_DB_11: + return raw * 952; // 1e6 * 3.9 / 4095 + default: // This is to satisfy the unused ADC_ATTEN_MAX + return raw * 244; // 1e6 * 1.0 / 4095 + } +#elif CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32H2 + switch (this->attenuation_) { + case ADC_ATTEN_DB_0: + return raw * 205; // 1e6 * 0.84 / 4095 + case ADC_ATTEN_DB_2_5: + return raw * 276; // 1e6 * 1.13 / 4095 + case ADC_ATTEN_DB_6: + return raw * 381; // 1e6 * 1.56 / 4095 + case ADC_ATTEN_DB_11: + return raw * 733; // 1e6 * 3.0 / 4095 + default: // This is to satisfy the unused ADC_ATTEN_MAX + return raw * 244; // 1e6 * 1.0 / 4095 + } +#endif +#endif + +#ifdef USE_ESP8266 + return raw * 977; // 1e6 / 1024 +#endif +} +float ADCSensor::sample() { + int raw = this->read_raw_(); + uint32_t v = this->raw_to_microvolts_(raw); +#ifdef USE_ESP32 + if (autorange_) { + int raw11 = raw, raw6 = 4095, raw2 = 4095, raw0 = 4095; + uint32_t v11 = v, v6 = 0, v2 = 0, v0 = 0; + if (raw11 < 4095) { // Progressively read all attenuation ranges + adc1_config_channel_atten(gpio_to_adc1(pin_->get_pin()), ADC_ATTEN_DB_6); + raw6 = this->read_raw_(); + v6 = this->raw_to_microvolts_(raw6); + if (raw6 < 4095) { + adc1_config_channel_atten(gpio_to_adc1(pin_->get_pin()), ADC_ATTEN_DB_2_5); + raw2 = this->read_raw_(); + v2 = this->raw_to_microvolts_(raw2); + if (raw2 < 4095) { + adc1_config_channel_atten(gpio_to_adc1(pin_->get_pin()), ADC_ATTEN_DB_0); + raw0 = this->read_raw_(); + v0 = this->raw_to_microvolts_(raw0); + } + } + adc1_config_channel_atten(gpio_to_adc1(pin_->get_pin()), ADC_ATTEN_DB_11); + } // Contribution coefficients (normalized to 2048) + uint16_t c11 = clamp(raw11, 0, 2048); // high 1, middle 1, low 0 + uint16_t c6 = (2048 - abs(raw6 - 2048)); // high 0, middle 1, low 0 + uint16_t c2 = (2048 - abs(raw2 - 2048)); // high 0, middle 1, low 0 + uint16_t c0 = clamp(4095 - raw0, 0, 2048); // high 0, middle 1, low 1 + uint32_t csum = c11 + c6 + c2 + c0; // sum to normalize the final result + if (csum > 0) + v = (v11 * c11) + (v6 * c6) + (v2 * c2) + (v0 * c0); + else // in case of error, this keeps the 11db output (v) + csum = 1; + csum *= 1e6; // include the 1e6 microvolts->volts conversion factor + return (float) v / (float) csum; // normalize, convert & return + } +#endif + return v / (float) 1e6; // convert from microvolts to volts +} #ifdef USE_ESP8266 std::string ADCSensor::unique_id() { return get_mac_address() + "-adc"; } #endif diff --git a/esphome/components/adc/adc_sensor.h b/esphome/components/adc/adc_sensor.h index b8c702be4e..fafb0d5ca0 100644 --- a/esphome/components/adc/adc_sensor.h +++ b/esphome/components/adc/adc_sensor.h @@ -18,6 +18,7 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage #ifdef USE_ESP32 /// Set the attenuation for this pin. Only available on the ESP32. void set_attenuation(adc_atten_t attenuation); + void set_autorange(bool autorange); #endif /// Update adc values. @@ -36,9 +37,12 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage protected: InternalGPIOPin *pin_; + uint16_t read_raw_(); + uint32_t raw_to_microvolts_(uint16_t raw); #ifdef USE_ESP32 adc_atten_t attenuation_{ADC_ATTEN_DB_0}; + bool autorange_{false}; #endif }; diff --git a/esphome/components/adc/sensor.py b/esphome/components/adc/sensor.py index 26ef504c1a..0265f52d31 100644 --- a/esphome/components/adc/sensor.py +++ b/esphome/components/adc/sensor.py @@ -21,6 +21,7 @@ ATTENUATION_MODES = { "2.5db": cg.global_ns.ADC_ATTEN_DB_2_5, "6db": cg.global_ns.ADC_ATTEN_DB_6, "11db": cg.global_ns.ADC_ATTEN_DB_11, + "auto": "auto", } @@ -92,4 +93,7 @@ async def to_code(config): cg.add(var.set_pin(pin)) if CONF_ATTENUATION in config: - cg.add(var.set_attenuation(config[CONF_ATTENUATION])) + if config[CONF_ATTENUATION] == "auto": + cg.add(var.set_autorange(cg.global_ns.true)) + else: + cg.add(var.set_attenuation(config[CONF_ATTENUATION])) From e4d17e0b15433e264023e00eb4c776bc4f543a33 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 039/142] 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 7cfede5b835cb72a652cf07ab3ce6129bfba778f 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 040/142] 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 a32cb1e123..b946019f18 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==10.0.0 # esp-idf requires this, but doesn't bundle it by default From 64a45dc6a67d261023a5823f5cfddfc0e4fcdab7 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Wed, 20 Oct 2021 20:15:09 +0200 Subject: [PATCH 041/142] 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 15b596841858af676c4adb3d0abee2e87e92947e Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Wed, 20 Oct 2021 20:31:13 +0200 Subject: [PATCH 042/142] 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 c51b5095014f7f303e5407ad48dee798b69469a4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 20 Oct 2021 22:59:46 +0200 Subject: [PATCH 043/142] Bump paho-mqtt from 1.5.1 to 1.6.0 (#2568) Bumps [paho-mqtt](https://github.com/eclipse/paho.mqtt.python) from 1.5.1 to 1.6.0. - [Release notes](https://github.com/eclipse/paho.mqtt.python/releases) - [Changelog](https://github.com/eclipse/paho.mqtt.python/blob/master/ChangeLog.txt) - [Commits](https://github.com/eclipse/paho.mqtt.python/commits) --- updated-dependencies: - dependency-name: paho-mqtt dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] 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 b946019f18..7a2eeb58d6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ voluptuous==0.12.2 PyYAML==6.0 -paho-mqtt==1.5.1 +paho-mqtt==1.6.0 colorama==0.4.4 tornado==6.1 tzlocal==3.0 # from time From 34606b0f1fb8fcb3b453d3f44c7121caeeeab4f3 Mon Sep 17 00:00:00 2001 From: Maurice Makaay Date: Thu, 21 Oct 2021 12:23:21 +0200 Subject: [PATCH 044/142] 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 7f34561e53b1146f47a4288bd2992aa073c7bb2d Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Thu, 21 Oct 2021 12:23:37 +0200 Subject: [PATCH 045/142] 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 4c76967842..836e895374 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 e39f314e7ab30756d23a269996c2f01885dd5f0e Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Thu, 21 Oct 2021 12:24:01 +0200 Subject: [PATCH 046/142] 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 f41f7994a3e05cac5012e71744547fbb4e8e991a Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Thu, 21 Oct 2021 14:20:23 +0200 Subject: [PATCH 047/142] 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 1caabb641927c787cc17aabe4d1ab7bbd6ac2249 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Thu, 21 Oct 2021 14:20:57 +0200 Subject: [PATCH 048/142] 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 c615dc573a223b7a20a363d868bf075d3a50ebfb Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Thu, 21 Oct 2021 14:39:36 +0200 Subject: [PATCH 049/142] 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 c248ba40438a3c1c83c5187ed80064db161072ad Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Thu, 21 Oct 2021 14:53:08 +0200 Subject: [PATCH 050/142] 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 7a2eeb58d6..69058e108b 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 156104d5f5d83970970a15c1f2e663533495a150 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Thu, 21 Oct 2021 15:29:32 +0200 Subject: [PATCH 051/142] 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 cac5b356db7deab24f5f66375b8ad4a6ee28d289 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Thu, 21 Oct 2021 16:01:33 +0200 Subject: [PATCH 052/142] ESP32 ADC use factory calibration data (#2574) --- esphome/components/adc/adc_sensor.cpp | 224 +++++++++++--------------- esphome/components/adc/adc_sensor.h | 8 +- esphome/components/adc/sensor.py | 91 +++++++++-- 3 files changed, 177 insertions(+), 146 deletions(-) diff --git a/esphome/components/adc/adc_sensor.cpp b/esphome/components/adc/adc_sensor.cpp index 0c24c615f3..9b7d0437e1 100644 --- a/esphome/components/adc/adc_sensor.cpp +++ b/esphome/components/adc/adc_sensor.cpp @@ -16,50 +16,6 @@ namespace adc { static const char *const TAG = "adc"; -#ifdef USE_ESP32 -inline adc1_channel_t gpio_to_adc1(uint8_t pin) { -#if CONFIG_IDF_TARGET_ESP32 - switch (pin) { - case 36: - return ADC1_CHANNEL_0; - case 37: - return ADC1_CHANNEL_1; - case 38: - return ADC1_CHANNEL_2; - case 39: - return ADC1_CHANNEL_3; - case 32: - return ADC1_CHANNEL_4; - case 33: - return ADC1_CHANNEL_5; - case 34: - return ADC1_CHANNEL_6; - case 35: - return ADC1_CHANNEL_7; - default: - return ADC1_CHANNEL_MAX; - } -#elif CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32H2 - switch (pin) { - case 0: - return ADC1_CHANNEL_0; - case 1: - return ADC1_CHANNEL_1; - case 2: - return ADC1_CHANNEL_2; - case 3: - return ADC1_CHANNEL_3; - case 4: - return ADC1_CHANNEL_4; - default: - return ADC1_CHANNEL_MAX; - } -#endif -} -void ADCSensor::set_attenuation(adc_atten_t attenuation) { this->attenuation_ = attenuation; } -void ADCSensor::set_autorange(bool autorange) { this->autorange_ = autorange; } -#endif - void ADCSensor::setup() { ESP_LOGCONFIG(TAG, "Setting up ADC '%s'...", this->get_name().c_str()); #ifndef USE_ADC_SENSOR_VCC @@ -67,15 +23,36 @@ void ADCSensor::setup() { #endif #ifdef USE_ESP32 - if (this->autorange_) - this->attenuation_ = ADC_ATTEN_DB_11; - adc1_config_channel_atten(gpio_to_adc1(pin_->get_pin()), attenuation_); adc1_config_width(ADC_WIDTH_BIT_12); -#if !CONFIG_IDF_TARGET_ESP32C3 && !CONFIG_IDF_TARGET_ESP32H2 - adc_gpio_init(ADC_UNIT_1, (adc_channel_t) gpio_to_adc1(pin_->get_pin())); -#endif + if (!autorange_) { + adc1_config_channel_atten(channel_, attenuation_); + } + + // load characteristics for each attenuation + for (int i = 0; i < (int) ADC_ATTEN_MAX; i++) { + auto cal_value = esp_adc_cal_characterize(ADC_UNIT_1, (adc_atten_t) i, ADC_WIDTH_BIT_12, + 1100, // default vref + &cal_characteristics_[i]); + switch (cal_value) { + case ESP_ADC_CAL_VAL_EFUSE_VREF: + ESP_LOGV(TAG, "Using eFuse Vref for calibration"); + break; + case ESP_ADC_CAL_VAL_EFUSE_TP: + ESP_LOGV(TAG, "Using two-point eFuse Vref for calibration"); + break; + case ESP_ADC_CAL_VAL_DEFAULT_VREF: + default: + break; + } + } + + // adc_gpio_init doesn't exist on ESP32-C3 or ESP32-H2 +#if !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32H2) + adc_gpio_init(ADC_UNIT_1, (adc_channel_t) channel_); #endif +#endif // USE_ESP32 } + void ADCSensor::dump_config() { LOG_SENSOR("", "ADC Sensor", this); #ifdef USE_ESP8266 @@ -84,7 +61,8 @@ void ADCSensor::dump_config() { #else LOG_PIN(" Pin: ", pin_); #endif -#endif +#endif // USE_ESP8266 + #ifdef USE_ESP32 LOG_PIN(" Pin: ", pin_); if (autorange_) @@ -106,101 +84,81 @@ void ADCSensor::dump_config() { default: // This is to satisfy the unused ADC_ATTEN_MAX break; } -#endif +#endif // USE_ESP32 LOG_UPDATE_INTERVAL(this); } + float ADCSensor::get_setup_priority() const { return setup_priority::DATA; } void ADCSensor::update() { float value_v = this->sample(); ESP_LOGD(TAG, "'%s': Got voltage=%.2fV", this->get_name().c_str(), value_v); this->publish_state(value_v); } -uint16_t ADCSensor::read_raw_() { -#ifdef USE_ESP32 - return adc1_get_raw(gpio_to_adc1(pin_->get_pin())); -#endif #ifdef USE_ESP8266 -#ifdef USE_ADC_SENSOR_VCC - return ESP.getVcc(); // NOLINT(readability-static-accessed-through-instance) -#else - return analogRead(this->pin_->get_pin()); // NOLINT -#endif -#endif -} -uint32_t ADCSensor::raw_to_microvolts_(uint16_t raw) { -#ifdef USE_ESP32 -#if CONFIG_IDF_TARGET_ESP32 - switch (this->attenuation_) { - case ADC_ATTEN_DB_0: - return raw * 269; // 1e6 * 1.1 / 4095 - case ADC_ATTEN_DB_2_5: - return raw * 366; // 1e6 * 1.5 / 4095 - case ADC_ATTEN_DB_6: - return raw * 537; // 1e6 * 2.2 / 4095 - case ADC_ATTEN_DB_11: - return raw * 952; // 1e6 * 3.9 / 4095 - default: // This is to satisfy the unused ADC_ATTEN_MAX - return raw * 244; // 1e6 * 1.0 / 4095 - } -#elif CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32H2 - switch (this->attenuation_) { - case ADC_ATTEN_DB_0: - return raw * 205; // 1e6 * 0.84 / 4095 - case ADC_ATTEN_DB_2_5: - return raw * 276; // 1e6 * 1.13 / 4095 - case ADC_ATTEN_DB_6: - return raw * 381; // 1e6 * 1.56 / 4095 - case ADC_ATTEN_DB_11: - return raw * 733; // 1e6 * 3.0 / 4095 - default: // This is to satisfy the unused ADC_ATTEN_MAX - return raw * 244; // 1e6 * 1.0 / 4095 - } -#endif -#endif - -#ifdef USE_ESP8266 - return raw * 977; // 1e6 / 1024 -#endif -} float ADCSensor::sample() { - int raw = this->read_raw_(); - uint32_t v = this->raw_to_microvolts_(raw); -#ifdef USE_ESP32 - if (autorange_) { - int raw11 = raw, raw6 = 4095, raw2 = 4095, raw0 = 4095; - uint32_t v11 = v, v6 = 0, v2 = 0, v0 = 0; - if (raw11 < 4095) { // Progressively read all attenuation ranges - adc1_config_channel_atten(gpio_to_adc1(pin_->get_pin()), ADC_ATTEN_DB_6); - raw6 = this->read_raw_(); - v6 = this->raw_to_microvolts_(raw6); - if (raw6 < 4095) { - adc1_config_channel_atten(gpio_to_adc1(pin_->get_pin()), ADC_ATTEN_DB_2_5); - raw2 = this->read_raw_(); - v2 = this->raw_to_microvolts_(raw2); - if (raw2 < 4095) { - adc1_config_channel_atten(gpio_to_adc1(pin_->get_pin()), ADC_ATTEN_DB_0); - raw0 = this->read_raw_(); - v0 = this->raw_to_microvolts_(raw0); - } - } - adc1_config_channel_atten(gpio_to_adc1(pin_->get_pin()), ADC_ATTEN_DB_11); - } // Contribution coefficients (normalized to 2048) - uint16_t c11 = clamp(raw11, 0, 2048); // high 1, middle 1, low 0 - uint16_t c6 = (2048 - abs(raw6 - 2048)); // high 0, middle 1, low 0 - uint16_t c2 = (2048 - abs(raw2 - 2048)); // high 0, middle 1, low 0 - uint16_t c0 = clamp(4095 - raw0, 0, 2048); // high 0, middle 1, low 1 - uint32_t csum = c11 + c6 + c2 + c0; // sum to normalize the final result - if (csum > 0) - v = (v11 * c11) + (v6 * c6) + (v2 * c2) + (v0 * c0); - else // in case of error, this keeps the 11db output (v) - csum = 1; - csum *= 1e6; // include the 1e6 microvolts->volts conversion factor - return (float) v / (float) csum; // normalize, convert & return - } +#ifdef USE_ADC_SENSOR_VCC + return ESP.getVcc() / 1024.0f; // NOLINT(readability-static-accessed-through-instance) +#else + return analogRead(this->pin_->get_pin()) / 1024.0f; // NOLINT #endif - return v / (float) 1e6; // convert from microvolts to volts } +#endif + +#ifdef USE_ESP32 +float ADCSensor::sample() { + if (!autorange_) { + int raw = adc1_get_raw(channel_); + if (raw == -1) { + return NAN; + } + uint32_t mv = esp_adc_cal_raw_to_voltage(raw, &cal_characteristics_[(int) attenuation_]); + return mv / 1000.0f; + } + + int raw11, raw6 = 4095, raw2 = 4095, raw0 = 4095; + adc1_config_channel_atten(channel_, ADC_ATTEN_DB_11); + raw11 = adc1_get_raw(channel_); + if (raw11 < 4095) { + adc1_config_channel_atten(channel_, ADC_ATTEN_DB_6); + raw6 = adc1_get_raw(channel_); + if (raw6 < 4095) { + adc1_config_channel_atten(channel_, ADC_ATTEN_DB_2_5); + raw2 = adc1_get_raw(channel_); + if (raw2 < 4095) { + adc1_config_channel_atten(channel_, ADC_ATTEN_DB_0); + raw0 = adc1_get_raw(channel_); + } + } + } + + if (raw0 == -1 || raw2 == -1 || raw6 == -1 || raw11 == -1) { + return NAN; + } + // prevent divide by zero + if (raw0 == 0 && raw2 == 0 && raw6 == 0 && raw11 == 0) { + return 0; + } + + uint32_t mv11 = esp_adc_cal_raw_to_voltage(raw11, &cal_characteristics_[(int) ADC_ATTEN_DB_11]); + uint32_t mv6 = esp_adc_cal_raw_to_voltage(raw6, &cal_characteristics_[(int) ADC_ATTEN_DB_6]); + uint32_t mv2 = esp_adc_cal_raw_to_voltage(raw2, &cal_characteristics_[(int) ADC_ATTEN_DB_2_5]); + uint32_t mv0 = esp_adc_cal_raw_to_voltage(raw0, &cal_characteristics_[(int) ADC_ATTEN_DB_0]); + + // Contribution of each value, in range 0-2048 + uint32_t c11 = std::min(raw11, 2048); + uint32_t c6 = 2048 - std::abs(raw6 - 2048); + uint32_t c2 = 2048 - std::abs(raw2 - 2048); + uint32_t c0 = std::min(4095 - raw0, 2048); + // max theoretical csum value is 2048*4 = 8192 + uint32_t csum = c11 + c6 + c2 + c0; + + // each mv is max 3900; so max value is 3900*2048*4, fits in unsigned + uint32_t mv_scaled = (mv11 * c11) + (mv6 * c6) + (mv2 * c2) + (mv0 * c0); + return mv_scaled / (float) (csum * 1000U); +} +#endif // USE_ESP32 + #ifdef USE_ESP8266 std::string ADCSensor::unique_id() { return get_mac_address() + "-adc"; } #endif diff --git a/esphome/components/adc/adc_sensor.h b/esphome/components/adc/adc_sensor.h index fafb0d5ca0..9984c72819 100644 --- a/esphome/components/adc/adc_sensor.h +++ b/esphome/components/adc/adc_sensor.h @@ -8,6 +8,7 @@ #ifdef USE_ESP32 #include "driver/adc.h" +#include #endif namespace esphome { @@ -17,8 +18,9 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage public: #ifdef USE_ESP32 /// Set the attenuation for this pin. Only available on the ESP32. - void set_attenuation(adc_atten_t attenuation); - void set_autorange(bool autorange); + void set_attenuation(adc_atten_t attenuation) { attenuation_ = attenuation; } + void set_channel(adc1_channel_t channel) { channel_ = channel; } + void set_autorange(bool autorange) { autorange_ = autorange; } #endif /// Update adc values. @@ -42,7 +44,9 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage #ifdef USE_ESP32 adc_atten_t attenuation_{ADC_ATTEN_DB_0}; + adc1_channel_t channel_{}; bool autorange_{false}; + esp_adc_cal_characteristics_t cal_characteristics_[(int) ADC_ATTEN_MAX] = {}; #endif }; diff --git a/esphome/components/adc/sensor.py b/esphome/components/adc/sensor.py index 0265f52d31..9fdddaa0a6 100644 --- a/esphome/components/adc/sensor.py +++ b/esphome/components/adc/sensor.py @@ -6,12 +6,21 @@ from esphome.const import ( CONF_ATTENUATION, CONF_ID, CONF_INPUT, + CONF_NUMBER, CONF_PIN, DEVICE_CLASS_VOLTAGE, STATE_CLASS_MEASUREMENT, UNIT_VOLT, ) from esphome.core import CORE +from esphome.components.esp32 import get_esp32_variant +from esphome.components.esp32.const import ( + VARIANT_ESP32, + VARIANT_ESP32C3, + VARIANT_ESP32H2, + VARIANT_ESP32S2, + VARIANT_ESP32S3, +) AUTO_LOAD = ["voltage_sampler"] @@ -24,21 +33,77 @@ ATTENUATION_MODES = { "auto": "auto", } +adc1_channel_t = cg.global_ns.enum("adc1_channel_t") + +# From https://github.com/espressif/esp-idf/blob/master/components/driver/include/driver/adc_common.h +# pin to adc1 channel mapping +ESP32_VARIANT_ADC1_PIN_TO_CHANNEL = { + VARIANT_ESP32: { + 36: adc1_channel_t.ADC1_CHANNEL_0, + 37: adc1_channel_t.ADC1_CHANNEL_1, + 38: adc1_channel_t.ADC1_CHANNEL_2, + 39: adc1_channel_t.ADC1_CHANNEL_3, + 32: adc1_channel_t.ADC1_CHANNEL_4, + 33: adc1_channel_t.ADC1_CHANNEL_5, + 34: adc1_channel_t.ADC1_CHANNEL_6, + 35: adc1_channel_t.ADC1_CHANNEL_7, + }, + VARIANT_ESP32S2: { + 1: adc1_channel_t.ADC1_CHANNEL_0, + 2: adc1_channel_t.ADC1_CHANNEL_1, + 3: adc1_channel_t.ADC1_CHANNEL_2, + 4: adc1_channel_t.ADC1_CHANNEL_3, + 5: adc1_channel_t.ADC1_CHANNEL_4, + 6: adc1_channel_t.ADC1_CHANNEL_5, + 7: adc1_channel_t.ADC1_CHANNEL_6, + 8: adc1_channel_t.ADC1_CHANNEL_7, + 9: adc1_channel_t.ADC1_CHANNEL_8, + 10: adc1_channel_t.ADC1_CHANNEL_9, + }, + VARIANT_ESP32S3: { + 1: adc1_channel_t.ADC1_CHANNEL_0, + 2: adc1_channel_t.ADC1_CHANNEL_1, + 3: adc1_channel_t.ADC1_CHANNEL_2, + 4: adc1_channel_t.ADC1_CHANNEL_3, + 5: adc1_channel_t.ADC1_CHANNEL_4, + 6: adc1_channel_t.ADC1_CHANNEL_5, + 7: adc1_channel_t.ADC1_CHANNEL_6, + 8: adc1_channel_t.ADC1_CHANNEL_7, + 9: adc1_channel_t.ADC1_CHANNEL_8, + 10: adc1_channel_t.ADC1_CHANNEL_9, + }, + VARIANT_ESP32C3: { + 0: adc1_channel_t.ADC1_CHANNEL_0, + 1: adc1_channel_t.ADC1_CHANNEL_1, + 2: adc1_channel_t.ADC1_CHANNEL_2, + 3: adc1_channel_t.ADC1_CHANNEL_3, + 4: adc1_channel_t.ADC1_CHANNEL_4, + }, + VARIANT_ESP32H2: { + 0: adc1_channel_t.ADC1_CHANNEL_0, + 1: adc1_channel_t.ADC1_CHANNEL_1, + 2: adc1_channel_t.ADC1_CHANNEL_2, + 3: adc1_channel_t.ADC1_CHANNEL_3, + 4: adc1_channel_t.ADC1_CHANNEL_4, + }, +} + def validate_adc_pin(value): if str(value).upper() == "VCC": return cv.only_on_esp8266("VCC") if CORE.is_esp32: - from esphome.components.esp32 import is_esp32c3 - value = pins.internal_gpio_input_pin_number(value) - if is_esp32c3(): - if not (0 <= value <= 4): # ADC1 - raise cv.Invalid("ESP32-C3: Only pins 0 though 4 support ADC.") - elif not (32 <= value <= 39): # ADC1 - raise cv.Invalid("ESP32: Only pins 32 though 39 support ADC.") - elif CORE.is_esp8266: + variant = get_esp32_variant() + if variant not in ESP32_VARIANT_ADC1_PIN_TO_CHANNEL: + raise cv.Invalid(f"This ESP32 variant ({variant}) is not supported") + + if value not in ESP32_VARIANT_ADC1_PIN_TO_CHANNEL[variant]: + raise cv.Invalid(f"{variant} doesn't support ADC on this pin") + return pins.internal_gpio_input_pin_schema(value) + + if CORE.is_esp8266: from esphome.components.esp8266.gpio import CONF_ANALOG value = pins.internal_gpio_pin_number({CONF_ANALOG: True, CONF_INPUT: True})( @@ -50,10 +115,8 @@ def validate_adc_pin(value): return pins.gpio_pin_schema( {CONF_ANALOG: True, CONF_INPUT: True}, internal=True )(value) - else: - raise NotImplementedError - return pins.internal_gpio_input_pin_schema(value) + raise NotImplementedError adc_ns = cg.esphome_ns.namespace("adc") @@ -97,3 +160,9 @@ async def to_code(config): cg.add(var.set_autorange(cg.global_ns.true)) else: cg.add(var.set_attenuation(config[CONF_ATTENUATION])) + + if CORE.is_esp32: + variant = get_esp32_variant() + pin_num = config[CONF_PIN][CONF_NUMBER] + chan = ESP32_VARIANT_ADC1_PIN_TO_CHANNEL[variant][pin_num] + cg.add(var.set_channel(chan)) From f2ebfe7aef849b3d39c43a24566a03b64e35e7b5 Mon Sep 17 00:00:00 2001 From: Maurice Makaay Date: Thu, 21 Oct 2021 16:02:28 +0200 Subject: [PATCH 053/142] Add mDNS config dump (#2576) --- esphome/components/mdns/mdns_component.cpp | 29 ++++++++++++++----- esphome/components/mdns/mdns_component.h | 6 ++-- .../components/mdns/mdns_esp32_arduino.cpp | 9 +++--- esphome/components/mdns/mdns_esp8266.cpp | 11 ++++--- esphome/components/mdns/mdns_esp_idf.cpp | 11 +++---- 5 files changed, 40 insertions(+), 26 deletions(-) diff --git a/esphome/components/mdns/mdns_component.cpp b/esphome/components/mdns/mdns_component.cpp index 631af9eba9..13f65edfc4 100644 --- a/esphome/components/mdns/mdns_component.cpp +++ b/esphome/components/mdns/mdns_component.cpp @@ -13,13 +13,16 @@ namespace esphome { namespace mdns { +static const char *const TAG = "mdns"; + #ifndef WEBSERVER_PORT #define WEBSERVER_PORT 80 // NOLINT #endif -std::vector MDNSComponent::compile_services_() { - std::vector res; +void MDNSComponent::compile_records_() { + this->hostname_ = App.get_name(); + this->services_.clear(); #ifdef USE_API if (api::global_api_server != nullptr) { MDNSService service{}; @@ -50,7 +53,7 @@ std::vector MDNSComponent::compile_services_() { service.txt_records.push_back({"package_import_url", dashboard_import::get_package_import_url()}); #endif - res.push_back(service); + this->services_.push_back(service); } #endif // USE_API @@ -60,11 +63,11 @@ std::vector MDNSComponent::compile_services_() { service.service_type = "_prometheus-http"; service.proto = "_tcp"; service.port = WEBSERVER_PORT; - res.push_back(service); + this->services_.push_back(service); } #endif - if (res.empty()) { + if (this->services_.empty()) { // Publish "http" service if not using native API // This is just to have *some* mDNS service so that .local resolution works MDNSService service{}; @@ -72,11 +75,21 @@ std::vector MDNSComponent::compile_services_() { service.proto = "_tcp"; service.port = WEBSERVER_PORT; service.txt_records.push_back({"version", ESPHOME_VERSION}); - res.push_back(service); + this->services_.push_back(service); + } +} + +void MDNSComponent::dump_config() { + ESP_LOGCONFIG(TAG, "mDNS:"); + ESP_LOGCONFIG(TAG, " Hostname: %s", this->hostname_.c_str()); + ESP_LOGV(TAG, " Services:"); + for (const auto &service : this->services_) { + ESP_LOGV(TAG, " - %s, %s, %d", service.service_type.c_str(), service.proto.c_str(), service.port); + for (const auto &record : service.txt_records) { + ESP_LOGV(TAG, " TXT: %s = %s", record.key.c_str(), record.value.c_str()); + } } - return res; } -std::string MDNSComponent::compile_hostname_() { return App.get_name(); } } // namespace mdns } // namespace esphome diff --git a/esphome/components/mdns/mdns_component.h b/esphome/components/mdns/mdns_component.h index 679fb1a768..45614d509a 100644 --- a/esphome/components/mdns/mdns_component.h +++ b/esphome/components/mdns/mdns_component.h @@ -26,6 +26,7 @@ struct MDNSService { class MDNSComponent : public Component { public: void setup() override; + void dump_config() override; #if defined(USE_ESP8266) && defined(USE_ARDUINO) void loop() override; @@ -33,8 +34,9 @@ class MDNSComponent : public Component { float get_setup_priority() const override { return setup_priority::AFTER_WIFI; } protected: - std::vector compile_services_(); - std::string compile_hostname_(); + std::vector services_{}; + std::string hostname_; + void compile_records_(); }; } // namespace mdns diff --git a/esphome/components/mdns/mdns_esp32_arduino.cpp b/esphome/components/mdns/mdns_esp32_arduino.cpp index 4d13b7321a..6a66beef92 100644 --- a/esphome/components/mdns/mdns_esp32_arduino.cpp +++ b/esphome/components/mdns/mdns_esp32_arduino.cpp @@ -7,13 +7,12 @@ namespace esphome { namespace mdns { -static const char *const TAG = "mdns"; - void MDNSComponent::setup() { - MDNS.begin(compile_hostname_().c_str()); + this->compile_records_(); - auto services = compile_services_(); - for (const auto &service : services) { + MDNS.begin(this->hostname_.c_str()); + + for (const auto &service : this->services_) { MDNS.addService(service.service_type.c_str(), service.proto.c_str(), 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()); diff --git a/esphome/components/mdns/mdns_esp8266.cpp b/esphome/components/mdns/mdns_esp8266.cpp index 1a73e4b5b3..ff305f907a 100644 --- a/esphome/components/mdns/mdns_esp8266.cpp +++ b/esphome/components/mdns/mdns_esp8266.cpp @@ -9,14 +9,13 @@ namespace esphome { namespace mdns { -static const char *const TAG = "mdns"; - void MDNSComponent::setup() { - network::IPAddress addr = network::get_ip_address(); - MDNS.begin(compile_hostname_().c_str(), (uint32_t) addr); + this->compile_records_(); - auto services = compile_services_(); - for (const auto &service : services) { + network::IPAddress addr = network::get_ip_address(); + MDNS.begin(this->hostname_.c_str(), (uint32_t) addr); + + for (const auto &service : this->services_) { // 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 diff --git a/esphome/components/mdns/mdns_esp_idf.cpp b/esphome/components/mdns/mdns_esp_idf.cpp index 17874f1ffe..40d9f1d5f3 100644 --- a/esphome/components/mdns/mdns_esp_idf.cpp +++ b/esphome/components/mdns/mdns_esp_idf.cpp @@ -11,18 +11,19 @@ namespace mdns { static const char *const TAG = "mdns"; void MDNSComponent::setup() { + this->compile_records_(); + esp_err_t err = mdns_init(); if (err != ESP_OK) { - ESP_LOGW(TAG, "MDNS init failed: %s", esp_err_to_name(err)); + ESP_LOGW(TAG, "mDNS init failed: %s", esp_err_to_name(err)); this->mark_failed(); return; } - mdns_hostname_set(compile_hostname_().c_str()); - mdns_instance_name_set(compile_hostname_().c_str()); + mdns_hostname_set(this->hostname_.c_str()); + mdns_instance_name_set(this->hostname_.c_str()); - auto services = compile_services_(); - for (const auto &service : services) { + for (const auto &service : this->services_) { std::vector txt_records; for (const auto &record : service.txt_records) { mdns_txt_item_t it{}; From eccdef82116b8878541ca96555b6f64857012e1c Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Thu, 21 Oct 2021 18:53:08 +0200 Subject: [PATCH 054/142] Fix mDNS ESP8266 log not included (#2589) --- esphome/components/mdns/mdns_component.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/esphome/components/mdns/mdns_component.cpp b/esphome/components/mdns/mdns_component.cpp index 13f65edfc4..915c640b06 100644 --- a/esphome/components/mdns/mdns_component.cpp +++ b/esphome/components/mdns/mdns_component.cpp @@ -2,6 +2,7 @@ #include "esphome/core/defines.h" #include "esphome/core/version.h" #include "esphome/core/application.h" +#include "esphome/core/log.h" #ifdef USE_API #include "esphome/components/api/api_server.h" From ca59dd13022ac9b217baf86046213dbd51cd6fb6 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Thu, 21 Oct 2021 18:57:03 +0200 Subject: [PATCH 055/142] 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 8735d3b83e41b5b72084290db3bc1803680e9019 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Thu, 21 Oct 2021 18:59:49 +0200 Subject: [PATCH 056/142] 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 c0fc5b48aec6c99e9e95bb74f050c54f54a9fe68 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Thu, 21 Oct 2021 19:55:19 +0200 Subject: [PATCH 057/142] 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 27d7d7ca699bf4a8008d9a36e22f956e16c1870b Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Thu, 21 Oct 2021 19:56:47 +0200 Subject: [PATCH 058/142] 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 4f7d95d694..e00eebb1a4 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -4,7 +4,10 @@ __version__ = "2021.11.0-dev" 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 f1f2640d0e752c053dc7cf26810ec8922d1985be 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 059/142] 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 69058e108b..9a64272df5 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==10.0.0 # esp-idf requires this, but doesn't bundle it by default From f408f074c49c7127892640c3bcb919896e5000ad Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 21 Oct 2021 20:01:27 +0200 Subject: [PATCH 060/142] Bump platformio from 5.2.1 to 5.2.2 (#2569) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Oxan van Leeuwen --- docker/Dockerfile | 2 +- requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 7928ff8901..7c4a6a270a 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.1 \ + platformio==5.2.2 \ # 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 9a64272df5..1eb76e51e0 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 # When updating platformio, also update Dockerfile +platformio==5.2.2 # When updating platformio, also update Dockerfile esptool==3.1 click==8.0.3 esphome-dashboard==20211021.1 From 07a9cb910fd13e98e0e5cb67f9590db163d74db4 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Thu, 21 Oct 2021 20:07:37 +0200 Subject: [PATCH 061/142] 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 4765173778a7fde84bd77449f49174b72729fdc8 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Thu, 21 Oct 2021 20:07:44 +0200 Subject: [PATCH 062/142] Update docker base images (#2583) --- docker/Dockerfile | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 7c4a6a270a..0d30bb0267 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -5,12 +5,12 @@ # One of "docker", "hassio" ARG BASEIMGTYPE=docker -FROM ghcr.io/hassio-addons/debian-base/amd64:5.1.0 AS base-hassio-amd64 -FROM ghcr.io/hassio-addons/debian-base/aarch64:5.1.0 AS base-hassio-arm64 -FROM ghcr.io/hassio-addons/debian-base/armv7:5.1.0 AS base-hassio-armv7 -FROM debian:bullseye-20210902-slim AS base-docker-amd64 -FROM debian:bullseye-20210902-slim AS base-docker-arm64 -FROM debian:bullseye-20210902-slim AS base-docker-armv7 +FROM ghcr.io/hassio-addons/debian-base/amd64:5.1.1 AS base-hassio-amd64 +FROM ghcr.io/hassio-addons/debian-base/aarch64:5.1.1 AS base-hassio-arm64 +FROM ghcr.io/hassio-addons/debian-base/armv7:5.1.1 AS base-hassio-armv7 +FROM debian:bullseye-20211011-slim AS base-docker-amd64 +FROM debian:bullseye-20211011-slim AS base-docker-arm64 +FROM debian:bullseye-20211011-slim AS base-docker-armv7 # Use TARGETARCH/TARGETVARIANT defined by docker # https://docs.docker.com/engine/reference/builder/#automatic-platform-args-in-the-global-scope From 5389382798129f7ae78d7074f89b1f3a86c2f2ac Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 21 Oct 2021 20:09:29 +0200 Subject: [PATCH 063/142] Bump paho-mqtt from 1.6.0 to 1.6.1 (#2596) 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 1eb76e51e0..3b6378c06f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ voluptuous==0.12.2 PyYAML==6.0 -paho-mqtt==1.6.0 +paho-mqtt==1.6.1 colorama==0.4.4 tornado==6.1 tzlocal==3.0 # from time From a88c0224065513176eeeac85f07bf3297df33809 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Fri, 22 Oct 2021 07:53:06 +1300 Subject: [PATCH 064/142] Logging a proper url allows terminals to make it clickable (#2554) --- 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 63378a38b5..084dd84a07 100644 --- a/esphome/dashboard/dashboard.py +++ b/esphome/dashboard/dashboard.py @@ -963,7 +963,7 @@ def start_web_server(args): server.add_socket(socket) else: _LOGGER.info( - "Starting dashboard web server on port %s and configuration dir %s...", + "Starting dashboard web server on http://0.0.0.0:%s and configuration dir %s...", args.port, settings.config_dir, ) From b141aea4c0936e7983ac31c5cd80c34569142420 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 21 Oct 2021 20:54:12 +0200 Subject: [PATCH 065/142] Bump aioesphomeapi from 10.0.0 to 10.0.3 (#2595) 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 3b6378c06f..fa95f2156c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,7 +10,7 @@ platformio==5.2.2 # When updating platformio, also update Dockerfile esptool==3.1 click==8.0.3 esphome-dashboard==20211021.1 -aioesphomeapi==10.0.0 +aioesphomeapi==10.0.3 # esp-idf requires this, but doesn't bundle it by default # https://github.com/espressif/esp-idf/blob/220590d599e134d7a5e7f1e683cc4550349ffbf8/requirements.txt#L24 From 1468acfced9e43e3201bec46e4819630e5cbbc1d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 21 Oct 2021 22:46:05 +0200 Subject: [PATCH 066/142] Bump tzlocal from 3.0 to 4.0.1 (#2553) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Oxan van Leeuwen --- esphome/components/time/__init__.py | 8 ++------ requirements.txt | 2 +- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/esphome/components/time/__init__.py b/esphome/components/time/__init__.py index 5c2155d764..2d73d0aef9 100644 --- a/esphome/components/time/__init__.py +++ b/esphome/components/time/__init__.py @@ -1,7 +1,6 @@ import logging from importlib import resources from typing import Optional -from datetime import timezone import tzlocal @@ -66,14 +65,11 @@ def _extract_tz_string(tzfile: bytes) -> str: def detect_tz() -> str: - localzone = tzlocal.get_localzone() - if localzone is timezone.utc: - return "UTC0" - if not hasattr(localzone, "key"): + iana_key = tzlocal.get_localzone_name() + if iana_key is None: raise cv.Invalid( "Could not automatically determine timezone, please set timezone manually." ) - iana_key = localzone.key _LOGGER.info("Detected timezone '%s'", iana_key) tzfile = _load_tzdata(iana_key) if tzfile is None: diff --git a/requirements.txt b/requirements.txt index fa95f2156c..3ed53d0c90 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,7 +3,7 @@ PyYAML==6.0 paho-mqtt==1.6.1 colorama==0.4.4 tornado==6.1 -tzlocal==3.0 # from time +tzlocal==4.0.1 # from time tzdata>=2021.1 # from time pyserial==3.5 platformio==5.2.2 # When updating platformio, also update Dockerfile From 68c854706752f387d51caf5adbf2d9091cf04b58 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Thu, 21 Oct 2021 22:48:28 +0200 Subject: [PATCH 067/142] Add IDF support to dallas (#2578) --- esphome/components/dallas/__init__.py | 26 ++--- .../components/dallas/dallas_component.cpp | 66 +++++------ esphome/components/dallas/dallas_component.h | 4 +- esphome/components/dallas/esp_one_wire.cpp | 103 ++++++++++-------- esphome/components/dallas/esp_one_wire.h | 7 +- esphome/components/esp32/gpio_arduino.cpp | 37 ++++--- esphome/components/esp32/gpio_idf.cpp | 87 +++++++++------ esphome/components/esp32/gpio_idf.h | 3 +- esphome/components/esp8266/gpio.cpp | 50 +++++---- esphome/core/gpio.h | 1 + 10 files changed, 201 insertions(+), 183 deletions(-) diff --git a/esphome/components/dallas/__init__.py b/esphome/components/dallas/__init__.py index 2dbc69b8e2..0f71399a7c 100644 --- a/esphome/components/dallas/__init__.py +++ b/esphome/components/dallas/__init__.py @@ -6,26 +6,20 @@ from esphome.const import CONF_ID, CONF_PIN MULTI_CONF = True AUTO_LOAD = ["sensor"] -CONF_ONE_WIRE_ID = "one_wire_id" dallas_ns = cg.esphome_ns.namespace("dallas") DallasComponent = dallas_ns.class_("DallasComponent", cg.PollingComponent) -ESPOneWire = dallas_ns.class_("ESPOneWire") -CONFIG_SCHEMA = cv.All( - cv.Schema( - { - cv.GenerateID(): cv.declare_id(DallasComponent), - cv.GenerateID(CONF_ONE_WIRE_ID): cv.declare_id(ESPOneWire), - cv.Required(CONF_PIN): pins.internal_gpio_output_pin_schema, - } - ).extend(cv.polling_component_schema("60s")), - # pin_mode call logs in esp-idf, but InterruptLock is active -> crash - cv.only_with_arduino, -) +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.declare_id(DallasComponent), + cv.Required(CONF_PIN): pins.internal_gpio_output_pin_schema, + } +).extend(cv.polling_component_schema("60s")) async def to_code(config): - pin = await cg.gpio_pin_expression(config[CONF_PIN]) - one_wire = cg.new_Pvariable(config[CONF_ONE_WIRE_ID], pin) - var = cg.new_Pvariable(config[CONF_ID], one_wire) + var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) + + pin = await cg.gpio_pin_expression(config[CONF_PIN]) + cg.add(var.set_pin(pin)) diff --git a/esphome/components/dallas/dallas_component.cpp b/esphome/components/dallas/dallas_component.cpp index 0fc4108687..8d7f2e4deb 100644 --- a/esphome/components/dallas/dallas_component.cpp +++ b/esphome/components/dallas/dallas_component.cpp @@ -31,12 +31,11 @@ uint16_t DallasTemperatureSensor::millis_to_wait_for_conversion() const { void DallasComponent::setup() { ESP_LOGCONFIG(TAG, "Setting up DallasComponent..."); - yield(); + pin_->setup(); + one_wire_ = new ESPOneWire(pin_); // NOLINT(cppcoreguidelines-owning-memory) + std::vector raw_sensors; - { - InterruptLock lock; - raw_sensors = this->one_wire_->search_vec(); - } + raw_sensors = this->one_wire_->search_vec(); for (auto &address : raw_sensors) { std::string s = uint64_to_string(address); @@ -70,7 +69,7 @@ void DallasComponent::setup() { } void DallasComponent::dump_config() { ESP_LOGCONFIG(TAG, "DallasComponent:"); - LOG_PIN(" Pin: ", this->one_wire_->get_pin()); + LOG_PIN(" Pin: ", this->pin_); LOG_UPDATE_INTERVAL(this); if (this->found_sensors_.empty()) { @@ -102,15 +101,12 @@ void DallasComponent::update() { this->status_clear_warning(); bool result; - { - InterruptLock lock; - if (!this->one_wire_->reset()) { - result = false; - } else { - result = true; - this->one_wire_->skip(); - this->one_wire_->write8(DALLAS_COMMAND_START_CONVERSION); - } + if (!this->one_wire_->reset()) { + result = false; + } else { + result = true; + this->one_wire_->skip(); + this->one_wire_->write8(DALLAS_COMMAND_START_CONVERSION); } if (!result) { @@ -121,11 +117,7 @@ void DallasComponent::update() { for (auto *sensor : this->sensors_) { this->set_timeout(sensor->get_address_name(), sensor->millis_to_wait_for_conversion(), [this, sensor] { - bool res; - { - InterruptLock lock; - res = sensor->read_scratch_pad(); - } + bool res = sensor->read_scratch_pad(); if (!res) { ESP_LOGW(TAG, "'%s' - Resetting bus for read failed!", sensor->get_name().c_str()); @@ -146,7 +138,6 @@ void DallasComponent::update() { }); } } -DallasComponent::DallasComponent(ESPOneWire *one_wire) : one_wire_(one_wire) {} void DallasTemperatureSensor::set_address(uint64_t address) { this->address_ = address; } uint8_t DallasTemperatureSensor::get_resolution() const { return this->resolution_; } @@ -162,7 +153,7 @@ const std::string &DallasTemperatureSensor::get_address_name() { return this->address_name_; } bool IRAM_ATTR DallasTemperatureSensor::read_scratch_pad() { - ESPOneWire *wire = this->parent_->one_wire_; + auto *wire = this->parent_->one_wire_; if (!wire->reset()) { return false; } @@ -176,11 +167,7 @@ bool IRAM_ATTR DallasTemperatureSensor::read_scratch_pad() { return true; } bool DallasTemperatureSensor::setup_sensor() { - bool r; - { - InterruptLock lock; - r = this->read_scratch_pad(); - } + bool r = this->read_scratch_pad(); if (!r) { ESP_LOGE(TAG, "Reading scratchpad failed: reset"); @@ -214,21 +201,18 @@ bool DallasTemperatureSensor::setup_sensor() { break; } - ESPOneWire *wire = this->parent_->one_wire_; - { - InterruptLock lock; - if (wire->reset()) { - wire->select(this->address_); - wire->write8(DALLAS_COMMAND_WRITE_SCRATCH_PAD); - wire->write8(this->scratch_pad_[2]); // high alarm temp - wire->write8(this->scratch_pad_[3]); // low alarm temp - wire->write8(this->scratch_pad_[4]); // resolution - wire->reset(); + auto *wire = this->parent_->one_wire_; + if (wire->reset()) { + wire->select(this->address_); + wire->write8(DALLAS_COMMAND_WRITE_SCRATCH_PAD); + wire->write8(this->scratch_pad_[2]); // high alarm temp + wire->write8(this->scratch_pad_[3]); // low alarm temp + wire->write8(this->scratch_pad_[4]); // resolution + wire->reset(); - // write value to EEPROM - wire->select(this->address_); - wire->write8(0x48); - } + // write value to EEPROM + wire->select(this->address_); + wire->write8(0x48); } delay(20); // allow it to finish operation diff --git a/esphome/components/dallas/dallas_component.h b/esphome/components/dallas/dallas_component.h index 8d405f6eab..37c098283a 100644 --- a/esphome/components/dallas/dallas_component.h +++ b/esphome/components/dallas/dallas_component.h @@ -11,8 +11,7 @@ class DallasTemperatureSensor; class DallasComponent : public PollingComponent { public: - explicit DallasComponent(ESPOneWire *one_wire); - + void set_pin(InternalGPIOPin *pin) { pin_ = pin; } void register_sensor(DallasTemperatureSensor *sensor); void setup() override; @@ -24,6 +23,7 @@ class DallasComponent : public PollingComponent { protected: friend DallasTemperatureSensor; + InternalGPIOPin *pin_; ESPOneWire *one_wire_; std::vector sensors_; std::vector found_sensors_; diff --git a/esphome/components/dallas/esp_one_wire.cpp b/esphome/components/dallas/esp_one_wire.cpp index 9278b83f7f..a0ab10f8a4 100644 --- a/esphome/components/dallas/esp_one_wire.cpp +++ b/esphome/components/dallas/esp_one_wire.cpp @@ -10,115 +10,123 @@ static const char *const TAG = "dallas.one_wire"; const uint8_t ONE_WIRE_ROM_SELECT = 0x55; const int ONE_WIRE_ROM_SEARCH = 0xF0; -ESPOneWire::ESPOneWire(GPIOPin *pin) : pin_(pin) {} +ESPOneWire::ESPOneWire(InternalGPIOPin *pin) { pin_ = pin->to_isr(); } bool HOT IRAM_ATTR ESPOneWire::reset() { - uint8_t retries = 125; + // See reset here: + // https://www.maximintegrated.com/en/design/technical-documents/app-notes/1/126.html + InterruptLock lock; - // Wait for communication to clear - this->pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP); + // Wait for communication to clear (delay G) + pin_.pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP); + uint8_t retries = 125; do { if (--retries == 0) return false; delayMicroseconds(2); - } while (!this->pin_->digital_read()); + } while (!pin_.digital_read()); - // Send 480µs LOW TX reset pulse - this->pin_->pin_mode(gpio::FLAG_OUTPUT); - this->pin_->digital_write(false); + // Send 480µs LOW TX reset pulse (drive bus low, delay H) + pin_.pin_mode(gpio::FLAG_OUTPUT); + pin_.digital_write(false); delayMicroseconds(480); - // Switch into RX mode, letting the pin float - this->pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP); - // after 15µs-60µs wait time, responder pulls low for 60µs-240µs - // let's have 70µs just in case + // Release the bus, delay I + pin_.pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP); delayMicroseconds(70); - bool r = !this->pin_->digital_read(); + // sample bus, 0=device(s) present, 1=no device present + bool r = !pin_.digital_read(); + // delay J delayMicroseconds(410); return r; } void HOT IRAM_ATTR ESPOneWire::write_bit(bool bit) { - // Initiate write/read by pulling low. - this->pin_->pin_mode(gpio::FLAG_OUTPUT); - this->pin_->digital_write(false); + // See write 1/0 bit here: + // https://www.maximintegrated.com/en/design/technical-documents/app-notes/1/126.html + InterruptLock lock; - // bus sampled within 15µs and 60µs after pulling LOW. - if (bit) { - // pull high/release within 15µs - delayMicroseconds(10); - this->pin_->digital_write(true); - // in total minimum of 60µs long - delayMicroseconds(55); - } else { - // continue pulling LOW for at least 60µs - delayMicroseconds(65); - this->pin_->digital_write(true); - // grace period, 1µs recovery time - delayMicroseconds(5); - } + // drive bus low + pin_.pin_mode(gpio::FLAG_OUTPUT); + pin_.digital_write(false); + + uint32_t delay0 = bit ? 10 : 65; + uint32_t delay1 = bit ? 55 : 5; + + // delay A/C + delayMicroseconds(delay0); + // release bus + pin_.digital_write(true); + // delay B/D + delayMicroseconds(delay1); } bool HOT IRAM_ATTR ESPOneWire::read_bit() { - // Initiate read slot by pulling LOW for at least 1µs - this->pin_->pin_mode(gpio::FLAG_OUTPUT); - this->pin_->digital_write(false); + // See read bit here: + // https://www.maximintegrated.com/en/design/technical-documents/app-notes/1/126.html + InterruptLock lock; + + // drive bus low, delay A + pin_.pin_mode(gpio::FLAG_OUTPUT); + pin_.digital_write(false); delayMicroseconds(3); - // release bus, we have to sample within 15µs of pulling low - this->pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP); + // release bus, delay E + pin_.pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP); delayMicroseconds(10); - bool r = this->pin_->digital_read(); - // read time slot at least 60µs long + 1µs recovery time between slots + // sample bus to read bit from peer + bool r = pin_.digital_read(); + + // delay F delayMicroseconds(53); return r; } -void IRAM_ATTR ESPOneWire::write8(uint8_t val) { +void ESPOneWire::write8(uint8_t val) { for (uint8_t i = 0; i < 8; i++) { this->write_bit(bool((1u << i) & val)); } } -void IRAM_ATTR ESPOneWire::write64(uint64_t val) { +void ESPOneWire::write64(uint64_t val) { for (uint8_t i = 0; i < 64; i++) { this->write_bit(bool((1ULL << i) & val)); } } -uint8_t IRAM_ATTR ESPOneWire::read8() { +uint8_t ESPOneWire::read8() { uint8_t ret = 0; for (uint8_t i = 0; i < 8; i++) { ret |= (uint8_t(this->read_bit()) << i); } return ret; } -uint64_t IRAM_ATTR ESPOneWire::read64() { +uint64_t ESPOneWire::read64() { uint64_t ret = 0; for (uint8_t i = 0; i < 8; i++) { ret |= (uint64_t(this->read_bit()) << i); } return ret; } -void IRAM_ATTR ESPOneWire::select(uint64_t address) { +void ESPOneWire::select(uint64_t address) { this->write8(ONE_WIRE_ROM_SELECT); this->write64(address); } -void IRAM_ATTR ESPOneWire::reset_search() { +void ESPOneWire::reset_search() { this->last_discrepancy_ = 0; this->last_device_flag_ = false; this->last_family_discrepancy_ = 0; this->rom_number_ = 0; } -uint64_t HOT IRAM_ATTR ESPOneWire::search() { +uint64_t ESPOneWire::search() { if (this->last_device_flag_) { return 0u; } if (!this->reset()) { - // Reset failed + // Reset failed or no devices present this->reset_search(); return 0u; } @@ -196,7 +204,7 @@ uint64_t HOT IRAM_ATTR ESPOneWire::search() { return this->rom_number_; } -std::vector IRAM_ATTR ESPOneWire::search_vec() { +std::vector ESPOneWire::search_vec() { std::vector res; this->reset_search(); @@ -206,10 +214,9 @@ std::vector IRAM_ATTR ESPOneWire::search_vec() { return res; } -void IRAM_ATTR ESPOneWire::skip() { +void ESPOneWire::skip() { this->write8(0xCC); // skip ROM } -GPIOPin *ESPOneWire::get_pin() { return this->pin_; } uint8_t IRAM_ATTR *ESPOneWire::rom_number8_() { return reinterpret_cast(&this->rom_number_); } diff --git a/esphome/components/dallas/esp_one_wire.h b/esphome/components/dallas/esp_one_wire.h index 728fa127d3..ef6f079f02 100644 --- a/esphome/components/dallas/esp_one_wire.h +++ b/esphome/components/dallas/esp_one_wire.h @@ -1,6 +1,5 @@ #pragma once -#include "esphome/core/component.h" #include "esphome/core/hal.h" #include @@ -12,7 +11,7 @@ extern const int ONE_WIRE_ROM_SEARCH; class ESPOneWire { public: - explicit ESPOneWire(GPIOPin *pin); + explicit ESPOneWire(InternalGPIOPin *pin); /** Reset the bus, should be done before all write operations. * @@ -55,13 +54,11 @@ class ESPOneWire { /// Helper that wraps search in a std::vector. std::vector search_vec(); - GPIOPin *get_pin(); - protected: /// Helper to get the internal 64-bit unsigned rom number as a 8-bit integer pointer. inline uint8_t *rom_number8_(); - GPIOPin *pin_; + ISRInternalGPIOPin pin_; uint8_t last_discrepancy_{0}; uint8_t last_family_discrepancy_{0}; bool last_device_flag_{false}; diff --git a/esphome/components/esp32/gpio_arduino.cpp b/esphome/components/esp32/gpio_arduino.cpp index c4bb21a0aa..ba92894f97 100644 --- a/esphome/components/esp32/gpio_arduino.cpp +++ b/esphome/components/esp32/gpio_arduino.cpp @@ -9,6 +9,22 @@ namespace esp32 { static const char *const TAG = "esp32"; +static int IRAM_ATTR flags_to_mode(gpio::Flags flags) { + if (flags == gpio::FLAG_INPUT) { + return INPUT; + } else if (flags == gpio::FLAG_OUTPUT) { + return OUTPUT; + } else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLUP)) { + return INPUT_PULLUP; + } else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLDOWN)) { + return INPUT_PULLDOWN; + } else if (flags == (gpio::FLAG_OUTPUT | gpio::FLAG_OPEN_DRAIN)) { + return OUTPUT_OPEN_DRAIN; + } else { + return 0; + } +} + struct ISRPinArg { uint8_t pin; bool inverted; @@ -43,22 +59,9 @@ void ArduinoInternalGPIOPin::attach_interrupt(void (*func)(void *), void *arg, g attachInterruptArg(pin_, func, arg, arduino_mode); } + void ArduinoInternalGPIOPin::pin_mode(gpio::Flags flags) { - uint8_t mode; - if (flags == gpio::FLAG_INPUT) { - mode = INPUT; - } else if (flags == gpio::FLAG_OUTPUT) { - mode = OUTPUT; - } else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLUP)) { - mode = INPUT_PULLUP; - } else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLDOWN)) { - mode = INPUT_PULLDOWN; - } else if (flags == (gpio::FLAG_OUTPUT | gpio::FLAG_OPEN_DRAIN)) { - mode = OUTPUT_OPEN_DRAIN; - } else { - return; - } - pinMode(pin_, mode); // NOLINT + pinMode(pin_, flags_to_mode(flags)); // NOLINT } std::string ArduinoInternalGPIOPin::dump_summary() const { @@ -101,6 +104,10 @@ void IRAM_ATTR ISRInternalGPIOPin::clear_interrupt() { } #endif } +void IRAM_ATTR ISRInternalGPIOPin::pin_mode(gpio::Flags flags) { + auto *arg = reinterpret_cast(arg_); + pinMode(arg->pin, flags_to_mode(flags)); // NOLINT +} } // namespace esphome diff --git a/esphome/components/esp32/gpio_idf.cpp b/esphome/components/esp32/gpio_idf.cpp index d1853e1f8b..498843ebff 100644 --- a/esphome/components/esp32/gpio_idf.cpp +++ b/esphome/components/esp32/gpio_idf.cpp @@ -10,38 +10,7 @@ static const char *const TAG = "esp32"; bool IDFInternalGPIOPin::isr_service_installed = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) -struct ISRPinArg { - gpio_num_t pin; - bool inverted; -}; - -ISRInternalGPIOPin IDFInternalGPIOPin::to_isr() const { - auto *arg = new ISRPinArg{}; // NOLINT(cppcoreguidelines-owning-memory) - arg->pin = pin_; - arg->inverted = inverted_; - return ISRInternalGPIOPin((void *) arg); -} - -void IDFInternalGPIOPin::setup() { - pin_mode(flags_); - gpio_set_drive_capability(pin_, drive_strength_); -} - -void IDFInternalGPIOPin::pin_mode(gpio::Flags flags) { - gpio_config_t conf{}; - conf.pin_bit_mask = 1ULL << static_cast(pin_); - conf.mode = flags_to_mode(flags); - conf.pull_up_en = flags & gpio::FLAG_PULLUP ? GPIO_PULLUP_ENABLE : GPIO_PULLUP_DISABLE; - conf.pull_down_en = flags & gpio::FLAG_PULLDOWN ? GPIO_PULLDOWN_ENABLE : GPIO_PULLDOWN_DISABLE; - conf.intr_type = GPIO_INTR_DISABLE; - gpio_config(&conf); -} - -bool IDFInternalGPIOPin::digital_read() { return bool(gpio_get_level(pin_)) != inverted_; } - -void IDFInternalGPIOPin::digital_write(bool value) { gpio_set_level(pin_, value != inverted_ ? 1 : 0); } - -gpio_mode_t IDFInternalGPIOPin::flags_to_mode(gpio::Flags flags) { +static gpio_mode_t IRAM_ATTR flags_to_mode(gpio::Flags flags) { flags = (gpio::Flags)(flags & ~(gpio::FLAG_PULLUP | gpio::FLAG_PULLDOWN)); if (flags == gpio::FLAG_NONE) { return GPIO_MODE_DISABLE; @@ -61,6 +30,18 @@ gpio_mode_t IDFInternalGPIOPin::flags_to_mode(gpio::Flags flags) { } } +struct ISRPinArg { + gpio_num_t pin; + bool inverted; +}; + +ISRInternalGPIOPin IDFInternalGPIOPin::to_isr() const { + auto *arg = new ISRPinArg{}; // NOLINT(cppcoreguidelines-owning-memory) + arg->pin = pin_; + arg->inverted = inverted_; + return ISRInternalGPIOPin((void *) arg); +} + void IDFInternalGPIOPin::attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const { gpio_int_type_t idf_type = GPIO_INTR_ANYEDGE; switch (type) { @@ -99,6 +80,35 @@ std::string IDFInternalGPIOPin::dump_summary() const { return buffer; } +void IDFInternalGPIOPin::setup() { + gpio_config_t conf{}; + conf.pin_bit_mask = 1ULL << static_cast(pin_); + conf.mode = flags_to_mode(flags_); + conf.pull_up_en = flags_ & gpio::FLAG_PULLUP ? GPIO_PULLUP_ENABLE : GPIO_PULLUP_DISABLE; + conf.pull_down_en = flags_ & gpio::FLAG_PULLDOWN ? GPIO_PULLDOWN_ENABLE : GPIO_PULLDOWN_DISABLE; + conf.intr_type = GPIO_INTR_DISABLE; + gpio_config(&conf); + gpio_set_drive_capability(pin_, drive_strength_); +} + +void IDFInternalGPIOPin::pin_mode(gpio::Flags flags) { + // can't call gpio_config here because that logs in esp-idf which may cause issues + gpio_set_direction(pin_, flags_to_mode(flags)); + gpio_pull_mode_t pull_mode = GPIO_FLOATING; + if (flags & (gpio::FLAG_PULLUP | gpio::FLAG_PULLDOWN)) { + pull_mode = GPIO_PULLUP_PULLDOWN; + } else if (flags & gpio::FLAG_PULLUP) { + pull_mode = GPIO_PULLUP_ONLY; + } else if (flags & gpio::FLAG_PULLDOWN) { + pull_mode = GPIO_PULLDOWN_ONLY; + } + gpio_set_pull_mode(pin_, pull_mode); +} + +bool IDFInternalGPIOPin::digital_read() { return bool(gpio_get_level(pin_)) != inverted_; } +void IDFInternalGPIOPin::digital_write(bool value) { gpio_set_level(pin_, value != inverted_ ? 1 : 0); } +void IDFInternalGPIOPin::detach_interrupt() const { gpio_intr_disable(pin_); } + } // namespace esp32 using namespace esp32; @@ -114,6 +124,19 @@ void IRAM_ATTR ISRInternalGPIOPin::digital_write(bool value) { void IRAM_ATTR ISRInternalGPIOPin::clear_interrupt() { // not supported } +void IRAM_ATTR ISRInternalGPIOPin::pin_mode(gpio::Flags flags) { + auto *arg = reinterpret_cast(arg_); + gpio_set_direction(arg->pin, flags_to_mode(flags)); + gpio_pull_mode_t pull_mode = GPIO_FLOATING; + if (flags & (gpio::FLAG_PULLUP | gpio::FLAG_PULLDOWN)) { + pull_mode = GPIO_PULLUP_PULLDOWN; + } else if (flags & gpio::FLAG_PULLUP) { + pull_mode = GPIO_PULLUP_ONLY; + } else if (flags & gpio::FLAG_PULLDOWN) { + pull_mode = GPIO_PULLDOWN_ONLY; + } + gpio_set_pull_mode(arg->pin, pull_mode); +} } // namespace esphome diff --git a/esphome/components/esp32/gpio_idf.h b/esphome/components/esp32/gpio_idf.h index a99571cc46..a07d11378a 100644 --- a/esphome/components/esp32/gpio_idf.h +++ b/esphome/components/esp32/gpio_idf.h @@ -18,13 +18,12 @@ class IDFInternalGPIOPin : public InternalGPIOPin { bool digital_read() override; void digital_write(bool value) override; std::string dump_summary() const override; - void detach_interrupt() const override { gpio_intr_disable(pin_); } + void detach_interrupt() const override; ISRInternalGPIOPin to_isr() const override; uint8_t get_pin() const override { return (uint8_t) pin_; } bool is_inverted() const override { return inverted_; } protected: - static gpio_mode_t flags_to_mode(gpio::Flags flags); void attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const override; gpio_num_t pin_; diff --git a/esphome/components/esp8266/gpio.cpp b/esphome/components/esp8266/gpio.cpp index 7805889b40..2660318182 100644 --- a/esphome/components/esp8266/gpio.cpp +++ b/esphome/components/esp8266/gpio.cpp @@ -8,6 +8,29 @@ namespace esp8266 { static const char *const TAG = "esp8266"; +static int IRAM_ATTR flags_to_mode(gpio::Flags flags, uint8_t pin) { + if (flags == gpio::FLAG_INPUT) { + return INPUT; + } else if (flags == gpio::FLAG_OUTPUT) { + return OUTPUT; + } else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLUP)) { + if (pin == 16) { + // GPIO16 doesn't have a pullup, so pinMode would fail. + // However, sometimes this method is called with pullup mode anyway + // for example from dallas one_wire. For those cases convert this + // to a INPUT mode. + return INPUT; + } + return INPUT_PULLUP; + } else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLDOWN)) { + return INPUT_PULLDOWN_16; + } else if (flags == (gpio::FLAG_OUTPUT | gpio::FLAG_OPEN_DRAIN)) { + return OUTPUT_OPEN_DRAIN; + } else { + return 0; + } +} + struct ISRPinArg { uint8_t pin; bool inverted; @@ -43,28 +66,7 @@ void ESP8266GPIOPin::attach_interrupt(void (*func)(void *), void *arg, gpio::Int attachInterruptArg(pin_, func, arg, arduino_mode); } void ESP8266GPIOPin::pin_mode(gpio::Flags flags) { - uint8_t mode; - if (flags == gpio::FLAG_INPUT) { - mode = INPUT; - } else if (flags == gpio::FLAG_OUTPUT) { - mode = OUTPUT; - } else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLUP)) { - mode = INPUT_PULLUP; - 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)) { - mode = OUTPUT_OPEN_DRAIN; - } else { - return; - } - pinMode(pin_, mode); // NOLINT + pinMode(pin_, flags_to_mode(flags, pin_)); // NOLINT } std::string ESP8266GPIOPin::dump_summary() const { @@ -97,6 +99,10 @@ void IRAM_ATTR ISRInternalGPIOPin::clear_interrupt() { auto *arg = reinterpret_cast(arg_); GPIO_REG_WRITE(GPIO_STATUS_W1TC_ADDRESS, 1UL << arg->pin); } +void IRAM_ATTR ISRInternalGPIOPin::pin_mode(gpio::Flags flags) { + auto *arg = reinterpret_cast(arg_); + pinMode(arg->pin, flags_to_mode(flags, arg->pin)); // NOLINT +} } // namespace esphome diff --git a/esphome/core/gpio.h b/esphome/core/gpio.h index 1d3fb89805..04658d567c 100644 --- a/esphome/core/gpio.h +++ b/esphome/core/gpio.h @@ -70,6 +70,7 @@ class ISRInternalGPIOPin { bool digital_read(); void digital_write(bool value); void clear_interrupt(); + void pin_mode(gpio::Flags flags); protected: void *arg_ = nullptr; From 9220d9fc5222b90187c4a4413d7def262f5c8ec3 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Fri, 22 Oct 2021 10:46:44 +0200 Subject: [PATCH 068/142] 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 f7b3f52731c6842dd9c44feea5ff91aeafef0497 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Fri, 22 Oct 2021 12:09:47 +0200 Subject: [PATCH 069/142] Limit hostnames to 31 characters (#2531) --- esphome/config_validation.py | 18 +--- esphome/core/config.py | 95 ++++++++++++++-------- tests/unit_tests/test_config_validation.py | 22 ----- 3 files changed, 62 insertions(+), 73 deletions(-) diff --git a/esphome/config_validation.py b/esphome/config_validation.py index fcec74b245..5d4ff64193 100644 --- a/esphome/config_validation.py +++ b/esphome/config_validation.py @@ -903,21 +903,9 @@ def validate_bytes(value): def hostname(value): value = string(value) - warned_underscore = False - if len(value) > 63: - raise Invalid("Hostnames can only be 63 characters long") - for c in value: - if not (c.isalnum() or c in "-_"): - raise Invalid("Hostname can only have alphanumeric characters and -") - if c in "_" and not warned_underscore: - _LOGGER.warning( - "'%s': Using the '_' (underscore) character in the hostname is discouraged " - "as it can cause problems with some DHCP and local name services. " - "For more information, see https://esphome.io/guides/faq.html#why-shouldn-t-i-use-underscores-in-my-device-name", - value, - ) - warned_underscore = True - return value + if re.match(r"^[a-z0-9-]{1,63}$", value, re.IGNORECASE) is not None: + return value + raise Invalid(f"Invalid hostname: {value}") def domain(value): diff --git a/esphome/core/config.py b/esphome/core/config.py index 235e0eeb84..ad132968b9 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -55,6 +55,24 @@ CONF_NAME_ADD_MAC_SUFFIX = "name_add_mac_suffix" VALID_INCLUDE_EXTS = {".h", ".hpp", ".tcc", ".ino", ".cpp", ".c"} +def validate_hostname(config): + max_length = 31 + if config[CONF_NAME_ADD_MAC_SUFFIX]: + max_length -= 7 # "-AABBCC" is appended when add mac suffix option is used + if len(config[CONF_NAME]) > max_length: + raise cv.Invalid( + f"Hostnames can only be {max_length} characters long", path=[CONF_NAME] + ) + if "_" in config[CONF_NAME]: + _LOGGER.warning( + "'%s': Using the '_' (underscore) character in the hostname is discouraged " + "as it can cause problems with some DHCP and local name services. " + "For more information, see https://esphome.io/guides/faq.html#why-shouldn-t-i-use-underscores-in-my-device-name", + config[CONF_NAME], + ) + return config + + def valid_include(value): try: return cv.directory(value) @@ -79,42 +97,47 @@ def valid_project_name(value: str): CONF_ESP8266_RESTORE_FROM_FLASH = "esp8266_restore_from_flash" -CONFIG_SCHEMA = cv.Schema( - { - cv.Required(CONF_NAME): cv.hostname, - cv.Optional(CONF_COMMENT): cv.string, - cv.Required(CONF_BUILD_PATH): cv.string, - cv.Optional(CONF_PLATFORMIO_OPTIONS, default={}): cv.Schema( - { - cv.string_strict: cv.Any([cv.string], cv.string), - } - ), - cv.Optional(CONF_ON_BOOT): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(StartupTrigger), - cv.Optional(CONF_PRIORITY, default=600.0): cv.float_, - } - ), - cv.Optional(CONF_ON_SHUTDOWN): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ShutdownTrigger), - } - ), - cv.Optional(CONF_ON_LOOP): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(LoopTrigger), - } - ), - cv.Optional(CONF_INCLUDES, default=[]): cv.ensure_list(valid_include), - cv.Optional(CONF_LIBRARIES, default=[]): cv.ensure_list(cv.string_strict), - cv.Optional(CONF_NAME_ADD_MAC_SUFFIX, default=False): cv.boolean, - cv.Optional(CONF_PROJECT): cv.Schema( - { - cv.Required(CONF_NAME): cv.All(cv.string_strict, valid_project_name), - cv.Required(CONF_VERSION): cv.string_strict, - } - ), - } +CONFIG_SCHEMA = cv.All( + cv.Schema( + { + cv.Required(CONF_NAME): cv.valid_name, + cv.Optional(CONF_COMMENT): cv.string, + cv.Required(CONF_BUILD_PATH): cv.string, + cv.Optional(CONF_PLATFORMIO_OPTIONS, default={}): cv.Schema( + { + cv.string_strict: cv.Any([cv.string], cv.string), + } + ), + cv.Optional(CONF_ON_BOOT): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(StartupTrigger), + cv.Optional(CONF_PRIORITY, default=600.0): cv.float_, + } + ), + cv.Optional(CONF_ON_SHUTDOWN): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ShutdownTrigger), + } + ), + cv.Optional(CONF_ON_LOOP): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(LoopTrigger), + } + ), + cv.Optional(CONF_INCLUDES, default=[]): cv.ensure_list(valid_include), + cv.Optional(CONF_LIBRARIES, default=[]): cv.ensure_list(cv.string_strict), + cv.Optional(CONF_NAME_ADD_MAC_SUFFIX, default=False): cv.boolean, + cv.Optional(CONF_PROJECT): cv.Schema( + { + cv.Required(CONF_NAME): cv.All( + cv.string_strict, valid_project_name + ), + cv.Required(CONF_VERSION): cv.string_strict, + } + ), + } + ), + validate_hostname, ) PRELOAD_CONFIG_SCHEMA = cv.Schema( diff --git a/tests/unit_tests/test_config_validation.py b/tests/unit_tests/test_config_validation.py index e34c7064fa..16cfb16e94 100644 --- a/tests/unit_tests/test_config_validation.py +++ b/tests/unit_tests/test_config_validation.py @@ -40,28 +40,6 @@ def test_valid_name__invalid(value): config_validation.valid_name(value) -@pytest.mark.parametrize("value", ("foo", "bar123", "foo-bar")) -def test_hostname__valid(value): - actual = config_validation.hostname(value) - - assert actual == value - - -@pytest.mark.parametrize("value", ("foo bar", "foobar ", "foo#bar")) -def test_hostname__invalid(value): - with pytest.raises(Invalid): - config_validation.hostname(value) - - -def test_hostname__warning(caplog): - actual = config_validation.hostname("foo_bar") - assert actual == "foo_bar" - assert ( - "Using the '_' (underscore) character in the hostname is discouraged" - in caplog.text - ) - - @given(one_of(integers(), text())) def test_string__valid(value): actual = config_validation.string(value) From be3cb9ef004991b91b68e86f96243dbb500fdac3 Mon Sep 17 00:00:00 2001 From: Paul Monigatti Date: Fri, 22 Oct 2021 23:10:29 +1300 Subject: [PATCH 070/142] Add EntityBase properties to ESP32 Camera (#2600) --- esphome/components/api/api.proto | 1 + esphome/components/api/api_connection.cpp | 1 + esphome/components/api/api_pb2.cpp | 9 +++++++++ esphome/components/api/api_pb2.h | 1 + esphome/components/esp32_camera/__init__.py | 11 ++++------- esphome/components/esp32_camera/esp32_camera.cpp | 1 + esphome/components/esp32_camera/esp32_camera.h | 1 + 7 files changed, 18 insertions(+), 7 deletions(-) diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index 5a6eba004c..3c6a36f032 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -701,6 +701,7 @@ message ListEntitiesCameraResponse { string name = 3; string unique_id = 4; bool disabled_by_default = 5; + string icon = 6; } message CameraImageResponse { diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index c87ccf4dc0..2151d6165c 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -680,6 +680,7 @@ bool APIConnection::send_camera_info(esp32_camera::ESP32Camera *camera) { msg.name = camera->get_name(); msg.unique_id = get_default_unique_id("camera", camera); msg.disabled_by_default = camera->is_disabled_by_default(); + msg.icon = camera->get_icon(); return this->send_list_entities_camera_response(msg); } void APIConnection::camera_image(const CameraImageRequest &msg) { diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index 6a87238186..17fc14c868 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -2910,6 +2910,10 @@ bool ListEntitiesCameraResponse::decode_length(uint32_t field_id, ProtoLengthDel this->unique_id = value.as_string(); return true; } + case 6: { + this->icon = value.as_string(); + return true; + } default: return false; } @@ -2930,6 +2934,7 @@ void ListEntitiesCameraResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(3, this->name); buffer.encode_string(4, this->unique_id); buffer.encode_bool(5, this->disabled_by_default); + buffer.encode_string(6, this->icon); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesCameraResponse::dump_to(std::string &out) const { @@ -2955,6 +2960,10 @@ void ListEntitiesCameraResponse::dump_to(std::string &out) const { out.append(" disabled_by_default: "); out.append(YESNO(this->disabled_by_default)); out.append("\n"); + + out.append(" icon: "); + out.append("'").append(this->icon).append("'"); + out.append("\n"); out.append("}"); } #endif diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index 13a21c4772..eea9ab06f6 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -803,6 +803,7 @@ class ListEntitiesCameraResponse : public ProtoMessage { std::string name{}; std::string unique_id{}; bool disabled_by_default{false}; + std::string icon{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; diff --git a/esphome/components/esp32_camera/__init__.py b/esphome/components/esp32_camera/__init__.py index 7f3aebe238..1a8b3a37ec 100644 --- a/esphome/components/esp32_camera/__init__.py +++ b/esphome/components/esp32_camera/__init__.py @@ -2,10 +2,8 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome import pins from esphome.const import ( - CONF_DISABLED_BY_DEFAULT, CONF_FREQUENCY, CONF_ID, - CONF_NAME, CONF_PIN, CONF_SCL, CONF_SDA, @@ -17,6 +15,7 @@ from esphome.const import ( ) from esphome.core import CORE from esphome.components.esp32 import add_idf_sdkconfig_option +from esphome.cpp_helpers import setup_entity DEPENDENCIES = ["esp32", "api"] @@ -63,11 +62,9 @@ CONF_TEST_PATTERN = "test_pattern" camera_range_param = cv.int_range(min=-2, max=2) -CONFIG_SCHEMA = cv.Schema( +CONFIG_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend( { cv.GenerateID(): cv.declare_id(ESP32Camera), - cv.Required(CONF_NAME): cv.string, - cv.Optional(CONF_DISABLED_BY_DEFAULT, default=False): cv.boolean, cv.Required(CONF_DATA_PINS): cv.All( [pins.internal_gpio_input_pin_number], cv.Length(min=8, max=8) ), @@ -127,8 +124,8 @@ SETTERS = { async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID], config[CONF_NAME]) - cg.add(var.set_disabled_by_default(config[CONF_DISABLED_BY_DEFAULT])) + var = cg.new_Pvariable(config[CONF_ID]) + await setup_entity(var, config) await cg.register_component(var, config) for key, setter in SETTERS.items(): diff --git a/esphome/components/esp32_camera/esp32_camera.cpp b/esphome/components/esp32_camera/esp32_camera.cpp index babfda4113..ad4304d89f 100644 --- a/esphome/components/esp32_camera/esp32_camera.cpp +++ b/esphome/components/esp32_camera/esp32_camera.cpp @@ -185,6 +185,7 @@ ESP32Camera::ESP32Camera(const std::string &name) : EntityBase(name) { global_esp32_camera = this; } +ESP32Camera::ESP32Camera() : ESP32Camera("") {} void ESP32Camera::set_data_pins(std::array pins) { this->config_.pin_d0 = pins[0]; this->config_.pin_d1 = pins[1]; diff --git a/esphome/components/esp32_camera/esp32_camera.h b/esphome/components/esp32_camera/esp32_camera.h index d0445607a4..84f8d9cbea 100644 --- a/esphome/components/esp32_camera/esp32_camera.h +++ b/esphome/components/esp32_camera/esp32_camera.h @@ -54,6 +54,7 @@ enum ESP32CameraFrameSize { class ESP32Camera : public Component, public EntityBase { public: ESP32Camera(const std::string &name); + ESP32Camera(); void set_data_pins(std::array pins); void set_vsync_pin(uint8_t pin); void set_href_pin(uint8_t pin); From c08b21b7cd5ec3101b0ade57d1b1f5c2462af308 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Fri, 22 Oct 2021 12:12:07 +0200 Subject: [PATCH 071/142] 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 0d90ef94aeabe45f7229696728acec24cdb55122 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Fri, 22 Oct 2021 13:02:55 +0200 Subject: [PATCH 072/142] 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 b5b3914bbfd28f7bbf2a3aa55113752e0df1a1fe Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Fri, 22 Oct 2021 14:14:07 +0200 Subject: [PATCH 073/142] 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 83bef854157e20210b7917baf54c86e645a7869c Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Fri, 22 Oct 2021 14:14:14 +0200 Subject: [PATCH 074/142] 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 6db9d1122f417c67054ba6e4dad25c9e95276a94 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Fri, 22 Oct 2021 16:52:43 +0200 Subject: [PATCH 075/142] 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 77a6461c9d873a932db9db44af8d31a872f7230c Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Fri, 22 Oct 2021 17:23:31 +0200 Subject: [PATCH 076/142] 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 83400d0417d3df46ab8e17e76e6912823e16ce35 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 077/142] 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 1c4700f447df78ebc5ff1f587d2fb66caa5575de 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 078/142] 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 d85b7a6bd079cba75f4a28380965f1d4975c0bf8 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Sat, 23 Oct 2021 12:37:50 +0200 Subject: [PATCH 079/142] 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 1a6a063e044cd0ad335e75db778377ab78ab5a62 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Sat, 23 Oct 2021 12:38:57 +0200 Subject: [PATCH 080/142] Move default build path to .esphome directory (#2586) --- esphome/core/config.py | 2 +- esphome/platformio_api.py | 2 +- esphome/writer.py | 77 ++------------------------------------- 3 files changed, 6 insertions(+), 75 deletions(-) diff --git a/esphome/core/config.py b/esphome/core/config.py index ad132968b9..04cac29344 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -165,7 +165,7 @@ def preload_core_config(config, result): CORE.data[KEY_CORE] = {} if CONF_BUILD_PATH not in conf: - conf[CONF_BUILD_PATH] = CORE.name + conf[CONF_BUILD_PATH] = f".esphome/build/{CORE.name}" CORE.build_path = CORE.relative_config_path(conf[CONF_BUILD_PATH]) has_oldstyle = CONF_PLATFORM in conf diff --git a/esphome/platformio_api.py b/esphome/platformio_api.py index 70e4430e71..2072e25ec5 100644 --- a/esphome/platformio_api.py +++ b/esphome/platformio_api.py @@ -125,7 +125,7 @@ def _run_idedata(config): def _load_idedata(config): platformio_ini = Path(CORE.relative_build_path("platformio.ini")) - temp_idedata = Path(CORE.relative_internal_path(CORE.name, "idedata.json")) + temp_idedata = Path(CORE.relative_internal_path("idedata", f"{CORE.name}.json")) changed = False if not platformio_ini.is_file() or not temp_idedata.is_file(): diff --git a/esphome/writer.py b/esphome/writer.py index 29532d4f64..8963572752 100644 --- a/esphome/writer.py +++ b/esphome/writer.py @@ -38,10 +38,8 @@ CPP_BASE_FORMAT = ( """" void setup() { - // ===== DO NOT EDIT ANYTHING BELOW THIS LINE ===== """, """ - // ========= YOU CAN EDIT AFTER THIS LINE ========= App.setup(); } @@ -59,10 +57,8 @@ lib_deps = build_flags = upload_flags = -; ===== DO NOT EDIT ANYTHING BELOW THIS LINE ===== """, """ -; ========= YOU CAN EDIT AFTER THIS LINE ========= """, ) @@ -102,61 +98,6 @@ def replace_file_content(text, pattern, repl): return content_new, count -def migrate_src_version_0_to_1(): - main_cpp = CORE.relative_build_path("src", "main.cpp") - if not os.path.isfile(main_cpp): - return - - content = read_file(main_cpp) - - if CPP_INCLUDE_BEGIN in content: - return - - content, count = replace_file_content(content, r"\s*delay\((?:16|20)\);", "") - if count != 0: - _LOGGER.info( - "Migration: Removed %s occurrence of 'delay(16);' in %s", count, main_cpp - ) - - content, count = replace_file_content(content, r"using namespace esphomelib;", "") - if count != 0: - _LOGGER.info( - "Migration: Removed %s occurrence of 'using namespace esphomelib;' " - "in %s", - count, - main_cpp, - ) - - if CPP_INCLUDE_BEGIN not in content: - content, count = replace_file_content( - content, - r'#include "esphomelib/application.h"', - f"{CPP_INCLUDE_BEGIN}\n{CPP_INCLUDE_END}", - ) - if count == 0: - _LOGGER.error( - "Migration failed. ESPHome 1.10.0 needs to have a new auto-generated " - "include section in the %s file. Please remove %s and let it be " - "auto-generated again.", - main_cpp, - main_cpp, - ) - _LOGGER.info("Migration: Added include section to %s", main_cpp) - - write_file_if_changed(main_cpp, content) - - -def migrate_src_version(old, new): - if old == new: - return - if old > new: - _LOGGER.warning("The source version rolled backwards! Ignoring.") - return - - if old == 0: - migrate_src_version_0_to_1() - - def storage_should_clean(old, new): # type: (StorageJSON, StorageJSON) -> bool if old is None: return True @@ -175,9 +116,6 @@ def update_storage_json(): if old == new: return - old_src_version = old.src_version if old is not None else 0 - migrate_src_version(old_src_version, new.src_version) - if storage_should_clean(old, new): _LOGGER.info("Core config or version changed, cleaning build files...") clean_build() @@ -277,12 +215,12 @@ VERSION_H_TARGET = "esphome/core/version.h" ESPHOME_README_TXT = """ THIS DIRECTORY IS AUTO-GENERATED, DO NOT MODIFY -ESPHome automatically populates the esphome/ directory, and any +ESPHome automatically populates the build directory, and any changes to this directory will be removed the next time esphome is run. -For modifying esphome's core files, please use a development esphome install -or use the custom_components folder. +For modifying esphome's core files, please use a development esphome install, +the custom_components folder or the external_components feature. """ @@ -339,9 +277,7 @@ def copy_src_tree(): write_file_if_changed( CORE.relative_src_path("esphome", "core", "defines.h"), generate_defines_h() ) - write_file_if_changed( - CORE.relative_src_path("esphome", "README.txt"), ESPHOME_README_TXT - ) + write_file_if_changed(CORE.relative_build_path("README.txt"), ESPHOME_README_TXT) write_file_if_changed( CORE.relative_src_path("esphome.h"), ESPHOME_H_FORMAT.format(include_s) ) @@ -413,11 +349,6 @@ GITIGNORE_CONTENT = """# Gitignore settings for ESPHome # This is an example and may include too much for your use-case. # You can modify this file to suit your needs. /.esphome/ -**/.pioenvs/ -**/.piolibdeps/ -**/lib/ -**/src/ -**/platformio.ini /secrets.yaml """ From b9e5c7eb35678fe1147575685fc03030c3f1713d Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Sat, 23 Oct 2021 13:25:46 +0200 Subject: [PATCH 081/142] 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 a687b083aed516cbfe93ed25dc51a31837f95fb0 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 082/142] 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 8e77e3c685b4f6a37fde1b04a31fdf87e6f504a2 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Sat, 23 Oct 2021 19:25:53 +0200 Subject: [PATCH 083/142] 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 04cac29344..68c253f7b4 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -237,6 +237,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 de06a781ff205d9b5c03ca7e0518210df1117c03 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Sat, 23 Oct 2021 19:44:55 +0200 Subject: [PATCH 084/142] ESP8266 disable PIO LDF (#2608) --- esphome/components/captive_portal/__init__.py | 2 ++ esphome/components/esp8266/__init__.py | 2 ++ esphome/components/http_request/__init__.py | 2 ++ esphome/components/spi/__init__.py | 4 +++- esphome/components/web_server_base/__init__.py | 2 +- platformio.ini | 2 +- 6 files changed, 11 insertions(+), 3 deletions(-) diff --git a/esphome/components/captive_portal/__init__.py b/esphome/components/captive_portal/__init__.py index 384a3f23a0..f024c94b01 100644 --- a/esphome/components/captive_portal/__init__.py +++ b/esphome/components/captive_portal/__init__.py @@ -36,3 +36,5 @@ async def to_code(config): if CORE.is_esp32: cg.add_library("DNSServer", None) cg.add_library("WiFi", None) + if CORE.is_esp8266: + cg.add_library("DNSServer", None) diff --git a/esphome/components/esp8266/__init__.py b/esphome/components/esp8266/__init__.py index b2706bd4fb..7c7758b943 100644 --- a/esphome/components/esp8266/__init__.py +++ b/esphome/components/esp8266/__init__.py @@ -142,6 +142,8 @@ CONFIG_SCHEMA = cv.All( async def to_code(config): cg.add(esp8266_ns.setup_preferences()) + cg.add_platformio_option("lib_ldf_mode", "off") + cg.add_platformio_option("board", config[CONF_BOARD]) cg.add_build_flag("-DUSE_ESP8266") cg.add_define("ESPHOME_BOARD", config[CONF_BOARD]) diff --git a/esphome/components/http_request/__init__.py b/esphome/components/http_request/__init__.py index 6e249c4247..774d6a0f91 100644 --- a/esphome/components/http_request/__init__.py +++ b/esphome/components/http_request/__init__.py @@ -96,6 +96,8 @@ async def to_code(config): if CORE.is_esp32: cg.add_library("WiFiClientSecure", None) cg.add_library("HTTPClient", None) + if CORE.is_esp8266: + cg.add_library("ESP8266HTTPClient", None) await cg.register_component(var, config) diff --git a/esphome/components/spi/__init__.py b/esphome/components/spi/__init__.py index 3a96cce99b..c917fe1ad8 100644 --- a/esphome/components/spi/__init__.py +++ b/esphome/components/spi/__init__.py @@ -46,7 +46,9 @@ async def to_code(config): mosi = await cg.gpio_pin_expression(config[CONF_MOSI_PIN]) cg.add(var.set_mosi(mosi)) - if CORE.is_esp32: + if CORE.is_esp32 and CORE.using_arduino: + cg.add_library("SPI", None) + if CORE.is_esp8266: cg.add_library("SPI", None) diff --git a/esphome/components/web_server_base/__init__.py b/esphome/components/web_server_base/__init__.py index 4da94d990a..dc1a2bc2f0 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.0") + cg.add_library("esphome/ESPAsyncWebServer-esphome", "2.0.1") diff --git a/platformio.ini b/platformio.ini index ee895ed882..fa8944bf9a 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.0 ; web_server_base + esphome/ESPAsyncWebServer-esphome@2.0.1 ; web_server_base fastled/FastLED@3.3.2 ; fastled_base mikalhart/TinyGPSPlus@1.0.2 ; gps freekode/TM1651@1.0.1 ; tm1651 From 49b17c5a2d0a2a2eab215d1cc2991298e38019f4 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Sat, 23 Oct 2021 21:58:04 +0200 Subject: [PATCH 085/142] Switch issue-close-app to GH Actions and workflow cleanup (#2624) --- .github/issue-close-app.yml | 7 ------- .github/lock.yml | 36 ------------------------------------ .github/workflows/lock.yml | 14 ++++++++++---- .github/workflows/stale.yml | 20 +++++++++++++++++++- 4 files changed, 29 insertions(+), 48 deletions(-) delete mode 100644 .github/issue-close-app.yml delete mode 100644 .github/lock.yml diff --git a/.github/issue-close-app.yml b/.github/issue-close-app.yml deleted file mode 100644 index 5f5fb7572d..0000000000 --- a/.github/issue-close-app.yml +++ /dev/null @@ -1,7 +0,0 @@ -comment: >- - https://github.com/esphome/esphome/issues/430 -issueConfigs: -- content: - - "OTHERWISE THE ISSUE WILL BE CLOSED AUTOMATICALLY" - -caseInsensitive: false diff --git a/.github/lock.yml b/.github/lock.yml deleted file mode 100644 index 0680577b2e..0000000000 --- a/.github/lock.yml +++ /dev/null @@ -1,36 +0,0 @@ -# Configuration for Lock Threads - https://github.com/dessant/lock-threads - -# Number of days of inactivity before a closed issue or pull request is locked -daysUntilLock: 7 - -# Skip issues and pull requests created before a given timestamp. Timestamp must -# follow ISO 8601 (`YYYY-MM-DD`). Set to `false` to disable -skipCreatedBefore: false - -# Issues and pull requests with these labels will be ignored. Set to `[]` to disable -exemptLabels: - - keep-open - -# Label to add before locking, such as `outdated`. Set to `false` to disable -lockLabel: false - -# Comment to post before locking. Set to `false` to disable -lockComment: false - -# Assign `resolved` as the reason for locking. Set to `false` to disable -setLockReason: false - -# Limit to only `issues` or `pulls` -# only: issues - -# Optionally, specify configuration settings just for `issues` or `pulls` -# issues: -# exemptLabels: -# - help-wanted -# lockLabel: outdated - -# pulls: -# daysUntilLock: 30 - -# Repository to extend settings from -# _extends: repo diff --git a/.github/workflows/lock.yml b/.github/workflows/lock.yml index 375b8f1db4..ceb45b2a91 100644 --- a/.github/workflows/lock.yml +++ b/.github/workflows/lock.yml @@ -9,13 +9,19 @@ permissions: issues: write pull-requests: write +concurrency: + group: lock + jobs: lock: runs-on: ubuntu-latest steps: - - uses: dessant/lock-threads@v2 + - uses: dessant/lock-threads@v3 with: - github-token: ${{ github.token }} - pr-lock-inactive-days: "1" + pr-inactive-days: "1" pr-lock-reason: "" - process-only: prs + exclude-any-pr-labels: keep-open + + issue-inactive-days: "7" + issue-lock-reason: "" + exclude-any-issue-labels: keep-open diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 712ae1a289..c3e450d0cf 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -9,13 +9,15 @@ permissions: issues: write pull-requests: write +concurrency: + group: lock + jobs: stale: runs-on: ubuntu-latest steps: - uses: actions/stale@v4 with: - repo-token: ${{ github.token }} days-before-pr-stale: 90 days-before-pr-close: 7 days-before-issue-stale: -1 @@ -28,3 +30,19 @@ jobs: pull request has been automatically marked as stale because of that and will be closed if no further activity occurs within 7 days. Thank you for your contributions. + + # Use stale to automatically close issues with a reference to the issue tracker + close-issues: + runs-on: ubuntu-latest + steps: + - uses: actions/stale@v4 + with: + days-before-pr-stale: -1 + days-before-pr-close: -1 + days-before-issue-stale: 1 + days-before-issue-close: 1 + remove-stale-when-updated: true + stale-issue-label: "stale" + exempt-issue-labels: "not-stale" + stale-issue-message: > + https://github.com/esphome/esphome/issues/430 From 81c11ba1f71249ef8b3c9bb6d72e65dcda4c6960 Mon Sep 17 00:00:00 2001 From: Martin <25747549+martgras@users.noreply.github.com> Date: Mon, 25 Oct 2021 21:53:47 +0200 Subject: [PATCH 086/142] relax max entities checking (#2629) --- esphome/components/modbus/modbus.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/esphome/components/modbus/modbus.cpp b/esphome/components/modbus/modbus.cpp index 45c5bfb603..9524f9daf4 100644 --- a/esphome/components/modbus/modbus.cpp +++ b/esphome/components/modbus/modbus.cpp @@ -139,7 +139,9 @@ void Modbus::send(uint8_t address, uint8_t function_code, uint16_t start_address uint8_t payload_len, const uint8_t *payload) { static const size_t MAX_VALUES = 128; - if (number_of_entities > MAX_VALUES) { + // Only check max number of registers for standard function codes + // Some devices use non standard codes like 0x43 + if (number_of_entities > MAX_VALUES && function_code <= 0x10) { ESP_LOGE(TAG, "send too many values %d max=%zu", number_of_entities, MAX_VALUES); return; } From 87328686a0b0ad81f0061094e904068579d11539 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Tue, 26 Oct 2021 10:55:09 +0200 Subject: [PATCH 087/142] Allow setting URL as platform_version (#2598) --- esphome/components/esp32/__init__.py | 31 ++++++++++++++++---------- esphome/components/esp8266/__init__.py | 23 ++++++++++++------- esphome/config_validation.py | 19 ++++++++++++++++ 3 files changed, 53 insertions(+), 20 deletions(-) diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index db24b9aa29..d84663b2d6 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -147,8 +147,9 @@ def _arduino_check_versions(value): value[CONF_VERSION] = str(version) value[CONF_SOURCE] = source or _format_framework_arduino_version(version) - platform_version = value.get(CONF_PLATFORM_VERSION, ARDUINO_PLATFORM_VERSION) - value[CONF_PLATFORM_VERSION] = str(platform_version) + value[CONF_PLATFORM_VERSION] = value.get( + CONF_PLATFORM_VERSION, _parse_platform_version(str(ARDUINO_PLATFORM_VERSION)) + ) if version != RECOMMENDED_ARDUINO_FRAMEWORK_VERSION: _LOGGER.warning( @@ -184,8 +185,9 @@ def _esp_idf_check_versions(value): 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) + value[CONF_PLATFORM_VERSION] = value.get( + CONF_PLATFORM_VERSION, _parse_platform_version(str(ESP_IDF_PLATFORM_VERSION)) + ) if version != RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION: _LOGGER.warning( @@ -196,6 +198,15 @@ def _esp_idf_check_versions(value): return value +def _parse_platform_version(value): + try: + # if platform version is a valid version constraint, prefix the default package + cv.platformio_version_constraint(value) + return f"platformio/espressif32 @ {value}" + except cv.Invalid: + return value + + def _detect_variant(value): if CONF_VARIANT not in value: board = value[CONF_BOARD] @@ -218,7 +229,7 @@ ARDUINO_FRAMEWORK_SCHEMA = cv.All( { cv.Optional(CONF_VERSION, default="recommended"): cv.string_strict, cv.Optional(CONF_SOURCE): cv.string_strict, - cv.Optional(CONF_PLATFORM_VERSION): cv.string_strict, + cv.Optional(CONF_PLATFORM_VERSION): _parse_platform_version, } ), _arduino_check_versions, @@ -230,7 +241,7 @@ ESP_IDF_FRAMEWORK_SCHEMA = cv.All( { cv.Optional(CONF_VERSION, default="recommended"): cv.string_strict, cv.Optional(CONF_SOURCE): cv.string_strict, - cv.Optional(CONF_PLATFORM_VERSION): cv.string_strict, + cv.Optional(CONF_PLATFORM_VERSION): _parse_platform_version, cv.Optional(CONF_SDKCONFIG_OPTIONS, default={}): { cv.string_strict: cv.string_strict }, @@ -280,10 +291,9 @@ async def to_code(config): cg.add_platformio_option("lib_ldf_mode", "off") conf = config[CONF_FRAMEWORK] + cg.add_platformio_option("platform", conf[CONF_PLATFORM_VERSION]) + if conf[CONF_TYPE] == FRAMEWORK_ESP_IDF: - cg.add_platformio_option( - "platform", f"espressif32 @ {conf[CONF_PLATFORM_VERSION]}" - ) cg.add_platformio_option("framework", "espidf") cg.add_build_flag("-DUSE_ESP_IDF") cg.add_build_flag("-DUSE_ESP32_FRAMEWORK_ESP_IDF") @@ -314,9 +324,6 @@ async def to_code(config): ) elif conf[CONF_TYPE] == FRAMEWORK_ARDUINO: - cg.add_platformio_option( - "platform", f"espressif32 @ {conf[CONF_PLATFORM_VERSION]}" - ) cg.add_platformio_option("framework", "arduino") cg.add_build_flag("-DUSE_ARDUINO") cg.add_build_flag("-DUSE_ESP32_FRAMEWORK_ARDUINO") diff --git a/esphome/components/esp8266/__init__.py b/esphome/components/esp8266/__init__.py index 7c7758b943..5b97d2d9d5 100644 --- a/esphome/components/esp8266/__init__.py +++ b/esphome/components/esp8266/__init__.py @@ -93,12 +93,12 @@ def _arduino_check_versions(value): 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 + platform_version = _parse_platform_version(str(ARDUINO_3_PLATFORM_VERSION)) elif version >= cv.Version(2, 5, 0): - platform_version = ARDUINO_2_PLATFORM_VERSION + platform_version = _parse_platform_version(str(ARDUINO_2_PLATFORM_VERSION)) else: - platform_version = cv.Version(1, 8, 0) - value[CONF_PLATFORM_VERSION] = str(platform_version) + platform_version = _parse_platform_version(str(cv.Version(1, 8, 0))) + value[CONF_PLATFORM_VERSION] = platform_version if version != RECOMMENDED_ARDUINO_FRAMEWORK_VERSION: _LOGGER.warning( @@ -109,13 +109,22 @@ def _arduino_check_versions(value): return value +def _parse_platform_version(value): + try: + # if platform version is a valid version constraint, prefix the default package + cv.platformio_version_constraint(value) + return f"platformio/espressif8266 @ {value}" + except cv.Invalid: + return value + + CONF_PLATFORM_VERSION = "platform_version" ARDUINO_FRAMEWORK_SCHEMA = cv.All( cv.Schema( { cv.Optional(CONF_VERSION, default="recommended"): cv.string_strict, cv.Optional(CONF_SOURCE): cv.string_strict, - cv.Optional(CONF_PLATFORM_VERSION): cv.string_strict, + cv.Optional(CONF_PLATFORM_VERSION): _parse_platform_version, } ), _arduino_check_versions, @@ -152,13 +161,11 @@ async def to_code(config): cg.add_platformio_option("framework", "arduino") cg.add_build_flag("-DUSE_ARDUINO") cg.add_build_flag("-DUSE_ESP8266_FRAMEWORK_ARDUINO") + cg.add_platformio_option("platform", conf[CONF_PLATFORM_VERSION]) cg.add_platformio_option( "platform_packages", [f"platformio/framework-arduinoespressif8266 @ {conf[CONF_SOURCE]}"], ) - cg.add_platformio_option( - "platform", f"platformio/espressif8266 @ {conf[CONF_PLATFORM_VERSION]}" - ) # Default for platformio is LWIP2_LOW_MEMORY with: # - MSS=536 diff --git a/esphome/config_validation.py b/esphome/config_validation.py index 5d4ff64193..fcd014f62d 100644 --- a/esphome/config_validation.py +++ b/esphome/config_validation.py @@ -1668,6 +1668,25 @@ def version_number(value): raise Invalid("Not a version number") from e +def platformio_version_constraint(value): + # for documentation on valid version constraints: + # https://docs.platformio.org/en/latest/core/userguide/platforms/cmd_install.html#cmd-platform-install + + value = string_strict(value) + constraints = [] + for item in value.split(","): + # find and strip prefix operator + op = None + for test_op in ("^", "~", ">=", ">", "<=", "<", "!="): + if item.startswith(test_op): + op = test_op + item = item[len(test_op) :] + break + + constraints.append((op, version_number(item))) + return constraints + + def require_framework_version( *, esp_idf=None, From a01f5f5cf19213eec4501d7166e5dc64a3f34054 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 088/142] 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 c612a3bf6074cf6cd2161a970d3e7d670344b0cf Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Tue, 26 Oct 2021 10:55:27 +0200 Subject: [PATCH 089/142] Constrain GH Actions workflows permissions (#2625) --- .github/workflows/ci-docker.yml | 4 ++++ .github/workflows/ci.yml | 3 +++ .github/workflows/release.yml | 9 +++++++++ 3 files changed, 16 insertions(+) diff --git a/.github/workflows/ci-docker.yml b/.github/workflows/ci-docker.yml index 12f5a7dfc2..1d1cc169b2 100644 --- a/.github/workflows/ci-docker.yml +++ b/.github/workflows/ci-docker.yml @@ -17,6 +17,10 @@ on: - 'requirements*.txt' - 'platformio.ini' +permissions: + contents: read + packages: read + jobs: check-docker: name: Build docker containers diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 45e2f2735c..02b64d2bf5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,6 +8,9 @@ on: pull_request: +permissions: + contents: read + jobs: ci: name: ${{ matrix.name }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index afd893d065..d6895becc0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -7,6 +7,9 @@ on: schedule: - cron: "0 2 * * *" +permissions: + contents: read + jobs: init: name: Initialize build @@ -52,6 +55,9 @@ jobs: deploy-docker: name: Build and publish docker containers if: github.repository == 'esphome/esphome' + permissions: + contents: read + packages: write runs-on: ubuntu-latest needs: [init] strategy: @@ -93,6 +99,9 @@ jobs: deploy-docker-manifest: if: github.repository == 'esphome/esphome' + permissions: + contents: read + packages: write runs-on: ubuntu-latest needs: [init, deploy-docker] strategy: From 2f85c27a053a6b4553670bea90800a81b13d87f2 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 090/142] 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 9f625ee7d1095b4a70605cbae45eca5441b27c13 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 091/142] 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 c2623a08e35e5d1560399315246c0cf110a0a20a 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 092/142] 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 2f4b9263c3b1c451bec78b20172b9529d059fd55 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 26 Oct 2021 22:12:37 +0200 Subject: [PATCH 093/142] Bump tzlocal from 4.0.1 to 4.0.2 (#2631) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 3ed53d0c90..266f61369b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,7 +3,7 @@ PyYAML==6.0 paho-mqtt==1.6.1 colorama==0.4.4 tornado==6.1 -tzlocal==4.0.1 # from time +tzlocal==4.0.2 # from time tzdata>=2021.1 # from time pyserial==3.5 platformio==5.2.2 # When updating platformio, also update Dockerfile From 68316cbcf996bbf20b078eac6022aad7ce9c4999 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 26 Oct 2021 22:14:44 +0200 Subject: [PATCH 094/142] Bump esptool from 3.1 to 3.2 (#2632) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 266f61369b..7f3470e467 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,7 +7,7 @@ tzlocal==4.0.2 # from time tzdata>=2021.1 # from time pyserial==3.5 platformio==5.2.2 # When updating platformio, also update Dockerfile -esptool==3.1 +esptool==3.2 click==8.0.3 esphome-dashboard==20211021.1 aioesphomeapi==10.0.3 From 980c2d4caef4c58fd3938144314958270b1c3e66 Mon Sep 17 00:00:00 2001 From: niklasweber Date: Wed, 27 Oct 2021 21:09:37 +0200 Subject: [PATCH 095/142] Add publish_initial_value option to rotary encoder (#2503) --- esphome/components/rotary_encoder/rotary_encoder.cpp | 3 ++- esphome/components/rotary_encoder/rotary_encoder.h | 2 ++ esphome/components/rotary_encoder/sensor.py | 3 +++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/esphome/components/rotary_encoder/rotary_encoder.cpp b/esphome/components/rotary_encoder/rotary_encoder.cpp index 7c95fac98e..e1f2584641 100644 --- a/esphome/components/rotary_encoder/rotary_encoder.cpp +++ b/esphome/components/rotary_encoder/rotary_encoder.cpp @@ -189,9 +189,10 @@ void RotaryEncoderSensor::loop() { this->store_.counter = 0; } int counter = this->store_.counter; - if (this->store_.last_read != counter) { + if (this->store_.last_read != counter || this->publish_initial_value_) { this->store_.last_read = counter; this->publish_state(counter); + this->publish_initial_value_ = false; } } diff --git a/esphome/components/rotary_encoder/rotary_encoder.h b/esphome/components/rotary_encoder/rotary_encoder.h index 4825e472a1..a134043152 100644 --- a/esphome/components/rotary_encoder/rotary_encoder.h +++ b/esphome/components/rotary_encoder/rotary_encoder.h @@ -58,6 +58,7 @@ class RotaryEncoderSensor : public sensor::Sensor, public Component { void set_reset_pin(GPIOPin *pin_i) { this->pin_i_ = pin_i; } void set_min_value(int32_t min_value); void set_max_value(int32_t max_value); + void set_publish_initial_value(bool publish_initial_value) { publish_initial_value_ = publish_initial_value; } // ========== INTERNAL METHODS ========== // (In most use cases you won't need these) @@ -79,6 +80,7 @@ class RotaryEncoderSensor : public sensor::Sensor, public Component { InternalGPIOPin *pin_a_; InternalGPIOPin *pin_b_; GPIOPin *pin_i_{nullptr}; /// Index pin, if this is not nullptr, the counter will reset to 0 once this pin is HIGH. + bool publish_initial_value_; RotaryEncoderSensorStore store_{}; diff --git a/esphome/components/rotary_encoder/sensor.py b/esphome/components/rotary_encoder/sensor.py index ef1110c6d8..d868438fd3 100644 --- a/esphome/components/rotary_encoder/sensor.py +++ b/esphome/components/rotary_encoder/sensor.py @@ -27,6 +27,7 @@ RESOLUTIONS = { CONF_PIN_RESET = "pin_reset" CONF_ON_CLOCKWISE = "on_clockwise" CONF_ON_ANTICLOCKWISE = "on_anticlockwise" +CONF_PUBLISH_INITIAL_VALUE = "publish_initial_value" RotaryEncoderSensor = rotary_encoder_ns.class_( "RotaryEncoderSensor", sensor.Sensor, cg.Component @@ -70,6 +71,7 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_RESOLUTION, default=1): cv.enum(RESOLUTIONS, int=True), cv.Optional(CONF_MIN_VALUE): cv.int_, cv.Optional(CONF_MAX_VALUE): cv.int_, + cv.Optional(CONF_PUBLISH_INITIAL_VALUE, default=False): cv.boolean, cv.Optional(CONF_ON_CLOCKWISE): automation.validate_automation( { cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( @@ -99,6 +101,7 @@ async def to_code(config): cg.add(var.set_pin_a(pin_a)) pin_b = await cg.gpio_pin_expression(config[CONF_PIN_B]) cg.add(var.set_pin_b(pin_b)) + cg.add(var.set_publish_initial_value(config[CONF_PUBLISH_INITIAL_VALUE])) if CONF_PIN_RESET in config: pin_i = await cg.gpio_pin_expression(config[CONF_PIN_RESET]) From 2147bcbc29687decea02bbf606727dc01f00ffdf Mon Sep 17 00:00:00 2001 From: Sean Brogan Date: Wed, 27 Oct 2021 13:16:12 -0700 Subject: [PATCH 096/142] Remove autoload of xiaomi_ble and ruuvi_ble (#2617) --- esphome/components/esp32_ble_tracker/__init__.py | 1 - tests/test2.yaml | 5 +++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/esphome/components/esp32_ble_tracker/__init__.py b/esphome/components/esp32_ble_tracker/__init__.py index e3d52f345a..e647b74a8f 100644 --- a/esphome/components/esp32_ble_tracker/__init__.py +++ b/esphome/components/esp32_ble_tracker/__init__.py @@ -18,7 +18,6 @@ from esphome.core import CORE from esphome.components.esp32 import add_idf_sdkconfig_option DEPENDENCIES = ["esp32"] -AUTO_LOAD = ["xiaomi_ble", "ruuvi_ble"] CONF_ESP32_BLE_ID = "esp32_ble_id" CONF_SCAN_PARAMETERS = "scan_parameters" diff --git a/tests/test2.yaml b/tests/test2.yaml index 7e71d1ab4e..6869eeecb1 100644 --- a/tests/test2.yaml +++ b/tests/test2.yaml @@ -401,6 +401,11 @@ ble_client: airthings_ble: +ruuvi_ble: + +xiaomi_ble: + + #esp32_ble_beacon: # type: iBeacon # uuid: 'c29ce823-e67a-4e71-bff2-abaa32e77a98' From b3d7cc637be49e50867bf6703ede12fcee6d5c35 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 28 Oct 2021 09:17:30 +1300 Subject: [PATCH 097/142] Bump aioesphomeapi from 10.0.3 to 10.1.0 (#2638) 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 7f3470e467..99efa6798f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,7 +10,7 @@ platformio==5.2.2 # When updating platformio, also update Dockerfile esptool==3.2 click==8.0.3 esphome-dashboard==20211021.1 -aioesphomeapi==10.0.3 +aioesphomeapi==10.1.0 # esp-idf requires this, but doesn't bundle it by default # https://github.com/espressif/esp-idf/blob/220590d599e134d7a5e7f1e683cc4550349ffbf8/requirements.txt#L24 From 0d3e6b2c4c4924901e6891426b5495e2211d7daa Mon Sep 17 00:00:00 2001 From: Alex Iribarren Date: Thu, 28 Oct 2021 00:46:55 +0200 Subject: [PATCH 098/142] Expose web_server port via the API (#2467) --- esphome/core/__init__.py | 14 ++++++++++++++ esphome/dashboard/dashboard.py | 7 +++++++ esphome/storage_json.py | 9 +++++++++ 3 files changed, 30 insertions(+) diff --git a/esphome/core/__init__.py b/esphome/core/__init__.py index 8bdef3a4ea..addecf1326 100644 --- a/esphome/core/__init__.py +++ b/esphome/core/__init__.py @@ -10,6 +10,7 @@ from esphome.const import ( CONF_USE_ADDRESS, CONF_ETHERNET, CONF_WIFI, + CONF_PORT, KEY_CORE, KEY_TARGET_FRAMEWORK, KEY_TARGET_PLATFORM, @@ -519,6 +520,19 @@ class EsphomeCore: return None + @property + def web_port(self) -> Optional[int]: + if self.config is None: + raise ValueError("Config has not been loaded yet") + + if "web_server" in self.config: + try: + return self.config["web_server"][CONF_PORT] + except KeyError: + return 80 + + return None + @property def comment(self) -> Optional[str]: if self.config is None: diff --git a/esphome/dashboard/dashboard.py b/esphome/dashboard/dashboard.py index 084dd84a07..11571ec889 100644 --- a/esphome/dashboard/dashboard.py +++ b/esphome/dashboard/dashboard.py @@ -509,6 +509,12 @@ class DashboardEntry: return None return self.storage.address + @property + def web_port(self): + if self.storage is None: + return None + return self.storage.web_port + @property def name(self): if self.storage is None: @@ -569,6 +575,7 @@ class ListDevicesHandler(BaseHandler): "path": entry.path, "comment": entry.comment, "address": entry.address, + "web_port": entry.web_port, "target_platform": entry.target_platform, } for entry in entries diff --git a/esphome/storage_json.py b/esphome/storage_json.py index 3262559116..207a3edf57 100644 --- a/esphome/storage_json.py +++ b/esphome/storage_json.py @@ -41,6 +41,7 @@ class StorageJSON: esphome_version, src_version, address, + web_port, target_platform, build_path, firmware_bin_path, @@ -60,6 +61,9 @@ class StorageJSON: self.src_version = src_version # type: int # Address of the ESP, for example livingroom.local or a static IP self.address = address # type: str + # Web server port of the ESP, for example 80 + assert web_port is None or isinstance(web_port, int) + self.web_port = web_port # type: int # The type of ESP in use, either ESP32 or ESP8266 self.target_platform = target_platform # type: str # The absolute path to the platformio project @@ -78,6 +82,7 @@ class StorageJSON: "esphome_version": self.esphome_version, "src_version": self.src_version, "address": self.address, + "web_port": self.web_port, "esp_platform": self.target_platform, "build_path": self.build_path, "firmware_bin_path": self.firmware_bin_path, @@ -101,6 +106,7 @@ class StorageJSON: esphome_version=const.__version__, src_version=1, address=esph.address, + web_port=esph.web_port, target_platform=esph.target_platform, build_path=esph.build_path, firmware_bin_path=esph.firmware_bin, @@ -117,6 +123,7 @@ class StorageJSON: esphome_version=const.__version__, src_version=1, address=address, + web_port=None, target_platform=esp_platform, build_path=None, firmware_bin_path=None, @@ -135,6 +142,7 @@ class StorageJSON: ) src_version = storage.get("src_version") address = storage.get("address") + web_port = storage.get("web_port") esp_platform = storage.get("esp_platform") build_path = storage.get("build_path") firmware_bin_path = storage.get("firmware_bin_path") @@ -146,6 +154,7 @@ class StorageJSON: esphome_version, src_version, address, + web_port, esp_platform, build_path, firmware_bin_path, From 73accf747f64daf32c25a127b8410d87335b615d Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Fri, 29 Oct 2021 07:12:05 +1300 Subject: [PATCH 099/142] Allow cloning/fetching Github PR branches in external_components (#2639) --- .../external_components/__init__.py | 24 ++++++++++++------- esphome/git.py | 10 +++++++- 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/esphome/components/external_components/__init__.py b/esphome/components/external_components/__init__.py index 110a8d95ed..e0548e8981 100644 --- a/esphome/components/external_components/__init__.py +++ b/esphome/components/external_components/__init__.py @@ -43,19 +43,27 @@ def validate_source_shorthand(value): # Regex for GitHub repo name with optional branch/tag # Note: git allows other branch/tag names as well, but never seen them used before m = re.match( - r"github://([a-zA-Z0-9\-]+)/([a-zA-Z0-9\-\._]+)(?:@([a-zA-Z0-9\-_.\./]+))?", + r"github://(?:([a-zA-Z0-9\-]+)/([a-zA-Z0-9\-\._]+)(?:@([a-zA-Z0-9\-_.\./]+))?|pr#([0-9]+))", value, ) if m is None: raise cv.Invalid( - "Source is not a file system path or in expected github://username/name[@branch-or-tag] format!" + "Source is not a file system path, in expected github://username/name[@branch-or-tag] or github://pr#1234 format!" ) - conf = { - CONF_TYPE: TYPE_GIT, - CONF_URL: f"https://github.com/{m.group(1)}/{m.group(2)}.git", - } - if m.group(3): - conf[CONF_REF] = m.group(3) + if m.group(4): + conf = { + CONF_TYPE: TYPE_GIT, + CONF_URL: "https://github.com/esphome/esphome.git", + CONF_REF: f"pull/{m.group(4)}/head", + } + else: + conf = { + CONF_TYPE: TYPE_GIT, + CONF_URL: f"https://github.com/{m.group(1)}/{m.group(2)}.git", + } + if m.group(3): + conf[CONF_REF] = m.group(3) + return SOURCE_SCHEMA(conf) diff --git a/esphome/git.py b/esphome/git.py index 12c6b41648..b64aa6a864 100644 --- a/esphome/git.py +++ b/esphome/git.py @@ -40,15 +40,23 @@ def clone_or_update( ) -> Path: key = f"{url}@{ref}" repo_dir = _compute_destination_path(key, domain) + fetch_pr_branch = ref.startswith("pull/") if not repo_dir.is_dir(): _LOGGER.info("Cloning %s", key) _LOGGER.debug("Location: %s", repo_dir) cmd = ["git", "clone", "--depth=1"] - if ref is not None: + if ref is not None and not fetch_pr_branch: cmd += ["--branch", ref] cmd += ["--", url, str(repo_dir)] run_git_command(cmd) + if fetch_pr_branch: + # We need to fetch the PR branch first, otherwise git will complain + # about missing objects + _LOGGER.info("Fetching %s", ref) + run_git_command(["git", "fetch", "--", "origin", ref], str(repo_dir)) + run_git_command(["git", "reset", "--hard", "FETCH_HEAD"], str(repo_dir)) + else: # Check refresh needed file_timestamp = Path(repo_dir / ".git" / "FETCH_HEAD") From 2350c5054c691dcbb54c7c5a979e3f6f7a615de3 Mon Sep 17 00:00:00 2001 From: Martin <25747549+martgras@users.noreply.github.com> Date: Thu, 28 Oct 2021 20:57:39 +0200 Subject: [PATCH 100/142] use update_interval for sntp synchronization (#2563) * use update_interval for sntp synchronization * revert override of default interval --- esphome/components/sntp/sntp_component.cpp | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/esphome/components/sntp/sntp_component.cpp b/esphome/components/sntp/sntp_component.cpp index 2b6cd10e80..96be0e9709 100644 --- a/esphome/components/sntp/sntp_component.cpp +++ b/esphome/components/sntp/sntp_component.cpp @@ -3,6 +3,9 @@ #ifdef USE_ESP32 #include "lwip/apps/sntp.h" +#ifdef USE_ESP_IDF +#include "esp_sntp.h" +#endif #endif #ifdef USE_ESP8266 #include "sntp.h" @@ -37,6 +40,9 @@ void SNTPComponent::setup() { if (!this->server_3_.empty()) { sntp_setservername(2, strdup(this->server_3_.c_str())); } +#ifdef USE_ESP_IDF + sntp_set_sync_interval(this->get_update_interval()); +#endif sntp_init(); } @@ -47,7 +53,16 @@ void SNTPComponent::dump_config() { ESP_LOGCONFIG(TAG, " Server 3: '%s'", this->server_3_.c_str()); ESP_LOGCONFIG(TAG, " Timezone: '%s'", this->timezone_.c_str()); } -void SNTPComponent::update() {} +void SNTPComponent::update() { +#ifndef USE_ESP_IDF + // force resync + if (sntp_enabled()) { + sntp_stop(); + this->has_time_ = false; + sntp_init(); + } +#endif +} void SNTPComponent::loop() { if (this->has_time_) return; @@ -56,7 +71,7 @@ void SNTPComponent::loop() { if (!time.is_valid()) return; - ESP_LOGD(TAG, "Synchronized time: %d-%d-%d %d:%d:%d", time.year, time.month, time.day_of_month, time.hour, + ESP_LOGD(TAG, "Synchronized time: %d-%d-%d %d:%02d:%02d", time.year, time.month, time.day_of_month, time.hour, time.minute, time.second); this->time_sync_callback_.call(); this->has_time_ = true; From 77dbf84e55f404ceea06fde1dfde33213209f6c7 Mon Sep 17 00:00:00 2001 From: Arturo Casal Date: Thu, 28 Oct 2021 20:58:48 +0200 Subject: [PATCH 101/142] Add support for CSE7761 sensor (#2546) * Add CSE7761 sensor support * CSE7761: Added test at test3.yaml * CSE7761: changed string style * CSE7761: fixed cpp lint * CSE7761: Added codeowners * Lots of code cleanup * Revert incorrect setup_priority suggestion * Added error log in read with retries. Co-authored-by: Oxan van Leeuwen * Improved log messages Co-authored-by: Oxan van Leeuwen --- CODEOWNERS | 1 + esphome/components/cse7761/__init__.py | 0 esphome/components/cse7761/cse7761.cpp | 244 +++++++++++++++++++++++++ esphome/components/cse7761/cse7761.h | 52 ++++++ esphome/components/cse7761/sensor.py | 90 +++++++++ tests/test3.yaml | 16 ++ 6 files changed, 403 insertions(+) create mode 100644 esphome/components/cse7761/__init__.py create mode 100644 esphome/components/cse7761/cse7761.cpp create mode 100644 esphome/components/cse7761/cse7761.h create mode 100644 esphome/components/cse7761/sensor.py diff --git a/CODEOWNERS b/CODEOWNERS index a7cf3a1b68..664bf9ad6b 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -39,6 +39,7 @@ esphome/components/color_temperature/* @jesserockz esphome/components/coolix/* @glmnet esphome/components/cover/* @esphome/core esphome/components/cs5460a/* @balrog-kun +esphome/components/cse7761/* @berfenger esphome/components/ct_clamp/* @jesserockz esphome/components/current_based/* @djwmarcx esphome/components/daly_bms/* @s1lvi0 diff --git a/esphome/components/cse7761/__init__.py b/esphome/components/cse7761/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esphome/components/cse7761/cse7761.cpp b/esphome/components/cse7761/cse7761.cpp new file mode 100644 index 0000000000..3b8364f0bc --- /dev/null +++ b/esphome/components/cse7761/cse7761.cpp @@ -0,0 +1,244 @@ +#include "cse7761.h" + +#include "esphome/core/log.h" + +namespace esphome { +namespace cse7761 { + +static const char *const TAG = "cse7761"; + +/*********************************************************************************************\ + * CSE7761 - Energy (Sonoff Dual R3 Pow v1.x) + * + * Based on Tasmota source code + * See https://github.com/arendst/Tasmota/discussions/10793 + * https://github.com/arendst/Tasmota/blob/development/tasmota/xnrg_19_cse7761.ino +\*********************************************************************************************/ + +static const int CSE7761_UREF = 42563; // RmsUc +static const int CSE7761_IREF = 52241; // RmsIAC +static const int CSE7761_PREF = 44513; // PowerPAC + +static const uint8_t CSE7761_REG_SYSCON = 0x00; // (2) System Control Register (0x0A04) +static const uint8_t CSE7761_REG_EMUCON = 0x01; // (2) Metering control register (0x0000) +static const uint8_t CSE7761_REG_EMUCON2 = 0x13; // (2) Metering control register 2 (0x0001) +static const uint8_t CSE7761_REG_PULSE1SEL = 0x1D; // (2) Pin function output select register (0x3210) + +static const uint8_t CSE7761_REG_RMSIA = 0x24; // (3) The effective value of channel A current (0x000000) +static const uint8_t CSE7761_REG_RMSIB = 0x25; // (3) The effective value of channel B current (0x000000) +static const uint8_t CSE7761_REG_RMSU = 0x26; // (3) Voltage RMS (0x000000) +static const uint8_t CSE7761_REG_POWERPA = 0x2C; // (4) Channel A active power, update rate 27.2Hz (0x00000000) +static const uint8_t CSE7761_REG_POWERPB = 0x2D; // (4) Channel B active power, update rate 27.2Hz (0x00000000) +static const uint8_t CSE7761_REG_SYSSTATUS = 0x43; // (1) System status register + +static const uint8_t CSE7761_REG_COEFFCHKSUM = 0x6F; // (2) Coefficient checksum +static const uint8_t CSE7761_REG_RMSIAC = 0x70; // (2) Channel A effective current conversion coefficient + +static const uint8_t CSE7761_SPECIAL_COMMAND = 0xEA; // Start special command +static const uint8_t CSE7761_CMD_RESET = 0x96; // Reset command, after receiving the command, the chip resets +static const uint8_t CSE7761_CMD_CLOSE_WRITE = 0xDC; // Close write operation +static const uint8_t CSE7761_CMD_ENABLE_WRITE = 0xE5; // Enable write operation + +enum CSE7761 { RMS_IAC, RMS_IBC, RMS_UC, POWER_PAC, POWER_PBC, POWER_SC, ENERGY_AC, ENERGY_BC }; + +void CSE7761Component::setup() { + ESP_LOGCONFIG(TAG, "Setting up CSE7761..."); + this->write_(CSE7761_SPECIAL_COMMAND, CSE7761_CMD_RESET); + uint16_t syscon = this->read_(0x00, 2); // Default 0x0A04 + if ((0x0A04 == syscon) && this->chip_init_()) { + this->write_(CSE7761_SPECIAL_COMMAND, CSE7761_CMD_CLOSE_WRITE); + ESP_LOGD(TAG, "CSE7761 found"); + this->data_.ready = true; + } else { + this->mark_failed(); + } +} + +void CSE7761Component::dump_config() { + ESP_LOGCONFIG(TAG, "CSE7761:"); + if (this->is_failed()) { + ESP_LOGE(TAG, "Communication with CSE7761 failed!"); + } + LOG_UPDATE_INTERVAL(this); + this->check_uart_settings(38400, 1, uart::UART_CONFIG_PARITY_EVEN, 8); +} + +float CSE7761Component::get_setup_priority() const { return setup_priority::DATA; } + +void CSE7761Component::update() { + if (this->data_.ready) { + this->get_data_(); + } +} + +void CSE7761Component::write_(uint8_t reg, uint16_t data) { + uint8_t buffer[5]; + + buffer[0] = 0xA5; + buffer[1] = reg; + uint32_t len = 2; + if (data) { + if (data < 0xFF) { + buffer[2] = data & 0xFF; + len = 3; + } else { + buffer[2] = (data >> 8) & 0xFF; + buffer[3] = data & 0xFF; + len = 4; + } + uint8_t crc = 0; + for (uint32_t i = 0; i < len; i++) { + crc += buffer[i]; + } + buffer[len] = ~crc; + len++; + } + + this->write_array(buffer, len); +} + +bool CSE7761Component::read_once_(uint8_t reg, uint8_t size, uint32_t *value) { + while (this->available()) { + this->read(); + } + + this->write_(reg, 0); + + uint8_t buffer[8] = {0}; + uint32_t rcvd = 0; + + for (uint32_t i = 0; i <= size; i++) { + int value = this->read(); + if (value > -1 && rcvd < sizeof(buffer) - 1) { + buffer[rcvd++] = value; + } + } + + if (!rcvd) { + ESP_LOGD(TAG, "Received 0 bytes for register %hhu", reg); + return false; + } + + rcvd--; + uint32_t result = 0; + // CRC check + uint8_t crc = 0xA5 + reg; + for (uint32_t i = 0; i < rcvd; i++) { + result = (result << 8) | buffer[i]; + crc += buffer[i]; + } + crc = ~crc; + if (crc != buffer[rcvd]) { + return false; + } + + *value = result; + return true; +} + +uint32_t CSE7761Component::read_(uint8_t reg, uint8_t size) { + bool result = false; // Start loop + uint8_t retry = 3; // Retry up to three times + uint32_t value = 0; // Default no value + while (!result && retry > 0) { + retry--; + if (this->read_once_(reg, size, &value)) + return value; + } + ESP_LOGE(TAG, "Reading register %hhu failed!", reg); + return value; +} + +uint32_t CSE7761Component::coefficient_by_unit_(uint32_t unit) { + switch (unit) { + case RMS_UC: + return 0x400000 * 100 / this->data_.coefficient[RMS_UC]; + case RMS_IAC: + return (0x800000 * 100 / this->data_.coefficient[RMS_IAC]) * 10; // Stay within 32 bits + case POWER_PAC: + return 0x80000000 / this->data_.coefficient[POWER_PAC]; + } + return 0; +} + +bool CSE7761Component::chip_init_() { + uint16_t calc_chksum = 0xFFFF; + for (uint32_t i = 0; i < 8; i++) { + this->data_.coefficient[i] = this->read_(CSE7761_REG_RMSIAC + i, 2); + calc_chksum += this->data_.coefficient[i]; + } + calc_chksum = ~calc_chksum; + uint16_t coeff_chksum = this->read_(CSE7761_REG_COEFFCHKSUM, 2); + if ((calc_chksum != coeff_chksum) || (!calc_chksum)) { + ESP_LOGD(TAG, "Default calibration"); + this->data_.coefficient[RMS_IAC] = CSE7761_IREF; + this->data_.coefficient[RMS_UC] = CSE7761_UREF; + this->data_.coefficient[POWER_PAC] = CSE7761_PREF; + } + + this->write_(CSE7761_SPECIAL_COMMAND, CSE7761_CMD_ENABLE_WRITE); + + uint8_t sys_status = this->read_(CSE7761_REG_SYSSTATUS, 1); + if (sys_status & 0x10) { // Write enable to protected registers (WREN) + this->write_(CSE7761_REG_SYSCON | 0x80, 0xFF04); + this->write_(CSE7761_REG_EMUCON | 0x80, 0x1183); + this->write_(CSE7761_REG_EMUCON2 | 0x80, 0x0FC1); + this->write_(CSE7761_REG_PULSE1SEL | 0x80, 0x3290); + } else { + ESP_LOGD(TAG, "Write failed at chip_init"); + return false; + } + return true; +} + +void CSE7761Component::get_data_() { + // The effective value of current and voltage Rms is a 24-bit signed number, + // the highest bit is 0 for valid data, + // and when the highest bit is 1, the reading will be processed as zero + // The active power parameter PowerA/B is in two’s complement format, 32-bit + // data, the highest bit is Sign bit. + uint32_t value = this->read_(CSE7761_REG_RMSU, 3); + this->data_.voltage_rms = (value >= 0x800000) ? 0 : value; + + value = this->read_(CSE7761_REG_RMSIA, 3); + this->data_.current_rms[0] = ((value >= 0x800000) || (value < 1600)) ? 0 : value; // No load threshold of 10mA + value = this->read_(CSE7761_REG_POWERPA, 4); + this->data_.active_power[0] = (0 == this->data_.current_rms[0]) ? 0 : ((uint32_t) abs((int) value)); + + value = this->read_(CSE7761_REG_RMSIB, 3); + this->data_.current_rms[1] = ((value >= 0x800000) || (value < 1600)) ? 0 : value; // No load threshold of 10mA + value = this->read_(CSE7761_REG_POWERPB, 4); + this->data_.active_power[1] = (0 == this->data_.current_rms[1]) ? 0 : ((uint32_t) abs((int) value)); + + // convert values and publish to sensors + + float voltage = (float) this->data_.voltage_rms / this->coefficient_by_unit_(RMS_UC); + if (this->voltage_sensor_ != nullptr) { + this->voltage_sensor_->publish_state(voltage); + } + + for (uint32_t channel = 0; channel < 2; channel++) { + // Active power = PowerPA * PowerPAC * 1000 / 0x80000000 + float active_power = (float) this->data_.active_power[channel] / this->coefficient_by_unit_(POWER_PAC); // W + float amps = (float) this->data_.current_rms[channel] / this->coefficient_by_unit_(RMS_IAC); // A + ESP_LOGD(TAG, "Channel %d power %f W, current %f A", channel + 1, active_power, amps); + if (channel == 0) { + if (this->power_sensor_1_ != nullptr) { + this->power_sensor_1_->publish_state(active_power); + } + if (this->current_sensor_1_ != nullptr) { + this->current_sensor_1_->publish_state(amps); + } + } else if (channel == 1) { + if (this->power_sensor_2_ != nullptr) { + this->power_sensor_2_->publish_state(active_power); + } + if (this->current_sensor_2_ != nullptr) { + this->current_sensor_2_->publish_state(amps); + } + } + } +} + +} // namespace cse7761 +} // namespace esphome diff --git a/esphome/components/cse7761/cse7761.h b/esphome/components/cse7761/cse7761.h new file mode 100644 index 0000000000..71846cdcab --- /dev/null +++ b/esphome/components/cse7761/cse7761.h @@ -0,0 +1,52 @@ +#pragma once + +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/uart/uart.h" +#include "esphome/core/component.h" + +namespace esphome { +namespace cse7761 { + +struct CSE7761DataStruct { + uint32_t frequency = 0; + uint32_t voltage_rms = 0; + uint32_t current_rms[2] = {0}; + uint32_t energy[2] = {0}; + uint32_t active_power[2] = {0}; + uint16_t coefficient[8] = {0}; + uint8_t energy_update = 0; + bool ready = false; +}; + +/// This class implements support for the CSE7761 UART power sensor. +class CSE7761Component : public PollingComponent, public uart::UARTDevice { + public: + void set_voltage_sensor(sensor::Sensor *voltage_sensor) { voltage_sensor_ = voltage_sensor; } + void set_active_power_1_sensor(sensor::Sensor *power_sensor_1) { power_sensor_1_ = power_sensor_1; } + void set_current_1_sensor(sensor::Sensor *current_sensor_1) { current_sensor_1_ = current_sensor_1; } + void set_active_power_2_sensor(sensor::Sensor *power_sensor_2) { power_sensor_2_ = power_sensor_2; } + void set_current_2_sensor(sensor::Sensor *current_sensor_2) { current_sensor_2_ = current_sensor_2; } + void setup() override; + void dump_config() override; + float get_setup_priority() const override; + void update() override; + + protected: + // Sensors + sensor::Sensor *voltage_sensor_{nullptr}; + sensor::Sensor *power_sensor_1_{nullptr}; + sensor::Sensor *current_sensor_1_{nullptr}; + sensor::Sensor *power_sensor_2_{nullptr}; + sensor::Sensor *current_sensor_2_{nullptr}; + CSE7761DataStruct data_; + + void write_(uint8_t reg, uint16_t data); + bool read_once_(uint8_t reg, uint8_t size, uint32_t *value); + uint32_t read_(uint8_t reg, uint8_t size); + uint32_t coefficient_by_unit_(uint32_t unit); + bool chip_init_(); + void get_data_(); +}; + +} // namespace cse7761 +} // namespace esphome diff --git a/esphome/components/cse7761/sensor.py b/esphome/components/cse7761/sensor.py new file mode 100644 index 0000000000..c5ec3e5b71 --- /dev/null +++ b/esphome/components/cse7761/sensor.py @@ -0,0 +1,90 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import sensor, uart +from esphome.const import ( + CONF_ID, + CONF_VOLTAGE, + DEVICE_CLASS_CURRENT, + DEVICE_CLASS_POWER, + DEVICE_CLASS_VOLTAGE, + STATE_CLASS_MEASUREMENT, + UNIT_VOLT, + UNIT_AMPERE, + UNIT_WATT, +) + +CODEOWNERS = ["@berfenger"] +DEPENDENCIES = ["uart"] + +cse7761_ns = cg.esphome_ns.namespace("cse7761") +CSE7761Component = cse7761_ns.class_( + "CSE7761Component", cg.PollingComponent, uart.UARTDevice +) + +CONF_CURRENT_1 = "current_1" +CONF_CURRENT_2 = "current_2" +CONF_ACTIVE_POWER_1 = "active_power_1" +CONF_ACTIVE_POWER_2 = "active_power_2" + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(CSE7761Component), + cv.Optional(CONF_VOLTAGE): sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_CURRENT_1): sensor.sensor_schema( + unit_of_measurement=UNIT_AMPERE, + accuracy_decimals=2, + device_class=DEVICE_CLASS_CURRENT, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_CURRENT_2): sensor.sensor_schema( + unit_of_measurement=UNIT_AMPERE, + accuracy_decimals=2, + device_class=DEVICE_CLASS_CURRENT, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_ACTIVE_POWER_1): sensor.sensor_schema( + unit_of_measurement=UNIT_WATT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_ACTIVE_POWER_2): sensor.sensor_schema( + unit_of_measurement=UNIT_WATT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(uart.UART_DEVICE_SCHEMA) +) + +FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema( + "cse7761", baud_rate=38400, require_rx=True, require_tx=True +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await uart.register_uart_device(var, config) + + for key in [ + CONF_VOLTAGE, + CONF_CURRENT_1, + CONF_CURRENT_2, + CONF_ACTIVE_POWER_1, + CONF_ACTIVE_POWER_2, + ]: + if key not in config: + continue + conf = config[key] + sens = await sensor.new_sensor(conf) + cg.add(getattr(var, f"set_{key}_sensor")(sens)) diff --git a/tests/test3.yaml b/tests/test3.yaml index 836e895374..0b7f1ad71e 100644 --- a/tests/test3.yaml +++ b/tests/test3.yaml @@ -250,6 +250,10 @@ uart: tx_pin: GPIO4 rx_pin: GPIO5 baud_rate: 9600 + - id: uart7 + tx_pin: GPIO4 + rx_pin: GPIO5 + baud_rate: 38400 modbus: uart_id: uart1 @@ -549,6 +553,18 @@ sensor: name: 'PMS Humidity' formaldehyde: name: 'PMS Formaldehyde Concentration' + - platform: cse7761 + uart_id: uart7 + voltage: + name: 'CSE7761 Voltage' + current_1: + name: 'CSE7761 Current 1' + current_2: + name: 'CSE7761 Current 2' + active_power_1: + name: 'CSE7761 Active Power 1' + active_power_2: + name: 'CSE7761 Active Power 2' - platform: cse7766 uart_id: uart3 voltage: From 7e54f97003688742e0e09242eb5d0433764c410b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 28 Oct 2021 21:24:48 +0200 Subject: [PATCH 102/142] Bump aioesphomeapi from 10.1.0 to 10.2.0 (#2642) Bumps [aioesphomeapi](https://github.com/esphome/aioesphomeapi) from 10.1.0 to 10.2.0. - [Release notes](https://github.com/esphome/aioesphomeapi/releases) - [Commits](https://github.com/esphome/aioesphomeapi/compare/v10.1.0...v10.2.0) --- updated-dependencies: - dependency-name: aioesphomeapi dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] 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 99efa6798f..2c3220e825 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,7 +10,7 @@ platformio==5.2.2 # When updating platformio, also update Dockerfile esptool==3.2 click==8.0.3 esphome-dashboard==20211021.1 -aioesphomeapi==10.1.0 +aioesphomeapi==10.2.0 # esp-idf requires this, but doesn't bundle it by default # https://github.com/espressif/esp-idf/blob/220590d599e134d7a5e7f1e683cc4550349ffbf8/requirements.txt#L24 From 696643d037751cac1c85a44fb8d9a8cbb8beaaec Mon Sep 17 00:00:00 2001 From: Ed <89483561+kixtarter@users.noreply.github.com> Date: Fri, 29 Oct 2021 00:51:57 +0200 Subject: [PATCH 103/142] BH1750: Fix a too high default H-res2 mode value (#2536) --- esphome/components/bh1750/bh1750.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/esphome/components/bh1750/bh1750.cpp b/esphome/components/bh1750/bh1750.cpp index 951fe3670c..4e6bb3c563 100644 --- a/esphome/components/bh1750/bh1750.cpp +++ b/esphome/components/bh1750/bh1750.cpp @@ -79,6 +79,9 @@ void BH1750Sensor::read_data_() { float lx = float(raw_value) / 1.2f; lx *= 69.0f / this->measurement_duration_; + if (this->resolution_ == BH1750_RESOLUTION_0P5_LX) { + lx /= 2.0f; + } ESP_LOGD(TAG, "'%s': Got illuminance=%.1flx", this->get_name().c_str(), lx); this->publish_state(lx); this->status_clear_warning(); From b12c7432e0ef751e2a0d4b4c45bbc73e447ff930 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 30 Oct 2021 20:12:57 +0200 Subject: [PATCH 104/142] Bump tzlocal from 4.0.2 to 4.1 (#2645) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 2c3220e825..6e1fe56057 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,7 +3,7 @@ PyYAML==6.0 paho-mqtt==1.6.1 colorama==0.4.4 tornado==6.1 -tzlocal==4.0.2 # from time +tzlocal==4.1 # from time tzdata>=2021.1 # from time pyserial==3.5 platformio==5.2.2 # When updating platformio, also update Dockerfile From 7eee3cdc7fde4de32da80d5a79328895d0e9c360 Mon Sep 17 00:00:00 2001 From: Geoffrey Van Landeghem Date: Sun, 31 Oct 2021 03:29:22 +0100 Subject: [PATCH 105/142] convert SCD30 into Component, polls dataready register (#2308) Co-authored-by: Oxan van Leeuwen Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/scd30/scd30.cpp | 37 +++++++++++++++++++++++------- esphome/components/scd30/scd30.h | 7 ++++-- esphome/components/scd30/sensor.py | 14 +++++++++-- 3 files changed, 46 insertions(+), 12 deletions(-) diff --git a/esphome/components/scd30/scd30.cpp b/esphome/components/scd30/scd30.cpp index d1246d9766..e6d6ec1c1a 100644 --- a/esphome/components/scd30/scd30.cpp +++ b/esphome/components/scd30/scd30.cpp @@ -60,7 +60,7 @@ void SCD30Component::setup() { // According ESP32 clock stretching is typically 30ms and up to 150ms "due to // internal calibration processes". The I2C peripheral only supports 13ms (at // least when running at 80MHz). - // In practise it seems that clock stretching occurs during this calibration + // In practice it seems that clock stretching occurs during this calibration // calls. It also seems that delays in between calls makes them // disappear/shorter. Hence work around with delays for ESP32. // @@ -69,6 +69,16 @@ void SCD30Component::setup() { delay(30); #endif + if (!this->write_command_(SCD30_CMD_MEASUREMENT_INTERVAL, update_interval_)) { + ESP_LOGE(TAG, "Sensor SCD30 error setting update interval."); + this->error_code_ = MEASUREMENT_INIT_FAILED; + this->mark_failed(); + return; + } +#ifdef USE_ESP32 + delay(30); +#endif + // The start measurement command disables the altitude compensation, if any, so we only set it if it's turned on if (this->altitude_compensation_ != 0xFFFF) { if (!this->write_command_(SCD30_CMD_ALTITUDE_COMPENSATION, altitude_compensation_)) { @@ -99,6 +109,12 @@ void SCD30Component::setup() { this->mark_failed(); return; } + + // check each 500ms if data is ready, and read it in that case + this->set_interval("status-check", 500, [this]() { + if (this->is_data_ready_()) + this->update(); + }); } void SCD30Component::dump_config() { @@ -128,19 +144,13 @@ void SCD30Component::dump_config() { ESP_LOGCONFIG(TAG, " Automatic self calibration: %s", ONOFF(this->enable_asc_)); ESP_LOGCONFIG(TAG, " Ambient pressure compensation: %dmBar", this->ambient_pressure_compensation_); ESP_LOGCONFIG(TAG, " Temperature offset: %.2f °C", this->temperature_offset_); - LOG_UPDATE_INTERVAL(this); + ESP_LOGCONFIG(TAG, " Update interval: %ds", this->update_interval_); LOG_SENSOR(" ", "CO2", this->co2_sensor_); LOG_SENSOR(" ", "Temperature", this->temperature_sensor_); LOG_SENSOR(" ", "Humidity", this->humidity_sensor_); } void SCD30Component::update() { - /// Check if measurement is ready before reading the value - if (!this->write_command_(SCD30_CMD_GET_DATA_READY_STATUS)) { - this->status_set_warning(); - return; - } - uint16_t raw_read_status[1]; if (!this->read_data_(raw_read_status, 1) || raw_read_status[0] == 0x00) { this->status_set_warning(); @@ -186,6 +196,17 @@ void SCD30Component::update() { }); } +bool SCD30Component::is_data_ready_() { + if (!this->write_command_(SCD30_CMD_GET_DATA_READY_STATUS)) { + return false; + } + uint16_t is_data_ready; + if (!this->read_data_(&is_data_ready, 1)) { + return false; + } + return is_data_ready == 1; +} + bool SCD30Component::write_command_(uint16_t command) { // Warning ugly, trick the I2Ccomponent base by setting register to the first 8 bit. return this->write_byte(command >> 8, command & 0xFF); diff --git a/esphome/components/scd30/scd30.h b/esphome/components/scd30/scd30.h index f11b7cc1f4..64193d0cb6 100644 --- a/esphome/components/scd30/scd30.h +++ b/esphome/components/scd30/scd30.h @@ -8,7 +8,7 @@ namespace esphome { namespace scd30 { /// This class implements support for the Sensirion scd30 i2c GAS (VOC and CO2eq) sensors. -class SCD30Component : public PollingComponent, public i2c::I2CDevice { +class SCD30Component : public Component, public i2c::I2CDevice { public: void set_co2_sensor(sensor::Sensor *co2) { co2_sensor_ = co2; } void set_humidity_sensor(sensor::Sensor *humidity) { humidity_sensor_ = humidity; } @@ -19,9 +19,10 @@ class SCD30Component : public PollingComponent, public i2c::I2CDevice { ambient_pressure_compensation_ = (uint16_t)(pressure * 1000); } void set_temperature_offset(float offset) { temperature_offset_ = offset; } + void set_update_interval(uint16_t interval) { update_interval_ = interval; } void setup() override; - void update() override; + void update(); void dump_config() override; float get_setup_priority() const override { return setup_priority::DATA; } @@ -30,6 +31,7 @@ class SCD30Component : public PollingComponent, public i2c::I2CDevice { bool write_command_(uint16_t command, uint16_t data); bool read_data_(uint16_t *data, uint8_t len); uint8_t sht_crc_(uint8_t data1, uint8_t data2); + bool is_data_ready_(); enum ErrorCode { COMMUNICATION_FAILED, @@ -41,6 +43,7 @@ class SCD30Component : public PollingComponent, public i2c::I2CDevice { uint16_t altitude_compensation_{0xFFFF}; uint16_t ambient_pressure_compensation_{0x0000}; float temperature_offset_{0.0}; + uint16_t update_interval_{0xFFFF}; sensor::Sensor *co2_sensor_{nullptr}; sensor::Sensor *humidity_sensor_{nullptr}; diff --git a/esphome/components/scd30/sensor.py b/esphome/components/scd30/sensor.py index c0317c96e0..cd25649f2a 100644 --- a/esphome/components/scd30/sensor.py +++ b/esphome/components/scd30/sensor.py @@ -1,3 +1,4 @@ +from esphome import core import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import i2c, sensor @@ -6,6 +7,7 @@ from esphome.const import ( CONF_HUMIDITY, CONF_TEMPERATURE, CONF_CO2, + CONF_UPDATE_INTERVAL, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_TEMPERATURE, STATE_CLASS_MEASUREMENT, @@ -18,7 +20,7 @@ from esphome.const import ( DEPENDENCIES = ["i2c"] scd30_ns = cg.esphome_ns.namespace("scd30") -SCD30Component = scd30_ns.class_("SCD30Component", cg.PollingComponent, i2c.I2CDevice) +SCD30Component = scd30_ns.class_("SCD30Component", cg.Component, i2c.I2CDevice) CONF_AUTOMATIC_SELF_CALIBRATION = "automatic_self_calibration" CONF_ALTITUDE_COMPENSATION = "altitude_compensation" @@ -55,9 +57,15 @@ CONFIG_SCHEMA = ( ), cv.Optional(CONF_AMBIENT_PRESSURE_COMPENSATION, default=0): cv.pressure, cv.Optional(CONF_TEMPERATURE_OFFSET): cv.temperature, + cv.Optional(CONF_UPDATE_INTERVAL, default="60s"): cv.All( + cv.positive_time_period_seconds, + cv.Range( + min=core.TimePeriod(seconds=1), max=core.TimePeriod(seconds=1800) + ), + ), } ) - .extend(cv.polling_component_schema("60s")) + .extend(cv.COMPONENT_SCHEMA) .extend(i2c.i2c_device_schema(0x61)) ) @@ -81,6 +89,8 @@ async def to_code(config): if CONF_TEMPERATURE_OFFSET in config: cg.add(var.set_temperature_offset(config[CONF_TEMPERATURE_OFFSET])) + cg.add(var.set_update_interval(config[CONF_UPDATE_INTERVAL])) + if CONF_CO2 in config: sens = await sensor.new_sensor(config[CONF_CO2]) cg.add(var.set_co2_sensor(sens)) From 331a3ac387e881e886d0a0f651ff1728c593b895 Mon Sep 17 00:00:00 2001 From: Paul Monigatti Date: Sun, 31 Oct 2021 15:34:08 +1300 Subject: [PATCH 106/142] Add option to use MQTT abbreviations (#2641) --- esphome/components/mqtt/__init__.py | 5 + .../components/mqtt/mqtt_binary_sensor.cpp | 8 +- esphome/components/mqtt/mqtt_climate.cpp | 42 +- esphome/components/mqtt/mqtt_component.cpp | 40 +- esphome/components/mqtt/mqtt_const.h | 518 ++++++++++++++++++ esphome/components/mqtt/mqtt_cover.cpp | 14 +- esphome/components/mqtt/mqtt_fan.cpp | 10 +- esphome/components/mqtt/mqtt_light.cpp | 6 +- esphome/components/mqtt/mqtt_number.cpp | 8 +- esphome/components/mqtt/mqtt_select.cpp | 4 +- esphome/components/mqtt/mqtt_sensor.cpp | 12 +- esphome/components/mqtt/mqtt_switch.cpp | 4 +- esphome/const.py | 1 + tests/test1.yaml | 1 + 14 files changed, 609 insertions(+), 64 deletions(-) create mode 100644 esphome/components/mqtt/mqtt_const.h diff --git a/esphome/components/mqtt/__init__.py b/esphome/components/mqtt/__init__.py index 3d52dab67f..0f7d246473 100644 --- a/esphome/components/mqtt/__init__.py +++ b/esphome/components/mqtt/__init__.py @@ -34,6 +34,7 @@ from esphome.const import ( CONF_TOPIC, CONF_TOPIC_PREFIX, CONF_TRIGGER_ID, + CONF_USE_ABBREVIATIONS, CONF_USERNAME, CONF_WILL_MESSAGE, ) @@ -152,6 +153,7 @@ CONFIG_SCHEMA = cv.All( cv.Optional( CONF_DISCOVERY_PREFIX, default="homeassistant" ): cv.publish_topic, + cv.Optional(CONF_USE_ABBREVIATIONS, default=True): cv.boolean, cv.Optional(CONF_BIRTH_MESSAGE): MQTT_MESSAGE_SCHEMA, cv.Optional(CONF_WILL_MESSAGE): MQTT_MESSAGE_SCHEMA, cv.Optional(CONF_SHUTDOWN_MESSAGE): MQTT_MESSAGE_SCHEMA, @@ -239,6 +241,9 @@ async def to_code(config): cg.add(var.set_topic_prefix(config[CONF_TOPIC_PREFIX])) + if config[CONF_USE_ABBREVIATIONS]: + cg.add_define("USE_MQTT_ABBREVIATIONS") + birth_message = config[CONF_BIRTH_MESSAGE] if not birth_message: cg.add(var.disable_birth_message()) diff --git a/esphome/components/mqtt/mqtt_binary_sensor.cpp b/esphome/components/mqtt/mqtt_binary_sensor.cpp index 188df0f7b9..0a161f89a1 100644 --- a/esphome/components/mqtt/mqtt_binary_sensor.cpp +++ b/esphome/components/mqtt/mqtt_binary_sensor.cpp @@ -1,6 +1,8 @@ #include "mqtt_binary_sensor.h" #include "esphome/core/log.h" +#include "mqtt_const.h" + #ifdef USE_MQTT #ifdef USE_BINARY_SENSOR @@ -29,11 +31,11 @@ MQTTBinarySensorComponent::MQTTBinarySensorComponent(binary_sensor::BinarySensor void MQTTBinarySensorComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) { if (!this->binary_sensor_->get_device_class().empty()) - root["device_class"] = this->binary_sensor_->get_device_class(); + root[MQTT_DEVICE_CLASS] = this->binary_sensor_->get_device_class(); if (this->binary_sensor_->is_status_binary_sensor()) - root["payload_on"] = mqtt::global_mqtt_client->get_availability().payload_available; + root[MQTT_PAYLOAD_ON] = mqtt::global_mqtt_client->get_availability().payload_available; if (this->binary_sensor_->is_status_binary_sensor()) - root["payload_off"] = mqtt::global_mqtt_client->get_availability().payload_not_available; + root[MQTT_PAYLOAD_OFF] = mqtt::global_mqtt_client->get_availability().payload_not_available; config.command_topic = false; } bool MQTTBinarySensorComponent::send_initial_state() { diff --git a/esphome/components/mqtt/mqtt_climate.cpp b/esphome/components/mqtt/mqtt_climate.cpp index 47b6684dec..a63eb9c4ff 100644 --- a/esphome/components/mqtt/mqtt_climate.cpp +++ b/esphome/components/mqtt/mqtt_climate.cpp @@ -1,6 +1,8 @@ #include "mqtt_climate.h" #include "esphome/core/log.h" +#include "mqtt_const.h" + #ifdef USE_MQTT #ifdef USE_CLIMATE @@ -16,14 +18,14 @@ void MQTTClimateComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryC // current_temperature_topic if (traits.get_supports_current_temperature()) { // current_temperature_topic - root["curr_temp_t"] = this->get_current_temperature_state_topic(); + root[MQTT_CURRENT_TEMPERATURE_TOPIC] = this->get_current_temperature_state_topic(); } // mode_command_topic - root["mode_cmd_t"] = this->get_mode_command_topic(); + root[MQTT_MODE_COMMAND_TOPIC] = this->get_mode_command_topic(); // mode_state_topic - root["mode_stat_t"] = this->get_mode_state_topic(); + root[MQTT_MODE_STATE_TOPIC] = this->get_mode_state_topic(); // modes - JsonArray &modes = root.createNestedArray("modes"); + JsonArray &modes = root.createNestedArray(MQTT_MODES); // sort array for nice UI in HA if (traits.supports_mode(CLIMATE_MODE_AUTO)) modes.add("auto"); @@ -41,45 +43,45 @@ void MQTTClimateComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryC if (traits.get_supports_two_point_target_temperature()) { // temperature_low_command_topic - root["temp_lo_cmd_t"] = this->get_target_temperature_low_command_topic(); + root[MQTT_TEMPERATURE_LOW_COMMAND_TOPIC] = this->get_target_temperature_low_command_topic(); // temperature_low_state_topic - root["temp_lo_stat_t"] = this->get_target_temperature_low_state_topic(); + root[MQTT_TEMPERATURE_LOW_STATE_TOPIC] = this->get_target_temperature_low_state_topic(); // temperature_high_command_topic - root["temp_hi_cmd_t"] = this->get_target_temperature_high_command_topic(); + root[MQTT_TEMPERATURE_HIGH_COMMAND_TOPIC] = this->get_target_temperature_high_command_topic(); // temperature_high_state_topic - root["temp_hi_stat_t"] = this->get_target_temperature_high_state_topic(); + root[MQTT_TEMPERATURE_HIGH_STATE_TOPIC] = this->get_target_temperature_high_state_topic(); } else { // temperature_command_topic - root["temp_cmd_t"] = this->get_target_temperature_command_topic(); + root[MQTT_TEMPERATURE_COMMAND_TOPIC] = this->get_target_temperature_command_topic(); // temperature_state_topic - root["temp_stat_t"] = this->get_target_temperature_state_topic(); + root[MQTT_TEMPERATURE_STATE_TOPIC] = this->get_target_temperature_state_topic(); } // min_temp - root["min_temp"] = traits.get_visual_min_temperature(); + root[MQTT_MIN_TEMP] = traits.get_visual_min_temperature(); // max_temp - root["max_temp"] = traits.get_visual_max_temperature(); + root[MQTT_MAX_TEMP] = traits.get_visual_max_temperature(); // temp_step root["temp_step"] = traits.get_visual_temperature_step(); // temperature units are always coerced to Celsius internally - root["temp_unit"] = "C"; + root[MQTT_TEMPERATURE_UNIT] = "C"; if (traits.supports_preset(CLIMATE_PRESET_AWAY)) { // away_mode_command_topic - root["away_mode_cmd_t"] = this->get_away_command_topic(); + root[MQTT_AWAY_MODE_COMMAND_TOPIC] = this->get_away_command_topic(); // away_mode_state_topic - root["away_mode_stat_t"] = this->get_away_state_topic(); + root[MQTT_AWAY_MODE_STATE_TOPIC] = this->get_away_state_topic(); } if (traits.get_supports_action()) { // action_topic - root["act_t"] = this->get_action_state_topic(); + root[MQTT_ACTION_TOPIC] = this->get_action_state_topic(); } if (traits.get_supports_fan_modes() || !traits.get_supported_custom_fan_modes().empty()) { // fan_mode_command_topic - root["fan_mode_cmd_t"] = this->get_fan_mode_command_topic(); + root[MQTT_FAN_MODE_COMMAND_TOPIC] = this->get_fan_mode_command_topic(); // fan_mode_state_topic - root["fan_mode_stat_t"] = this->get_fan_mode_state_topic(); + root[MQTT_FAN_MODE_STATE_TOPIC] = this->get_fan_mode_state_topic(); // fan_modes JsonArray &fan_modes = root.createNestedArray("fan_modes"); if (traits.supports_fan_mode(CLIMATE_FAN_ON)) @@ -106,9 +108,9 @@ void MQTTClimateComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryC if (traits.get_supports_swing_modes()) { // swing_mode_command_topic - root["swing_mode_cmd_t"] = this->get_swing_mode_command_topic(); + root[MQTT_SWING_MODE_COMMAND_TOPIC] = this->get_swing_mode_command_topic(); // swing_mode_state_topic - root["swing_mode_stat_t"] = this->get_swing_mode_state_topic(); + root[MQTT_SWING_MODE_STATE_TOPIC] = this->get_swing_mode_state_topic(); // swing_modes JsonArray &swing_modes = root.createNestedArray("swing_modes"); if (traits.supports_swing_mode(CLIMATE_SWING_OFF)) diff --git a/esphome/components/mqtt/mqtt_component.cpp b/esphome/components/mqtt/mqtt_component.cpp index 0ece4b3501..be1018d97d 100644 --- a/esphome/components/mqtt/mqtt_component.cpp +++ b/esphome/components/mqtt/mqtt_component.cpp @@ -7,6 +7,8 @@ #include "esphome/core/helpers.h" #include "esphome/core/version.h" +#include "mqtt_const.h" + namespace esphome { namespace mqtt { @@ -69,49 +71,49 @@ bool MQTTComponent::send_discovery_() { this->send_discovery(root, config); // Fields from EntityBase - root["name"] = this->friendly_name(); + root[MQTT_NAME] = this->friendly_name(); if (this->is_disabled_by_default()) - root["enabled_by_default"] = false; + root[MQTT_ENABLED_BY_DEFAULT] = false; if (!this->get_icon().empty()) - root["icon"] = this->get_icon(); + root[MQTT_ICON] = this->get_icon(); if (config.state_topic) - root["state_topic"] = this->get_state_topic_(); + root[MQTT_STATE_TOPIC] = this->get_state_topic_(); if (config.command_topic) - root["command_topic"] = this->get_command_topic_(); + root[MQTT_COMMAND_TOPIC] = this->get_command_topic_(); if (this->availability_ == nullptr) { if (!global_mqtt_client->get_availability().topic.empty()) { - root["availability_topic"] = global_mqtt_client->get_availability().topic; + root[MQTT_AVAILABILITY_TOPIC] = global_mqtt_client->get_availability().topic; if (global_mqtt_client->get_availability().payload_available != "online") - root["payload_available"] = global_mqtt_client->get_availability().payload_available; + root[MQTT_PAYLOAD_AVAILABLE] = global_mqtt_client->get_availability().payload_available; if (global_mqtt_client->get_availability().payload_not_available != "offline") - root["payload_not_available"] = global_mqtt_client->get_availability().payload_not_available; + root[MQTT_PAYLOAD_NOT_AVAILABLE] = global_mqtt_client->get_availability().payload_not_available; } } else if (!this->availability_->topic.empty()) { - root["availability_topic"] = this->availability_->topic; + root[MQTT_AVAILABILITY_TOPIC] = this->availability_->topic; if (this->availability_->payload_available != "online") - root["payload_available"] = this->availability_->payload_available; + root[MQTT_PAYLOAD_AVAILABLE] = this->availability_->payload_available; if (this->availability_->payload_not_available != "offline") - root["payload_not_available"] = this->availability_->payload_not_available; + root[MQTT_PAYLOAD_NOT_AVAILABLE] = this->availability_->payload_not_available; } const std::string &node_name = App.get_name(); std::string unique_id = this->unique_id(); if (!unique_id.empty()) { - root["unique_id"] = unique_id; + root[MQTT_UNIQUE_ID] = unique_id; } else { // default to almost-unique ID. It's a hack but the only way to get that // gorgeous device registry view. - root["unique_id"] = "ESP" + this->component_type() + this->get_default_object_id_(); + root[MQTT_UNIQUE_ID] = "ESP" + this->component_type() + this->get_default_object_id_(); } - JsonObject &device_info = root.createNestedObject("device"); - device_info["identifiers"] = get_mac_address(); - device_info["name"] = node_name; - device_info["sw_version"] = "esphome v" ESPHOME_VERSION " " + App.get_compilation_time(); - device_info["model"] = ESPHOME_BOARD; - device_info["manufacturer"] = "espressif"; + JsonObject &device_info = root.createNestedObject(MQTT_DEVICE); + device_info[MQTT_DEVICE_IDENTIFIERS] = get_mac_address(); + device_info[MQTT_DEVICE_NAME] = node_name; + device_info[MQTT_DEVICE_SW_VERSION] = "esphome v" ESPHOME_VERSION " " + App.get_compilation_time(); + device_info[MQTT_DEVICE_MODEL] = ESPHOME_BOARD; + device_info[MQTT_DEVICE_MANUFACTURER] = "espressif"; }, 0, discovery_info.retain); } diff --git a/esphome/components/mqtt/mqtt_const.h b/esphome/components/mqtt/mqtt_const.h new file mode 100644 index 0000000000..df5465ce9a --- /dev/null +++ b/esphome/components/mqtt/mqtt_const.h @@ -0,0 +1,518 @@ +#pragma once + +#include "esphome/core/defines.h" + +#ifdef USE_MQTT + +namespace esphome { +namespace mqtt { + +#ifdef USE_MQTT_ABBREVIATIONS + +constexpr const char *const MQTT_ACTION_TOPIC = "act_t"; +constexpr const char *const MQTT_ACTION_TEMPLATE = "act_tpl"; +constexpr const char *const MQTT_AUTOMATION_TYPE = "atype"; +constexpr const char *const MQTT_AUX_COMMAND_TOPIC = "aux_cmd_t"; +constexpr const char *const MQTT_AUX_STATE_TEMPLATE = "aux_stat_tpl"; +constexpr const char *const MQTT_AUX_STATE_TOPIC = "aux_stat_t"; +constexpr const char *const MQTT_AVAILABILITY = "avty"; +constexpr const char *const MQTT_AVAILABILITY_MODE = "avty_mode"; +constexpr const char *const MQTT_AVAILABILITY_TOPIC = "avty_t"; +constexpr const char *const MQTT_AWAY_MODE_COMMAND_TOPIC = "away_mode_cmd_t"; +constexpr const char *const MQTT_AWAY_MODE_STATE_TEMPLATE = "away_mode_stat_tpl"; +constexpr const char *const MQTT_AWAY_MODE_STATE_TOPIC = "away_mode_stat_t"; +constexpr const char *const MQTT_BLUE_TEMPLATE = "b_tpl"; +constexpr const char *const MQTT_BRIGHTNESS_COMMAND_TOPIC = "bri_cmd_t"; +constexpr const char *const MQTT_BRIGHTNESS_SCALE = "bri_scl"; +constexpr const char *const MQTT_BRIGHTNESS_STATE_TOPIC = "bri_stat_t"; +constexpr const char *const MQTT_BRIGHTNESS_TEMPLATE = "bri_tpl"; +constexpr const char *const MQTT_BRIGHTNESS_VALUE_TEMPLATE = "bri_val_tpl"; +constexpr const char *const MQTT_COLOR_TEMP_COMMAND_TEMPLATE = "clr_temp_cmd_tpl"; +constexpr const char *const MQTT_BATTERY_LEVEL_TOPIC = "bat_lev_t"; +constexpr const char *const MQTT_BATTERY_LEVEL_TEMPLATE = "bat_lev_tpl"; +constexpr const char *const MQTT_CONFIGURATION_URL = "cu"; +constexpr const char *const MQTT_CHARGING_TOPIC = "chrg_t"; +constexpr const char *const MQTT_CHARGING_TEMPLATE = "chrg_tpl"; +constexpr const char *const MQTT_COLOR_MODE = "clrm"; +constexpr const char *const MQTT_COLOR_MODE_STATE_TOPIC = "clrm_stat_t"; +constexpr const char *const MQTT_COLOR_MODE_VALUE_TEMPLATE = "clrm_val_tpl"; +constexpr const char *const MQTT_COLOR_TEMP_COMMAND_TOPIC = "clr_temp_cmd_t"; +constexpr const char *const MQTT_COLOR_TEMP_STATE_TOPIC = "clr_temp_stat_t"; +constexpr const char *const MQTT_COLOR_TEMP_TEMPLATE = "clr_temp_tpl"; +constexpr const char *const MQTT_COLOR_TEMP_VALUE_TEMPLATE = "clr_temp_val_tpl"; +constexpr const char *const MQTT_CLEANING_TOPIC = "cln_t"; +constexpr const char *const MQTT_CLEANING_TEMPLATE = "cln_tpl"; +constexpr const char *const MQTT_COMMAND_OFF_TEMPLATE = "cmd_off_tpl"; +constexpr const char *const MQTT_COMMAND_ON_TEMPLATE = "cmd_on_tpl"; +constexpr const char *const MQTT_COMMAND_TOPIC = "cmd_t"; +constexpr const char *const MQTT_COMMAND_TEMPLATE = "cmd_tpl"; +constexpr const char *const MQTT_CODE_ARM_REQUIRED = "cod_arm_req"; +constexpr const char *const MQTT_CODE_DISARM_REQUIRED = "cod_dis_req"; +constexpr const char *const MQTT_CURRENT_TEMPERATURE_TOPIC = "curr_temp_t"; +constexpr const char *const MQTT_CURRENT_TEMPERATURE_TEMPLATE = "curr_temp_tpl"; +constexpr const char *const MQTT_DEVICE = "dev"; +constexpr const char *const MQTT_DEVICE_CLASS = "dev_cla"; +constexpr const char *const MQTT_DOCKED_TOPIC = "dock_t"; +constexpr const char *const MQTT_DOCKED_TEMPLATE = "dock_tpl"; +constexpr const char *const MQTT_ENABLED_BY_DEFAULT = "en"; +constexpr const char *const MQTT_ERROR_TOPIC = "err_t"; +constexpr const char *const MQTT_ERROR_TEMPLATE = "err_tpl"; +constexpr const char *const MQTT_FAN_SPEED_TOPIC = "fanspd_t"; +constexpr const char *const MQTT_FAN_SPEED_TEMPLATE = "fanspd_tpl"; +constexpr const char *const MQTT_FAN_SPEED_LIST = "fanspd_lst"; +constexpr const char *const MQTT_FLASH_TIME_LONG = "flsh_tlng"; +constexpr const char *const MQTT_FLASH_TIME_SHORT = "flsh_tsht"; +constexpr const char *const MQTT_EFFECT_COMMAND_TOPIC = "fx_cmd_t"; +constexpr const char *const MQTT_EFFECT_LIST = "fx_list"; +constexpr const char *const MQTT_EFFECT_STATE_TOPIC = "fx_stat_t"; +constexpr const char *const MQTT_EFFECT_TEMPLATE = "fx_tpl"; +constexpr const char *const MQTT_EFFECT_VALUE_TEMPLATE = "fx_val_tpl"; +constexpr const char *const MQTT_EXPIRE_AFTER = "exp_aft"; +constexpr const char *const MQTT_FAN_MODE_COMMAND_TEMPLATE = "fan_mode_cmd_tpl"; +constexpr const char *const MQTT_FAN_MODE_COMMAND_TOPIC = "fan_mode_cmd_t"; +constexpr const char *const MQTT_FAN_MODE_STATE_TEMPLATE = "fan_mode_stat_tpl"; +constexpr const char *const MQTT_FAN_MODE_STATE_TOPIC = "fan_mode_stat_t"; +constexpr const char *const MQTT_FORCE_UPDATE = "frc_upd"; +constexpr const char *const MQTT_GREEN_TEMPLATE = "g_tpl"; +constexpr const char *const MQTT_HOLD_COMMAND_TEMPLATE = "hold_cmd_tpl"; +constexpr const char *const MQTT_HOLD_COMMAND_TOPIC = "hold_cmd_t"; +constexpr const char *const MQTT_HOLD_STATE_TEMPLATE = "hold_stat_tpl"; +constexpr const char *const MQTT_HOLD_STATE_TOPIC = "hold_stat_t"; +constexpr const char *const MQTT_HS_COMMAND_TOPIC = "hs_cmd_t"; +constexpr const char *const MQTT_HS_STATE_TOPIC = "hs_stat_t"; +constexpr const char *const MQTT_HS_VALUE_TEMPLATE = "hs_val_tpl"; +constexpr const char *const MQTT_ICON = "ic"; +constexpr const char *const MQTT_INITIAL = "init"; +constexpr const char *const MQTT_TARGET_HUMIDITY_COMMAND_TOPIC = "hum_cmd_t"; +constexpr const char *const MQTT_TARGET_HUMIDITY_COMMAND_TEMPLATE = "hum_cmd_tpl"; +constexpr const char *const MQTT_TARGET_HUMIDITY_STATE_TOPIC = "hum_stat_t"; +constexpr const char *const MQTT_TARGET_HUMIDITY_STATE_TEMPLATE = "hum_state_tpl"; +constexpr const char *const MQTT_JSON_ATTRIBUTES = "json_attr"; +constexpr const char *const MQTT_JSON_ATTRIBUTES_TOPIC = "json_attr_t"; +constexpr const char *const MQTT_JSON_ATTRIBUTES_TEMPLATE = "json_attr_tpl"; +constexpr const char *const MQTT_LAST_RESET_TOPIC = "lrst_t"; +constexpr const char *const MQTT_LAST_RESET_VALUE_TEMPLATE = "lrst_val_tpl"; +constexpr const char *const MQTT_MAX = "max"; +constexpr const char *const MQTT_MIN = "min"; +constexpr const char *const MQTT_MAX_HUMIDITY = "max_hum"; +constexpr const char *const MQTT_MIN_HUMIDITY = "min_hum"; +constexpr const char *const MQTT_MAX_MIREDS = "max_mirs"; +constexpr const char *const MQTT_MIN_MIREDS = "min_mirs"; +constexpr const char *const MQTT_MAX_TEMP = "max_temp"; +constexpr const char *const MQTT_MIN_TEMP = "min_temp"; +constexpr const char *const MQTT_MODE_COMMAND_TEMPLATE = "mode_cmd_tpl"; +constexpr const char *const MQTT_MODE_COMMAND_TOPIC = "mode_cmd_t"; +constexpr const char *const MQTT_MODE_STATE_TOPIC = "mode_stat_t"; +constexpr const char *const MQTT_MODE_STATE_TEMPLATE = "mode_stat_tpl"; +constexpr const char *const MQTT_MODES = "modes"; +constexpr const char *const MQTT_NAME = "name"; +constexpr const char *const MQTT_OFF_DELAY = "off_dly"; +constexpr const char *const MQTT_ON_COMMAND_TYPE = "on_cmd_type"; +constexpr const char *const MQTT_OPTIONS = "ops"; +constexpr const char *const MQTT_OPTIMISTIC = "opt"; +constexpr const char *const MQTT_OSCILLATION_COMMAND_TOPIC = "osc_cmd_t"; +constexpr const char *const MQTT_OSCILLATION_COMMAND_TEMPLATE = "osc_cmd_tpl"; +constexpr const char *const MQTT_OSCILLATION_STATE_TOPIC = "osc_stat_t"; +constexpr const char *const MQTT_OSCILLATION_VALUE_TEMPLATE = "osc_val_tpl"; +constexpr const char *const MQTT_PERCENTAGE_COMMAND_TOPIC = "pct_cmd_t"; +constexpr const char *const MQTT_PERCENTAGE_COMMAND_TEMPLATE = "pct_cmd_tpl"; +constexpr const char *const MQTT_PERCENTAGE_STATE_TOPIC = "pct_stat_t"; +constexpr const char *const MQTT_PERCENTAGE_VALUE_TEMPLATE = "pct_val_tpl"; +constexpr const char *const MQTT_PAYLOAD = "pl"; +constexpr const char *const MQTT_PAYLOAD_ARM_AWAY = "pl_arm_away"; +constexpr const char *const MQTT_PAYLOAD_ARM_HOME = "pl_arm_home"; +constexpr const char *const MQTT_PAYLOAD_ARM_NIGHT = "pl_arm_nite"; +constexpr const char *const MQTT_PAYLOAD_ARM_VACATION = "pl_arm_vacation"; +constexpr const char *const MQTT_PAYLOAD_ARM_CUSTOM_BYPASS = "pl_arm_custom_b"; +constexpr const char *const MQTT_PAYLOAD_AVAILABLE = "pl_avail"; +constexpr const char *const MQTT_PAYLOAD_CLEAN_SPOT = "pl_cln_sp"; +constexpr const char *const MQTT_PAYLOAD_CLOSE = "pl_cls"; +constexpr const char *const MQTT_PAYLOAD_DISARM = "pl_disarm"; +constexpr const char *const MQTT_PAYLOAD_HIGH_SPEED = "pl_hi_spd"; +constexpr const char *const MQTT_PAYLOAD_HOME = "pl_home"; +constexpr const char *const MQTT_PAYLOAD_LOCK = "pl_lock"; +constexpr const char *const MQTT_PAYLOAD_LOCATE = "pl_loc"; +constexpr const char *const MQTT_PAYLOAD_LOW_SPEED = "pl_lo_spd"; +constexpr const char *const MQTT_PAYLOAD_MEDIUM_SPEED = "pl_med_spd"; +constexpr const char *const MQTT_PAYLOAD_NOT_AVAILABLE = "pl_not_avail"; +constexpr const char *const MQTT_PAYLOAD_NOT_HOME = "pl_not_home"; +constexpr const char *const MQTT_PAYLOAD_OFF = "pl_off"; +constexpr const char *const MQTT_PAYLOAD_OFF_SPEED = "pl_off_spd"; +constexpr const char *const MQTT_PAYLOAD_ON = "pl_on"; +constexpr const char *const MQTT_PAYLOAD_OPEN = "pl_open"; +constexpr const char *const MQTT_PAYLOAD_OSCILLATION_OFF = "pl_osc_off"; +constexpr const char *const MQTT_PAYLOAD_OSCILLATION_ON = "pl_osc_on"; +constexpr const char *const MQTT_PAYLOAD_PAUSE = "pl_paus"; +constexpr const char *const MQTT_PAYLOAD_RESET = "pl_rst"; +constexpr const char *const MQTT_PAYLOAD_RESET_HUMIDITY = "pl_rst_hum"; +constexpr const char *const MQTT_PAYLOAD_RESET_MODE = "pl_rst_mode"; +constexpr const char *const MQTT_PAYLOAD_RESET_PERCENTAGE = "pl_rst_pct"; +constexpr const char *const MQTT_PAYLOAD_RESET_PRESET_MODE = "pl_rst_pr_mode"; +constexpr const char *const MQTT_PAYLOAD_STOP = "pl_stop"; +constexpr const char *const MQTT_PAYLOAD_START = "pl_strt"; +constexpr const char *const MQTT_PAYLOAD_START_PAUSE = "pl_stpa"; +constexpr const char *const MQTT_PAYLOAD_RETURN_TO_BASE = "pl_ret"; +constexpr const char *const MQTT_PAYLOAD_TURN_OFF = "pl_toff"; +constexpr const char *const MQTT_PAYLOAD_TURN_ON = "pl_ton"; +constexpr const char *const MQTT_PAYLOAD_UNLOCK = "pl_unlk"; +constexpr const char *const MQTT_POSITION_CLOSED = "pos_clsd"; +constexpr const char *const MQTT_POSITION_OPEN = "pos_open"; +constexpr const char *const MQTT_POWER_COMMAND_TOPIC = "pow_cmd_t"; +constexpr const char *const MQTT_POWER_STATE_TOPIC = "pow_stat_t"; +constexpr const char *const MQTT_POWER_STATE_TEMPLATE = "pow_stat_tpl"; +constexpr const char *const MQTT_PRESET_MODE_COMMAND_TOPIC = "pr_mode_cmd_t"; +constexpr const char *const MQTT_PRESET_MODE_COMMAND_TEMPLATE = "pr_mode_cmd_tpl"; +constexpr const char *const MQTT_PRESET_MODE_STATE_TOPIC = "pr_mode_stat_t"; +constexpr const char *const MQTT_PRESET_MODE_VALUE_TEMPLATE = "pr_mode_val_tpl"; +constexpr const char *const MQTT_PRESET_MODES = "pr_modes"; +constexpr const char *const MQTT_RED_TEMPLATE = "r_tpl"; +constexpr const char *const MQTT_RETAIN = "ret"; +constexpr const char *const MQTT_RGB_COMMAND_TEMPLATE = "rgb_cmd_tpl"; +constexpr const char *const MQTT_RGB_COMMAND_TOPIC = "rgb_cmd_t"; +constexpr const char *const MQTT_RGB_STATE_TOPIC = "rgb_stat_t"; +constexpr const char *const MQTT_RGB_VALUE_TEMPLATE = "rgb_val_tpl"; +constexpr const char *const MQTT_RGBW_COMMAND_TEMPLATE = "rgbw_cmd_tpl"; +constexpr const char *const MQTT_RGBW_COMMAND_TOPIC = "rgbw_cmd_t"; +constexpr const char *const MQTT_RGBW_STATE_TOPIC = "rgbw_stat_t"; +constexpr const char *const MQTT_RGBW_VALUE_TEMPLATE = "rgbw_val_tpl"; +constexpr const char *const MQTT_RGBWW_COMMAND_TEMPLATE = "rgbww_cmd_tpl"; +constexpr const char *const MQTT_RGBWW_COMMAND_TOPIC = "rgbww_cmd_t"; +constexpr const char *const MQTT_RGBWW_STATE_TOPIC = "rgbww_stat_t"; +constexpr const char *const MQTT_RGBWW_VALUE_TEMPLATE = "rgbww_val_tpl"; +constexpr const char *const MQTT_SEND_COMMAND_TOPIC = "send_cmd_t"; +constexpr const char *const MQTT_SEND_IF_OFF = "send_if_off"; +constexpr const char *const MQTT_SET_FAN_SPEED_TOPIC = "set_fan_spd_t"; +constexpr const char *const MQTT_SET_POSITION_TEMPLATE = "set_pos_tpl"; +constexpr const char *const MQTT_SET_POSITION_TOPIC = "set_pos_t"; +constexpr const char *const MQTT_POSITION_TOPIC = "pos_t"; +constexpr const char *const MQTT_POSITION_TEMPLATE = "pos_tpl"; +constexpr const char *const MQTT_SPEED_COMMAND_TOPIC = "spd_cmd_t"; +constexpr const char *const MQTT_SPEED_STATE_TOPIC = "spd_stat_t"; +constexpr const char *const MQTT_SPEED_RANGE_MIN = "spd_rng_min"; +constexpr const char *const MQTT_SPEED_RANGE_MAX = "spd_rng_max"; +constexpr const char *const MQTT_SPEED_VALUE_TEMPLATE = "spd_val_tpl"; +constexpr const char *const MQTT_SPEEDS = "spds"; +constexpr const char *const MQTT_SOURCE_TYPE = "src_type"; +constexpr const char *const MQTT_STATE_CLASS = "stat_cla"; +constexpr const char *const MQTT_STATE_CLOSED = "stat_clsd"; +constexpr const char *const MQTT_STATE_CLOSING = "stat_closing"; +constexpr const char *const MQTT_STATE_OFF = "stat_off"; +constexpr const char *const MQTT_STATE_ON = "stat_on"; +constexpr const char *const MQTT_STATE_OPEN = "stat_open"; +constexpr const char *const MQTT_STATE_OPENING = "stat_opening"; +constexpr const char *const MQTT_STATE_STOPPED = "stat_stopped"; +constexpr const char *const MQTT_STATE_LOCKED = "stat_locked"; +constexpr const char *const MQTT_STATE_UNLOCKED = "stat_unlocked"; +constexpr const char *const MQTT_STATE_TOPIC = "stat_t"; +constexpr const char *const MQTT_STATE_TEMPLATE = "stat_tpl"; +constexpr const char *const MQTT_STATE_VALUE_TEMPLATE = "stat_val_tpl"; +constexpr const char *const MQTT_STEP = "step"; +constexpr const char *const MQTT_SUBTYPE = "stype"; +constexpr const char *const MQTT_SUPPORTED_FEATURES = "sup_feat"; +constexpr const char *const MQTT_SUPPORTED_COLOR_MODES = "sup_clrm"; +constexpr const char *const MQTT_SWING_MODE_COMMAND_TEMPLATE = "swing_mode_cmd_tpl"; +constexpr const char *const MQTT_SWING_MODE_COMMAND_TOPIC = "swing_mode_cmd_t"; +constexpr const char *const MQTT_SWING_MODE_STATE_TEMPLATE = "swing_mode_stat_tpl"; +constexpr const char *const MQTT_SWING_MODE_STATE_TOPIC = "swing_mode_stat_t"; +constexpr const char *const MQTT_TEMPERATURE_COMMAND_TEMPLATE = "temp_cmd_tpl"; +constexpr const char *const MQTT_TEMPERATURE_COMMAND_TOPIC = "temp_cmd_t"; +constexpr const char *const MQTT_TEMPERATURE_HIGH_COMMAND_TEMPLATE = "temp_hi_cmd_tpl"; +constexpr const char *const MQTT_TEMPERATURE_HIGH_COMMAND_TOPIC = "temp_hi_cmd_t"; +constexpr const char *const MQTT_TEMPERATURE_HIGH_STATE_TEMPLATE = "temp_hi_stat_tpl"; +constexpr const char *const MQTT_TEMPERATURE_HIGH_STATE_TOPIC = "temp_hi_stat_t"; +constexpr const char *const MQTT_TEMPERATURE_LOW_COMMAND_TEMPLATE = "temp_lo_cmd_tpl"; +constexpr const char *const MQTT_TEMPERATURE_LOW_COMMAND_TOPIC = "temp_lo_cmd_t"; +constexpr const char *const MQTT_TEMPERATURE_LOW_STATE_TEMPLATE = "temp_lo_stat_tpl"; +constexpr const char *const MQTT_TEMPERATURE_LOW_STATE_TOPIC = "temp_lo_stat_t"; +constexpr const char *const MQTT_TEMPERATURE_STATE_TEMPLATE = "temp_stat_tpl"; +constexpr const char *const MQTT_TEMPERATURE_STATE_TOPIC = "temp_stat_t"; +constexpr const char *const MQTT_TEMPERATURE_UNIT = "temp_unit"; +constexpr const char *const MQTT_TILT_CLOSED_VALUE = "tilt_clsd_val"; +constexpr const char *const MQTT_TILT_COMMAND_TOPIC = "tilt_cmd_t"; +constexpr const char *const MQTT_TILT_COMMAND_TEMPLATE = "tilt_cmd_tpl"; +constexpr const char *const MQTT_TILT_INVERT_STATE = "tilt_inv_stat"; +constexpr const char *const MQTT_TILT_MAX = "tilt_max"; +constexpr const char *const MQTT_TILT_MIN = "tilt_min"; +constexpr const char *const MQTT_TILT_OPENED_VALUE = "tilt_opnd_val"; +constexpr const char *const MQTT_TILT_OPTIMISTIC = "tilt_opt"; +constexpr const char *const MQTT_TILT_STATUS_TOPIC = "tilt_status_t"; +constexpr const char *const MQTT_TILT_STATUS_TEMPLATE = "tilt_status_tpl"; +constexpr const char *const MQTT_TOPIC = "t"; +constexpr const char *const MQTT_UNIQUE_ID = "uniq_id"; +constexpr const char *const MQTT_UNIT_OF_MEASUREMENT = "unit_of_meas"; +constexpr const char *const MQTT_VALUE_TEMPLATE = "val_tpl"; +constexpr const char *const MQTT_WHITE_COMMAND_TOPIC = "whit_cmd_t"; +constexpr const char *const MQTT_WHITE_SCALE = "whit_scl"; +constexpr const char *const MQTT_WHITE_VALUE_COMMAND_TOPIC = "whit_val_cmd_t"; +constexpr const char *const MQTT_WHITE_VALUE_SCALE = "whit_val_scl"; +constexpr const char *const MQTT_WHITE_VALUE_STATE_TOPIC = "whit_val_stat_t"; +constexpr const char *const MQTT_WHITE_VALUE_TEMPLATE = "whit_val_tpl"; +constexpr const char *const MQTT_XY_COMMAND_TOPIC = "xy_cmd_t"; +constexpr const char *const MQTT_XY_STATE_TOPIC = "xy_stat_t"; +constexpr const char *const MQTT_XY_VALUE_TEMPLATE = "xy_val_tpl"; + +constexpr const char *const MQTT_DEVICE_CONNECTIONS = "cns"; +constexpr const char *const MQTT_DEVICE_IDENTIFIERS = "ids"; +constexpr const char *const MQTT_DEVICE_NAME = "name"; +constexpr const char *const MQTT_DEVICE_MANUFACTURER = "mf"; +constexpr const char *const MQTT_DEVICE_MODEL = "mdl"; +constexpr const char *const MQTT_DEVICE_SW_VERSION = "sw"; +constexpr const char *const MQTT_DEVICE_SUGGESTED_AREA = "sa"; + +#else + +constexpr const char *const MQTT_ACTION_TOPIC = "action_topic"; +constexpr const char *const MQTT_ACTION_TEMPLATE = "action_template"; +constexpr const char *const MQTT_AUTOMATION_TYPE = "automation_type"; +constexpr const char *const MQTT_AUX_COMMAND_TOPIC = "aux_command_topic"; +constexpr const char *const MQTT_AUX_STATE_TEMPLATE = "aux_state_template"; +constexpr const char *const MQTT_AUX_STATE_TOPIC = "aux_state_topic"; +constexpr const char *const MQTT_AVAILABILITY = "availability"; +constexpr const char *const MQTT_AVAILABILITY_MODE = "availability_mode"; +constexpr const char *const MQTT_AVAILABILITY_TOPIC = "availability_topic"; +constexpr const char *const MQTT_AWAY_MODE_COMMAND_TOPIC = "away_mode_command_topic"; +constexpr const char *const MQTT_AWAY_MODE_STATE_TEMPLATE = "away_mode_state_template"; +constexpr const char *const MQTT_AWAY_MODE_STATE_TOPIC = "away_mode_state_topic"; +constexpr const char *const MQTT_BLUE_TEMPLATE = "blue_template"; +constexpr const char *const MQTT_BRIGHTNESS_COMMAND_TOPIC = "brightness_command_topic"; +constexpr const char *const MQTT_BRIGHTNESS_SCALE = "brightness_scale"; +constexpr const char *const MQTT_BRIGHTNESS_STATE_TOPIC = "brightness_state_topic"; +constexpr const char *const MQTT_BRIGHTNESS_TEMPLATE = "brightness_template"; +constexpr const char *const MQTT_BRIGHTNESS_VALUE_TEMPLATE = "brightness_value_template"; +constexpr const char *const MQTT_COLOR_TEMP_COMMAND_TEMPLATE = "color_temp_command_template"; +constexpr const char *const MQTT_BATTERY_LEVEL_TOPIC = "battery_level_topic"; +constexpr const char *const MQTT_BATTERY_LEVEL_TEMPLATE = "battery_level_template"; +constexpr const char *const MQTT_CONFIGURATION_URL = "configuration_url"; +constexpr const char *const MQTT_CHARGING_TOPIC = "charging_topic"; +constexpr const char *const MQTT_CHARGING_TEMPLATE = "charging_template"; +constexpr const char *const MQTT_COLOR_MODE = "color_mode"; +constexpr const char *const MQTT_COLOR_MODE_STATE_TOPIC = "color_mode_state_topic"; +constexpr const char *const MQTT_COLOR_MODE_VALUE_TEMPLATE = "color_mode_value_template"; +constexpr const char *const MQTT_COLOR_TEMP_COMMAND_TOPIC = "color_temp_command_topic"; +constexpr const char *const MQTT_COLOR_TEMP_STATE_TOPIC = "color_temp_state_topic"; +constexpr const char *const MQTT_COLOR_TEMP_TEMPLATE = "color_temp_template"; +constexpr const char *const MQTT_COLOR_TEMP_VALUE_TEMPLATE = "color_temp_value_template"; +constexpr const char *const MQTT_CLEANING_TOPIC = "cleaning_topic"; +constexpr const char *const MQTT_CLEANING_TEMPLATE = "cleaning_template"; +constexpr const char *const MQTT_COMMAND_OFF_TEMPLATE = "command_off_template"; +constexpr const char *const MQTT_COMMAND_ON_TEMPLATE = "command_on_template"; +constexpr const char *const MQTT_COMMAND_TOPIC = "command_topic"; +constexpr const char *const MQTT_COMMAND_TEMPLATE = "command_template"; +constexpr const char *const MQTT_CODE_ARM_REQUIRED = "code_arm_required"; +constexpr const char *const MQTT_CODE_DISARM_REQUIRED = "code_disarm_required"; +constexpr const char *const MQTT_CURRENT_TEMPERATURE_TOPIC = "current_temperature_topic"; +constexpr const char *const MQTT_CURRENT_TEMPERATURE_TEMPLATE = "current_temperature_template"; +constexpr const char *const MQTT_DEVICE = "device"; +constexpr const char *const MQTT_DEVICE_CLASS = "device_class"; +constexpr const char *const MQTT_DOCKED_TOPIC = "docked_topic"; +constexpr const char *const MQTT_DOCKED_TEMPLATE = "docked_template"; +constexpr const char *const MQTT_ENABLED_BY_DEFAULT = "enabled_by_default"; +constexpr const char *const MQTT_ERROR_TOPIC = "error_topic"; +constexpr const char *const MQTT_ERROR_TEMPLATE = "error_template"; +constexpr const char *const MQTT_FAN_SPEED_TOPIC = "fan_speed_topic"; +constexpr const char *const MQTT_FAN_SPEED_TEMPLATE = "fan_speed_template"; +constexpr const char *const MQTT_FAN_SPEED_LIST = "fan_speed_list"; +constexpr const char *const MQTT_FLASH_TIME_LONG = "flash_time_long"; +constexpr const char *const MQTT_FLASH_TIME_SHORT = "flash_time_short"; +constexpr const char *const MQTT_EFFECT_COMMAND_TOPIC = "effect_command_topic"; +constexpr const char *const MQTT_EFFECT_LIST = "effect_list"; +constexpr const char *const MQTT_EFFECT_STATE_TOPIC = "effect_state_topic"; +constexpr const char *const MQTT_EFFECT_TEMPLATE = "effect_template"; +constexpr const char *const MQTT_EFFECT_VALUE_TEMPLATE = "effect_value_template"; +constexpr const char *const MQTT_EXPIRE_AFTER = "expire_after"; +constexpr const char *const MQTT_FAN_MODE_COMMAND_TEMPLATE = "fan_mode_command_template"; +constexpr const char *const MQTT_FAN_MODE_COMMAND_TOPIC = "fan_mode_command_topic"; +constexpr const char *const MQTT_FAN_MODE_STATE_TEMPLATE = "fan_mode_state_template"; +constexpr const char *const MQTT_FAN_MODE_STATE_TOPIC = "fan_mode_state_topic"; +constexpr const char *const MQTT_FORCE_UPDATE = "force_update"; +constexpr const char *const MQTT_GREEN_TEMPLATE = "green_template"; +constexpr const char *const MQTT_HOLD_COMMAND_TEMPLATE = "hold_command_template"; +constexpr const char *const MQTT_HOLD_COMMAND_TOPIC = "hold_command_topic"; +constexpr const char *const MQTT_HOLD_STATE_TEMPLATE = "hold_state_template"; +constexpr const char *const MQTT_HOLD_STATE_TOPIC = "hold_state_topic"; +constexpr const char *const MQTT_HS_COMMAND_TOPIC = "hs_command_topic"; +constexpr const char *const MQTT_HS_STATE_TOPIC = "hs_state_topic"; +constexpr const char *const MQTT_HS_VALUE_TEMPLATE = "hs_value_template"; +constexpr const char *const MQTT_ICON = "icon"; +constexpr const char *const MQTT_INITIAL = "initial"; +constexpr const char *const MQTT_TARGET_HUMIDITY_COMMAND_TOPIC = "target_humidity_command_topic"; +constexpr const char *const MQTT_TARGET_HUMIDITY_COMMAND_TEMPLATE = "target_humidity_command_template"; +constexpr const char *const MQTT_TARGET_HUMIDITY_STATE_TOPIC = "target_humidity_state_topic"; +constexpr const char *const MQTT_TARGET_HUMIDITY_STATE_TEMPLATE = "target_humidity_state_template"; +constexpr const char *const MQTT_JSON_ATTRIBUTES = "json_attributes"; +constexpr const char *const MQTT_JSON_ATTRIBUTES_TOPIC = "json_attributes_topic"; +constexpr const char *const MQTT_JSON_ATTRIBUTES_TEMPLATE = "json_attributes_template"; +constexpr const char *const MQTT_LAST_RESET_TOPIC = "last_reset_topic"; +constexpr const char *const MQTT_LAST_RESET_VALUE_TEMPLATE = "last_reset_value_template"; +constexpr const char *const MQTT_MAX = "max"; +constexpr const char *const MQTT_MIN = "min"; +constexpr const char *const MQTT_MAX_HUMIDITY = "max_humidity"; +constexpr const char *const MQTT_MIN_HUMIDITY = "min_humidity"; +constexpr const char *const MQTT_MAX_MIREDS = "max_mireds"; +constexpr const char *const MQTT_MIN_MIREDS = "min_mireds"; +constexpr const char *const MQTT_MAX_TEMP = "max_temp"; +constexpr const char *const MQTT_MIN_TEMP = "min_temp"; +constexpr const char *const MQTT_MODE_COMMAND_TEMPLATE = "mode_command_template"; +constexpr const char *const MQTT_MODE_COMMAND_TOPIC = "mode_command_topic"; +constexpr const char *const MQTT_MODE_STATE_TOPIC = "mode_state_topic"; +constexpr const char *const MQTT_MODE_STATE_TEMPLATE = "mode_state_template"; +constexpr const char *const MQTT_MODES = "modes"; +constexpr const char *const MQTT_NAME = "name"; +constexpr const char *const MQTT_OFF_DELAY = "off_delay"; +constexpr const char *const MQTT_ON_COMMAND_TYPE = "on_command_type"; +constexpr const char *const MQTT_OPTIONS = "options"; +constexpr const char *const MQTT_OPTIMISTIC = "optimistic"; +constexpr const char *const MQTT_OSCILLATION_COMMAND_TOPIC = "oscillation_command_topic"; +constexpr const char *const MQTT_OSCILLATION_COMMAND_TEMPLATE = "oscillation_command_template"; +constexpr const char *const MQTT_OSCILLATION_STATE_TOPIC = "oscillation_state_topic"; +constexpr const char *const MQTT_OSCILLATION_VALUE_TEMPLATE = "oscillation_value_template"; +constexpr const char *const MQTT_PERCENTAGE_COMMAND_TOPIC = "percentage_command_topic"; +constexpr const char *const MQTT_PERCENTAGE_COMMAND_TEMPLATE = "percentage_command_template"; +constexpr const char *const MQTT_PERCENTAGE_STATE_TOPIC = "percentage_state_topic"; +constexpr const char *const MQTT_PERCENTAGE_VALUE_TEMPLATE = "percentage_value_template"; +constexpr const char *const MQTT_PAYLOAD = "payload"; +constexpr const char *const MQTT_PAYLOAD_ARM_AWAY = "payload_arm_away"; +constexpr const char *const MQTT_PAYLOAD_ARM_HOME = "payload_arm_home"; +constexpr const char *const MQTT_PAYLOAD_ARM_NIGHT = "payload_arm_night"; +constexpr const char *const MQTT_PAYLOAD_ARM_VACATION = "payload_arm_vacation"; +constexpr const char *const MQTT_PAYLOAD_ARM_CUSTOM_BYPASS = "payload_arm_custom_bypass"; +constexpr const char *const MQTT_PAYLOAD_AVAILABLE = "payload_available"; +constexpr const char *const MQTT_PAYLOAD_CLEAN_SPOT = "payload_clean_spot"; +constexpr const char *const MQTT_PAYLOAD_CLOSE = "payload_close"; +constexpr const char *const MQTT_PAYLOAD_DISARM = "payload_disarm"; +constexpr const char *const MQTT_PAYLOAD_HIGH_SPEED = "payload_high_speed"; +constexpr const char *const MQTT_PAYLOAD_HOME = "payload_home"; +constexpr const char *const MQTT_PAYLOAD_LOCK = "payload_lock"; +constexpr const char *const MQTT_PAYLOAD_LOCATE = "payload_locate"; +constexpr const char *const MQTT_PAYLOAD_LOW_SPEED = "payload_low_speed"; +constexpr const char *const MQTT_PAYLOAD_MEDIUM_SPEED = "payload_medium_speed"; +constexpr const char *const MQTT_PAYLOAD_NOT_AVAILABLE = "payload_not_available"; +constexpr const char *const MQTT_PAYLOAD_NOT_HOME = "payload_not_home"; +constexpr const char *const MQTT_PAYLOAD_OFF = "payload_off"; +constexpr const char *const MQTT_PAYLOAD_OFF_SPEED = "payload_off_speed"; +constexpr const char *const MQTT_PAYLOAD_ON = "payload_on"; +constexpr const char *const MQTT_PAYLOAD_OPEN = "payload_open"; +constexpr const char *const MQTT_PAYLOAD_OSCILLATION_OFF = "payload_oscillation_off"; +constexpr const char *const MQTT_PAYLOAD_OSCILLATION_ON = "payload_oscillation_on"; +constexpr const char *const MQTT_PAYLOAD_PAUSE = "payload_pause"; +constexpr const char *const MQTT_PAYLOAD_RESET = "payload_reset"; +constexpr const char *const MQTT_PAYLOAD_RESET_HUMIDITY = "payload_reset_humidity"; +constexpr const char *const MQTT_PAYLOAD_RESET_MODE = "payload_reset_mode"; +constexpr const char *const MQTT_PAYLOAD_RESET_PERCENTAGE = "payload_reset_percentage"; +constexpr const char *const MQTT_PAYLOAD_RESET_PRESET_MODE = "payload_reset_preset_mode"; +constexpr const char *const MQTT_PAYLOAD_STOP = "payload_stop"; +constexpr const char *const MQTT_PAYLOAD_START = "payload_start"; +constexpr const char *const MQTT_PAYLOAD_START_PAUSE = "payload_start_pause"; +constexpr const char *const MQTT_PAYLOAD_RETURN_TO_BASE = "payload_return_to_base"; +constexpr const char *const MQTT_PAYLOAD_TURN_OFF = "payload_turn_off"; +constexpr const char *const MQTT_PAYLOAD_TURN_ON = "payload_turn_on"; +constexpr const char *const MQTT_PAYLOAD_UNLOCK = "payload_unlock"; +constexpr const char *const MQTT_POSITION_CLOSED = "position_closed"; +constexpr const char *const MQTT_POSITION_OPEN = "position_open"; +constexpr const char *const MQTT_POWER_COMMAND_TOPIC = "power_command_topic"; +constexpr const char *const MQTT_POWER_STATE_TOPIC = "power_state_topic"; +constexpr const char *const MQTT_POWER_STATE_TEMPLATE = "power_state_template"; +constexpr const char *const MQTT_PRESET_MODE_COMMAND_TOPIC = "preset_mode_command_topic"; +constexpr const char *const MQTT_PRESET_MODE_COMMAND_TEMPLATE = "preset_mode_command_template"; +constexpr const char *const MQTT_PRESET_MODE_STATE_TOPIC = "preset_mode_state_topic"; +constexpr const char *const MQTT_PRESET_MODE_VALUE_TEMPLATE = "preset_mode_value_template"; +constexpr const char *const MQTT_PRESET_MODES = "preset_modes"; +constexpr const char *const MQTT_RED_TEMPLATE = "red_template"; +constexpr const char *const MQTT_RETAIN = "retain"; +constexpr const char *const MQTT_RGB_COMMAND_TEMPLATE = "rgb_command_template"; +constexpr const char *const MQTT_RGB_COMMAND_TOPIC = "rgb_command_topic"; +constexpr const char *const MQTT_RGB_STATE_TOPIC = "rgb_state_topic"; +constexpr const char *const MQTT_RGB_VALUE_TEMPLATE = "rgb_value_template"; +constexpr const char *const MQTT_RGBW_COMMAND_TEMPLATE = "rgbw_command_template"; +constexpr const char *const MQTT_RGBW_COMMAND_TOPIC = "rgbw_command_topic"; +constexpr const char *const MQTT_RGBW_STATE_TOPIC = "rgbw_state_topic"; +constexpr const char *const MQTT_RGBW_VALUE_TEMPLATE = "rgbw_value_template"; +constexpr const char *const MQTT_RGBWW_COMMAND_TEMPLATE = "rgbww_command_template"; +constexpr const char *const MQTT_RGBWW_COMMAND_TOPIC = "rgbww_command_topic"; +constexpr const char *const MQTT_RGBWW_STATE_TOPIC = "rgbww_state_topic"; +constexpr const char *const MQTT_RGBWW_VALUE_TEMPLATE = "rgbww_value_template"; +constexpr const char *const MQTT_SEND_COMMAND_TOPIC = "send_command_topic"; +constexpr const char *const MQTT_SEND_IF_OFF = "send_if_off"; +constexpr const char *const MQTT_SET_FAN_SPEED_TOPIC = "set_fan_speed_topic"; +constexpr const char *const MQTT_SET_POSITION_TEMPLATE = "set_position_template"; +constexpr const char *const MQTT_SET_POSITION_TOPIC = "set_position_topic"; +constexpr const char *const MQTT_POSITION_TOPIC = "position_topic"; +constexpr const char *const MQTT_POSITION_TEMPLATE = "position_template"; +constexpr const char *const MQTT_SPEED_COMMAND_TOPIC = "speed_command_topic"; +constexpr const char *const MQTT_SPEED_STATE_TOPIC = "speed_state_topic"; +constexpr const char *const MQTT_SPEED_RANGE_MIN = "speed_range_min"; +constexpr const char *const MQTT_SPEED_RANGE_MAX = "speed_range_max"; +constexpr const char *const MQTT_SPEED_VALUE_TEMPLATE = "speed_value_template"; +constexpr const char *const MQTT_SPEEDS = "speeds"; +constexpr const char *const MQTT_SOURCE_TYPE = "source_type"; +constexpr const char *const MQTT_STATE_CLASS = "state_class"; +constexpr const char *const MQTT_STATE_CLOSED = "state_closed"; +constexpr const char *const MQTT_STATE_CLOSING = "state_closing"; +constexpr const char *const MQTT_STATE_OFF = "state_off"; +constexpr const char *const MQTT_STATE_ON = "state_on"; +constexpr const char *const MQTT_STATE_OPEN = "state_open"; +constexpr const char *const MQTT_STATE_OPENING = "state_opening"; +constexpr const char *const MQTT_STATE_STOPPED = "state_stopped"; +constexpr const char *const MQTT_STATE_LOCKED = "state_locked"; +constexpr const char *const MQTT_STATE_UNLOCKED = "state_unlocked"; +constexpr const char *const MQTT_STATE_TOPIC = "state_topic"; +constexpr const char *const MQTT_STATE_TEMPLATE = "state_template"; +constexpr const char *const MQTT_STATE_VALUE_TEMPLATE = "state_value_template"; +constexpr const char *const MQTT_STEP = "step"; +constexpr const char *const MQTT_SUBTYPE = "subtype"; +constexpr const char *const MQTT_SUPPORTED_FEATURES = "supported_features"; +constexpr const char *const MQTT_SUPPORTED_COLOR_MODES = "supported_color_modes"; +constexpr const char *const MQTT_SWING_MODE_COMMAND_TEMPLATE = "swing_mode_command_template"; +constexpr const char *const MQTT_SWING_MODE_COMMAND_TOPIC = "swing_mode_command_topic"; +constexpr const char *const MQTT_SWING_MODE_STATE_TEMPLATE = "swing_mode_state_template"; +constexpr const char *const MQTT_SWING_MODE_STATE_TOPIC = "swing_mode_state_topic"; +constexpr const char *const MQTT_TEMPERATURE_COMMAND_TEMPLATE = "temperature_command_template"; +constexpr const char *const MQTT_TEMPERATURE_COMMAND_TOPIC = "temperature_command_topic"; +constexpr const char *const MQTT_TEMPERATURE_HIGH_COMMAND_TEMPLATE = "temperature_high_command_template"; +constexpr const char *const MQTT_TEMPERATURE_HIGH_COMMAND_TOPIC = "temperature_high_command_topic"; +constexpr const char *const MQTT_TEMPERATURE_HIGH_STATE_TEMPLATE = "temperature_high_state_template"; +constexpr const char *const MQTT_TEMPERATURE_HIGH_STATE_TOPIC = "temperature_high_state_topic"; +constexpr const char *const MQTT_TEMPERATURE_LOW_COMMAND_TEMPLATE = "temperature_low_command_template"; +constexpr const char *const MQTT_TEMPERATURE_LOW_COMMAND_TOPIC = "temperature_low_command_topic"; +constexpr const char *const MQTT_TEMPERATURE_LOW_STATE_TEMPLATE = "temperature_low_state_template"; +constexpr const char *const MQTT_TEMPERATURE_LOW_STATE_TOPIC = "temperature_low_state_topic"; +constexpr const char *const MQTT_TEMPERATURE_STATE_TEMPLATE = "temperature_state_template"; +constexpr const char *const MQTT_TEMPERATURE_STATE_TOPIC = "temperature_state_topic"; +constexpr const char *const MQTT_TEMPERATURE_UNIT = "temperature_unit"; +constexpr const char *const MQTT_TILT_CLOSED_VALUE = "tilt_closed_value"; +constexpr const char *const MQTT_TILT_COMMAND_TOPIC = "tilt_command_topic"; +constexpr const char *const MQTT_TILT_COMMAND_TEMPLATE = "tilt_command_template"; +constexpr const char *const MQTT_TILT_INVERT_STATE = "tilt_invert_state"; +constexpr const char *const MQTT_TILT_MAX = "tilt_max"; +constexpr const char *const MQTT_TILT_MIN = "tilt_min"; +constexpr const char *const MQTT_TILT_OPENED_VALUE = "tilt_opened_value"; +constexpr const char *const MQTT_TILT_OPTIMISTIC = "tilt_optimistic"; +constexpr const char *const MQTT_TILT_STATUS_TOPIC = "tilt_status_topic"; +constexpr const char *const MQTT_TILT_STATUS_TEMPLATE = "tilt_status_template"; +constexpr const char *const MQTT_TOPIC = "topic"; +constexpr const char *const MQTT_UNIQUE_ID = "unique_id"; +constexpr const char *const MQTT_UNIT_OF_MEASUREMENT = "unit_of_measurement"; +constexpr const char *const MQTT_VALUE_TEMPLATE = "value_template"; +constexpr const char *const MQTT_WHITE_COMMAND_TOPIC = "white_command_topic"; +constexpr const char *const MQTT_WHITE_SCALE = "white_scale"; +constexpr const char *const MQTT_WHITE_VALUE_COMMAND_TOPIC = "white_value_command_topic"; +constexpr const char *const MQTT_WHITE_VALUE_SCALE = "white_value_scale"; +constexpr const char *const MQTT_WHITE_VALUE_STATE_TOPIC = "white_value_state_topic"; +constexpr const char *const MQTT_WHITE_VALUE_TEMPLATE = "white_value_template"; +constexpr const char *const MQTT_XY_COMMAND_TOPIC = "xy_command_topic"; +constexpr const char *const MQTT_XY_STATE_TOPIC = "xy_state_topic"; +constexpr const char *const MQTT_XY_VALUE_TEMPLATE = "xy_value_template"; + +constexpr const char *const MQTT_DEVICE_CONNECTIONS = "connections"; +constexpr const char *const MQTT_DEVICE_IDENTIFIERS = "identifiers"; +constexpr const char *const MQTT_DEVICE_NAME = "name"; +constexpr const char *const MQTT_DEVICE_MANUFACTURER = "manufacturer"; +constexpr const char *const MQTT_DEVICE_MODEL = "model"; +constexpr const char *const MQTT_DEVICE_SW_VERSION = "sw_version"; +constexpr const char *const MQTT_DEVICE_SUGGESTED_AREA = "suggested_area"; +#endif + +} // namespace mqtt +} // namespace esphome + +#endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_cover.cpp b/esphome/components/mqtt/mqtt_cover.cpp index e8bc7f0e30..7bf3204222 100644 --- a/esphome/components/mqtt/mqtt_cover.cpp +++ b/esphome/components/mqtt/mqtt_cover.cpp @@ -1,6 +1,8 @@ #include "mqtt_cover.h" #include "esphome/core/log.h" +#include "mqtt_const.h" + #ifdef USE_MQTT #ifdef USE_COVER @@ -63,20 +65,20 @@ void MQTTCoverComponent::dump_config() { } void MQTTCoverComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) { if (!this->cover_->get_device_class().empty()) - root["device_class"] = this->cover_->get_device_class(); + root[MQTT_DEVICE_CLASS] = this->cover_->get_device_class(); auto traits = this->cover_->get_traits(); if (traits.get_is_assumed_state()) { - root["optimistic"] = true; + root[MQTT_OPTIMISTIC] = true; } if (traits.get_supports_position()) { config.state_topic = false; - root["position_topic"] = this->get_position_state_topic(); - root["set_position_topic"] = this->get_position_command_topic(); + root[MQTT_POSITION_TOPIC] = this->get_position_state_topic(); + root[MQTT_SET_POSITION_TOPIC] = this->get_position_command_topic(); } if (traits.get_supports_tilt()) { - root["tilt_status_topic"] = this->get_tilt_state_topic(); - root["tilt_command_topic"] = this->get_tilt_command_topic(); + root[MQTT_TILT_STATUS_TOPIC] = this->get_tilt_state_topic(); + root[MQTT_TILT_COMMAND_TOPIC] = this->get_tilt_command_topic(); } if (traits.get_supports_tilt() && !traits.get_supports_position()) { config.command_topic = false; diff --git a/esphome/components/mqtt/mqtt_fan.cpp b/esphome/components/mqtt/mqtt_fan.cpp index 1703343a77..a9d77789e1 100644 --- a/esphome/components/mqtt/mqtt_fan.cpp +++ b/esphome/components/mqtt/mqtt_fan.cpp @@ -1,6 +1,8 @@ #include "mqtt_fan.h" #include "esphome/core/log.h" +#include "mqtt_const.h" + #ifdef USE_MQTT #ifdef USE_FAN #include "esphome/components/fan/fan_helpers.h" @@ -120,14 +122,14 @@ bool MQTTFanComponent::send_initial_state() { return this->publish_state(); } void MQTTFanComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) { if (this->state_->get_traits().supports_oscillation()) { - root["oscillation_command_topic"] = this->get_oscillation_command_topic(); - root["oscillation_state_topic"] = this->get_oscillation_state_topic(); + root[MQTT_OSCILLATION_COMMAND_TOPIC] = this->get_oscillation_command_topic(); + 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["speed_command_topic"] = this->get_speed_command_topic(); - root["speed_state_topic"] = this->get_speed_state_topic(); + root[MQTT_SPEED_COMMAND_TOPIC] = this->get_speed_command_topic(); + root[MQTT_SPEED_STATE_TOPIC] = this->get_speed_state_topic(); } } bool MQTTFanComponent::publish_state() { diff --git a/esphome/components/mqtt/mqtt_light.cpp b/esphome/components/mqtt/mqtt_light.cpp index a88358a6b2..54204a9e7f 100644 --- a/esphome/components/mqtt/mqtt_light.cpp +++ b/esphome/components/mqtt/mqtt_light.cpp @@ -1,6 +1,8 @@ #include "mqtt_light.h" #include "esphome/core/log.h" +#include "mqtt_const.h" + #ifdef USE_MQTT #ifdef USE_LIGHT @@ -38,7 +40,7 @@ void MQTTJSONLightComponent::send_discovery(JsonObject &root, mqtt::SendDiscover root["schema"] = "json"; auto traits = this->state_->get_traits(); - root["color_mode"] = true; + root[MQTT_COLOR_MODE] = true; JsonArray &color_modes = root.createNestedArray("supported_color_modes"); if (traits.supports_color_mode(ColorMode::ON_OFF)) color_modes.add("onoff"); @@ -64,7 +66,7 @@ void MQTTJSONLightComponent::send_discovery(JsonObject &root, mqtt::SendDiscover if (this->state_->supports_effects()) { root["effect"] = true; - JsonArray &effect_list = root.createNestedArray("effect_list"); + JsonArray &effect_list = root.createNestedArray(MQTT_EFFECT_LIST); for (auto *effect : this->state_->get_effects()) effect_list.add(effect->get_name()); effect_list.add("None"); diff --git a/esphome/components/mqtt/mqtt_number.cpp b/esphome/components/mqtt/mqtt_number.cpp index 674fd77bdf..9b2292cd76 100644 --- a/esphome/components/mqtt/mqtt_number.cpp +++ b/esphome/components/mqtt/mqtt_number.cpp @@ -1,6 +1,8 @@ #include "mqtt_number.h" #include "esphome/core/log.h" +#include "mqtt_const.h" + #ifdef USE_MQTT #ifdef USE_NUMBER @@ -38,9 +40,9 @@ const EntityBase *MQTTNumberComponent::get_entity() const { return this->number_ void MQTTNumberComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) { const auto &traits = number_->traits; // https://www.home-assistant.io/integrations/number.mqtt/ - root["min"] = traits.get_min_value(); - root["max"] = traits.get_max_value(); - root["step"] = traits.get_step(); + root[MQTT_MIN] = traits.get_min_value(); + root[MQTT_MAX] = traits.get_max_value(); + root[MQTT_STEP] = traits.get_step(); config.command_topic = true; } diff --git a/esphome/components/mqtt/mqtt_select.cpp b/esphome/components/mqtt/mqtt_select.cpp index b499636006..b8371de00e 100644 --- a/esphome/components/mqtt/mqtt_select.cpp +++ b/esphome/components/mqtt/mqtt_select.cpp @@ -1,6 +1,8 @@ #include "mqtt_select.h" #include "esphome/core/log.h" +#include "mqtt_const.h" + #ifdef USE_MQTT #ifdef USE_SELECT @@ -33,7 +35,7 @@ const EntityBase *MQTTSelectComponent::get_entity() const { return this->select_ void MQTTSelectComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) { const auto &traits = select_->traits; // https://www.home-assistant.io/integrations/select.mqtt/ - JsonArray &options = root.createNestedArray("options"); + JsonArray &options = root.createNestedArray(MQTT_OPTIONS); for (const auto &option : traits.get_options()) options.add(option); diff --git a/esphome/components/mqtt/mqtt_sensor.cpp b/esphome/components/mqtt/mqtt_sensor.cpp index 78710ff403..dd6423e8f3 100644 --- a/esphome/components/mqtt/mqtt_sensor.cpp +++ b/esphome/components/mqtt/mqtt_sensor.cpp @@ -1,6 +1,8 @@ #include "mqtt_sensor.h" #include "esphome/core/log.h" +#include "mqtt_const.h" + #ifdef USE_MQTT #ifdef USE_SENSOR @@ -42,19 +44,19 @@ void MQTTSensorComponent::disable_expire_after() { this->expire_after_ = 0; } void MQTTSensorComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) { if (!this->sensor_->get_device_class().empty()) - root["device_class"] = this->sensor_->get_device_class(); + root[MQTT_DEVICE_CLASS] = this->sensor_->get_device_class(); if (!this->sensor_->get_unit_of_measurement().empty()) - root["unit_of_measurement"] = this->sensor_->get_unit_of_measurement(); + root[MQTT_UNIT_OF_MEASUREMENT] = this->sensor_->get_unit_of_measurement(); if (this->get_expire_after() > 0) - root["expire_after"] = this->get_expire_after() / 1000; + root[MQTT_EXPIRE_AFTER] = this->get_expire_after() / 1000; if (this->sensor_->get_force_update()) - root["force_update"] = true; + root[MQTT_FORCE_UPDATE] = true; if (this->sensor_->get_state_class() != STATE_CLASS_NONE) - root["state_class"] = state_class_to_string(this->sensor_->get_state_class()); + root[MQTT_STATE_CLASS] = state_class_to_string(this->sensor_->get_state_class()); config.command_topic = false; } diff --git a/esphome/components/mqtt/mqtt_switch.cpp b/esphome/components/mqtt/mqtt_switch.cpp index 16cf102f7e..edaa6e7859 100644 --- a/esphome/components/mqtt/mqtt_switch.cpp +++ b/esphome/components/mqtt/mqtt_switch.cpp @@ -1,6 +1,8 @@ #include "mqtt_switch.h" #include "esphome/core/log.h" +#include "mqtt_const.h" + #ifdef USE_MQTT #ifdef USE_SWITCH @@ -44,7 +46,7 @@ std::string MQTTSwitchComponent::component_type() const { return "switch"; } const EntityBase *MQTTSwitchComponent::get_entity() const { return this->switch_; } void MQTTSwitchComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) { if (this->switch_->assumed_state()) - root["optimistic"] = true; + root[MQTT_OPTIMISTIC] = true; } bool MQTTSwitchComponent::send_initial_state() { return this->publish_state(this->switch_->state); } diff --git a/esphome/const.py b/esphome/const.py index e00eebb1a4..bac446a7c6 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -709,6 +709,7 @@ CONF_UNIT_OF_MEASUREMENT = "unit_of_measurement" CONF_UPDATE_INTERVAL = "update_interval" CONF_UPDATE_ON_BOOT = "update_on_boot" CONF_URL = "url" +CONF_USE_ABBREVIATIONS = "use_abbreviations" CONF_USE_ADDRESS = "use_address" CONF_USERNAME = "username" CONF_UUID = "uuid" diff --git a/tests/test1.yaml b/tests/test1.yaml index 157ccfc5d1..d8075e980b 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -94,6 +94,7 @@ mqtt: username: 'debug' password: 'debug' client_id: someclient + use_abbreviations: false discovery: True discovery_retain: False discovery_prefix: discovery From 2b04152482da3e9faaa4f6d0fd3370134d792fd1 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Sun, 31 Oct 2021 16:07:06 +0100 Subject: [PATCH 107/142] Fix deep sleep invert_wakeup mode (#2644) --- esphome/components/deep_sleep/deep_sleep_component.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/esphome/components/deep_sleep/deep_sleep_component.cpp b/esphome/components/deep_sleep/deep_sleep_component.cpp index e4b1edfb7b..0998a57af3 100644 --- a/esphome/components/deep_sleep/deep_sleep_component.cpp +++ b/esphome/components/deep_sleep/deep_sleep_component.cpp @@ -78,8 +78,9 @@ void DeepSleepComponent::begin_sleep(bool manual) { 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()) + if (this->wakeup_pin_mode_ == WAKEUP_PIN_MODE_INVERT_WAKEUP && !this->wakeup_pin_->digital_read()) { level = !level; + } esp_sleep_enable_ext0_wakeup(gpio_num_t(this->wakeup_pin_->get_pin()), level); } if (this->ext1_wakeup_.has_value()) { From d8b3af3815dcb4bed73bebf5c1659b6dd325c78e Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 1 Nov 2021 09:33:04 +1300 Subject: [PATCH 108/142] Expose webserver_port to the native API (#2640) --- esphome/components/api/api.proto | 2 ++ esphome/components/api/api_connection.cpp | 3 +++ esphome/components/api/api_pb2.cpp | 10 ++++++++++ esphome/components/api/api_pb2.h | 1 + esphome/components/web_server/__init__.py | 1 + esphome/core/defines.h | 3 +++ 6 files changed, 20 insertions(+) diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index 3c6a36f032..b1ac998608 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -182,6 +182,8 @@ message DeviceInfoResponse { // The esphome project details if set string project_name = 8; string project_version = 9; + + uint32 webserver_port = 10; } message ListEntitiesRequest { diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 2151d6165c..79c53ee840 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -759,6 +759,9 @@ DeviceInfoResponse APIConnection::device_info(const DeviceInfoRequest &msg) { #ifdef ESPHOME_PROJECT_NAME resp.project_name = ESPHOME_PROJECT_NAME; resp.project_version = ESPHOME_PROJECT_VERSION; +#endif +#ifdef USE_WEBSERVER + resp.webserver_port = WEBSERVER_PORT; #endif return resp; } diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index 17fc14c868..1d59d98f52 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -396,6 +396,10 @@ bool DeviceInfoResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { this->has_deep_sleep = value.as_bool(); return true; } + case 10: { + this->webserver_port = value.as_uint32(); + return true; + } default: return false; } @@ -444,6 +448,7 @@ void DeviceInfoResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(7, this->has_deep_sleep); buffer.encode_string(8, this->project_name); buffer.encode_string(9, this->project_version); + buffer.encode_uint32(10, this->webserver_port); } #ifdef HAS_PROTO_MESSAGE_DUMP void DeviceInfoResponse::dump_to(std::string &out) const { @@ -484,6 +489,11 @@ void DeviceInfoResponse::dump_to(std::string &out) const { out.append(" project_version: "); out.append("'").append(this->project_version).append("'"); out.append("\n"); + + out.append(" webserver_port: "); + sprintf(buffer, "%u", this->webserver_port); + out.append(buffer); + out.append("\n"); out.append("}"); } #endif diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index eea9ab06f6..af85ed6856 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -224,6 +224,7 @@ class DeviceInfoResponse : public ProtoMessage { bool has_deep_sleep{false}; std::string project_name{}; std::string project_version{}; + uint32_t webserver_port{0}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; diff --git a/esphome/components/web_server/__init__.py b/esphome/components/web_server/__init__.py index 240ba7c8a0..ba2d866593 100644 --- a/esphome/components/web_server/__init__.py +++ b/esphome/components/web_server/__init__.py @@ -54,6 +54,7 @@ async def to_code(config): cg.add(paren.set_port(config[CONF_PORT])) cg.add_define("WEBSERVER_PORT", config[CONF_PORT]) + cg.add_define("USE_WEBSERVER") cg.add(var.set_css_url(config[CONF_CSS_URL])) cg.add(var.set_js_url(config[CONF_JS_URL])) if CONF_AUTH in config: diff --git a/esphome/core/defines.h b/esphome/core/defines.h index b44987a768..dc07bde196 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -36,8 +36,11 @@ #define USE_SWITCH #define USE_TEXT_SENSOR #define USE_TIME +#define USE_WEBSERVER #define USE_WIFI +#define WEBSERVER_PORT 80 // NOLINT + // Arduino-specific feature flags #ifdef USE_ARDUINO #define USE_CAPTIVE_PORTAL From d54b4e7c4466487b23c54c0e5f72892bdaed0a00 Mon Sep 17 00:00:00 2001 From: Carlos Garcia Saura Date: Mon, 1 Nov 2021 20:27:57 +0100 Subject: [PATCH 109/142] Fix for noise in pulse_counter and duty_cycle components (#2646) --- .../duty_cycle/duty_cycle_sensor.cpp | 22 +++++++++---------- .../components/duty_cycle/duty_cycle_sensor.h | 2 +- .../pulse_counter/pulse_counter_sensor.cpp | 12 ++++++---- .../pulse_counter/pulse_counter_sensor.h | 3 ++- 4 files changed, 22 insertions(+), 17 deletions(-) diff --git a/esphome/components/duty_cycle/duty_cycle_sensor.cpp b/esphome/components/duty_cycle/duty_cycle_sensor.cpp index 3d7f731d5d..aed22312a7 100644 --- a/esphome/components/duty_cycle/duty_cycle_sensor.cpp +++ b/esphome/components/duty_cycle/duty_cycle_sensor.cpp @@ -12,7 +12,6 @@ void DutyCycleSensor::setup() { this->pin_->setup(); this->store_.pin = this->pin_->to_isr(); this->store_.last_level = this->pin_->digital_read(); - this->last_update_ = micros(); this->store_.last_interrupt = micros(); this->pin_->attach_interrupt(DutyCycleSensorStore::gpio_intr, &this->store_, gpio::INTERRUPT_ANY_EDGE); @@ -24,19 +23,20 @@ void DutyCycleSensor::dump_config() { } void DutyCycleSensor::update() { const uint32_t now = micros(); - const bool level = this->store_.last_level; - const uint32_t last_interrupt = this->store_.last_interrupt; - uint32_t on_time = this->store_.on_time; + if (this->last_update_ != 0) { + const bool level = this->store_.last_level; + const uint32_t last_interrupt = this->store_.last_interrupt; + uint32_t on_time = this->store_.on_time; - if (level) - on_time += now - last_interrupt; + if (level) + on_time += now - last_interrupt; - const float total_time = float(now - this->last_update_); - - const float value = (on_time / total_time) * 100.0f; - ESP_LOGD(TAG, "'%s' Got duty cycle=%.1f%%", this->get_name().c_str(), value); - this->publish_state(value); + const float total_time = float(now - this->last_update_); + const float value = (on_time / total_time) * 100.0f; + ESP_LOGD(TAG, "'%s' Got duty cycle=%.1f%%", this->get_name().c_str(), value); + this->publish_state(value); + } this->store_.on_time = 0; this->store_.last_interrupt = now; this->last_update_ = now; diff --git a/esphome/components/duty_cycle/duty_cycle_sensor.h b/esphome/components/duty_cycle/duty_cycle_sensor.h index 22d3588fb7..ffb1802e14 100644 --- a/esphome/components/duty_cycle/duty_cycle_sensor.h +++ b/esphome/components/duty_cycle/duty_cycle_sensor.h @@ -30,7 +30,7 @@ class DutyCycleSensor : public sensor::Sensor, public PollingComponent { InternalGPIOPin *pin_; DutyCycleSensorStore store_{}; - uint32_t last_update_; + uint32_t last_update_{0}; }; } // namespace duty_cycle diff --git a/esphome/components/pulse_counter/pulse_counter_sensor.cpp b/esphome/components/pulse_counter/pulse_counter_sensor.cpp index f538a4c905..d9f198f4fc 100644 --- a/esphome/components/pulse_counter/pulse_counter_sensor.cpp +++ b/esphome/components/pulse_counter/pulse_counter_sensor.cpp @@ -155,16 +155,20 @@ void PulseCounterSensor::dump_config() { void PulseCounterSensor::update() { pulse_counter_t raw = this->storage_.read_raw_value(); - float value = (60000.0f * raw) / float(this->get_update_interval()); // per minute - - ESP_LOGD(TAG, "'%s': Retrieved counter: %0.2f pulses/min", this->get_name().c_str(), value); - this->publish_state(value); + uint32_t now = millis(); + if (this->last_time_ != 0) { + uint32_t interval = now - this->last_time_; + float value = (60000.0f * raw) / float(interval); // per minute + ESP_LOGD(TAG, "'%s': Retrieved counter: %0.2f pulses/min", this->get_name().c_str(), value); + this->publish_state(value); + } if (this->total_sensor_ != nullptr) { current_total_ += raw; ESP_LOGD(TAG, "'%s': Total : %i pulses", this->get_name().c_str(), current_total_); this->total_sensor_->publish_state(current_total_); } + this->last_time_ = now; } } // namespace pulse_counter diff --git a/esphome/components/pulse_counter/pulse_counter_sensor.h b/esphome/components/pulse_counter/pulse_counter_sensor.h index 94e37bc232..9ed2159ae3 100644 --- a/esphome/components/pulse_counter/pulse_counter_sensor.h +++ b/esphome/components/pulse_counter/pulse_counter_sensor.h @@ -65,7 +65,8 @@ class PulseCounterSensor : public sensor::Sensor, public PollingComponent { protected: InternalGPIOPin *pin_; PulseCounterStorage storage_; - uint32_t current_total_ = 0; + uint32_t last_time_{0}; + uint32_t current_total_{0}; sensor::Sensor *total_sensor_; }; From 5ea77894b72c0e36ab6fe589d00ee78a341111f0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Nov 2021 21:03:23 +0100 Subject: [PATCH 110/142] Bump black from 21.9b0 to 21.10b0 (#2650) Bumps [black](https://github.com/psf/black) from 21.9b0 to 21.10b0. - [Release notes](https://github.com/psf/black/releases) - [Changelog](https://github.com/psf/black/blob/main/CHANGES.md) - [Commits](https://github.com/psf/black/commits) --- updated-dependencies: - dependency-name: black dependency-type: direct:production ... Signed-off-by: dependabot[bot] 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 e40d51f4bf..03879c5d0e 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,6 +1,6 @@ pylint==2.11.1 flake8==4.0.1 -black==21.9b0 +black==21.10b0 pexpect==4.8.0 pre-commit From 379c3e98f5b03af91a941fd7dbc1d19067851880 Mon Sep 17 00:00:00 2001 From: niklasweber Date: Tue, 2 Nov 2021 19:32:24 +0100 Subject: [PATCH 111/142] Add restore_mode to rotary_encoder (#2643) --- .../rotary_encoder/rotary_encoder.cpp | 34 +++++++++++++++++++ .../rotary_encoder/rotary_encoder.h | 17 ++++++++++ esphome/components/rotary_encoder/sensor.py | 12 +++++++ 3 files changed, 63 insertions(+) diff --git a/esphome/components/rotary_encoder/rotary_encoder.cpp b/esphome/components/rotary_encoder/rotary_encoder.cpp index e1f2584641..aff8fc381c 100644 --- a/esphome/components/rotary_encoder/rotary_encoder.cpp +++ b/esphome/components/rotary_encoder/rotary_encoder.cpp @@ -125,6 +125,22 @@ void IRAM_ATTR HOT RotaryEncoderSensorStore::gpio_intr(RotaryEncoderSensorStore void RotaryEncoderSensor::setup() { ESP_LOGCONFIG(TAG, "Setting up Rotary Encoder '%s'...", this->name_.c_str()); + + int32_t initial_value = 0; + switch (this->restore_mode_) { + case ROTARY_ENCODER_RESTORE_DEFAULT_ZERO: + this->rtc_ = global_preferences->make_preference(this->get_object_id_hash()); + if (!this->rtc_.load(&initial_value)) { + initial_value = 0; + } + break; + case ROTARY_ENCODER_ALWAYS_ZERO: + initial_value = 0; + break; + } + this->store_.counter = initial_value; + this->store_.last_read = initial_value; + this->pin_a_->setup(); this->store_.pin_a = this->pin_a_->to_isr(); this->pin_b_->setup(); @@ -142,6 +158,18 @@ void RotaryEncoderSensor::dump_config() { LOG_PIN(" Pin A: ", this->pin_a_); LOG_PIN(" Pin B: ", this->pin_b_); LOG_PIN(" Pin I: ", this->pin_i_); + + const LogString *restore_mode = LOG_STR(""); + switch (this->restore_mode_) { + case ROTARY_ENCODER_RESTORE_DEFAULT_ZERO: + restore_mode = LOG_STR("Restore (Defaults to zero)"); + break; + case ROTARY_ENCODER_ALWAYS_ZERO: + restore_mode = LOG_STR("Always zero"); + break; + } + ESP_LOGCONFIG(TAG, " Restore Mode: %s", LOG_STR_ARG(restore_mode)); + switch (this->store_.resolution) { case ROTARY_ENCODER_1_PULSE_PER_CYCLE: ESP_LOGCONFIG(TAG, " Resolution: 1 Pulse Per Cycle"); @@ -190,6 +218,9 @@ void RotaryEncoderSensor::loop() { } int counter = this->store_.counter; if (this->store_.last_read != counter || this->publish_initial_value_) { + if (this->restore_mode_ == ROTARY_ENCODER_RESTORE_DEFAULT_ZERO) { + this->rtc_.save(&counter); + } this->store_.last_read = counter; this->publish_state(counter); this->publish_initial_value_ = false; @@ -197,6 +228,9 @@ void RotaryEncoderSensor::loop() { } float RotaryEncoderSensor::get_setup_priority() const { return setup_priority::DATA; } +void RotaryEncoderSensor::set_restore_mode(RotaryEncoderRestoreMode restore_mode) { + this->restore_mode_ = restore_mode; +} void RotaryEncoderSensor::set_resolution(RotaryEncoderResolution mode) { this->store_.resolution = mode; } void RotaryEncoderSensor::set_min_value(int32_t min_value) { this->store_.min_value = min_value; } void RotaryEncoderSensor::set_max_value(int32_t max_value) { this->store_.max_value = max_value; } diff --git a/esphome/components/rotary_encoder/rotary_encoder.h b/esphome/components/rotary_encoder/rotary_encoder.h index a134043152..a69d738fa8 100644 --- a/esphome/components/rotary_encoder/rotary_encoder.h +++ b/esphome/components/rotary_encoder/rotary_encoder.h @@ -10,6 +10,12 @@ namespace esphome { namespace rotary_encoder { +/// All possible restore modes for the rotary encoder +enum RotaryEncoderRestoreMode { + ROTARY_ENCODER_RESTORE_DEFAULT_ZERO, /// try to restore counter, otherwise set to zero + ROTARY_ENCODER_ALWAYS_ZERO, /// do not restore counter, always set to zero +}; + /// All possible resolutions for the rotary encoder enum RotaryEncoderResolution { ROTARY_ENCODER_1_PULSE_PER_CYCLE = @@ -40,6 +46,15 @@ class RotaryEncoderSensor : public sensor::Sensor, public Component { void set_pin_a(InternalGPIOPin *pin_a) { pin_a_ = pin_a; } void set_pin_b(InternalGPIOPin *pin_b) { pin_b_ = pin_b; } + /** Set the restore mode of the rotary encoder. + * + * By default (if possible) the last known counter state is restored. Otherwise the value 0 is used. + * Restoring the state can also be turned off. + * + * @param restore_mode The restore mode to use. + */ + void set_restore_mode(RotaryEncoderRestoreMode restore_mode); + /** Set the resolution of the rotary encoder. * * By default, this component will increment the counter by 1 with every A-B input cycle. @@ -81,6 +96,8 @@ class RotaryEncoderSensor : public sensor::Sensor, public Component { InternalGPIOPin *pin_b_; GPIOPin *pin_i_{nullptr}; /// Index pin, if this is not nullptr, the counter will reset to 0 once this pin is HIGH. bool publish_initial_value_; + ESPPreferenceObject rtc_; + RotaryEncoderRestoreMode restore_mode_{ROTARY_ENCODER_RESTORE_DEFAULT_ZERO}; RotaryEncoderSensorStore store_{}; diff --git a/esphome/components/rotary_encoder/sensor.py b/esphome/components/rotary_encoder/sensor.py index d868438fd3..cd747264b3 100644 --- a/esphome/components/rotary_encoder/sensor.py +++ b/esphome/components/rotary_encoder/sensor.py @@ -14,9 +14,17 @@ from esphome.const import ( CONF_PIN_A, CONF_PIN_B, CONF_TRIGGER_ID, + CONF_RESTORE_MODE, ) rotary_encoder_ns = cg.esphome_ns.namespace("rotary_encoder") + +RotaryEncoderRestoreMode = rotary_encoder_ns.enum("RotaryEncoderRestoreMode") +RESTORE_MODES = { + "RESTORE_DEFAULT_ZERO": RotaryEncoderRestoreMode.ROTARY_ENCODER_RESTORE_DEFAULT_ZERO, + "ALWAYS_ZERO": RotaryEncoderRestoreMode.ROTARY_ENCODER_ALWAYS_ZERO, +} + RotaryEncoderResolution = rotary_encoder_ns.enum("RotaryEncoderResolution") RESOLUTIONS = { 1: RotaryEncoderResolution.ROTARY_ENCODER_1_PULSE_PER_CYCLE, @@ -72,6 +80,9 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_MIN_VALUE): cv.int_, cv.Optional(CONF_MAX_VALUE): cv.int_, cv.Optional(CONF_PUBLISH_INITIAL_VALUE, default=False): cv.boolean, + cv.Optional(CONF_RESTORE_MODE, default="RESTORE_DEFAULT_ZERO"): cv.enum( + RESTORE_MODES, upper=True, space="_" + ), cv.Optional(CONF_ON_CLOCKWISE): automation.validate_automation( { cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( @@ -102,6 +113,7 @@ async def to_code(config): pin_b = await cg.gpio_pin_expression(config[CONF_PIN_B]) cg.add(var.set_pin_b(pin_b)) cg.add(var.set_publish_initial_value(config[CONF_PUBLISH_INITIAL_VALUE])) + cg.add(var.set_restore_mode(config[CONF_RESTORE_MODE])) if CONF_PIN_RESET in config: pin_i = await cg.gpio_pin_expression(config[CONF_PIN_RESET]) From 11f1e28139cf07aafbb4ff1f8257ad5caa369780 Mon Sep 17 00:00:00 2001 From: Tim Niemueller Date: Wed, 3 Nov 2021 17:56:09 +0100 Subject: [PATCH 112/142] Make per-loop display clearing optional (#2626) Currently, in each loop during DisplayBuffer::update_() the display is cleared by calling DisplayBuffer::clear(). This prevents more efficient display usages that do not render the screen in each loop, but only if necessary. This can be helpful, for example, if images are rendered. This would cause the loop time to be exceeded frequently. This change adds a new optional flag "auto_clear" that can be used to control the clearing behavior. If unset, the DisplayBuffer defaults to enabled auto clearing, the current behavior and thus backward compatible. This flag applies to displays that use DisplayBuffer. Example excerpt: globals: - id: state type: bool restore_value: no initial_value: "false" - id: state_processed type: bool restore_value: no initial_value: "false" switch: - platform: template name: "State" id: state_switch lambda: |- return id(state); turn_on_action: - globals.set: id: state value: "true" - globals.set: id: state_processed value: "false" turn_off_action: - globals.set: id: state value: "false" - globals.set: id: state_processed value: "false" display: - platform: ili9341 # ... auto_clear_enabled: false lambda: |- if (!id(state_processed)) { it.fill(COLOR_WHITE); if (id(state)) { it.image(80, 20, id(image1)); } else { it.image(80, 20, id(image2)); } id(state_processed) = true; } Co-authored-by: Tim Niemueller --- esphome/components/display/__init__.py | 6 ++++ esphome/components/display/display_buffer.cpp | 4 ++- esphome/components/display/display_buffer.h | 4 +++ esphome/const.py | 1 + tests/test1.yaml | 29 +++++++++++++++++++ 5 files changed, 43 insertions(+), 1 deletion(-) diff --git a/esphome/components/display/__init__.py b/esphome/components/display/__init__.py index 947b09a258..0d403f99f0 100644 --- a/esphome/components/display/__init__.py +++ b/esphome/components/display/__init__.py @@ -3,6 +3,7 @@ import esphome.config_validation as cv from esphome import core, automation from esphome.automation import maybe_simple_id from esphome.const import ( + CONF_AUTO_CLEAR_ENABLED, CONF_ID, CONF_LAMBDA, CONF_PAGES, @@ -79,6 +80,7 @@ FULL_DISPLAY_SCHEMA = BASIC_DISPLAY_SCHEMA.extend( cv.Optional(CONF_TO): cv.use_id(DisplayPage), } ), + cv.Optional(CONF_AUTO_CLEAR_ENABLED, default=True): cv.boolean, } ) @@ -86,6 +88,10 @@ FULL_DISPLAY_SCHEMA = BASIC_DISPLAY_SCHEMA.extend( async def setup_display_core_(var, config): if CONF_ROTATION in config: cg.add(var.set_rotation(DISPLAY_ROTATIONS[config[CONF_ROTATION]])) + + if CONF_AUTO_CLEAR_ENABLED in config: + cg.add(var.set_auto_clear(config[CONF_AUTO_CLEAR_ENABLED])) + if CONF_PAGES in config: pages = [] for conf in config[CONF_PAGES]: diff --git a/esphome/components/display/display_buffer.cpp b/esphome/components/display/display_buffer.cpp index 2ee06e379f..ac806611b5 100644 --- a/esphome/components/display/display_buffer.cpp +++ b/esphome/components/display/display_buffer.cpp @@ -336,7 +336,9 @@ void DisplayBuffer::show_page(DisplayPage *page) { void DisplayBuffer::show_next_page() { this->page_->show_next(); } void DisplayBuffer::show_prev_page() { this->page_->show_prev(); } void DisplayBuffer::do_update_() { - this->clear(); + if (this->auto_clear_enabled_) { + this->clear(); + } if (this->page_ != nullptr) { this->page_->get_writer()(*this); } else if (this->writer_.has_value()) { diff --git a/esphome/components/display/display_buffer.h b/esphome/components/display/display_buffer.h index 54488f18f7..c803180a2d 100644 --- a/esphome/components/display/display_buffer.h +++ b/esphome/components/display/display_buffer.h @@ -333,6 +333,9 @@ class DisplayBuffer { /// Internal method to set the display rotation with. void set_rotation(DisplayRotation rotation); + // Internal method to set display auto clearing. + void set_auto_clear(bool auto_clear_enabled) { this->auto_clear_enabled_ = auto_clear_enabled; } + protected: void vprintf_(int x, int y, Font *font, Color color, TextAlign align, const char *format, va_list arg); @@ -352,6 +355,7 @@ class DisplayBuffer { DisplayPage *page_{nullptr}; DisplayPage *previous_page_{nullptr}; std::vector on_page_change_triggers_; + bool auto_clear_enabled_{true}; }; class DisplayPage { diff --git a/esphome/const.py b/esphome/const.py index bac446a7c6..eb7d56d7e1 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -59,6 +59,7 @@ CONF_AT = "at" CONF_ATTENUATION = "attenuation" CONF_ATTRIBUTE = "attribute" CONF_AUTH = "auth" +CONF_AUTO_CLEAR_ENABLED = "auto_clear_enabled" CONF_AUTO_MODE = "auto_mode" CONF_AUTOCONF = "autoconf" CONF_AUTOMATION_ID = "automation_id" diff --git a/tests/test1.yaml b/tests/test1.yaml index d8075e980b..585e01635f 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -2214,6 +2214,31 @@ display: row_start: 0 lambda: |- it.rectangle(0, 0, it.get_width(), it.get_height()); + - platform: ili9341 + model: "TFT 2.4" + cs_pin: GPIO5 + dc_pin: GPIO4 + reset_pin: GPIO22 + led_pin: + number: GPIO15 + inverted: true + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); + - platform: ili9341 + model: "TFT 2.4" + cs_pin: GPIO5 + dc_pin: GPIO4 + reset_pin: GPIO22 + led_pin: + number: GPIO15 + inverted: true + auto_clear_enabled: false + rotation: 90 + lambda: |- + if (!id(glob_bool_processed)) { + it.fill(Color::WHITE); + id(glob_bool_processed) = true; + } tm1651: id: tm1651_battery @@ -2393,6 +2418,10 @@ globals: type: std::string restore_value: no # initial_value: "" + - id: glob_bool_processed + type: bool + restore_value: no + initial_value: 'false' text_sensor: - platform: mqtt_subscribe From d536509a63189490f314e5923471828ca28ba26d Mon Sep 17 00:00:00 2001 From: Guillermo Ruffino Date: Thu, 4 Nov 2021 18:52:38 -0300 Subject: [PATCH 113/142] Allow esp8266 to compile with no wifi (#2664) --- esphome/core/helpers.cpp | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index bc97259a71..ada9a48c3b 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -6,7 +6,9 @@ #include #if defined(USE_ESP8266) +#ifdef USE_WIFI #include +#endif #include #elif defined(USE_ESP32_FRAMEWORK_ARDUINO) #include @@ -39,7 +41,7 @@ void get_mac_address_raw(uint8_t *mac) { esp_efuse_mac_get_default(mac); #endif #endif -#ifdef USE_ESP8266 +#if (defined USE_ESP8266 && defined USE_WIFI) WiFi.macAddress(mac); #endif } @@ -48,7 +50,11 @@ std::string get_mac_address() { char tmp[20]; uint8_t mac[6]; get_mac_address_raw(mac); +#ifdef USE_WIFI sprintf(tmp, "%02x%02x%02x%02x%02x%02x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); +#else + return ""; +#endif return std::string(tmp); } @@ -475,8 +481,13 @@ 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 b450d4c734e18c1cfe04c07da09431b57ffdc8e3 Mon Sep 17 00:00:00 2001 From: Maurice Makaay Date: Sat, 6 Nov 2021 22:52:04 +0100 Subject: [PATCH 114/142] Fix CRC error during DSMR chunked message reading (#2622) * DSMR chunk size from 50 to 500 * Still a few CRC errors with 500, upping to 1024. * Adding timers to measure how long processing DSMR takes * Handle chunked output from smart meter. * Cleaning up and commenting the new chunk handling code * Remove debug code. * Fixing clang-tidy issues. * Implementing chunked reading support for encrypted telegrams. * Remove redundant extra delay for encrypted reader * Beware not to flush crypted telegram headers * Use insane data timeout for testing * Improve logging * Make clang-tidy happy Co-authored-by: Maurice Makaay Co-authored-by: Maurice Makaay --- esphome/components/dsmr/dsmr.cpp | 104 ++++++++++++++++++------------- esphome/components/dsmr/dsmr.h | 14 ++++- 2 files changed, 71 insertions(+), 47 deletions(-) diff --git a/esphome/components/dsmr/dsmr.cpp b/esphome/components/dsmr/dsmr.cpp index b798fe5d44..031fb275f5 100644 --- a/esphome/components/dsmr/dsmr.cpp +++ b/esphome/components/dsmr/dsmr.cpp @@ -19,14 +19,30 @@ void Dsmr::loop() { this->receive_encrypted_(); } +bool Dsmr::available_within_timeout_() { + uint8_t tries = READ_TIMEOUT_MS / 5; + while (tries--) { + delay(5); + if (available()) { + return true; + } + } + return false; +} + void Dsmr::receive_telegram_() { - int count = MAX_BYTES_PER_LOOP; - while (available() && count-- > 0) { + while (true) { + if (!available()) { + if (!header_found_ || !available_within_timeout_()) { + return; + } + } + const char c = read(); // Find a new telegram header, i.e. forward slash. if (c == '/') { - ESP_LOGV(TAG, "Header found"); + ESP_LOGV(TAG, "Header of telegram found"); header_found_ = true; footer_found_ = false; telegram_len_ = 0; @@ -38,7 +54,7 @@ void Dsmr::receive_telegram_() { if (telegram_len_ >= MAX_TELEGRAM_LENGTH) { header_found_ = false; footer_found_ = false; - ESP_LOGE(TAG, "Error: Message larger than buffer"); + ESP_LOGE(TAG, "Error: telegram larger than buffer (%d bytes)", MAX_TELEGRAM_LENGTH); return; } @@ -54,7 +70,7 @@ void Dsmr::receive_telegram_() { // Check for a footer, i.e. exlamation mark, followed by a hex checksum. if (c == '!') { - ESP_LOGV(TAG, "Footer found"); + ESP_LOGV(TAG, "Footer of telegram found"); footer_found_ = true; continue; } @@ -62,8 +78,8 @@ void Dsmr::receive_telegram_() { if (footer_found_ && c == '\n') { header_found_ = false; // Parse the telegram and publish sensor values. - if (parse_telegram()) - return; + parse_telegram(); + return; } } } @@ -72,41 +88,46 @@ void Dsmr::receive_encrypted_() { // Encrypted buffer uint8_t buffer[MAX_TELEGRAM_LENGTH]; size_t buffer_length = 0; - size_t packet_size = 0; - while (available()) { - const char c = read(); - if (!header_found_) { - if ((uint8_t) c == 0xdb) { - ESP_LOGV(TAG, "Start byte 0xDB found"); - header_found_ = true; + while (true) { + if (!available()) { + if (!header_found_) { + return; + } + if (!available_within_timeout_()) { + ESP_LOGW(TAG, "Timeout while reading data for encrypted telegram"); + return; } } - // Sanity check - if (!header_found_ || buffer_length >= MAX_TELEGRAM_LENGTH) { - if (buffer_length == 0) { - ESP_LOGE(TAG, "First byte of encrypted telegram should be 0xDB, aborting."); - } else { - ESP_LOGW(TAG, "Unexpected data"); + const char c = read(); + + // Find a new telegram start byte. + if (!header_found_) { + if ((uint8_t) c == 0xDB) { + ESP_LOGV(TAG, "Start byte 0xDB of encrypted telegram found"); + header_found_ = true; } - this->status_momentary_warning("unexpected_data"); - this->flush(); - while (available()) - read(); + continue; + } + + // Check for buffer overflow. + if (buffer_length >= MAX_TELEGRAM_LENGTH) { + header_found_ = false; + ESP_LOGE(TAG, "Error: encrypted telegram larger than buffer (%d bytes)", MAX_TELEGRAM_LENGTH); return; } buffer[buffer_length++] = c; if (packet_size == 0 && buffer_length > 20) { - // Complete header + a few bytes of data - packet_size = buffer[11] << 8 | buffer[12]; + // Complete header + data bytes + packet_size = 13 + (buffer[11] << 8 | buffer[12]); + ESP_LOGV(TAG, "Encrypted telegram size: %d bytes", packet_size); } - if (buffer_length == packet_size + 13 && packet_size > 0) { - ESP_LOGV(TAG, "Encrypted data: %d bytes", buffer_length); - + if (buffer_length == packet_size && packet_size > 0) { + ESP_LOGV(TAG, "End of encrypted telegram found"); GCM *gcmaes128{new GCM()}; gcmaes128->setKey(this->decryption_key_.data(), gcmaes128->keySize()); // the iv is 8 bytes of the system title + 4 bytes frame counter @@ -123,28 +144,21 @@ void Dsmr::receive_encrypted_() { delete gcmaes128; // NOLINT(cppcoreguidelines-owning-memory) telegram_len_ = strnlen(this->telegram_, sizeof(this->telegram_)); - ESP_LOGV(TAG, "Decrypted data length: %d", telegram_len_); - ESP_LOGVV(TAG, "Decrypted data %s", this->telegram_); + ESP_LOGV(TAG, "Decrypted telegram size: %d bytes", telegram_len_); + ESP_LOGVV(TAG, "Decrypted telegram: %s", this->telegram_); + + header_found_ = false; + telegram_len_ = 0; parse_telegram(); - telegram_len_ = 0; return; } - - if (!available()) { - // baud rate is 115200 for encrypted data, this means a few byte should arrive every time - // program runs faster than buffer loading then available() might return false in the middle - delay(4); // Wait for data - } - } - if (buffer_length > 0) { - ESP_LOGW(TAG, "Timeout while waiting for encrypted data or invalid data received."); } } bool Dsmr::parse_telegram() { MyData data; - ESP_LOGV(TAG, "Trying to parse"); + ESP_LOGV(TAG, "Trying to parse telegram"); ::dsmr::ParseResult res = ::dsmr::P1Parser::parse(&data, telegram_, telegram_len_, false, this->crc_check_); // Parse telegram according to data definition. Ignore unknown values. @@ -161,7 +175,7 @@ bool Dsmr::parse_telegram() { } void Dsmr::dump_config() { - ESP_LOGCONFIG(TAG, "dsmr:"); + ESP_LOGCONFIG(TAG, "DSMR:"); #define DSMR_LOG_SENSOR(s) LOG_SENSOR(" ", #s, this->s_##s##_); DSMR_SENSOR_LIST(DSMR_LOG_SENSOR, ) @@ -178,12 +192,12 @@ void Dsmr::set_decryption_key(const std::string &decryption_key) { } if (decryption_key.length() != 32) { - ESP_LOGE(TAG, "Error, decryption key must be 32 character long."); + ESP_LOGE(TAG, "Error, decryption key must be 32 character long"); return; } this->decryption_key_.clear(); - ESP_LOGI(TAG, "Decryption key is set."); + ESP_LOGI(TAG, "Decryption key is set"); // Verbose level prints decryption key ESP_LOGV(TAG, "Using decryption key: %s", decryption_key.c_str()); diff --git a/esphome/components/dsmr/dsmr.h b/esphome/components/dsmr/dsmr.h index 4f9a66b3d0..ca2c0f0877 100644 --- a/esphome/components/dsmr/dsmr.h +++ b/esphome/components/dsmr/dsmr.h @@ -17,8 +17,7 @@ namespace esphome { namespace dsmr { static constexpr uint32_t MAX_TELEGRAM_LENGTH = 1500; -static constexpr uint32_t MAX_BYTES_PER_LOOP = 50; -static constexpr uint32_t POLL_TIMEOUT = 1000; +static constexpr uint32_t READ_TIMEOUT_MS = 200; using namespace ::dsmr::fields; @@ -86,6 +85,17 @@ class Dsmr : public Component, public uart::UARTDevice { void receive_telegram_(); void receive_encrypted_(); + /// Wait for UART data to become available within the read timeout. + /// + /// The smart meter might provide data in chunks, causing available() to + /// return 0. When we're already reading a telegram, then we don't return + /// right away (to handle further data in an upcoming loop) but wait a + /// little while using this method to see if more data are incoming. + /// By not returning, we prevent other components from taking so much + /// time that the UART RX buffer overflows and bytes of the telegram get + /// lost in the process. + bool available_within_timeout_(); + // Telegram buffer char telegram_[MAX_TELEGRAM_LENGTH]; int telegram_len_{0}; From 3c0414c42027d8cc3cab8e59c878116f62d8fac7 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 8 Nov 2021 07:24:52 +1300 Subject: [PATCH 115/142] Add Entity categories for Home Assistant (#2636) --- esphome/codegen.py | 1 + esphome/components/am43/sensor.py | 12 +- esphome/components/api/api.proto | 19 +++ esphome/components/api/api_connection.cpp | 12 +- esphome/components/api/api_pb2.cpp | 111 ++++++++++++++++++ esphome/components/api/api_pb2.h | 16 +++ .../components/atc_mithermometer/sensor.py | 3 + esphome/components/atm90e32/sensor.py | 2 + esphome/components/b_parasite/sensor.py | 2 + esphome/components/binary_sensor/__init__.py | 1 + esphome/components/fingerprint_grow/sensor.py | 7 ++ .../components/inkbird_ibsth1_mini/sensor.py | 2 + esphome/components/number/__init__.py | 1 - .../components/pvvx_mithermometer/sensor.py | 3 + esphome/components/restart/switch.py | 12 +- esphome/components/ruuvitag/sensor.py | 5 + .../components/safe_mode/switch/__init__.py | 5 + esphome/components/select/__init__.py | 1 - esphome/components/sensor/__init__.py | 11 +- esphome/components/shutdown/switch.py | 12 +- esphome/components/status/binary_sensor.py | 11 +- esphome/components/switch/__init__.py | 1 + esphome/components/text_sensor/__init__.py | 1 + esphome/components/uptime/sensor.py | 2 + esphome/components/version/text_sensor.py | 12 +- esphome/components/wifi_info/text_sensor.py | 17 +++ esphome/components/wifi_signal/sensor.py | 2 + esphome/components/xiaomi_cgd1/sensor.py | 2 + esphome/components/xiaomi_cgdk2/sensor.py | 2 + esphome/components/xiaomi_cgg1/sensor.py | 2 + .../components/xiaomi_cgpr1/binary_sensor.py | 16 ++- esphome/components/xiaomi_hhccjcy01/sensor.py | 2 + esphome/components/xiaomi_jqjcy01ym/sensor.py | 2 + esphome/components/xiaomi_lywsd02/sensor.py | 2 + .../components/xiaomi_lywsd03mmc/sensor.py | 2 + esphome/components/xiaomi_lywsdcgq/sensor.py | 2 + esphome/components/xiaomi_mhoc401/sensor.py | 2 + .../xiaomi_mjyd02yla/binary_sensor.py | 2 + .../components/xiaomi_wx08zm/binary_sensor.py | 2 + esphome/config_validation.py | 17 +++ esphome/const.py | 10 ++ esphome/core/entity_base.cpp | 4 + esphome/core/entity_base.h | 11 ++ esphome/cpp_helpers.py | 3 + esphome/cpp_types.py | 1 + 45 files changed, 354 insertions(+), 14 deletions(-) diff --git a/esphome/codegen.py b/esphome/codegen.py index 4f9f67245d..5e1e934e58 100644 --- a/esphome/codegen.py +++ b/esphome/codegen.py @@ -81,4 +81,5 @@ from esphome.cpp_types import ( # noqa GPIOPin, InternalGPIOPin, gpio_Flags, + EntityCategory, ) diff --git a/esphome/components/am43/sensor.py b/esphome/components/am43/sensor.py index c88e529a0c..68c85d0e9c 100644 --- a/esphome/components/am43/sensor.py +++ b/esphome/components/am43/sensor.py @@ -4,7 +4,8 @@ from esphome.components import sensor, ble_client from esphome.const import ( CONF_ID, CONF_BATTERY_LEVEL, - ICON_BATTERY, + DEVICE_CLASS_BATTERY, + ENTITY_CATEGORY_DIAGNOSTIC, CONF_ILLUMINANCE, ICON_BRIGHTNESS_5, UNIT_PERCENT, @@ -20,10 +21,15 @@ CONFIG_SCHEMA = ( { cv.GenerateID(): cv.declare_id(Am43), cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema( - UNIT_PERCENT, ICON_BATTERY, 0 + unit_of_measurement=UNIT_PERCENT, + device_class=DEVICE_CLASS_BATTERY, + accuracy_decimals=0, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), cv.Optional(CONF_ILLUMINANCE): sensor.sensor_schema( - UNIT_PERCENT, ICON_BRIGHTNESS_5, 0 + unit_of_measurement=UNIT_PERCENT, + icon=ICON_BRIGHTNESS_5, + accuracy_decimals=0, ), } ) diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index b1ac998608..0eb7ead735 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -203,6 +203,14 @@ message SubscribeStatesRequest { // Empty } +// ==================== COMMON ===================== + +enum EntityCategory { + ENTITY_CATEGORY_NONE = 0; + ENTITY_CATEGORY_CONFIG = 1; + ENTITY_CATEGORY_DIAGNOSTIC = 2; +} + // ==================== BINARY SENSOR ==================== message ListEntitiesBinarySensorResponse { option (id) = 12; @@ -218,6 +226,7 @@ message ListEntitiesBinarySensorResponse { bool is_status_binary_sensor = 6; bool disabled_by_default = 7; string icon = 8; + EntityCategory entity_category = 9; } message BinarySensorStateResponse { option (id) = 21; @@ -249,6 +258,7 @@ message ListEntitiesCoverResponse { string device_class = 8; bool disabled_by_default = 9; string icon = 10; + EntityCategory entity_category = 11; } enum LegacyCoverState { @@ -318,6 +328,7 @@ message ListEntitiesFanResponse { int32 supported_speed_count = 8; bool disabled_by_default = 9; string icon = 10; + EntityCategory entity_category = 11; } enum FanSpeed { FAN_SPEED_LOW = 0; @@ -394,6 +405,7 @@ message ListEntitiesLightResponse { repeated string effects = 11; bool disabled_by_default = 13; string icon = 14; + EntityCategory entity_category = 15; } message LightStateResponse { option (id) = 24; @@ -482,6 +494,7 @@ message ListEntitiesSensorResponse { // Last reset type removed in 2021.9.0 SensorLastResetType legacy_last_reset_type = 11; bool disabled_by_default = 12; + EntityCategory entity_category = 13; } message SensorStateResponse { option (id) = 25; @@ -510,6 +523,7 @@ message ListEntitiesSwitchResponse { string icon = 5; bool assumed_state = 6; bool disabled_by_default = 7; + EntityCategory entity_category = 8; } message SwitchStateResponse { option (id) = 26; @@ -543,6 +557,7 @@ message ListEntitiesTextSensorResponse { string icon = 5; bool disabled_by_default = 6; + EntityCategory entity_category = 7; } message TextSensorStateResponse { option (id) = 27; @@ -704,6 +719,7 @@ message ListEntitiesCameraResponse { string unique_id = 4; bool disabled_by_default = 5; string icon = 6; + EntityCategory entity_category = 7; } message CameraImageResponse { @@ -798,6 +814,7 @@ message ListEntitiesClimateResponse { repeated string supported_custom_presets = 17; bool disabled_by_default = 18; string icon = 19; + EntityCategory entity_category = 20; } message ClimateStateResponse { option (id) = 47; @@ -866,6 +883,7 @@ message ListEntitiesNumberResponse { float max_value = 7; float step = 8; bool disabled_by_default = 9; + EntityCategory entity_category = 10; } message NumberStateResponse { option (id) = 50; @@ -903,6 +921,7 @@ message ListEntitiesSelectResponse { string icon = 5; repeated string options = 6; bool disabled_by_default = 7; + EntityCategory entity_category = 8; } message SelectStateResponse { option (id) = 53; diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 79c53ee840..715a4f48c1 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -184,6 +184,7 @@ bool APIConnection::send_binary_sensor_info(binary_sensor::BinarySensor *binary_ msg.is_status_binary_sensor = binary_sensor->is_status_binary_sensor(); msg.disabled_by_default = binary_sensor->is_disabled_by_default(); msg.icon = binary_sensor->get_icon(); + msg.entity_category = static_cast(binary_sensor->get_entity_category()); return this->send_list_entities_binary_sensor_response(msg); } #endif @@ -217,6 +218,7 @@ bool APIConnection::send_cover_info(cover::Cover *cover) { msg.device_class = cover->get_device_class(); msg.disabled_by_default = cover->is_disabled_by_default(); msg.icon = cover->get_icon(); + msg.entity_category = static_cast(cover->get_entity_category()); return this->send_list_entities_cover_response(msg); } void APIConnection::cover_command(const CoverCommandRequest &msg) { @@ -283,6 +285,7 @@ bool APIConnection::send_fan_info(fan::FanState *fan) { msg.supported_speed_count = traits.supported_speed_count(); msg.disabled_by_default = fan->is_disabled_by_default(); msg.icon = fan->get_icon(); + msg.entity_category = static_cast(fan->get_entity_category()); return this->send_list_entities_fan_response(msg); } void APIConnection::fan_command(const FanCommandRequest &msg) { @@ -346,6 +349,7 @@ bool APIConnection::send_light_info(light::LightState *light) { msg.disabled_by_default = light->is_disabled_by_default(); msg.icon = light->get_icon(); + msg.entity_category = static_cast(light->get_entity_category()); for (auto mode : traits.get_supported_color_modes()) msg.supported_color_modes.push_back(static_cast(mode)); @@ -432,7 +436,7 @@ bool APIConnection::send_sensor_info(sensor::Sensor *sensor) { msg.device_class = sensor->get_device_class(); msg.state_class = static_cast(sensor->get_state_class()); msg.disabled_by_default = sensor->is_disabled_by_default(); - + msg.entity_category = static_cast(sensor->get_entity_category()); return this->send_list_entities_sensor_response(msg); } #endif @@ -456,6 +460,7 @@ bool APIConnection::send_switch_info(switch_::Switch *a_switch) { msg.icon = a_switch->get_icon(); msg.assumed_state = a_switch->assumed_state(); msg.disabled_by_default = a_switch->is_disabled_by_default(); + msg.entity_category = static_cast(a_switch->get_entity_category()); return this->send_list_entities_switch_response(msg); } void APIConnection::switch_command(const SwitchCommandRequest &msg) { @@ -491,6 +496,7 @@ bool APIConnection::send_text_sensor_info(text_sensor::TextSensor *text_sensor) msg.unique_id = get_default_unique_id("text_sensor", text_sensor); msg.icon = text_sensor->get_icon(); msg.disabled_by_default = text_sensor->is_disabled_by_default(); + msg.entity_category = static_cast(text_sensor->get_entity_category()); return this->send_list_entities_text_sensor_response(msg); } #endif @@ -537,6 +543,7 @@ bool APIConnection::send_climate_info(climate::Climate *climate) { msg.disabled_by_default = climate->is_disabled_by_default(); msg.icon = climate->get_icon(); + msg.entity_category = static_cast(climate->get_entity_category()); msg.supports_current_temperature = traits.get_supports_current_temperature(); msg.supports_two_point_target_temperature = traits.get_supports_two_point_target_temperature(); @@ -611,6 +618,7 @@ bool APIConnection::send_number_info(number::Number *number) { msg.unique_id = get_default_unique_id("number", number); msg.icon = number->get_icon(); msg.disabled_by_default = number->is_disabled_by_default(); + msg.entity_category = static_cast(number->get_entity_category()); msg.min_value = number->traits.get_min_value(); msg.max_value = number->traits.get_max_value(); @@ -648,6 +656,7 @@ bool APIConnection::send_select_info(select::Select *select) { msg.unique_id = get_default_unique_id("select", select); msg.icon = select->get_icon(); msg.disabled_by_default = select->is_disabled_by_default(); + msg.entity_category = static_cast(select->get_entity_category()); for (const auto &option : select->traits.get_options()) msg.options.push_back(option); @@ -681,6 +690,7 @@ bool APIConnection::send_camera_info(esp32_camera::ESP32Camera *camera) { msg.unique_id = get_default_unique_id("camera", camera); msg.disabled_by_default = camera->is_disabled_by_default(); msg.icon = camera->get_icon(); + msg.entity_category = static_cast(camera->get_entity_category()); return this->send_list_entities_camera_response(msg); } void APIConnection::camera_image(const CameraImageRequest &msg) { diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index 1d59d98f52..7bfa1e9edb 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -6,6 +6,18 @@ namespace esphome { namespace api { +template<> const char *proto_enum_to_string(enums::EntityCategory value) { + switch (value) { + case enums::ENTITY_CATEGORY_NONE: + return "ENTITY_CATEGORY_NONE"; + case enums::ENTITY_CATEGORY_CONFIG: + return "ENTITY_CATEGORY_CONFIG"; + case enums::ENTITY_CATEGORY_DIAGNOSTIC: + return "ENTITY_CATEGORY_DIAGNOSTIC"; + default: + return "UNKNOWN"; + } +} template<> const char *proto_enum_to_string(enums::LegacyCoverState value) { switch (value) { case enums::LEGACY_COVER_STATE_OPEN: @@ -519,6 +531,10 @@ bool ListEntitiesBinarySensorResponse::decode_varint(uint32_t field_id, ProtoVar this->disabled_by_default = value.as_bool(); return true; } + case 9: { + this->entity_category = value.as_enum(); + return true; + } default: return false; } @@ -568,6 +584,7 @@ void ListEntitiesBinarySensorResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(6, this->is_status_binary_sensor); buffer.encode_bool(7, this->disabled_by_default); buffer.encode_string(8, this->icon); + buffer.encode_enum(9, this->entity_category); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesBinarySensorResponse::dump_to(std::string &out) const { @@ -605,6 +622,10 @@ void ListEntitiesBinarySensorResponse::dump_to(std::string &out) const { out.append(" icon: "); out.append("'").append(this->icon).append("'"); out.append("\n"); + + out.append(" entity_category: "); + out.append(proto_enum_to_string(this->entity_category)); + out.append("\n"); out.append("}"); } #endif @@ -674,6 +695,10 @@ bool ListEntitiesCoverResponse::decode_varint(uint32_t field_id, ProtoVarInt val this->disabled_by_default = value.as_bool(); return true; } + case 11: { + this->entity_category = value.as_enum(); + return true; + } default: return false; } @@ -725,6 +750,7 @@ void ListEntitiesCoverResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(8, this->device_class); buffer.encode_bool(9, this->disabled_by_default); buffer.encode_string(10, this->icon); + buffer.encode_enum(11, this->entity_category); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesCoverResponse::dump_to(std::string &out) const { @@ -770,6 +796,10 @@ void ListEntitiesCoverResponse::dump_to(std::string &out) const { out.append(" icon: "); out.append("'").append(this->icon).append("'"); out.append("\n"); + + out.append(" entity_category: "); + out.append(proto_enum_to_string(this->entity_category)); + out.append("\n"); out.append("}"); } #endif @@ -958,6 +988,10 @@ bool ListEntitiesFanResponse::decode_varint(uint32_t field_id, ProtoVarInt value this->disabled_by_default = value.as_bool(); return true; } + case 11: { + this->entity_category = value.as_enum(); + return true; + } default: return false; } @@ -1005,6 +1039,7 @@ void ListEntitiesFanResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_int32(8, this->supported_speed_count); buffer.encode_bool(9, this->disabled_by_default); buffer.encode_string(10, this->icon); + buffer.encode_enum(11, this->entity_category); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesFanResponse::dump_to(std::string &out) const { @@ -1051,6 +1086,10 @@ void ListEntitiesFanResponse::dump_to(std::string &out) const { out.append(" icon: "); out.append("'").append(this->icon).append("'"); out.append("\n"); + + out.append(" entity_category: "); + out.append(proto_enum_to_string(this->entity_category)); + out.append("\n"); out.append("}"); } #endif @@ -1277,6 +1316,10 @@ bool ListEntitiesLightResponse::decode_varint(uint32_t field_id, ProtoVarInt val this->disabled_by_default = value.as_bool(); return true; } + case 15: { + this->entity_category = value.as_enum(); + return true; + } default: return false; } @@ -1344,6 +1387,7 @@ void ListEntitiesLightResponse::encode(ProtoWriteBuffer buffer) const { } buffer.encode_bool(13, this->disabled_by_default); buffer.encode_string(14, this->icon); + buffer.encode_enum(15, this->entity_category); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesLightResponse::dump_to(std::string &out) const { @@ -1411,6 +1455,10 @@ void ListEntitiesLightResponse::dump_to(std::string &out) const { out.append(" icon: "); out.append("'").append(this->icon).append("'"); out.append("\n"); + + out.append(" entity_category: "); + out.append(proto_enum_to_string(this->entity_category)); + out.append("\n"); out.append("}"); } #endif @@ -1870,6 +1918,10 @@ bool ListEntitiesSensorResponse::decode_varint(uint32_t field_id, ProtoVarInt va this->disabled_by_default = value.as_bool(); return true; } + case 13: { + this->entity_category = value.as_enum(); + return true; + } default: return false; } @@ -1927,6 +1979,7 @@ void ListEntitiesSensorResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_enum(10, this->state_class); buffer.encode_enum(11, this->legacy_last_reset_type); buffer.encode_bool(12, this->disabled_by_default); + buffer.encode_enum(13, this->entity_category); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesSensorResponse::dump_to(std::string &out) const { @@ -1981,6 +2034,10 @@ void ListEntitiesSensorResponse::dump_to(std::string &out) const { out.append(" disabled_by_default: "); out.append(YESNO(this->disabled_by_default)); out.append("\n"); + + out.append(" entity_category: "); + out.append(proto_enum_to_string(this->entity_category)); + out.append("\n"); out.append("}"); } #endif @@ -2043,6 +2100,10 @@ bool ListEntitiesSwitchResponse::decode_varint(uint32_t field_id, ProtoVarInt va this->disabled_by_default = value.as_bool(); return true; } + case 8: { + this->entity_category = value.as_enum(); + return true; + } default: return false; } @@ -2087,6 +2148,7 @@ void ListEntitiesSwitchResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(5, this->icon); buffer.encode_bool(6, this->assumed_state); buffer.encode_bool(7, this->disabled_by_default); + buffer.encode_enum(8, this->entity_category); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesSwitchResponse::dump_to(std::string &out) const { @@ -2120,6 +2182,10 @@ void ListEntitiesSwitchResponse::dump_to(std::string &out) const { out.append(" disabled_by_default: "); out.append(YESNO(this->disabled_by_default)); out.append("\n"); + + out.append(" entity_category: "); + out.append(proto_enum_to_string(this->entity_category)); + out.append("\n"); out.append("}"); } #endif @@ -2207,6 +2273,10 @@ bool ListEntitiesTextSensorResponse::decode_varint(uint32_t field_id, ProtoVarIn this->disabled_by_default = value.as_bool(); return true; } + case 7: { + this->entity_category = value.as_enum(); + return true; + } default: return false; } @@ -2250,6 +2320,7 @@ void ListEntitiesTextSensorResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(4, this->unique_id); buffer.encode_string(5, this->icon); buffer.encode_bool(6, this->disabled_by_default); + buffer.encode_enum(7, this->entity_category); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesTextSensorResponse::dump_to(std::string &out) const { @@ -2279,6 +2350,10 @@ void ListEntitiesTextSensorResponse::dump_to(std::string &out) const { out.append(" disabled_by_default: "); out.append(YESNO(this->disabled_by_default)); out.append("\n"); + + out.append(" entity_category: "); + out.append(proto_enum_to_string(this->entity_category)); + out.append("\n"); out.append("}"); } #endif @@ -2902,6 +2977,10 @@ bool ListEntitiesCameraResponse::decode_varint(uint32_t field_id, ProtoVarInt va this->disabled_by_default = value.as_bool(); return true; } + case 7: { + this->entity_category = value.as_enum(); + return true; + } default: return false; } @@ -2945,6 +3024,7 @@ void ListEntitiesCameraResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(4, this->unique_id); buffer.encode_bool(5, this->disabled_by_default); buffer.encode_string(6, this->icon); + buffer.encode_enum(7, this->entity_category); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesCameraResponse::dump_to(std::string &out) const { @@ -2974,6 +3054,10 @@ void ListEntitiesCameraResponse::dump_to(std::string &out) const { out.append(" icon: "); out.append("'").append(this->icon).append("'"); out.append("\n"); + + out.append(" entity_category: "); + out.append(proto_enum_to_string(this->entity_category)); + out.append("\n"); out.append("}"); } #endif @@ -3101,6 +3185,10 @@ bool ListEntitiesClimateResponse::decode_varint(uint32_t field_id, ProtoVarInt v this->disabled_by_default = value.as_bool(); return true; } + case 20: { + this->entity_category = value.as_enum(); + return true; + } default: return false; } @@ -3189,6 +3277,7 @@ void ListEntitiesClimateResponse::encode(ProtoWriteBuffer buffer) const { } buffer.encode_bool(18, this->disabled_by_default); buffer.encode_string(19, this->icon); + buffer.encode_enum(20, this->entity_category); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesClimateResponse::dump_to(std::string &out) const { @@ -3285,6 +3374,10 @@ void ListEntitiesClimateResponse::dump_to(std::string &out) const { out.append(" icon: "); out.append("'").append(this->icon).append("'"); out.append("\n"); + + out.append(" entity_category: "); + out.append(proto_enum_to_string(this->entity_category)); + out.append("\n"); out.append("}"); } #endif @@ -3661,6 +3754,10 @@ bool ListEntitiesNumberResponse::decode_varint(uint32_t field_id, ProtoVarInt va this->disabled_by_default = value.as_bool(); return true; } + case 10: { + this->entity_category = value.as_enum(); + return true; + } default: return false; } @@ -3719,6 +3816,7 @@ void ListEntitiesNumberResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_float(7, this->max_value); buffer.encode_float(8, this->step); buffer.encode_bool(9, this->disabled_by_default); + buffer.encode_enum(10, this->entity_category); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesNumberResponse::dump_to(std::string &out) const { @@ -3763,6 +3861,10 @@ void ListEntitiesNumberResponse::dump_to(std::string &out) const { out.append(" disabled_by_default: "); out.append(YESNO(this->disabled_by_default)); out.append("\n"); + + out.append(" entity_category: "); + out.append(proto_enum_to_string(this->entity_category)); + out.append("\n"); out.append("}"); } #endif @@ -3855,6 +3957,10 @@ bool ListEntitiesSelectResponse::decode_varint(uint32_t field_id, ProtoVarInt va this->disabled_by_default = value.as_bool(); return true; } + case 8: { + this->entity_category = value.as_enum(); + return true; + } default: return false; } @@ -3905,6 +4011,7 @@ void ListEntitiesSelectResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(6, it, true); } buffer.encode_bool(7, this->disabled_by_default); + buffer.encode_enum(8, this->entity_category); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesSelectResponse::dump_to(std::string &out) const { @@ -3940,6 +4047,10 @@ void ListEntitiesSelectResponse::dump_to(std::string &out) const { out.append(" disabled_by_default: "); out.append(YESNO(this->disabled_by_default)); out.append("\n"); + + out.append(" entity_category: "); + out.append(proto_enum_to_string(this->entity_category)); + out.append("\n"); out.append("}"); } #endif diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index af85ed6856..ec11732c7d 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -9,6 +9,11 @@ namespace api { namespace enums { +enum EntityCategory : uint32_t { + ENTITY_CATEGORY_NONE = 0, + ENTITY_CATEGORY_CONFIG = 1, + ENTITY_CATEGORY_DIAGNOSTIC = 2, +}; enum LegacyCoverState : uint32_t { LEGACY_COVER_STATE_OPEN = 0, LEGACY_COVER_STATE_CLOSED = 1, @@ -271,6 +276,7 @@ class ListEntitiesBinarySensorResponse : public ProtoMessage { bool is_status_binary_sensor{false}; bool disabled_by_default{false}; std::string icon{}; + enums::EntityCategory entity_category{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -307,6 +313,7 @@ class ListEntitiesCoverResponse : public ProtoMessage { std::string device_class{}; bool disabled_by_default{false}; std::string icon{}; + enums::EntityCategory entity_category{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -364,6 +371,7 @@ class ListEntitiesFanResponse : public ProtoMessage { int32_t supported_speed_count{0}; bool disabled_by_default{false}; std::string icon{}; + enums::EntityCategory entity_category{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -429,6 +437,7 @@ class ListEntitiesLightResponse : public ProtoMessage { std::vector effects{}; bool disabled_by_default{false}; std::string icon{}; + enums::EntityCategory entity_category{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -517,6 +526,7 @@ class ListEntitiesSensorResponse : public ProtoMessage { enums::SensorStateClass state_class{}; enums::SensorLastResetType legacy_last_reset_type{}; bool disabled_by_default{false}; + enums::EntityCategory entity_category{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -550,6 +560,7 @@ class ListEntitiesSwitchResponse : public ProtoMessage { std::string icon{}; bool assumed_state{false}; bool disabled_by_default{false}; + enums::EntityCategory entity_category{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -594,6 +605,7 @@ class ListEntitiesTextSensorResponse : public ProtoMessage { std::string unique_id{}; std::string icon{}; bool disabled_by_default{false}; + enums::EntityCategory entity_category{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -805,6 +817,7 @@ class ListEntitiesCameraResponse : public ProtoMessage { std::string unique_id{}; bool disabled_by_default{false}; std::string icon{}; + enums::EntityCategory entity_category{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -863,6 +876,7 @@ class ListEntitiesClimateResponse : public ProtoMessage { std::vector supported_custom_presets{}; bool disabled_by_default{false}; std::string icon{}; + enums::EntityCategory entity_category{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -942,6 +956,7 @@ class ListEntitiesNumberResponse : public ProtoMessage { float max_value{0.0f}; float step{0.0f}; bool disabled_by_default{false}; + enums::EntityCategory entity_category{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -987,6 +1002,7 @@ class ListEntitiesSelectResponse : public ProtoMessage { std::string icon{}; std::vector options{}; bool disabled_by_default{false}; + enums::EntityCategory entity_category{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; diff --git a/esphome/components/atc_mithermometer/sensor.py b/esphome/components/atc_mithermometer/sensor.py index 0f6cc1abcb..bde83c28b6 100644 --- a/esphome/components/atc_mithermometer/sensor.py +++ b/esphome/components/atc_mithermometer/sensor.py @@ -12,6 +12,7 @@ from esphome.const import ( DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_VOLTAGE, + ENTITY_CATEGORY_DIAGNOSTIC, STATE_CLASS_MEASUREMENT, UNIT_CELSIUS, UNIT_PERCENT, @@ -49,12 +50,14 @@ CONFIG_SCHEMA = ( accuracy_decimals=0, device_class=DEVICE_CLASS_BATTERY, state_class=STATE_CLASS_MEASUREMENT, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), cv.Optional(CONF_BATTERY_VOLTAGE): sensor.sensor_schema( unit_of_measurement=UNIT_VOLT, accuracy_decimals=3, device_class=DEVICE_CLASS_VOLTAGE, state_class=STATE_CLASS_MEASUREMENT, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), } ) diff --git a/esphome/components/atm90e32/sensor.py b/esphome/components/atm90e32/sensor.py index 05e5250d89..9c876bb62c 100644 --- a/esphome/components/atm90e32/sensor.py +++ b/esphome/components/atm90e32/sensor.py @@ -17,6 +17,7 @@ from esphome.const import ( DEVICE_CLASS_POWER_FACTOR, DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_VOLTAGE, + ENTITY_CATEGORY_DIAGNOSTIC, ICON_LIGHTBULB, ICON_CURRENT_AC, STATE_CLASS_MEASUREMENT, @@ -125,6 +126,7 @@ CONFIG_SCHEMA = ( accuracy_decimals=1, device_class=DEVICE_CLASS_TEMPERATURE, state_class=STATE_CLASS_MEASUREMENT, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), cv.Required(CONF_LINE_FREQUENCY): cv.enum(LINE_FREQS, upper=True), cv.Optional(CONF_CURRENT_PHASES, default="3"): cv.enum( diff --git a/esphome/components/b_parasite/sensor.py b/esphome/components/b_parasite/sensor.py index d51c48c602..201685adc4 100644 --- a/esphome/components/b_parasite/sensor.py +++ b/esphome/components/b_parasite/sensor.py @@ -13,6 +13,7 @@ from esphome.const import ( DEVICE_CLASS_ILLUMINANCE, DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_VOLTAGE, + ENTITY_CATEGORY_DIAGNOSTIC, STATE_CLASS_MEASUREMENT, UNIT_CELSIUS, UNIT_LUX, @@ -51,6 +52,7 @@ CONFIG_SCHEMA = ( accuracy_decimals=3, device_class=DEVICE_CLASS_VOLTAGE, state_class=STATE_CLASS_MEASUREMENT, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), cv.Optional(CONF_MOISTURE): sensor.sensor_schema( unit_of_measurement=UNIT_PERCENT, diff --git a/esphome/components/binary_sensor/__init__.py b/esphome/components/binary_sensor/__init__.py index ec199cc5fa..faafcddd06 100644 --- a/esphome/components/binary_sensor/__init__.py +++ b/esphome/components/binary_sensor/__init__.py @@ -313,6 +313,7 @@ def validate_multi_click_timing(value): device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space="_") + BINARY_SENSOR_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend( { cv.GenerateID(): cv.declare_id(BinarySensor), diff --git a/esphome/components/fingerprint_grow/sensor.py b/esphome/components/fingerprint_grow/sensor.py index f359a10348..4ae670743d 100644 --- a/esphome/components/fingerprint_grow/sensor.py +++ b/esphome/components/fingerprint_grow/sensor.py @@ -8,6 +8,7 @@ from esphome.const import ( CONF_LAST_FINGER_ID, CONF_SECURITY_LEVEL, CONF_STATUS, + ENTITY_CATEGORY_DIAGNOSTIC, ICON_ACCOUNT, ICON_ACCOUNT_CHECK, ICON_DATABASE, @@ -26,30 +27,36 @@ CONFIG_SCHEMA = cv.Schema( icon=ICON_FINGERPRINT, accuracy_decimals=0, state_class=STATE_CLASS_NONE, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), cv.Optional(CONF_STATUS): sensor.sensor_schema( accuracy_decimals=0, state_class=STATE_CLASS_NONE, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), cv.Optional(CONF_CAPACITY): sensor.sensor_schema( icon=ICON_DATABASE, accuracy_decimals=0, state_class=STATE_CLASS_NONE, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), cv.Optional(CONF_SECURITY_LEVEL): sensor.sensor_schema( icon=ICON_SECURITY, accuracy_decimals=0, state_class=STATE_CLASS_NONE, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), cv.Optional(CONF_LAST_FINGER_ID): sensor.sensor_schema( icon=ICON_ACCOUNT, accuracy_decimals=0, state_class=STATE_CLASS_NONE, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), cv.Optional(CONF_LAST_CONFIDENCE): sensor.sensor_schema( icon=ICON_ACCOUNT_CHECK, accuracy_decimals=0, state_class=STATE_CLASS_NONE, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), } ) diff --git a/esphome/components/inkbird_ibsth1_mini/sensor.py b/esphome/components/inkbird_ibsth1_mini/sensor.py index 0ab9f8b3e0..aa11fb3172 100644 --- a/esphome/components/inkbird_ibsth1_mini/sensor.py +++ b/esphome/components/inkbird_ibsth1_mini/sensor.py @@ -9,6 +9,7 @@ from esphome.const import ( DEVICE_CLASS_BATTERY, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_TEMPERATURE, + ENTITY_CATEGORY_DIAGNOSTIC, STATE_CLASS_MEASUREMENT, UNIT_CELSIUS, UNIT_PERCENT, @@ -53,6 +54,7 @@ CONFIG_SCHEMA = ( accuracy_decimals=0, device_class=DEVICE_CLASS_BATTERY, state_class=STATE_CLASS_MEASUREMENT, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), } ) diff --git a/esphome/components/number/__init__.py b/esphome/components/number/__init__.py index 2856a25ee7..bb3427e4bd 100644 --- a/esphome/components/number/__init__.py +++ b/esphome/components/number/__init__.py @@ -41,7 +41,6 @@ NumberInRangeCondition = number_ns.class_( icon = cv.icon - NUMBER_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend( { cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTNumberComponent), diff --git a/esphome/components/pvvx_mithermometer/sensor.py b/esphome/components/pvvx_mithermometer/sensor.py index b17878f01b..12090bddba 100644 --- a/esphome/components/pvvx_mithermometer/sensor.py +++ b/esphome/components/pvvx_mithermometer/sensor.py @@ -12,6 +12,7 @@ from esphome.const import ( DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_VOLTAGE, + ENTITY_CATEGORY_DIAGNOSTIC, STATE_CLASS_MEASUREMENT, UNIT_CELSIUS, UNIT_PERCENT, @@ -49,12 +50,14 @@ CONFIG_SCHEMA = ( accuracy_decimals=0, device_class=DEVICE_CLASS_BATTERY, state_class=STATE_CLASS_MEASUREMENT, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), cv.Optional(CONF_BATTERY_VOLTAGE): sensor.sensor_schema( unit_of_measurement=UNIT_VOLT, accuracy_decimals=3, device_class=DEVICE_CLASS_VOLTAGE, state_class=STATE_CLASS_MEASUREMENT, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), } ) diff --git a/esphome/components/restart/switch.py b/esphome/components/restart/switch.py index 4f1904e273..de30392b45 100644 --- a/esphome/components/restart/switch.py +++ b/esphome/components/restart/switch.py @@ -1,7 +1,14 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import switch -from esphome.const import CONF_ID, CONF_INVERTED, CONF_ICON, ICON_RESTART +from esphome.const import ( + CONF_ENTITY_CATEGORY, + CONF_ID, + CONF_INVERTED, + CONF_ICON, + ENTITY_CATEGORY_CONFIG, + ICON_RESTART, +) restart_ns = cg.esphome_ns.namespace("restart") RestartSwitch = restart_ns.class_("RestartSwitch", switch.Switch, cg.Component) @@ -13,6 +20,9 @@ CONFIG_SCHEMA = switch.SWITCH_SCHEMA.extend( "Restart switches do not support inverted mode!" ), cv.Optional(CONF_ICON, default=ICON_RESTART): switch.icon, + cv.Optional( + CONF_ENTITY_CATEGORY, default=ENTITY_CATEGORY_CONFIG + ): cv.entity_category, } ).extend(cv.COMPONENT_SCHEMA) diff --git a/esphome/components/ruuvitag/sensor.py b/esphome/components/ruuvitag/sensor.py index 342a5eff24..2bb9549195 100644 --- a/esphome/components/ruuvitag/sensor.py +++ b/esphome/components/ruuvitag/sensor.py @@ -19,6 +19,7 @@ from esphome.const import ( DEVICE_CLASS_SIGNAL_STRENGTH, DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_VOLTAGE, + ENTITY_CATEGORY_DIAGNOSTIC, STATE_CLASS_MEASUREMENT, STATE_CLASS_NONE, UNIT_CELSIUS, @@ -95,22 +96,26 @@ CONFIG_SCHEMA = ( accuracy_decimals=3, device_class=DEVICE_CLASS_VOLTAGE, state_class=STATE_CLASS_MEASUREMENT, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), cv.Optional(CONF_TX_POWER): sensor.sensor_schema( unit_of_measurement=UNIT_DECIBEL_MILLIWATT, accuracy_decimals=0, device_class=DEVICE_CLASS_SIGNAL_STRENGTH, state_class=STATE_CLASS_MEASUREMENT, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), cv.Optional(CONF_MOVEMENT_COUNTER): sensor.sensor_schema( icon=ICON_GAUGE, accuracy_decimals=0, state_class=STATE_CLASS_NONE, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), cv.Optional(CONF_MEASUREMENT_SEQUENCE_NUMBER): sensor.sensor_schema( icon=ICON_GAUGE, accuracy_decimals=0, state_class=STATE_CLASS_NONE, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), } ) diff --git a/esphome/components/safe_mode/switch/__init__.py b/esphome/components/safe_mode/switch/__init__.py index 0ad814ff4f..b6c3e852f6 100644 --- a/esphome/components/safe_mode/switch/__init__.py +++ b/esphome/components/safe_mode/switch/__init__.py @@ -3,10 +3,12 @@ import esphome.config_validation as cv from esphome.components import switch from esphome.components.ota import OTAComponent from esphome.const import ( + CONF_ENTITY_CATEGORY, CONF_ID, CONF_INVERTED, CONF_ICON, CONF_OTA, + ENTITY_CATEGORY_CONFIG, ICON_RESTART_ALERT, ) from .. import safe_mode_ns @@ -23,6 +25,9 @@ CONFIG_SCHEMA = switch.SWITCH_SCHEMA.extend( "Safe Mode Restart switches do not support inverted mode!" ), cv.Optional(CONF_ICON, default=ICON_RESTART_ALERT): switch.icon, + cv.Optional( + CONF_ENTITY_CATEGORY, default=ENTITY_CATEGORY_CONFIG + ): cv.entity_category, } ).extend(cv.COMPONENT_SCHEMA) diff --git a/esphome/components/select/__init__.py b/esphome/components/select/__init__.py index 7e4047d3c8..8ea159d657 100644 --- a/esphome/components/select/__init__.py +++ b/esphome/components/select/__init__.py @@ -30,7 +30,6 @@ SelectSetAction = select_ns.class_("SelectSetAction", automation.Action) icon = cv.icon - SELECT_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend( { cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTSelectComponent), diff --git a/esphome/components/sensor/__init__.py b/esphome/components/sensor/__init__.py index 4b2e9dc019..d9d226aab6 100644 --- a/esphome/components/sensor/__init__.py +++ b/esphome/components/sensor/__init__.py @@ -10,6 +10,7 @@ from esphome.const import ( CONF_ACCURACY_DECIMALS, CONF_ALPHA, CONF_BELOW, + CONF_ENTITY_CATEGORY, CONF_EXPIRE_AFTER, CONF_FILTERS, CONF_FROM, @@ -133,7 +134,6 @@ def validate_datapoint(value): # Base -sensor_ns = cg.esphome_ns.namespace("sensor") Sensor = sensor_ns.class_("Sensor", cg.EntityBase) SensorPtr = Sensor.operator("ptr") @@ -226,6 +226,7 @@ def sensor_schema( accuracy_decimals: int = _UNDEF, device_class: str = _UNDEF, state_class: str = _UNDEF, + entity_category: str = _UNDEF, ) -> cv.Schema: schema = SENSOR_SCHEMA if unit_of_measurement is not _UNDEF: @@ -258,6 +259,14 @@ def sensor_schema( schema = schema.extend( {cv.Optional(CONF_STATE_CLASS, default=state_class): validate_state_class} ) + if entity_category is not _UNDEF: + schema = schema.extend( + { + cv.Optional( + CONF_ENTITY_CATEGORY, default=entity_category + ): cv.entity_category + } + ) return schema diff --git a/esphome/components/shutdown/switch.py b/esphome/components/shutdown/switch.py index 30c2bc2b74..49970b4c2f 100644 --- a/esphome/components/shutdown/switch.py +++ b/esphome/components/shutdown/switch.py @@ -1,7 +1,14 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import switch -from esphome.const import CONF_ID, CONF_INVERTED, CONF_ICON, ICON_POWER +from esphome.const import ( + CONF_ENTITY_CATEGORY, + CONF_ID, + CONF_INVERTED, + CONF_ICON, + ENTITY_CATEGORY_CONFIG, + ICON_POWER, +) shutdown_ns = cg.esphome_ns.namespace("shutdown") ShutdownSwitch = shutdown_ns.class_("ShutdownSwitch", switch.Switch, cg.Component) @@ -13,6 +20,9 @@ CONFIG_SCHEMA = switch.SWITCH_SCHEMA.extend( "Shutdown switches do not support inverted mode!" ), cv.Optional(CONF_ICON, default=ICON_POWER): switch.icon, + cv.Optional( + CONF_ENTITY_CATEGORY, default=ENTITY_CATEGORY_CONFIG + ): cv.entity_category, } ).extend(cv.COMPONENT_SCHEMA) diff --git a/esphome/components/status/binary_sensor.py b/esphome/components/status/binary_sensor.py index e462bc5385..9367706388 100644 --- a/esphome/components/status/binary_sensor.py +++ b/esphome/components/status/binary_sensor.py @@ -1,7 +1,13 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import binary_sensor -from esphome.const import CONF_ID, CONF_DEVICE_CLASS, DEVICE_CLASS_CONNECTIVITY +from esphome.const import ( + CONF_ENTITY_CATEGORY, + CONF_ID, + CONF_DEVICE_CLASS, + DEVICE_CLASS_CONNECTIVITY, + ENTITY_CATEGORY_DIAGNOSTIC, +) status_ns = cg.esphome_ns.namespace("status") StatusBinarySensor = status_ns.class_( @@ -14,6 +20,9 @@ CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend( cv.Optional( CONF_DEVICE_CLASS, default=DEVICE_CLASS_CONNECTIVITY ): binary_sensor.device_class, + cv.Optional( + CONF_ENTITY_CATEGORY, default=ENTITY_CATEGORY_DIAGNOSTIC + ): cv.entity_category, } ).extend(cv.COMPONENT_SCHEMA) diff --git a/esphome/components/switch/__init__.py b/esphome/components/switch/__init__.py index 88341e0add..08cbccbe35 100644 --- a/esphome/components/switch/__init__.py +++ b/esphome/components/switch/__init__.py @@ -36,6 +36,7 @@ SwitchTurnOffTrigger = switch_ns.class_( icon = cv.icon + SWITCH_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend( { cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTSwitchComponent), diff --git a/esphome/components/text_sensor/__init__.py b/esphome/components/text_sensor/__init__.py index 5c739e1d0a..a070e6f86d 100644 --- a/esphome/components/text_sensor/__init__.py +++ b/esphome/components/text_sensor/__init__.py @@ -108,6 +108,7 @@ async def substitute_filter_to_code(config, filter_id): icon = cv.icon + TEXT_SENSOR_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend( { cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTTextSensor), diff --git a/esphome/components/uptime/sensor.py b/esphome/components/uptime/sensor.py index 7989f3befc..16a1e4c125 100644 --- a/esphome/components/uptime/sensor.py +++ b/esphome/components/uptime/sensor.py @@ -3,6 +3,7 @@ import esphome.config_validation as cv from esphome.components import sensor from esphome.const import ( CONF_ID, + ENTITY_CATEGORY_DIAGNOSTIC, STATE_CLASS_TOTAL_INCREASING, UNIT_SECOND, ICON_TIMER, @@ -17,6 +18,7 @@ CONFIG_SCHEMA = ( icon=ICON_TIMER, accuracy_decimals=0, state_class=STATE_CLASS_TOTAL_INCREASING, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ) .extend( { diff --git a/esphome/components/version/text_sensor.py b/esphome/components/version/text_sensor.py index e67f881d32..4835caf35b 100644 --- a/esphome/components/version/text_sensor.py +++ b/esphome/components/version/text_sensor.py @@ -1,7 +1,14 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import text_sensor -from esphome.const import CONF_ID, CONF_ICON, ICON_NEW_BOX, CONF_HIDE_TIMESTAMP +from esphome.const import ( + CONF_ENTITY_CATEGORY, + CONF_ID, + CONF_ICON, + ENTITY_CATEGORY_DIAGNOSTIC, + ICON_NEW_BOX, + CONF_HIDE_TIMESTAMP, +) version_ns = cg.esphome_ns.namespace("version") VersionTextSensor = version_ns.class_( @@ -13,6 +20,9 @@ CONFIG_SCHEMA = text_sensor.TEXT_SENSOR_SCHEMA.extend( cv.GenerateID(): cv.declare_id(VersionTextSensor), cv.Optional(CONF_ICON, default=ICON_NEW_BOX): text_sensor.icon, cv.Optional(CONF_HIDE_TIMESTAMP, default=False): cv.boolean, + cv.Optional( + CONF_ENTITY_CATEGORY, default=ENTITY_CATEGORY_DIAGNOSTIC + ): cv.entity_category, } ).extend(cv.COMPONENT_SCHEMA) diff --git a/esphome/components/wifi_info/text_sensor.py b/esphome/components/wifi_info/text_sensor.py index 1922502204..706a8967be 100644 --- a/esphome/components/wifi_info/text_sensor.py +++ b/esphome/components/wifi_info/text_sensor.py @@ -3,11 +3,13 @@ import esphome.config_validation as cv from esphome.components import text_sensor from esphome.const import ( CONF_BSSID, + CONF_ENTITY_CATEGORY, CONF_ID, CONF_IP_ADDRESS, CONF_SCAN_RESULTS, CONF_SSID, CONF_MAC_ADDRESS, + ENTITY_CATEGORY_DIAGNOSTIC, ) DEPENDENCIES = ["wifi"] @@ -32,26 +34,41 @@ CONFIG_SCHEMA = cv.Schema( cv.Optional(CONF_IP_ADDRESS): text_sensor.TEXT_SENSOR_SCHEMA.extend( { cv.GenerateID(): cv.declare_id(IPAddressWiFiInfo), + cv.Optional( + CONF_ENTITY_CATEGORY, default=ENTITY_CATEGORY_DIAGNOSTIC + ): cv.entity_category, } ), cv.Optional(CONF_SCAN_RESULTS): text_sensor.TEXT_SENSOR_SCHEMA.extend( { cv.GenerateID(): cv.declare_id(ScanResultsWiFiInfo), + cv.Optional( + CONF_ENTITY_CATEGORY, default=ENTITY_CATEGORY_DIAGNOSTIC + ): cv.entity_category, } ).extend(cv.polling_component_schema("60s")), cv.Optional(CONF_SSID): text_sensor.TEXT_SENSOR_SCHEMA.extend( { cv.GenerateID(): cv.declare_id(SSIDWiFiInfo), + cv.Optional( + CONF_ENTITY_CATEGORY, default=ENTITY_CATEGORY_DIAGNOSTIC + ): cv.entity_category, } ), cv.Optional(CONF_BSSID): text_sensor.TEXT_SENSOR_SCHEMA.extend( { cv.GenerateID(): cv.declare_id(BSSIDWiFiInfo), + cv.Optional( + CONF_ENTITY_CATEGORY, default=ENTITY_CATEGORY_DIAGNOSTIC + ): cv.entity_category, } ), cv.Optional(CONF_MAC_ADDRESS): text_sensor.TEXT_SENSOR_SCHEMA.extend( { cv.GenerateID(): cv.declare_id(MacAddressWifiInfo), + cv.Optional( + CONF_ENTITY_CATEGORY, default=ENTITY_CATEGORY_DIAGNOSTIC + ): cv.entity_category, } ), } diff --git a/esphome/components/wifi_signal/sensor.py b/esphome/components/wifi_signal/sensor.py index 37bee75928..2097c21bd7 100644 --- a/esphome/components/wifi_signal/sensor.py +++ b/esphome/components/wifi_signal/sensor.py @@ -4,6 +4,7 @@ from esphome.components import sensor from esphome.const import ( CONF_ID, DEVICE_CLASS_SIGNAL_STRENGTH, + ENTITY_CATEGORY_DIAGNOSTIC, STATE_CLASS_MEASUREMENT, UNIT_DECIBEL_MILLIWATT, ) @@ -20,6 +21,7 @@ CONFIG_SCHEMA = ( accuracy_decimals=0, device_class=DEVICE_CLASS_SIGNAL_STRENGTH, state_class=STATE_CLASS_MEASUREMENT, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ) .extend( { diff --git a/esphome/components/xiaomi_cgd1/sensor.py b/esphome/components/xiaomi_cgd1/sensor.py index 774c87fee9..5b88121d7c 100644 --- a/esphome/components/xiaomi_cgd1/sensor.py +++ b/esphome/components/xiaomi_cgd1/sensor.py @@ -10,6 +10,7 @@ from esphome.const import ( DEVICE_CLASS_BATTERY, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_TEMPERATURE, + ENTITY_CATEGORY_DIAGNOSTIC, STATE_CLASS_MEASUREMENT, UNIT_CELSIUS, UNIT_PERCENT, @@ -47,6 +48,7 @@ CONFIG_SCHEMA = ( accuracy_decimals=0, device_class=DEVICE_CLASS_BATTERY, state_class=STATE_CLASS_MEASUREMENT, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), } ) diff --git a/esphome/components/xiaomi_cgdk2/sensor.py b/esphome/components/xiaomi_cgdk2/sensor.py index d4e7230fd0..ac487d87fc 100644 --- a/esphome/components/xiaomi_cgdk2/sensor.py +++ b/esphome/components/xiaomi_cgdk2/sensor.py @@ -9,6 +9,7 @@ from esphome.const import ( DEVICE_CLASS_BATTERY, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_TEMPERATURE, + ENTITY_CATEGORY_DIAGNOSTIC, STATE_CLASS_MEASUREMENT, UNIT_CELSIUS, UNIT_PERCENT, @@ -47,6 +48,7 @@ CONFIG_SCHEMA = ( accuracy_decimals=0, device_class=DEVICE_CLASS_BATTERY, state_class=STATE_CLASS_MEASUREMENT, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), } ) diff --git a/esphome/components/xiaomi_cgg1/sensor.py b/esphome/components/xiaomi_cgg1/sensor.py index 4e606d95f8..a4f9a39aff 100644 --- a/esphome/components/xiaomi_cgg1/sensor.py +++ b/esphome/components/xiaomi_cgg1/sensor.py @@ -11,6 +11,7 @@ from esphome.const import ( DEVICE_CLASS_BATTERY, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_TEMPERATURE, + ENTITY_CATEGORY_DIAGNOSTIC, STATE_CLASS_MEASUREMENT, UNIT_CELSIUS, UNIT_PERCENT, @@ -47,6 +48,7 @@ CONFIG_SCHEMA = ( accuracy_decimals=0, device_class=DEVICE_CLASS_BATTERY, state_class=STATE_CLASS_MEASUREMENT, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), } ) diff --git a/esphome/components/xiaomi_cgpr1/binary_sensor.py b/esphome/components/xiaomi_cgpr1/binary_sensor.py index a7f6c41225..7f0aac873d 100644 --- a/esphome/components/xiaomi_cgpr1/binary_sensor.py +++ b/esphome/components/xiaomi_cgpr1/binary_sensor.py @@ -10,6 +10,8 @@ from esphome.const import ( DEVICE_CLASS_EMPTY, DEVICE_CLASS_BATTERY, DEVICE_CLASS_ILLUMINANCE, + DEVICE_CLASS_MOTION, + ENTITY_CATEGORY_DIAGNOSTIC, ICON_EMPTY, UNIT_PERCENT, CONF_IDLE_TIME, @@ -37,13 +39,21 @@ CONFIG_SCHEMA = cv.All( cv.Required(CONF_BINDKEY): cv.bind_key, cv.Required(CONF_MAC_ADDRESS): cv.mac_address, cv.Optional( - CONF_DEVICE_CLASS, default="motion" + CONF_DEVICE_CLASS, + default=DEVICE_CLASS_MOTION, ): binary_sensor.device_class, cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema( - UNIT_PERCENT, ICON_EMPTY, 0, DEVICE_CLASS_BATTERY + unit_of_measurement=UNIT_PERCENT, + accuracy_decimals=0, + device_class=DEVICE_CLASS_BATTERY, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), cv.Optional(CONF_IDLE_TIME): sensor.sensor_schema( - UNIT_MINUTE, ICON_TIMELAPSE, 0, DEVICE_CLASS_EMPTY + unit_of_measurement=UNIT_MINUTE, + icon=ICON_TIMELAPSE, + accuracy_decimals=0, + device_class=DEVICE_CLASS_EMPTY, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), cv.Optional(CONF_ILLUMINANCE): sensor.sensor_schema( UNIT_LUX, ICON_EMPTY, 0, DEVICE_CLASS_ILLUMINANCE diff --git a/esphome/components/xiaomi_hhccjcy01/sensor.py b/esphome/components/xiaomi_hhccjcy01/sensor.py index 1818731a0f..535316e246 100644 --- a/esphome/components/xiaomi_hhccjcy01/sensor.py +++ b/esphome/components/xiaomi_hhccjcy01/sensor.py @@ -6,6 +6,7 @@ from esphome.const import ( CONF_TEMPERATURE, DEVICE_CLASS_ILLUMINANCE, DEVICE_CLASS_TEMPERATURE, + ENTITY_CATEGORY_DIAGNOSTIC, ICON_WATER_PERCENT, STATE_CLASS_MEASUREMENT, UNIT_CELSIUS, @@ -63,6 +64,7 @@ CONFIG_SCHEMA = ( accuracy_decimals=0, device_class=DEVICE_CLASS_BATTERY, state_class=STATE_CLASS_MEASUREMENT, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), } ) diff --git a/esphome/components/xiaomi_jqjcy01ym/sensor.py b/esphome/components/xiaomi_jqjcy01ym/sensor.py index 40991c3d0f..f4d2b342fd 100644 --- a/esphome/components/xiaomi_jqjcy01ym/sensor.py +++ b/esphome/components/xiaomi_jqjcy01ym/sensor.py @@ -9,6 +9,7 @@ from esphome.const import ( DEVICE_CLASS_BATTERY, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_TEMPERATURE, + ENTITY_CATEGORY_DIAGNOSTIC, STATE_CLASS_MEASUREMENT, UNIT_CELSIUS, UNIT_PERCENT, @@ -54,6 +55,7 @@ CONFIG_SCHEMA = ( accuracy_decimals=0, device_class=DEVICE_CLASS_BATTERY, state_class=STATE_CLASS_MEASUREMENT, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), } ) diff --git a/esphome/components/xiaomi_lywsd02/sensor.py b/esphome/components/xiaomi_lywsd02/sensor.py index 339c5e673a..20629a0a9c 100644 --- a/esphome/components/xiaomi_lywsd02/sensor.py +++ b/esphome/components/xiaomi_lywsd02/sensor.py @@ -7,6 +7,7 @@ from esphome.const import ( CONF_MAC_ADDRESS, CONF_TEMPERATURE, DEVICE_CLASS_TEMPERATURE, + ENTITY_CATEGORY_DIAGNOSTIC, STATE_CLASS_MEASUREMENT, UNIT_CELSIUS, UNIT_PERCENT, @@ -45,6 +46,7 @@ CONFIG_SCHEMA = ( accuracy_decimals=0, device_class=DEVICE_CLASS_BATTERY, state_class=STATE_CLASS_MEASUREMENT, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), } ) diff --git a/esphome/components/xiaomi_lywsd03mmc/sensor.py b/esphome/components/xiaomi_lywsd03mmc/sensor.py index f27cee3800..b2784e58fc 100644 --- a/esphome/components/xiaomi_lywsd03mmc/sensor.py +++ b/esphome/components/xiaomi_lywsd03mmc/sensor.py @@ -6,6 +6,7 @@ from esphome.const import ( CONF_HUMIDITY, CONF_MAC_ADDRESS, CONF_TEMPERATURE, + ENTITY_CATEGORY_DIAGNOSTIC, STATE_CLASS_MEASUREMENT, UNIT_CELSIUS, UNIT_PERCENT, @@ -49,6 +50,7 @@ CONFIG_SCHEMA = ( accuracy_decimals=0, device_class=DEVICE_CLASS_BATTERY, state_class=STATE_CLASS_MEASUREMENT, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), } ) diff --git a/esphome/components/xiaomi_lywsdcgq/sensor.py b/esphome/components/xiaomi_lywsdcgq/sensor.py index 39a207327e..80f24ac0ef 100644 --- a/esphome/components/xiaomi_lywsdcgq/sensor.py +++ b/esphome/components/xiaomi_lywsdcgq/sensor.py @@ -6,6 +6,7 @@ from esphome.const import ( CONF_HUMIDITY, CONF_MAC_ADDRESS, CONF_TEMPERATURE, + ENTITY_CATEGORY_DIAGNOSTIC, STATE_CLASS_MEASUREMENT, UNIT_CELSIUS, UNIT_PERCENT, @@ -45,6 +46,7 @@ CONFIG_SCHEMA = ( accuracy_decimals=0, device_class=DEVICE_CLASS_BATTERY, state_class=STATE_CLASS_MEASUREMENT, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), } ) diff --git a/esphome/components/xiaomi_mhoc401/sensor.py b/esphome/components/xiaomi_mhoc401/sensor.py index 57b2190150..9e92e34230 100644 --- a/esphome/components/xiaomi_mhoc401/sensor.py +++ b/esphome/components/xiaomi_mhoc401/sensor.py @@ -6,6 +6,7 @@ from esphome.const import ( CONF_HUMIDITY, CONF_MAC_ADDRESS, CONF_TEMPERATURE, + ENTITY_CATEGORY_DIAGNOSTIC, STATE_CLASS_MEASUREMENT, UNIT_CELSIUS, UNIT_PERCENT, @@ -48,6 +49,7 @@ CONFIG_SCHEMA = ( accuracy_decimals=0, device_class=DEVICE_CLASS_BATTERY, state_class=STATE_CLASS_MEASUREMENT, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), } ) diff --git a/esphome/components/xiaomi_mjyd02yla/binary_sensor.py b/esphome/components/xiaomi_mjyd02yla/binary_sensor.py index fd4bae60c1..1bedae26cf 100644 --- a/esphome/components/xiaomi_mjyd02yla/binary_sensor.py +++ b/esphome/components/xiaomi_mjyd02yla/binary_sensor.py @@ -10,6 +10,7 @@ from esphome.const import ( CONF_BATTERY_LEVEL, DEVICE_CLASS_BATTERY, DEVICE_CLASS_ILLUMINANCE, + ENTITY_CATEGORY_DIAGNOSTIC, STATE_CLASS_MEASUREMENT, STATE_CLASS_NONE, UNIT_PERCENT, @@ -51,6 +52,7 @@ CONFIG_SCHEMA = cv.All( accuracy_decimals=0, device_class=DEVICE_CLASS_BATTERY, state_class=STATE_CLASS_MEASUREMENT, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), cv.Optional(CONF_ILLUMINANCE): sensor.sensor_schema( unit_of_measurement=UNIT_LUX, diff --git a/esphome/components/xiaomi_wx08zm/binary_sensor.py b/esphome/components/xiaomi_wx08zm/binary_sensor.py index d2b353beff..8667794923 100644 --- a/esphome/components/xiaomi_wx08zm/binary_sensor.py +++ b/esphome/components/xiaomi_wx08zm/binary_sensor.py @@ -6,6 +6,7 @@ from esphome.const import ( CONF_MAC_ADDRESS, CONF_TABLET, DEVICE_CLASS_BATTERY, + ENTITY_CATEGORY_DIAGNOSTIC, STATE_CLASS_MEASUREMENT, UNIT_PERCENT, ICON_BUG, @@ -40,6 +41,7 @@ CONFIG_SCHEMA = cv.All( accuracy_decimals=0, device_class=DEVICE_CLASS_BATTERY, state_class=STATE_CLASS_MEASUREMENT, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), } ) diff --git a/esphome/config_validation.py b/esphome/config_validation.py index fcd014f62d..3e5c7940a4 100644 --- a/esphome/config_validation.py +++ b/esphome/config_validation.py @@ -12,12 +12,14 @@ from string import ascii_letters, digits import voluptuous as vol from esphome import core +import esphome.codegen as cg from esphome.const import ( ALLOWED_NAME_CHARS, CONF_AVAILABILITY, CONF_COMMAND_TOPIC, CONF_DISABLED_BY_DEFAULT, CONF_DISCOVERY, + CONF_ENTITY_CATEGORY, CONF_ICON, CONF_ID, CONF_INTERNAL, @@ -35,6 +37,9 @@ from esphome.const import ( CONF_UPDATE_INTERVAL, CONF_TYPE_ID, CONF_TYPE, + ENTITY_CATEGORY_CONFIG, + ENTITY_CATEGORY_DIAGNOSTIC, + ENTITY_CATEGORY_NONE, KEY_CORE, KEY_FRAMEWORK_VERSION, KEY_TARGET_FRAMEWORK, @@ -1551,6 +1556,17 @@ def maybe_simple_value(*validators, **kwargs): return validate +_ENTITY_CATEGORIES = { + ENTITY_CATEGORY_NONE: cg.EntityCategory.ENTITY_CATEGORY_NONE, + ENTITY_CATEGORY_CONFIG: cg.EntityCategory.ENTITY_CATEGORY_CONFIG, + ENTITY_CATEGORY_DIAGNOSTIC: cg.EntityCategory.ENTITY_CATEGORY_DIAGNOSTIC, +} + + +def entity_category(value): + return enum(_ENTITY_CATEGORIES, lower=True)(value) + + MQTT_COMPONENT_AVAILABILITY_SCHEMA = Schema( { Required(CONF_TOPIC): subscribe_topic, @@ -1582,6 +1598,7 @@ ENTITY_BASE_SCHEMA = Schema( Optional(CONF_INTERNAL): boolean, Optional(CONF_DISABLED_BY_DEFAULT, default=False): boolean, Optional(CONF_ICON): icon, + Optional(CONF_ENTITY_CATEGORY): entity_category, } ) diff --git a/esphome/const.py b/esphome/const.py index eb7d56d7e1..ff8510b40e 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -203,6 +203,7 @@ CONF_ELSE = "else" CONF_ENABLE_PIN = "enable_pin" CONF_ENABLE_TIME = "enable_time" CONF_ENERGY = "energy" +CONF_ENTITY_CATEGORY = "entity_category" CONF_ENTITY_ID = "entity_id" CONF_ESP8266_DISABLE_SSL_SUPPORT = "esp8266_disable_ssl_support" CONF_ESPHOME = "esphome" @@ -919,3 +920,12 @@ KEY_CORE = "core" KEY_TARGET_PLATFORM = "target_platform" KEY_TARGET_FRAMEWORK = "target_framework" KEY_FRAMEWORK_VERSION = "framework_version" + +# Entity categories +ENTITY_CATEGORY_NONE = "" + +# The entity category for configuration values/controls +ENTITY_CATEGORY_CONFIG = "config" + +# The entity category for read only diagnostic values, for example RSSI, uptime or MAC Address +ENTITY_CATEGORY_DIAGNOSTIC = "diagnostic" diff --git a/esphome/core/entity_base.cpp b/esphome/core/entity_base.cpp index bc94da85fe..41f08b28a6 100644 --- a/esphome/core/entity_base.cpp +++ b/esphome/core/entity_base.cpp @@ -26,6 +26,10 @@ void EntityBase::set_disabled_by_default(bool disabled_by_default) { this->disab const std::string &EntityBase::get_icon() const { return this->icon_; } void EntityBase::set_icon(const std::string &name) { this->icon_ = name; } +// Entity Category +EntityCategory EntityBase::get_entity_category() const { return this->entity_category_; } +void EntityBase::set_entity_category(EntityCategory entity_category) { this->entity_category_ = entity_category; } + // Entity Object ID const std::string &EntityBase::get_object_id() { return this->object_id_; } diff --git a/esphome/core/entity_base.h b/esphome/core/entity_base.h index 263747b721..c489d71910 100644 --- a/esphome/core/entity_base.h +++ b/esphome/core/entity_base.h @@ -5,6 +5,12 @@ namespace esphome { +enum EntityCategory : uint8_t { + ENTITY_CATEGORY_NONE = 0, + ENTITY_CATEGORY_CONFIG = 1, + ENTITY_CATEGORY_DIAGNOSTIC = 2, +}; + // The generic Entity base class that provides an interface common to all Entities. class EntityBase { public: @@ -31,6 +37,10 @@ class EntityBase { bool is_disabled_by_default() const; void set_disabled_by_default(bool disabled_by_default); + // Get/set the entity category. + EntityCategory get_entity_category() const; + void set_entity_category(EntityCategory entity_category); + // Get/set this entity's icon const std::string &get_icon() const; void set_icon(const std::string &name); @@ -45,6 +55,7 @@ class EntityBase { uint32_t object_id_hash_; bool internal_{false}; bool disabled_by_default_{false}; + EntityCategory entity_category_{ENTITY_CATEGORY_NONE}; }; } // namespace esphome diff --git a/esphome/cpp_helpers.py b/esphome/cpp_helpers.py index 5b081698ad..9127f88e39 100644 --- a/esphome/cpp_helpers.py +++ b/esphome/cpp_helpers.py @@ -2,6 +2,7 @@ import logging from esphome.const import ( CONF_DISABLED_BY_DEFAULT, + CONF_ENTITY_CATEGORY, CONF_ICON, CONF_INTERNAL, CONF_NAME, @@ -102,6 +103,8 @@ async def setup_entity(var, config): add(var.set_internal(config[CONF_INTERNAL])) if CONF_ICON in config: add(var.set_icon(config[CONF_ICON])) + if CONF_ENTITY_CATEGORY in config: + add(var.set_entity_category(config[CONF_ENTITY_CATEGORY])) def extract_registry_entry_config(registry, full_config): diff --git a/esphome/cpp_types.py b/esphome/cpp_types.py index 888c319024..13d088e1cb 100644 --- a/esphome/cpp_types.py +++ b/esphome/cpp_types.py @@ -34,3 +34,4 @@ GPIOPin = esphome_ns.class_("GPIOPin") InternalGPIOPin = esphome_ns.class_("InternalGPIOPin", GPIOPin) gpio_ns = esphome_ns.namespace("gpio") gpio_Flags = gpio_ns.enum("Flags", is_class=True) +EntityCategory = esphome_ns.enum("EntityCategory") From 96a50f5c6b3113ee774875929a2552c2bc35baeb Mon Sep 17 00:00:00 2001 From: Maurice Makaay Date: Sun, 7 Nov 2021 19:31:41 +0100 Subject: [PATCH 116/142] Add SPI lib for ESP8266 and only add lib for ESP32 when using Arduino (#2677) * Add SPI lib for ESP8266 and only add lib for ESP32 when using Arduino * Make inclusion of the SPI library unconditional As suggested by @Oxan. Because the component requires Arduino anyway, there is no need to make the inclusion conditional. Co-authored-by: Oxan van Leeuwen * Fix Python lint issue Co-authored-by: Maurice Makaay Co-authored-by: Oxan van Leeuwen --- esphome/components/bme680_bsec/__init__.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/esphome/components/bme680_bsec/__init__.py b/esphome/components/bme680_bsec/__init__.py index 2f844fa666..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"] @@ -62,9 +61,8 @@ 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) + # 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("boschsensortec/BSEC Software Library", "1.6.1480") From be9439f10d5468c0c864722a4e66047aea5dee20 Mon Sep 17 00:00:00 2001 From: Maurice Makaay Date: Mon, 8 Nov 2021 00:39:16 +0100 Subject: [PATCH 117/142] Fix for encrypted DSMR regression (#2679) Co-authored-by: Maurice Makaay --- esphome/components/dsmr/dsmr.cpp | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/esphome/components/dsmr/dsmr.cpp b/esphome/components/dsmr/dsmr.cpp index 031fb275f5..ea852e626e 100644 --- a/esphome/components/dsmr/dsmr.cpp +++ b/esphome/components/dsmr/dsmr.cpp @@ -76,9 +76,10 @@ void Dsmr::receive_telegram_() { } // Check for the end of the hex checksum, i.e. a newline. if (footer_found_ && c == '\n') { - header_found_ = false; // Parse the telegram and publish sensor values. parse_telegram(); + + header_found_ = false; return; } } @@ -105,11 +106,11 @@ void Dsmr::receive_encrypted_() { // Find a new telegram start byte. if (!header_found_) { - if ((uint8_t) c == 0xDB) { - ESP_LOGV(TAG, "Start byte 0xDB of encrypted telegram found"); - header_found_ = true; + if ((uint8_t) c != 0xDB) { + continue; } - continue; + ESP_LOGV(TAG, "Start byte 0xDB of encrypted telegram found"); + header_found_ = true; } // Check for buffer overflow. @@ -147,10 +148,10 @@ void Dsmr::receive_encrypted_() { ESP_LOGV(TAG, "Decrypted telegram size: %d bytes", telegram_len_); ESP_LOGVV(TAG, "Decrypted telegram: %s", this->telegram_); + parse_telegram(); + header_found_ = false; telegram_len_ = 0; - - parse_telegram(); return; } } From a17a6d53465b12b7e86e3262cc92c10d25f80b2d Mon Sep 17 00:00:00 2001 From: Paul Monigatti Date: Mon, 8 Nov 2021 22:03:30 +1300 Subject: [PATCH 118/142] Add HA Entity Category support to MQTT (#2678) --- esphome/components/mqtt/mqtt_component.cpp | 11 +++++++++++ esphome/components/mqtt/mqtt_const.h | 3 +++ 2 files changed, 14 insertions(+) diff --git a/esphome/components/mqtt/mqtt_component.cpp b/esphome/components/mqtt/mqtt_component.cpp index be1018d97d..cebb8dd086 100644 --- a/esphome/components/mqtt/mqtt_component.cpp +++ b/esphome/components/mqtt/mqtt_component.cpp @@ -77,6 +77,17 @@ bool MQTTComponent::send_discovery_() { if (!this->get_icon().empty()) root[MQTT_ICON] = this->get_icon(); + switch (this->get_entity()->get_entity_category()) { + case ENTITY_CATEGORY_NONE: + break; + case ENTITY_CATEGORY_CONFIG: + root[MQTT_ENTITY_CATEGORY] = "config"; + break; + case ENTITY_CATEGORY_DIAGNOSTIC: + root[MQTT_ENTITY_CATEGORY] = "diagnostic"; + break; + } + if (config.state_topic) root[MQTT_STATE_TOPIC] = this->get_state_topic_(); if (config.command_topic) diff --git a/esphome/components/mqtt/mqtt_const.h b/esphome/components/mqtt/mqtt_const.h index df5465ce9a..1d5e22efde 100644 --- a/esphome/components/mqtt/mqtt_const.h +++ b/esphome/components/mqtt/mqtt_const.h @@ -512,6 +512,9 @@ constexpr const char *const MQTT_DEVICE_SW_VERSION = "sw_version"; constexpr const char *const MQTT_DEVICE_SUGGESTED_AREA = "suggested_area"; #endif +// Additional MQTT fields where no abbreviation is defined in HA source +constexpr const char *const MQTT_ENTITY_CATEGORY = "entity_category"; + } // namespace mqtt } // namespace esphome From add484a2ea06ba2bf2091306d3a53bd8967214bc Mon Sep 17 00:00:00 2001 From: Martin <25747549+martgras@users.noreply.github.com> Date: Mon, 8 Nov 2021 19:29:28 +0100 Subject: [PATCH 119/142] Fix gpio validation for esp32 variants (#2609) Co-authored-by: Otto Winter Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/esp32/gpio.py | 96 +++++++++++++---------- esphome/components/esp32/gpio_esp32.py | 77 ++++++++++++++++++ esphome/components/esp32/gpio_esp32_c3.py | 53 +++++++++++++ esphome/components/esp32/gpio_esp32_h2.py | 11 +++ esphome/components/esp32/gpio_esp32_s2.py | 80 +++++++++++++++++++ esphome/components/esp32/gpio_esp32_s3.py | 74 +++++++++++++++++ 6 files changed, 348 insertions(+), 43 deletions(-) create mode 100644 esphome/components/esp32/gpio_esp32.py create mode 100644 esphome/components/esp32/gpio_esp32_c3.py create mode 100644 esphome/components/esp32/gpio_esp32_h2.py create mode 100644 esphome/components/esp32/gpio_esp32_s2.py create mode 100644 esphome/components/esp32/gpio_esp32_s3.py diff --git a/esphome/components/esp32/gpio.py b/esphome/components/esp32/gpio.py index 93ab17db22..5819943f37 100644 --- a/esphome/components/esp32/gpio.py +++ b/esphome/components/esp32/gpio.py @@ -1,4 +1,5 @@ -import logging +from dataclasses import dataclass +from typing import Any from esphome.const import ( CONF_ID, @@ -17,10 +18,24 @@ import esphome.config_validation as cv import esphome.codegen as cg from . import boards -from .const import KEY_BOARD, KEY_ESP32, esp32_ns +from .const import ( + KEY_BOARD, + KEY_ESP32, + KEY_VARIANT, + VARIANT_ESP32, + VARIANT_ESP32C3, + VARIANT_ESP32S2, + VARIANT_ESP32S3, + VARIANT_ESP32H2, + esp32_ns, +) -_LOGGER = logging.getLogger(__name__) +from .gpio_esp32 import esp32_validate_gpio_pin, esp32_validate_supports +from .gpio_esp32_s2 import esp32_s2_validate_gpio_pin, esp32_s2_validate_supports +from .gpio_esp32_c3 import esp32_c3_validate_gpio_pin, esp32_c3_validate_supports +from .gpio_esp32_s3 import esp32_s3_validate_gpio_pin, esp32_s3_validate_supports +from .gpio_esp32_h2 import esp32_h2_validate_gpio_pin, esp32_h2_validate_supports IDFInternalGPIOPin = esp32_ns.class_("IDFInternalGPIOPin", cg.InternalGPIOPin) @@ -59,65 +74,61 @@ def _translate_pin(value): return _lookup_pin(value) -_ESP_SDIO_PINS = { - 6: "Flash Clock", - 7: "Flash Data 0", - 8: "Flash Data 1", - 11: "Flash Command", +@dataclass +class ESP32ValidationFunctions: + pin_validation: Any + usage_validation: Any + + +_esp32_validations = { + VARIANT_ESP32: ESP32ValidationFunctions( + pin_validation=esp32_validate_gpio_pin, usage_validation=esp32_validate_supports + ), + VARIANT_ESP32S2: ESP32ValidationFunctions( + pin_validation=esp32_s2_validate_gpio_pin, + usage_validation=esp32_s2_validate_supports, + ), + VARIANT_ESP32C3: ESP32ValidationFunctions( + pin_validation=esp32_c3_validate_gpio_pin, + usage_validation=esp32_c3_validate_supports, + ), + VARIANT_ESP32S3: ESP32ValidationFunctions( + pin_validation=esp32_s3_validate_gpio_pin, + usage_validation=esp32_s3_validate_supports, + ), + VARIANT_ESP32H2: ESP32ValidationFunctions( + pin_validation=esp32_h2_validate_gpio_pin, + usage_validation=esp32_h2_validate_supports, + ), } def validate_gpio_pin(value): value = _translate_pin(value) - if value < 0 or value > 39: - raise cv.Invalid(f"Invalid pin number: {value} (must be 0-39)") - if value in _ESP_SDIO_PINS: - raise cv.Invalid( - f"This pin cannot be used on ESP32s and is already used by the flash interface (function: {_ESP_SDIO_PINS[value]})" - ) - if 9 <= value <= 10: - _LOGGER.warning( - "Pin %s (9-10) might already be used by the " - "flash interface in QUAD IO flash mode.", - value, - ) - if value in (20, 24, 28, 29, 30, 31): - # These pins are not exposed in GPIO mux (reason unknown) - # but they're missing from IO_MUX list in datasheet - raise cv.Invalid(f"The pin GPIO{value} is not usable on ESP32s.") - return value + variant = CORE.data[KEY_ESP32][KEY_VARIANT] + if variant not in _esp32_validations: + raise cv.Invalid("Unsupported ESP32 variant {variant}") + + return _esp32_validations[variant].pin_validation(value) def validate_supports(value): - num = value[CONF_NUMBER] mode = value[CONF_MODE] is_input = mode[CONF_INPUT] is_output = mode[CONF_OUTPUT] is_open_drain = mode[CONF_OPEN_DRAIN] is_pullup = mode[CONF_PULLUP] is_pulldown = mode[CONF_PULLDOWN] + variant = CORE.data[KEY_ESP32][KEY_VARIANT] + if variant not in _esp32_validations: + raise cv.Invalid("Unsupported ESP32 variant {variant}") - if is_input: - # All ESP32 pins support input mode - pass - if is_output and 34 <= num <= 39: - raise cv.Invalid( - f"GPIO{num} (34-39) does not support output pin mode.", - [CONF_MODE, CONF_OUTPUT], - ) if is_open_drain and not is_output: raise cv.Invalid( "Open-drain only works with output mode", [CONF_MODE, CONF_OPEN_DRAIN] ) - if is_pullup and 34 <= num <= 39: - raise cv.Invalid( - f"GPIO{num} (34-39) does not support pullups.", [CONF_MODE, CONF_PULLUP] - ) - if is_pulldown and 34 <= num <= 39: - raise cv.Invalid( - f"GPIO{num} (34-39) does not support pulldowns.", [CONF_MODE, CONF_PULLDOWN] - ) + value = _esp32_validations[variant].usage_validation(value) if CORE.using_arduino: # (input, output, open_drain, pullup, pulldown) supported_modes = { @@ -138,7 +149,6 @@ def validate_supports(value): "This pin mode is not supported on ESP32 for arduino frameworks", [CONF_MODE], ) - return value diff --git a/esphome/components/esp32/gpio_esp32.py b/esphome/components/esp32/gpio_esp32.py new file mode 100644 index 0000000000..425d77b343 --- /dev/null +++ b/esphome/components/esp32/gpio_esp32.py @@ -0,0 +1,77 @@ +import logging + +from esphome.const import ( + CONF_INPUT, + CONF_MODE, + CONF_NUMBER, + CONF_OUTPUT, + CONF_PULLDOWN, + CONF_PULLUP, +) +import esphome.config_validation as cv + + +_ESP_SDIO_PINS = { + 6: "Flash Clock", + 7: "Flash Data 0", + 8: "Flash Data 1", + 11: "Flash Command", +} + +_ESP32_STRAPPING_PINS = {0, 2, 4, 15} +_LOGGER = logging.getLogger(__name__) + + +def esp32_validate_gpio_pin(value): + if value < 0 or value > 39: + raise cv.Invalid(f"Invalid pin number: {value} (must be 0-39)") + if value in _ESP_SDIO_PINS: + raise cv.Invalid( + f"This pin cannot be used on ESP32s and is already used by the flash interface (function: {_ESP_SDIO_PINS[value]})" + ) + if 9 <= value <= 10: + _LOGGER.warning( + "Pin %s (9-10) might already be used by the " + "flash interface in QUAD IO flash mode.", + value, + ) + if value in _ESP32_STRAPPING_PINS: + _LOGGER.warning( + "GPIO%d is a Strapping PIN and should be avoided.\n" + "Attaching external pullup/down resistors to strapping pins can cause unexpected failures.\n" + "See https://esphome.io/guides/faq.html#why-am-i-getting-a-warning-about-strapping-pins", + value, + ) + if value in (20, 24, 28, 29, 30, 31): + # These pins are not exposed in GPIO mux (reason unknown) + # but they're missing from IO_MUX list in datasheet + raise cv.Invalid(f"The pin GPIO{value} is not usable on ESP32s.") + return value + + +def esp32_validate_supports(value): + num = value[CONF_NUMBER] + mode = value[CONF_MODE] + is_input = mode[CONF_INPUT] + is_output = mode[CONF_OUTPUT] + is_pullup = mode[CONF_PULLUP] + is_pulldown = mode[CONF_PULLDOWN] + + if is_input: + # All ESP32 pins support input mode + pass + if is_output and 34 <= num <= 39: + raise cv.Invalid( + f"GPIO{num} (34-39) does not support output pin mode.", + [CONF_MODE, CONF_OUTPUT], + ) + if is_pullup and 34 <= num <= 39: + raise cv.Invalid( + f"GPIO{num} (34-39) does not support pullups.", [CONF_MODE, CONF_PULLUP] + ) + if is_pulldown and 34 <= num <= 39: + raise cv.Invalid( + f"GPIO{num} (34-39) does not support pulldowns.", [CONF_MODE, CONF_PULLDOWN] + ) + + return value diff --git a/esphome/components/esp32/gpio_esp32_c3.py b/esphome/components/esp32/gpio_esp32_c3.py new file mode 100644 index 0000000000..fc1cef29e5 --- /dev/null +++ b/esphome/components/esp32/gpio_esp32_c3.py @@ -0,0 +1,53 @@ +import logging + +from esphome.const import ( + CONF_INPUT, + CONF_MODE, + CONF_NUMBER, +) +import esphome.config_validation as cv + +_ESP32C3_SPI_PSRAM_PINS = { + 12: "SPIHD", + 13: "SPIWP", + 14: "SPICS0", + 15: "SPICLK", + 16: "SPID", + 17: "SPIQ", +} + +_ESP32C3_STRAPPING_PINS = {2, 8, 9} + +_LOGGER = logging.getLogger(__name__) + + +def esp32_c3_validate_gpio_pin(value): + if value < 0 or value > 21: + raise cv.Invalid(f"Invalid pin number: {value} (must be 0-21)") + if value in _ESP32C3_SPI_PSRAM_PINS: + raise cv.Invalid( + f"This pin cannot be used on ESP32-C3s and is already used by the SPI/PSRAM interface (function: {_ESP32C3_SPI_PSRAM_PINS[value]})" + ) + if value in _ESP32C3_STRAPPING_PINS: + _LOGGER.warning( + "GPIO%d is a Strapping PIN and should be avoided.\n" + "Attaching external pullup/down resistors to strapping pins can cause unexpected failures.\n" + "See https://esphome.io/guides/faq.html#why-am-i-getting-a-warning-about-strapping-pins", + value, + ) + + return value + + +def esp32_c3_validate_supports(value): + num = value[CONF_NUMBER] + mode = value[CONF_MODE] + is_input = mode[CONF_INPUT] + + if num < 0 or num > 21: + raise cv.Invalid(f"Invalid pin number: {value} (must be 0-21)") + + if is_input: + # All ESP32 pins support input mode + pass + return value diff --git a/esphome/components/esp32/gpio_esp32_h2.py b/esphome/components/esp32/gpio_esp32_h2.py new file mode 100644 index 0000000000..5196ef0c09 --- /dev/null +++ b/esphome/components/esp32/gpio_esp32_h2.py @@ -0,0 +1,11 @@ +import esphome.config_validation as cv + + +def esp32_h2_validate_gpio_pin(value): + # ESP32-H2 not yet supported + raise cv.Invalid("ESP32-H2 isn't supported yet") + + +def esp32_h2_validate_supports(value): + # ESP32-H2 not yet supported + raise cv.Invalid("ESP32-H2 isn't supported yet") diff --git a/esphome/components/esp32/gpio_esp32_s2.py b/esphome/components/esp32/gpio_esp32_s2.py new file mode 100644 index 0000000000..db244b6259 --- /dev/null +++ b/esphome/components/esp32/gpio_esp32_s2.py @@ -0,0 +1,80 @@ +import logging + +from esphome.const import ( + CONF_INPUT, + CONF_MODE, + CONF_NUMBER, + CONF_OUTPUT, + CONF_PULLDOWN, + CONF_PULLUP, +) + +import esphome.config_validation as cv + +_ESP32S2_SPI_PSRAM_PINS = { + 26: "SPICS1", + 27: "SPIHD", + 28: "SPIWP", + 29: "SPICS0", + 30: "SPICLK", + 31: "SPIQ", + 32: "SPID", +} + +_ESP32S2_STRAPPING_PINS = {0, 45, 46} + +_LOGGER = logging.getLogger(__name__) + + +def esp32_s2_validate_gpio_pin(value): + if value < 0 or value > 46: + raise cv.Invalid(f"Invalid pin number: {value} (must be 0-46)") + + if value in _ESP32S2_SPI_PSRAM_PINS: + raise cv.Invalid( + f"This pin cannot be used on ESP32-S2s and is already used by the SPI/PSRAM interface (function: {_ESP32S2_SPI_PSRAM_PINS[value]})" + ) + if value in _ESP32S2_STRAPPING_PINS: + _LOGGER.warning( + "GPIO%d is a Strapping PIN and should be avoided.\n" + "Attaching external pullup/down resistors to strapping pins can cause unexpected failures.\n" + "See https://esphome.io/guides/faq.html#why-am-i-getting-a-warning-about-strapping-pins", + value, + ) + + if value in (22, 23, 24, 25): + # These pins are not exposed in GPIO mux (reason unknown) + # but they're missing from IO_MUX list in datasheet + raise cv.Invalid(f"The pin GPIO{value} is not usable on ESP32-S2s.") + + return value + + +def esp32_s2_validate_supports(value): + num = value[CONF_NUMBER] + mode = value[CONF_MODE] + is_input = mode[CONF_INPUT] + is_output = mode[CONF_OUTPUT] + is_pullup = mode[CONF_PULLUP] + is_pulldown = mode[CONF_PULLDOWN] + + if num < 0 or num > 46: + raise cv.Invalid(f"Invalid pin number: {num} (must be 0-46)") + if is_input: + # All ESP32 pins support input mode + pass + if is_output and num == 46: + raise cv.Invalid( + f"GPIO{num} does not support output pin mode.", + [CONF_MODE, CONF_OUTPUT], + ) + if is_pullup and num == 46: + raise cv.Invalid( + f"GPIO{num} does not support pullups.", [CONF_MODE, CONF_PULLUP] + ) + if is_pulldown and num == 46: + raise cv.Invalid( + f"GPIO{num} does not support pulldowns.", [CONF_MODE, CONF_PULLDOWN] + ) + + return value diff --git a/esphome/components/esp32/gpio_esp32_s3.py b/esphome/components/esp32/gpio_esp32_s3.py new file mode 100644 index 0000000000..f729a757c2 --- /dev/null +++ b/esphome/components/esp32/gpio_esp32_s3.py @@ -0,0 +1,74 @@ +import logging + +from esphome.const import ( + CONF_INPUT, + CONF_MODE, + CONF_NUMBER, +) + +import esphome.config_validation as cv + +_ESP_32S3_SPI_PSRAM_PINS = { + 26: "SPICS1", + 27: "SPIHD", + 28: "SPIWP", + 29: "SPICS0", + 30: "SPICLK", + 31: "SPIQ", + 32: "SPID", +} + +_ESP_32_ESP32_S3R8_PSRAM_PINS = { + 33: "SPIIO4", + 34: "SPIIO5", + 35: "SPIIO6", + 36: "SPIIO7", + 37: "SPIDQS", +} + +_ESP_32S3_STRAPPING_PINS = {0, 3, 45, 46} + +_LOGGER = logging.getLogger(__name__) + + +def esp32_s3_validate_gpio_pin(value): + if value < 0 or value > 48: + raise cv.Invalid(f"Invalid pin number: {value} (must be 0-46)") + + if value in _ESP_32S3_SPI_PSRAM_PINS: + raise cv.Invalid( + f"This pin cannot be used on ESP32-S3s and is already used by the SPI/PSRAM interface(function: {_ESP_32S3_SPI_PSRAM_PINS[value]})" + ) + if value in _ESP_32_ESP32_S3R8_PSRAM_PINS: + _LOGGER.warning( + "GPIO%d is used by the PSRAM interface on ESP32-S3R8 / ESP32-S3R8V and should be avoided on these models", + value, + ) + + if value in _ESP_32S3_STRAPPING_PINS: + _LOGGER.warning( + "GPIO%d is a Strapping PIN and should be avoided.\n" + "Attaching external pullup/down resistors to strapping pins can cause unexpected failures.\n" + "See https://esphome.io/guides/faq.html#why-am-i-getting-a-warning-about-strapping-pins", + value, + ) + + if value in (22, 23, 24, 25): + # These pins are not exposed in GPIO mux (reason unknown) + # but they're missing from IO_MUX list in datasheet + raise cv.Invalid(f"The pin GPIO{value} is not usable on ESP32-S3s.") + + return value + + +def esp32_s3_validate_supports(value): + num = value[CONF_NUMBER] + mode = value[CONF_MODE] + is_input = mode[CONF_INPUT] + + if num < 0 or num > 48: + raise cv.Invalid(f"Invalid pin number: {num} (must be 0-46)") + if is_input: + # All ESP32 pins support input mode + pass + return value From a509f6ccd29b84c2fb894c9d72f00553fbbc7ce5 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 9 Nov 2021 17:14:08 +1300 Subject: [PATCH 120/142] Fix when package url has no branch/ref (#2683) --- esphome/git.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/git.py b/esphome/git.py index b64aa6a864..25d893b2f5 100644 --- a/esphome/git.py +++ b/esphome/git.py @@ -40,7 +40,7 @@ def clone_or_update( ) -> Path: key = f"{url}@{ref}" repo_dir = _compute_destination_path(key, domain) - fetch_pr_branch = ref.startswith("pull/") + fetch_pr_branch = ref is not None and ref.startswith("pull/") if not repo_dir.is_dir(): _LOGGER.info("Cloning %s", key) _LOGGER.debug("Location: %s", repo_dir) From f72389147d79f282f577b7f0884905c6e1d06296 Mon Sep 17 00:00:00 2001 From: ychieux Date: Tue, 9 Nov 2021 20:47:19 +0300 Subject: [PATCH 121/142] SSD1306_base: Add support for 64x32 size and fix flip functions (#2682) * Add support for SSD1306 OLED display 0.42inch 64x32 and fix a typo in __init__.py preventing flip functions to operate as intended * convert tab to spaces * fix typo on filename for __init__.py --- esphome/components/ssd1306_base/__init__.py | 3 ++- esphome/components/ssd1306_base/ssd1306_base.cpp | 6 ++++++ esphome/components/ssd1306_base/ssd1306_base.h | 1 + 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/esphome/components/ssd1306_base/__init__.py b/esphome/components/ssd1306_base/__init__.py index bc2e558f1b..e4f62e5ff9 100644 --- a/esphome/components/ssd1306_base/__init__.py +++ b/esphome/components/ssd1306_base/__init__.py @@ -26,6 +26,7 @@ MODELS = { "SSD1306_128X64": SSD1306Model.SSD1306_MODEL_128_64, "SSD1306_96X16": SSD1306Model.SSD1306_MODEL_96_16, "SSD1306_64X48": SSD1306Model.SSD1306_MODEL_64_48, + "SSD1306_64X32": SSD1306Model.SSD1306_MODEL_64_32, "SH1106_128X32": SSD1306Model.SH1106_MODEL_128_32, "SH1106_128X64": SSD1306Model.SH1106_MODEL_128_64, "SH1106_96X16": SSD1306Model.SH1106_MODEL_96_16, @@ -84,7 +85,7 @@ async def setup_ssd1306(var, config): if CONF_FLIP_X in config: cg.add(var.init_flip_x(config[CONF_FLIP_X])) if CONF_FLIP_Y in config: - cg.add(var.init_flip_y(config[CONF_FLIP_X])) + cg.add(var.init_flip_y(config[CONF_FLIP_Y])) if CONF_OFFSET_X in config: cg.add(var.init_offset_x(config[CONF_OFFSET_X])) if CONF_OFFSET_Y in config: diff --git a/esphome/components/ssd1306_base/ssd1306_base.cpp b/esphome/components/ssd1306_base/ssd1306_base.cpp index b1a2538ebd..2537133605 100644 --- a/esphome/components/ssd1306_base/ssd1306_base.cpp +++ b/esphome/components/ssd1306_base/ssd1306_base.cpp @@ -94,6 +94,7 @@ void SSD1306::setup() { case SSD1306_MODEL_128_64: case SH1106_MODEL_128_64: case SSD1306_MODEL_64_48: + case SSD1306_MODEL_64_32: case SH1106_MODEL_64_48: case SSD1305_MODEL_128_32: case SSD1305_MODEL_128_64: @@ -141,6 +142,7 @@ void SSD1306::display() { this->command(SSD1306_COMMAND_COLUMN_ADDRESS); switch (this->model_) { case SSD1306_MODEL_64_48: + case SSD1306_MODEL_64_32: this->command(0x20 + this->offset_x_); this->command(0x20 + this->offset_x_ + this->get_width_internal() - 1); break; @@ -197,6 +199,7 @@ void SSD1306::turn_off() { int SSD1306::get_height_internal() { switch (this->model_) { case SSD1306_MODEL_128_32: + case SSD1306_MODEL_64_32: case SH1106_MODEL_128_32: case SSD1305_MODEL_128_32: return 32; @@ -227,6 +230,7 @@ int SSD1306::get_width_internal() { case SH1106_MODEL_96_16: return 96; case SSD1306_MODEL_64_48: + case SSD1306_MODEL_64_32: case SH1106_MODEL_64_48: return 64; default: @@ -271,6 +275,8 @@ const char *SSD1306::model_str_() { return "SSD1306 128x32"; case SSD1306_MODEL_128_64: return "SSD1306 128x64"; + case SSD1306_MODEL_64_32: + return "SSD1306 64x32"; case SSD1306_MODEL_96_16: return "SSD1306 96x16"; case SSD1306_MODEL_64_48: diff --git a/esphome/components/ssd1306_base/ssd1306_base.h b/esphome/components/ssd1306_base/ssd1306_base.h index 09417a2c10..c77b1985e4 100644 --- a/esphome/components/ssd1306_base/ssd1306_base.h +++ b/esphome/components/ssd1306_base/ssd1306_base.h @@ -12,6 +12,7 @@ enum SSD1306Model { SSD1306_MODEL_128_64, SSD1306_MODEL_96_16, SSD1306_MODEL_64_48, + SSD1306_MODEL_64_32, SH1106_MODEL_128_32, SH1106_MODEL_128_64, SH1106_MODEL_96_16, From d6717c0032a2370364141bca66aae1011714f39a Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 10 Nov 2021 08:38:20 +1300 Subject: [PATCH 122/142] Fix dashboard imports for adoption (#2684) --- .../components/dashboard_import/__init__.py | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/esphome/components/dashboard_import/__init__.py b/esphome/components/dashboard_import/__init__.py index 2b884d3b9a..d483c77c61 100644 --- a/esphome/components/dashboard_import/__init__.py +++ b/esphome/components/dashboard_import/__init__.py @@ -29,6 +29,14 @@ CONFIG_SCHEMA = cv.Schema( } ) +WIFI_MESSAGE = """ + +# Do not forget to add your own wifi configuration before installing this configuration +# wifi: +# ssid: !secret wifi_ssid +# password: !secret wifi_password +""" + async def to_code(config): cg.add_define("USE_DASHBOARD_IMPORT") @@ -41,5 +49,12 @@ def import_config(path: str, name: str, project_name: str, import_url: str) -> N if p.exists(): raise FileExistsError - config = {"substitutions": {"name": name}, "packages": {project_name: import_url}} - p.write_text(dump(config), encoding="utf8") + config = { + "substitutions": {"name": name}, + "packages": {project_name: import_url}, + "esphome": {"name_add_mac_suffix": False}, + } + p.write_text( + dump(config) + WIFI_MESSAGE, + encoding="utf8", + ) From fb57ab0adde56b24a056c925bec3205278472b1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Trzci=C5=84ski?= Date: Wed, 10 Nov 2021 01:10:07 +0100 Subject: [PATCH 123/142] Add `esp32_camera_web_server:` to expose mjpg/jpg images (#2237) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- CODEOWNERS | 1 + esphome/components/api/api_server.cpp | 2 +- esphome/components/esp32_camera/__init__.py | 2 +- .../components/esp32_camera/esp32_camera.cpp | 1 + .../esp32_camera_web_server/__init__.py | 28 ++ .../camera_web_server.cpp | 239 ++++++++++++++++++ .../camera_web_server.h | 51 ++++ tests/test4.yaml | 6 + 8 files changed, 328 insertions(+), 2 deletions(-) create mode 100644 esphome/components/esp32_camera_web_server/__init__.py create mode 100644 esphome/components/esp32_camera_web_server/camera_web_server.cpp create mode 100644 esphome/components/esp32_camera_web_server/camera_web_server.h diff --git a/CODEOWNERS b/CODEOWNERS index 664bf9ad6b..e535608db3 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -52,6 +52,7 @@ esphome/components/dsmr/* @glmnet @zuidwijk esphome/components/esp32/* @esphome/core esphome/components/esp32_ble/* @jesserockz esphome/components/esp32_ble_server/* @jesserockz +esphome/components/esp32_camera_web_server/* @ayufan esphome/components/esp32_improv/* @jesserockz esphome/components/esp8266/* @esphome/core esphome/components/exposure_notifications/* @OttoWinter diff --git a/esphome/components/api/api_server.cpp b/esphome/components/api/api_server.cpp index 4e2899d94f..25081a809a 100644 --- a/esphome/components/api/api_server.cpp +++ b/esphome/components/api/api_server.cpp @@ -77,7 +77,7 @@ void APIServer::setup() { this->last_connected_ = millis(); #ifdef USE_ESP32_CAMERA - if (esp32_camera::global_esp32_camera != nullptr) { + if (esp32_camera::global_esp32_camera != nullptr && !esp32_camera::global_esp32_camera->is_internal()) { esp32_camera::global_esp32_camera->add_image_callback( [this](const std::shared_ptr &image) { for (auto &c : this->clients_) diff --git a/esphome/components/esp32_camera/__init__.py b/esphome/components/esp32_camera/__init__.py index 1a8b3a37ec..2b1890267f 100644 --- a/esphome/components/esp32_camera/__init__.py +++ b/esphome/components/esp32_camera/__init__.py @@ -17,7 +17,7 @@ from esphome.core import CORE from esphome.components.esp32 import add_idf_sdkconfig_option from esphome.cpp_helpers import setup_entity -DEPENDENCIES = ["esp32", "api"] +DEPENDENCIES = ["esp32"] esp32_camera_ns = cg.esphome_ns.namespace("esp32_camera") ESP32Camera = esp32_camera_ns.class_("ESP32Camera", cg.PollingComponent, cg.EntityBase) diff --git a/esphome/components/esp32_camera/esp32_camera.cpp b/esphome/components/esp32_camera/esp32_camera.cpp index ad4304d89f..6f93532f47 100644 --- a/esphome/components/esp32_camera/esp32_camera.cpp +++ b/esphome/components/esp32_camera/esp32_camera.cpp @@ -45,6 +45,7 @@ void ESP32Camera::dump_config() { auto conf = this->config_; ESP_LOGCONFIG(TAG, "ESP32 Camera:"); ESP_LOGCONFIG(TAG, " Name: %s", this->name_.c_str()); + ESP_LOGCONFIG(TAG, " Internal: %s", YESNO(this->internal_)); #ifdef USE_ARDUINO ESP_LOGCONFIG(TAG, " Board Has PSRAM: %s", YESNO(psramFound())); #endif // USE_ARDUINO diff --git a/esphome/components/esp32_camera_web_server/__init__.py b/esphome/components/esp32_camera_web_server/__init__.py new file mode 100644 index 0000000000..d8afea27b4 --- /dev/null +++ b/esphome/components/esp32_camera_web_server/__init__.py @@ -0,0 +1,28 @@ +import esphome.config_validation as cv +import esphome.codegen as cg +from esphome.const import CONF_ID, CONF_PORT, CONF_MODE + +CODEOWNERS = ["@ayufan"] +DEPENDENCIES = ["esp32_camera"] +MULTI_CONF = True + +esp32_camera_web_server_ns = cg.esphome_ns.namespace("esp32_camera_web_server") +CameraWebServer = esp32_camera_web_server_ns.class_("CameraWebServer", cg.Component) +Mode = esp32_camera_web_server_ns.enum("Mode") + +MODES = {"STREAM": Mode.STREAM, "SNAPSHOT": Mode.SNAPSHOT} + +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.declare_id(CameraWebServer), + cv.Required(CONF_PORT): cv.port, + cv.Required(CONF_MODE): cv.enum(MODES, upper=True), + }, +).extend(cv.COMPONENT_SCHEMA) + + +async def to_code(config): + server = cg.new_Pvariable(config[CONF_ID]) + cg.add(server.set_port(config[CONF_PORT])) + cg.add(server.set_mode(config[CONF_MODE])) + await cg.register_component(server, config) diff --git a/esphome/components/esp32_camera_web_server/camera_web_server.cpp b/esphome/components/esp32_camera_web_server/camera_web_server.cpp new file mode 100644 index 0000000000..ecaef78b77 --- /dev/null +++ b/esphome/components/esp32_camera_web_server/camera_web_server.cpp @@ -0,0 +1,239 @@ +#ifdef USE_ESP32 + +#include "camera_web_server.h" +#include "esphome/core/application.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" +#include "esphome/core/util.h" + +#include +#include +#include + +namespace esphome { +namespace esp32_camera_web_server { + +static const int IMAGE_REQUEST_TIMEOUT = 2000; +static const char *const TAG = "esp32_camera_web_server"; + +#define PART_BOUNDARY "123456789000000000000987654321" +#define CONTENT_TYPE "image/jpeg" +#define CONTENT_LENGTH "Content-Length" + +static const char *const STREAM_HEADER = + "HTTP/1.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_PART = "Content-Type: " CONTENT_TYPE "\r\n" CONTENT_LENGTH ": %u\r\n\r\n"; + +CameraWebServer::CameraWebServer() {} + +CameraWebServer::~CameraWebServer() {} + +void CameraWebServer::setup() { + if (!esp32_camera::global_esp32_camera || esp32_camera::global_esp32_camera->is_failed()) { + this->mark_failed(); + return; + } + + this->semaphore_ = xSemaphoreCreateBinary(); + + httpd_config_t config = HTTPD_DEFAULT_CONFIG(); + config.server_port = this->port_; + config.ctrl_port = this->port_; + config.max_open_sockets = 1; + config.backlog_conn = 2; + + if (httpd_start(&this->httpd_, &config) != ESP_OK) { + mark_failed(); + return; + } + + httpd_uri_t uri = { + .uri = "/", + .method = HTTP_GET, + .handler = [](struct httpd_req *req) { return ((CameraWebServer *) req->user_ctx)->handler_(req); }, + .user_ctx = this}; + + httpd_register_uri_handler(this->httpd_, &uri); + + esp32_camera::global_esp32_camera->add_image_callback([this](std::shared_ptr image) { + if (this->running_) { + this->image_ = std::move(image); + xSemaphoreGive(this->semaphore_); + } + }); +} + +void CameraWebServer::on_shutdown() { + this->running_ = false; + this->image_ = nullptr; + httpd_stop(this->httpd_); + this->httpd_ = nullptr; + vSemaphoreDelete(this->semaphore_); + this->semaphore_ = nullptr; +} + +void CameraWebServer::dump_config() { + ESP_LOGCONFIG(TAG, "ESP32 Camera Web Server:"); + ESP_LOGCONFIG(TAG, " Port: %d", this->port_); + if (this->mode_ == STREAM) + ESP_LOGCONFIG(TAG, " Mode: stream"); + else + ESP_LOGCONFIG(TAG, " Mode: snapshot"); + + if (this->is_failed()) { + ESP_LOGE(TAG, " Setup Failed"); + } +} + +float CameraWebServer::get_setup_priority() const { return setup_priority::LATE; } + +void CameraWebServer::loop() { + if (!this->running_) { + this->image_ = nullptr; + } +} + +std::shared_ptr CameraWebServer::wait_for_image_() { + std::shared_ptr image; + image.swap(this->image_); + + if (!image) { + // retry as we might still be fetching image + xSemaphoreTake(this->semaphore_, IMAGE_REQUEST_TIMEOUT / portTICK_PERIOD_MS); + image.swap(this->image_); + } + + return image; +} + +esp_err_t CameraWebServer::handler_(struct httpd_req *req) { + esp_err_t res = ESP_FAIL; + + this->image_ = nullptr; + this->running_ = true; + + switch (this->mode_) { + case STREAM: + res = this->streaming_handler_(req); + break; + + case SNAPSHOT: + res = this->snapshot_handler_(req); + break; + } + + this->running_ = false; + this->image_ = nullptr; + return res; +} + +static esp_err_t httpd_send_all(httpd_req_t *r, const char *buf, size_t buf_len) { + int ret; + + while (buf_len > 0) { + ret = httpd_send(r, buf, buf_len); + if (ret < 0) { + return ESP_FAIL; + } + buf += ret; + buf_len -= ret; + } + return ESP_OK; +} + +esp_err_t CameraWebServer::streaming_handler_(struct httpd_req *req) { + esp_err_t res = ESP_OK; + char part_buf[64]; + + // This manually constructs HTTP response to avoid chunked encoding + // which is not supported by some clients + + res = httpd_send_all(req, STREAM_HEADER, strlen(STREAM_HEADER)); + if (res != ESP_OK) { + ESP_LOGW(TAG, "STREAM: failed to set HTTP header"); + return res; + } + + uint32_t last_frame = millis(); + uint32_t frames = 0; + + while (res == ESP_OK && this->running_) { + if (esp32_camera::global_esp32_camera != nullptr) { + esp32_camera::global_esp32_camera->request_stream(); + } + + auto image = this->wait_for_image_(); + + if (!image) { + ESP_LOGW(TAG, "STREAM: failed to acquire frame"); + res = ESP_FAIL; + } + if (res == ESP_OK) { + 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); + } + if (res == ESP_OK) { + res = httpd_send_all(req, (const char *) image->get_data_buffer(), image->get_data_length()); + } + if (res == ESP_OK) { + frames++; + int64_t frame_time = millis() - last_frame; + last_frame = millis(); + + ESP_LOGD(TAG, "MJPG: %uB %ums (%.1ffps)", (uint32_t) image->get_data_length(), (uint32_t) frame_time, + 1000.0 / (uint32_t) frame_time); + } + } + + if (!frames) { + res = httpd_send_all(req, STREAM_500, strlen(STREAM_500)); + } + + ESP_LOGI(TAG, "STREAM: closed. Frames: %u", frames); + + return res; +} + +esp_err_t CameraWebServer::snapshot_handler_(struct httpd_req *req) { + esp_err_t res = ESP_OK; + + if (esp32_camera::global_esp32_camera != nullptr) { + esp32_camera::global_esp32_camera->request_image(); + } + + auto image = this->wait_for_image_(); + + if (!image) { + ESP_LOGW(TAG, "SNAPSHOT: failed to acquire frame"); + httpd_resp_send_500(req); + res = ESP_FAIL; + return res; + } + + res = httpd_resp_set_type(req, CONTENT_TYPE); + if (res != ESP_OK) { + ESP_LOGW(TAG, "SNAPSHOT: failed to set HTTP response type"); + return res; + } + + httpd_resp_set_hdr(req, "Content-Disposition", "inline; filename=capture.jpg"); + + if (res == ESP_OK) { + res = httpd_resp_set_hdr(req, CONTENT_LENGTH, esphome::to_string(image->get_data_length()).c_str()); + } + if (res == ESP_OK) { + res = httpd_resp_send(req, (const char *) image->get_data_buffer(), image->get_data_length()); + } + return res; +} + +} // namespace esp32_camera_web_server +} // namespace esphome + +#endif // USE_ESP32 diff --git a/esphome/components/esp32_camera_web_server/camera_web_server.h b/esphome/components/esp32_camera_web_server/camera_web_server.h new file mode 100644 index 0000000000..df30a43ed2 --- /dev/null +++ b/esphome/components/esp32_camera_web_server/camera_web_server.h @@ -0,0 +1,51 @@ +#pragma once + +#ifdef USE_ESP32 + +#include +#include + +#include "esphome/components/esp32_camera/esp32_camera.h" +#include "esphome/core/component.h" +#include "esphome/core/helpers.h" +#include "esphome/core/preferences.h" + +struct httpd_req; + +namespace esphome { +namespace esp32_camera_web_server { + +enum Mode { STREAM, SNAPSHOT }; + +class CameraWebServer : public Component { + public: + CameraWebServer(); + ~CameraWebServer(); + + void setup() override; + void on_shutdown() override; + void dump_config() override; + float get_setup_priority() const override; + void set_port(uint16_t port) { this->port_ = port; } + void set_mode(Mode mode) { this->mode_ = mode; } + void loop() override; + + protected: + std::shared_ptr wait_for_image_(); + esp_err_t handler_(struct httpd_req *req); + esp_err_t streaming_handler_(struct httpd_req *req); + esp_err_t snapshot_handler_(struct httpd_req *req); + + protected: + uint16_t port_{0}; + void *httpd_{nullptr}; + SemaphoreHandle_t semaphore_; + std::shared_ptr image_; + bool running_{false}; + Mode mode_{STREAM}; +}; + +} // namespace esp32_camera_web_server +} // namespace esphome + +#endif // USE_ESP32 diff --git a/tests/test4.yaml b/tests/test4.yaml index bc249c5ecb..4228c7494c 100644 --- a/tests/test4.yaml +++ b/tests/test4.yaml @@ -481,6 +481,12 @@ esp32_camera: resolution: 640x480 jpeg_quality: 10 +esp32_camera_web_server: + - port: 8080 + mode: stream + - port: 8081 + mode: snapshot + external_components: - source: github://esphome/esphome@dev refresh: 1d From 57b07441a1fed3fbab934cdae35ff3098471c537 Mon Sep 17 00:00:00 2001 From: Guillermo Ruffino Date: Tue, 9 Nov 2021 21:15:02 -0300 Subject: [PATCH 124/142] fix esp32 rmt receiver item array length (#2671) --- .../remote_receiver/remote_receiver_esp32.cpp | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/esphome/components/remote_receiver/remote_receiver_esp32.cpp b/esphome/components/remote_receiver/remote_receiver_esp32.cpp index dde9b843c9..5a7fb3c985 100644 --- a/esphome/components/remote_receiver/remote_receiver_esp32.cpp +++ b/esphome/components/remote_receiver/remote_receiver_esp32.cpp @@ -78,6 +78,7 @@ void RemoteReceiverComponent::loop() { if (this->temp_.empty()) return; + this->temp_.push_back(-this->idle_us_); this->call_listeners_dumpers_(); } } @@ -86,9 +87,10 @@ void RemoteReceiverComponent::decode_rmt_(rmt_item32_t *item, size_t len) { uint32_t prev_length = 0; this->temp_.clear(); int32_t multiplier = this->pin_->is_inverted() ? -1 : 1; + size_t item_count = len / sizeof(rmt_item32_t); ESP_LOGVV(TAG, "START:"); - for (size_t i = 0; i < len; i++) { + for (size_t i = 0; i < item_count; i++) { if (item[i].level0) { ESP_LOGVV(TAG, "%u A: ON %uus (%u ticks)", i, this->to_microseconds_(item[i].duration0), item[i].duration0); } else { @@ -102,8 +104,8 @@ void RemoteReceiverComponent::decode_rmt_(rmt_item32_t *item, size_t len) { } ESP_LOGVV(TAG, "\n"); - this->temp_.reserve(len / 4); - for (size_t i = 0; i < len; i++) { + this->temp_.reserve(item_count * 2); // each RMT item has 2 pulses + for (size_t i = 0; i < item_count; i++) { if (item[i].duration0 == 0u) { // Do nothing } else if (bool(item[i].level0) == prev_level) { @@ -120,10 +122,6 @@ void RemoteReceiverComponent::decode_rmt_(rmt_item32_t *item, size_t len) { prev_length = item[i].duration0; } - if (this->to_microseconds_(prev_length) > this->idle_us_) { - break; - } - if (item[i].duration1 == 0u) { // Do nothing } else if (bool(item[i].level1) == prev_level) { @@ -139,10 +137,6 @@ void RemoteReceiverComponent::decode_rmt_(rmt_item32_t *item, size_t len) { prev_level = bool(item[i].level1); prev_length = item[i].duration1; } - - if (this->to_microseconds_(prev_length) > this->idle_us_) { - break; - } } if (prev_length > 0) { if (prev_level) { From 366552a9692ef5e80ffc986d6b38bec900633646 Mon Sep 17 00:00:00 2001 From: cvwillegen Date: Wed, 10 Nov 2021 04:11:35 +0100 Subject: [PATCH 125/142] Remote base add pronto protocol (#2619) --- esphome/components/remote_base/__init__.py | 43 ++++++ .../remote_base/pronto_protocol.cpp | 135 ++++++++++++++++++ .../components/remote_base/pronto_protocol.h | 40 ++++++ 3 files changed, 218 insertions(+) create mode 100644 esphome/components/remote_base/pronto_protocol.cpp create mode 100644 esphome/components/remote_base/pronto_protocol.h diff --git a/esphome/components/remote_base/__init__.py b/esphome/components/remote_base/__init__.py index d2b848600d..914ce42efe 100644 --- a/esphome/components/remote_base/__init__.py +++ b/esphome/components/remote_base/__init__.py @@ -439,6 +439,49 @@ async def pioneer_action(var, config, args): cg.add(var.set_rc_code_2(template_)) +# Pronto +( + ProntoData, + ProntoBinarySensor, + ProntoTrigger, + ProntoAction, + ProntoDumper, +) = declare_protocol("Pronto") +PRONTO_SCHEMA = cv.Schema( + { + cv.Required(CONF_DATA): cv.string, + } +) + + +@register_binary_sensor("pronto", ProntoBinarySensor, PRONTO_SCHEMA) +def pronto_binary_sensor(var, config): + cg.add( + var.set_data( + cg.StructInitializer( + ProntoData, + ("data", config[CONF_DATA]), + ) + ) + ) + + +@register_trigger("pronto", ProntoTrigger, ProntoData) +def pronto_trigger(var, config): + pass + + +@register_dumper("pronto", ProntoDumper) +def pronto_dumper(var, config): + pass + + +@register_action("pronto", ProntoAction, PRONTO_SCHEMA) +async def pronto_action(var, config, args): + template_ = await cg.templatable(config[CONF_DATA], args, cg.std_string) + cg.add(var.set_data(template_)) + + # Sony SonyData, SonyBinarySensor, SonyTrigger, SonyAction, SonyDumper = declare_protocol( "Sony" diff --git a/esphome/components/remote_base/pronto_protocol.cpp b/esphome/components/remote_base/pronto_protocol.cpp new file mode 100644 index 0000000000..11aebb6c5d --- /dev/null +++ b/esphome/components/remote_base/pronto_protocol.cpp @@ -0,0 +1,135 @@ +/* + * @file irPronto.cpp + * @brief In this file, the functions IRrecv::compensateAndPrintPronto and IRsend::sendPronto are defined. + * + * See http://www.harctoolbox.org/Glossary.html#ProntoSemantics + * Pronto database http://www.remotecentral.com/search.htm + * + * This file is part of Arduino-IRremote https://github.com/Arduino-IRremote/Arduino-IRremote. + * + ************************************************************************************ + * MIT License + * + * Copyright (c) 2020 Bengt Martensson + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is furnished + * to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE + * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + ************************************************************************************ + */ + +#include "pronto_protocol.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace remote_base { + +static const char *const TAG = "remote.pronto"; + +// DO NOT EXPORT from this file +static const uint16_t MICROSECONDS_T_MAX = 0xFFFFU; +static const uint16_t LEARNED_TOKEN = 0x0000U; +static const uint16_t LEARNED_NON_MODULATED_TOKEN = 0x0100U; +static const uint16_t BITS_IN_HEXADECIMAL = 4U; +static const uint16_t DIGITS_IN_PRONTO_NUMBER = 4U; +static const uint16_t NUMBERS_IN_PREAMBLE = 4U; +static const uint16_t HEX_MASK = 0xFU; +static const uint32_t REFERENCE_FREQUENCY = 4145146UL; +static const uint16_t FALLBACK_FREQUENCY = 64767U; // To use with frequency = 0; +static const uint32_t MICROSECONDS_IN_SECONDS = 1000000UL; +static const uint16_t PRONTO_DEFAULT_GAP = 45000; + +static uint16_t to_frequency_k_hz(uint16_t code) { + if (code == 0) + return 0; + + return ((REFERENCE_FREQUENCY / code) + 500) / 1000; +} + +/* + * Parse the string given as Pronto Hex, and send it a number of times given as argument. + */ +void ProntoProtocol::send_pronto_(RemoteTransmitData *dst, const std::vector &data) { + if (data.size() < 4) + return; + + uint16_t timebase = (MICROSECONDS_IN_SECONDS * data[1] + REFERENCE_FREQUENCY / 2) / REFERENCE_FREQUENCY; + uint16_t khz; + switch (data[0]) { + case LEARNED_TOKEN: // normal, "learned" + khz = to_frequency_k_hz(data[1]); + break; + case LEARNED_NON_MODULATED_TOKEN: // non-demodulated, "learned" + khz = 0U; + break; + default: + return; // There are other types, but they are not handled yet. + } + ESP_LOGD(TAG, "Send Pronto: frequency=%dkHz", khz); + dst->set_carrier_frequency(khz * 1000); + + uint16_t intros = 2 * data[2]; + uint16_t repeats = 2 * data[3]; + ESP_LOGD(TAG, "Send Pronto: intros=%d", intros); + ESP_LOGD(TAG, "Send Pronto: repeats=%d", repeats); + if (NUMBERS_IN_PREAMBLE + intros + repeats != data.size()) { // inconsistent sizes + return; + } + + /* + * Generate a new microseconds timing array for sendRaw. + * If recorded by IRremote, intro contains the whole IR data and repeat is empty + */ + dst->reserve(intros + repeats); + + for (uint16_t i = 0; i < intros + repeats; i += 2) { + uint32_t duration0 = ((uint32_t) data[i + 0 + NUMBERS_IN_PREAMBLE]) * timebase; + duration0 = duration0 < MICROSECONDS_T_MAX ? duration0 : MICROSECONDS_T_MAX; + + uint32_t duration1 = ((uint32_t) data[i + 1 + NUMBERS_IN_PREAMBLE]) * timebase; + duration1 = duration1 < MICROSECONDS_T_MAX ? duration1 : MICROSECONDS_T_MAX; + + dst->item(duration0, duration1); + } +} + +void ProntoProtocol::send_pronto_(RemoteTransmitData *dst, const std::string &str) { + size_t len = str.length() / (DIGITS_IN_PRONTO_NUMBER + 1) + 1; + std::vector data; + const char *p = str.c_str(); + char *endptr[1]; + + for (uint16_t i = 0; i < len; i++) { + uint16_t x = strtol(p, endptr, 16); + if (x == 0 && i >= NUMBERS_IN_PREAMBLE) { + // Alignment error?, bail immediately (often right result). + break; + } + data.push_back(x); // If input is conforming, there can be no overflow! + p = *endptr; + } + send_pronto_(dst, data); +} + +void ProntoProtocol::encode(RemoteTransmitData *dst, const ProntoData &data) { send_pronto_(dst, data.data); } + +optional ProntoProtocol::decode(RemoteReceiveData src) { return {}; } + +void ProntoProtocol::dump(const ProntoData &data) { ESP_LOGD(TAG, "Received Pronto: data=%s", data.data.c_str()); } + +} // namespace remote_base +} // namespace esphome diff --git a/esphome/components/remote_base/pronto_protocol.h b/esphome/components/remote_base/pronto_protocol.h new file mode 100644 index 0000000000..e96511383f --- /dev/null +++ b/esphome/components/remote_base/pronto_protocol.h @@ -0,0 +1,40 @@ +#pragma once + +#include "esphome/core/component.h" +#include "remote_base.h" + +namespace esphome { +namespace remote_base { + +struct ProntoData { + std::string data; + + bool operator==(const ProntoData &rhs) const { return data == rhs.data; } +}; + +class ProntoProtocol : public RemoteProtocol { + private: + void send_pronto_(RemoteTransmitData *dst, const std::vector &data); + void send_pronto_(RemoteTransmitData *dst, const std::string &str); + + public: + void encode(RemoteTransmitData *dst, const ProntoData &data) override; + optional decode(RemoteReceiveData src) override; + void dump(const ProntoData &data) override; +}; + +DECLARE_REMOTE_PROTOCOL(Pronto) + +template class ProntoAction : public RemoteTransmitterActionBase { + public: + TEMPLATABLE_VALUE(std::string, data) + + void encode(RemoteTransmitData *dst, Ts... x) override { + ProntoData data{}; + data.data = this->data_.value(x...); + ProntoProtocol().encode(dst, data); + } +}; + +} // namespace remote_base +} // namespace esphome From 97eaf3d4a1f009a33d05262ee32449fa423fdbb6 Mon Sep 17 00:00:00 2001 From: Duncan Findlay Date: Tue, 9 Nov 2021 19:12:20 -0800 Subject: [PATCH 126/142] Set up output_switch at priority DATA instead of HARDWARE. (#2648) --- esphome/components/output/switch/output_switch.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/output/switch/output_switch.h b/esphome/components/output/switch/output_switch.h index fc9540fede..a184a342fe 100644 --- a/esphome/components/output/switch/output_switch.h +++ b/esphome/components/output/switch/output_switch.h @@ -12,7 +12,7 @@ class OutputSwitch : public switch_::Switch, public Component { void set_output(BinaryOutput *output) { output_ = output; } void setup() override; - float get_setup_priority() const override { return setup_priority::HARDWARE; } + float get_setup_priority() const override { return setup_priority::HARDWARE - 1.0f; } void dump_config() override; protected: From 6e5cfac927c8d222d3557b959924c33ee4870833 Mon Sep 17 00:00:00 2001 From: Guillermo Ruffino Date: Wed, 10 Nov 2021 00:15:15 -0300 Subject: [PATCH 127/142] fix rc switch protocol 6 (#2672) --- esphome/components/remote_base/rc_switch_protocol.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/esphome/components/remote_base/rc_switch_protocol.cpp b/esphome/components/remote_base/rc_switch_protocol.cpp index 6b7d1b725a..1dc094d552 100644 --- a/esphome/components/remote_base/rc_switch_protocol.cpp +++ b/esphome/components/remote_base/rc_switch_protocol.cpp @@ -101,10 +101,13 @@ bool RCSwitchBase::expect_sync(RemoteReceiveData &src) const { if (!src.peek_space(this->sync_low_, 1)) return false; } else { - if (!src.peek_space(this->sync_high_)) - return false; - if (!src.peek_mark(this->sync_low_, 1)) + // We cant peek a space at the beginning because signals starts with a low to high transition. + // this long space at the beginning is the separation between the transmissions itself, so it is actually + // added at the end kind of artificially (by the value given to "idle:" option by the user in the yaml) + if (!src.peek_mark(this->sync_low_)) return false; + src.advance(1); + return true; } src.advance(2); return true; From 875b803483a064f08457a821dc090807149bd81b Mon Sep 17 00:00:00 2001 From: Carlos Garcia Saura Date: Wed, 10 Nov 2021 04:22:00 +0100 Subject: [PATCH 128/142] Remove "delay_microseconds_accurate()" and improve systemwide delayMicroseconds() (#2497) --- esphome/components/aht10/aht10.cpp | 2 +- esphome/components/esp32/core.cpp | 6 +----- esphome/components/esp8266/core.cpp | 2 +- .../remote_transmitter_esp32.cpp | 6 ++---- .../remote_transmitter_esp8266.cpp | 13 ++++++------ esphome/components/sdp3x/sdp3x.cpp | 3 ++- esphome/core/helpers.cpp | 21 ++++++++++--------- esphome/core/helpers.h | 2 +- 8 files changed, 25 insertions(+), 30 deletions(-) diff --git a/esphome/components/aht10/aht10.cpp b/esphome/components/aht10/aht10.cpp index 713199212c..e5e04ac181 100644 --- a/esphome/components/aht10/aht10.cpp +++ b/esphome/components/aht10/aht10.cpp @@ -73,7 +73,7 @@ void AHT10Component::update() { bool success = false; for (int i = 0; i < AHT10_ATTEMPTS; ++i) { ESP_LOGVV(TAG, "Attempt %d at %6u", i, millis()); - delay_microseconds_accurate(4); + delayMicroseconds(4); uint8_t reg = 0; if (this->write(®, 1) != i2c::ERROR_OK) { diff --git a/esphome/components/esp32/core.cpp b/esphome/components/esp32/core.cpp index 96047df535..359999120f 100644 --- a/esphome/components/esp32/core.cpp +++ b/esphome/components/esp32/core.cpp @@ -21,11 +21,7 @@ void IRAM_ATTR HOT yield() { vPortYield(); } uint32_t IRAM_ATTR HOT millis() { return (uint32_t)(esp_timer_get_time() / 1000ULL); } void IRAM_ATTR HOT delay(uint32_t ms) { vTaskDelay(ms / portTICK_PERIOD_MS); } uint32_t IRAM_ATTR HOT micros() { return (uint32_t) esp_timer_get_time(); } -void IRAM_ATTR HOT delayMicroseconds(uint32_t us) { - auto start = (uint64_t) esp_timer_get_time(); - while (((uint64_t) esp_timer_get_time()) - start < us) - ; -} +void IRAM_ATTR HOT delayMicroseconds(uint32_t us) { delay_microseconds_safe(us); } void arch_restart() { esp_restart(); // restart() doesn't always end execution diff --git a/esphome/components/esp8266/core.cpp b/esphome/components/esp8266/core.cpp index b78600e7a3..51f3ca50ec 100644 --- a/esphome/components/esp8266/core.cpp +++ b/esphome/components/esp8266/core.cpp @@ -12,7 +12,7 @@ void IRAM_ATTR HOT yield() { ::yield(); } uint32_t IRAM_ATTR HOT millis() { return ::millis(); } void IRAM_ATTR HOT delay(uint32_t ms) { ::delay(ms); } uint32_t IRAM_ATTR HOT micros() { return ::micros(); } -void IRAM_ATTR HOT delayMicroseconds(uint32_t us) { ::delayMicroseconds(us); } +void IRAM_ATTR HOT delayMicroseconds(uint32_t us) { delay_microseconds_safe(us); } void arch_restart() { ESP.restart(); // NOLINT(readability-static-accessed-through-instance) // restart() doesn't always end execution diff --git a/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp b/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp index 500d7193f3..c3b61b72c2 100644 --- a/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp +++ b/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp @@ -121,10 +121,8 @@ void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t sen } else { this->status_clear_warning(); } - if (i + 1 < send_times) { - delay(send_wait / 1000UL); - delayMicroseconds(send_wait % 1000UL); - } + if (i + 1 < send_times) + delayMicroseconds(send_wait); } } diff --git a/esphome/components/remote_transmitter/remote_transmitter_esp8266.cpp b/esphome/components/remote_transmitter/remote_transmitter_esp8266.cpp index 33c01985d7..74e62d4e3b 100644 --- a/esphome/components/remote_transmitter/remote_transmitter_esp8266.cpp +++ b/esphome/components/remote_transmitter/remote_transmitter_esp8266.cpp @@ -36,7 +36,7 @@ void RemoteTransmitterComponent::calculate_on_off_time_(uint32_t carrier_frequen 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); - delay_microseconds_accurate(usec); + delayMicroseconds(usec); this->pin_->digital_write(false); return; } @@ -48,19 +48,19 @@ void RemoteTransmitterComponent::mark_(uint32_t on_time, uint32_t off_time, uint const uint32_t elapsed = current_time - start_time; this->pin_->digital_write(true); - delay_microseconds_accurate(std::min(on_time, usec - elapsed)); + delayMicroseconds(std::min(on_time, usec - elapsed)); this->pin_->digital_write(false); if (elapsed + on_time >= usec) return; - delay_microseconds_accurate(std::min(usec - elapsed - on_time, off_time)); + delayMicroseconds(std::min(usec - elapsed - on_time, off_time)); current_time = micros(); } } void RemoteTransmitterComponent::space_(uint32_t usec) { this->pin_->digital_write(false); - delay_microseconds_accurate(usec); + delayMicroseconds(usec); } void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t send_wait) { ESP_LOGD(TAG, "Sending remote code..."); @@ -81,9 +81,8 @@ void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t sen } } - if (i + 1 < send_times) { - delay_microseconds_accurate(send_wait); - } + if (i + 1 < send_times) + delayMicroseconds(send_wait); } } diff --git a/esphome/components/sdp3x/sdp3x.cpp b/esphome/components/sdp3x/sdp3x.cpp index ba7a028f8e..b0d8bcc6c4 100644 --- a/esphome/components/sdp3x/sdp3x.cpp +++ b/esphome/components/sdp3x/sdp3x.cpp @@ -1,5 +1,6 @@ #include "sdp3x.h" #include "esphome/core/log.h" +#include "esphome/core/hal.h" #include "esphome/core/helpers.h" namespace esphome { @@ -25,7 +26,7 @@ void SDP3XComponent::setup() { ESP_LOGW(TAG, "Soft Reset SDP3X failed!"); // This sometimes fails for no good reason } - delay_microseconds_accurate(20000); + delayMicroseconds(20000); if (this->write(SDP3X_READ_ID1, 2) != i2c::ERROR_OK) { ESP_LOGE(TAG, "Read ID1 SDP3X failed!"); diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index ada9a48c3b..8cf972e4db 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -209,17 +209,18 @@ uint8_t crc8(uint8_t *data, uint8_t len) { return crc; } -void delay_microseconds_accurate(uint32_t usec) { - if (usec == 0) - return; - if (usec < 5000UL) { - delayMicroseconds(usec); - return; - } - uint32_t start = micros(); - while (micros() - start < usec) { - delay(0); +void delay_microseconds_safe(uint32_t us) { // avoids CPU locks that could trigger WDT or affect WiFi/BT stability + auto start = micros(); + const uint32_t lag = 5000; // microseconds, specifies the maximum time for a CPU busy-loop. + // it must be larger than the worst-case duration of a delay(1) call (hardware tasks) + // 5ms is conservative, it could be reduced when exact BT/WiFi stack delays are known + if (us > lag) { + delay((us - lag) / 1000UL); // note: in disabled-interrupt contexts delay() won't actually sleep + while (micros() - start < us - lag) + delay(1); // in those cases, this loop allows to yield for BT/WiFi stack tasks } + while (micros() - start < us) // fine delay the remaining usecs + ; } uint8_t reverse_bits_8(uint8_t x) { diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index 905cb76170..040780e072 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -255,7 +255,7 @@ struct is_callable // NOLINT static constexpr auto value = decltype(test(nullptr))::value; // NOLINT }; -void delay_microseconds_accurate(uint32_t usec); +void delay_microseconds_safe(uint32_t us); template class Deduplicator { public: From 662773b075331d3efdbb59c02458c733e7005105 Mon Sep 17 00:00:00 2001 From: Martin <25747549+martgras@users.noreply.github.com> Date: Wed, 10 Nov 2021 04:24:44 +0100 Subject: [PATCH 129/142] modbus_controller: remove hard coded register size (#2654) --- .../modbus_controller/modbus_controller.h | 32 +++---------------- .../text_sensor/modbus_textsensor.h | 7 ---- 2 files changed, 4 insertions(+), 35 deletions(-) diff --git a/esphome/components/modbus_controller/modbus_controller.h b/esphome/components/modbus_controller/modbus_controller.h index 4b5f4337db..222ebbd020 100644 --- a/esphome/components/modbus_controller/modbus_controller.h +++ b/esphome/components/modbus_controller/modbus_controller.h @@ -260,35 +260,11 @@ struct SensorItem { virtual void parse_and_publish(const std::vector &data) = 0; uint64_t getkey() const { return calc_key(register_type, start_address, offset, bitmask); } - size_t virtual get_register_size() const { - size_t size = 0; - switch (sensor_value_type) { - case SensorValueType::BIT: - size = 1; - break; - case SensorValueType::U_WORD: - case SensorValueType::S_WORD: - size = 2; - break; - case SensorValueType::U_DWORD: - case SensorValueType::S_DWORD: - case SensorValueType::U_DWORD_R: - case SensorValueType::S_DWORD_R: - case SensorValueType::FP32: - case SensorValueType::FP32_R: - size = 4; - break; - case SensorValueType::U_QWORD: - case SensorValueType::U_QWORD_R: - case SensorValueType::S_QWORD: - case SensorValueType::S_QWORD_R: - size = 8; - break; - case SensorValueType::RAW: - size = this->register_count * 2; - } - return size; + if (register_type == ModbusRegisterType::COIL || register_type == ModbusRegisterType::DISCRETE_INPUT) + return 1; + else + return register_count * 2; } }; diff --git a/esphome/components/modbus_controller/text_sensor/modbus_textsensor.h b/esphome/components/modbus_controller/text_sensor/modbus_textsensor.h index 28d0f0b241..77b5b9363a 100644 --- a/esphome/components/modbus_controller/text_sensor/modbus_textsensor.h +++ b/esphome/components/modbus_controller/text_sensor/modbus_textsensor.h @@ -25,13 +25,6 @@ class ModbusTextSensor : public Component, public text_sensor::TextSensor, publi this->sensor_value_type = SensorValueType::RAW; this->force_new_range = force_new_range; } - size_t get_register_size() const override { - if (sensor_value_type == SensorValueType::RAW) { - return this->response_bytes_; - } else { - return SensorItem::get_register_size(); - } - } void dump_config() override; From 710866ff4e0525949fdb1d5ae6334e68a707923b Mon Sep 17 00:00:00 2001 From: Sam Hughes Date: Wed, 10 Nov 2021 17:52:49 +0000 Subject: [PATCH 130/142] CAP1188 Capacitive Touch Sensor Support (#2653) --- CODEOWNERS | 1 + esphome/components/cap1188/__init__.py | 45 +++++++++++ esphome/components/cap1188/binary_sensor.py | 25 ++++++ esphome/components/cap1188/cap1188.cpp | 88 +++++++++++++++++++++ esphome/components/cap1188/cap1188.h | 68 ++++++++++++++++ tests/test2.yaml | 6 ++ 6 files changed, 233 insertions(+) create mode 100644 esphome/components/cap1188/__init__.py create mode 100644 esphome/components/cap1188/binary_sensor.py create mode 100644 esphome/components/cap1188/cap1188.cpp create mode 100644 esphome/components/cap1188/cap1188.h diff --git a/CODEOWNERS b/CODEOWNERS index e535608db3..8f98fe1f7f 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -31,6 +31,7 @@ esphome/components/binary_sensor/* @esphome/core esphome/components/ble_client/* @buxtronix esphome/components/bme680_bsec/* @trvrnrth esphome/components/canbus/* @danielschramm @mvturnho +esphome/components/cap1188/* @MrEditor97 esphome/components/captive_portal/* @OttoWinter esphome/components/ccs811/* @habbie esphome/components/climate/* @esphome/core diff --git a/esphome/components/cap1188/__init__.py b/esphome/components/cap1188/__init__.py new file mode 100644 index 0000000000..80794c5146 --- /dev/null +++ b/esphome/components/cap1188/__init__.py @@ -0,0 +1,45 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import i2c +from esphome.const import CONF_ID, CONF_RESET_PIN +from esphome import pins + +CONF_TOUCH_THRESHOLD = "touch_threshold" +CONF_ALLOW_MULTIPLE_TOUCHES = "allow_multiple_touches" + +DEPENDENCIES = ["i2c"] +AUTO_LOAD = ["binary_sensor", "output"] +CODEOWNERS = ["@MrEditor97"] + +cap1188_ns = cg.esphome_ns.namespace("cap1188") +CONF_CAP1188_ID = "cap1188_id" +CAP1188Component = cap1188_ns.class_("CAP1188Component", cg.Component, i2c.I2CDevice) + +MULTI_CONF = True +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(CAP1188Component), + cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema, + cv.Optional(CONF_TOUCH_THRESHOLD, default=0x20): cv.int_range( + min=0x01, max=0x80 + ), + cv.Optional(CONF_ALLOW_MULTIPLE_TOUCHES, default=False): cv.boolean, + } + ) + .extend(cv.COMPONENT_SCHEMA) + .extend(i2c.i2c_device_schema(0x29)) +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + cg.add(var.set_touch_threshold(config[CONF_TOUCH_THRESHOLD])) + cg.add(var.set_allow_multiple_touches(config[CONF_ALLOW_MULTIPLE_TOUCHES])) + + if CONF_RESET_PIN in config: + pin = await cg.gpio_pin_expression(config[CONF_RESET_PIN]) + cg.add(var.set_reset_pin(pin)) + + await cg.register_component(var, config) + await i2c.register_i2c_device(var, config) diff --git a/esphome/components/cap1188/binary_sensor.py b/esphome/components/cap1188/binary_sensor.py new file mode 100644 index 0000000000..c249eb7330 --- /dev/null +++ b/esphome/components/cap1188/binary_sensor.py @@ -0,0 +1,25 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import binary_sensor +from esphome.const import CONF_CHANNEL, CONF_ID +from . import cap1188_ns, CAP1188Component, CONF_CAP1188_ID + +DEPENDENCIES = ["cap1188"] +CAP1188Channel = cap1188_ns.class_("CAP1188Channel", binary_sensor.BinarySensor) + +CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(CAP1188Channel), + cv.GenerateID(CONF_CAP1188_ID): cv.use_id(CAP1188Component), + cv.Required(CONF_CHANNEL): cv.int_range(min=0, max=7), + } +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await binary_sensor.register_binary_sensor(var, config) + hub = await cg.get_variable(config[CONF_CAP1188_ID]) + cg.add(var.set_channel(config[CONF_CHANNEL])) + + cg.add(hub.register_channel(var)) diff --git a/esphome/components/cap1188/cap1188.cpp b/esphome/components/cap1188/cap1188.cpp new file mode 100644 index 0000000000..10d8325537 --- /dev/null +++ b/esphome/components/cap1188/cap1188.cpp @@ -0,0 +1,88 @@ +#include "cap1188.h" +#include "esphome/core/log.h" +#include "esphome/core/hal.h" + +namespace esphome { +namespace cap1188 { + +static const char *const TAG = "cap1188"; + +void CAP1188Component::setup() { + ESP_LOGCONFIG(TAG, "Setting up CAP1188..."); + + // Reset device using the reset pin + if (this->reset_pin_ != nullptr) { + this->reset_pin_->setup(); + this->reset_pin_->digital_write(false); + delay(100); // NOLINT + this->reset_pin_->digital_write(true); + delay(100); // NOLINT + this->reset_pin_->digital_write(false); + delay(100); // NOLINT + } + + // Check if CAP1188 is actually connected + this->read_byte(CAP1188_PRODUCT_ID, &this->cap1188_product_id_); + this->read_byte(CAP1188_MANUFACTURE_ID, &this->cap1188_manufacture_id_); + this->read_byte(CAP1188_REVISION, &this->cap1188_revision_); + + if ((this->cap1188_product_id_ != 0x50) || (this->cap1188_manufacture_id_ != 0x5D)) { + this->error_code_ = COMMUNICATION_FAILED; + this->mark_failed(); + return; + } + + // Set sensitivity + uint8_t sensitivity = 0; + this->read_byte(CAP1188_SENSITVITY, &sensitivity); + sensitivity = sensitivity & 0x0f; + this->write_byte(CAP1188_SENSITVITY, sensitivity | this->touch_threshold_); + + // Allow multiple touches + this->write_byte(CAP1188_MULTI_TOUCH, this->allow_multiple_touches_); + + // Have LEDs follow touches + this->write_byte(CAP1188_LED_LINK, 0xFF); + + // Speed up a bit + this->write_byte(CAP1188_STAND_BY_CONFIGURATION, 0x30); +} + +void CAP1188Component::dump_config() { + ESP_LOGCONFIG(TAG, "CAP1188:"); + LOG_I2C_DEVICE(this); + LOG_PIN(" Reset Pin: ", this->reset_pin_); + ESP_LOGCONFIG(TAG, " Product ID: 0x%x", this->cap1188_product_id_); + ESP_LOGCONFIG(TAG, " Manufacture ID: 0x%x", this->cap1188_manufacture_id_); + ESP_LOGCONFIG(TAG, " Revision ID: 0x%x", this->cap1188_revision_); + + switch (this->error_code_) { + case COMMUNICATION_FAILED: + ESP_LOGE(TAG, "Product ID or Manufacture ID of the connected device does not match a known CAP1188."); + break; + case NONE: + default: + break; + } +} + +void CAP1188Component::loop() { + uint8_t touched = 0; + + this->read_register(CAP1188_SENSOR_INPUT_STATUS, &touched, 1); + + if (touched) { + uint8_t data = 0; + this->read_register(CAP1188_MAIN, &data, 1); + data = data & ~CAP1188_MAIN_INT; + + this->write_register(CAP1188_MAIN, &data, 2); + } + + for (auto *channel : this->channels_) { + channel->process(touched); + } +} + +} // namespace cap1188 +} // namespace esphome diff --git a/esphome/components/cap1188/cap1188.h b/esphome/components/cap1188/cap1188.h new file mode 100644 index 0000000000..a1433deb0f --- /dev/null +++ b/esphome/components/cap1188/cap1188.h @@ -0,0 +1,68 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/hal.h" +#include "esphome/components/i2c/i2c.h" +#include "esphome/components/output/binary_output.h" +#include "esphome/components/binary_sensor/binary_sensor.h" + +namespace esphome { +namespace cap1188 { + +enum { + CAP1188_I2CADDR = 0x29, + CAP1188_SENSOR_INPUT_STATUS = 0x3, + CAP1188_MULTI_TOUCH = 0x2A, + CAP1188_LED_LINK = 0x72, + CAP1188_PRODUCT_ID = 0xFD, + CAP1188_MANUFACTURE_ID = 0xFE, + CAP1188_STAND_BY_CONFIGURATION = 0x41, + CAP1188_REVISION = 0xFF, + CAP1188_MAIN = 0x00, + CAP1188_MAIN_INT = 0x01, + CAP1188_LEDPOL = 0x73, + CAP1188_INTERUPT_REPEAT = 0x28, + CAP1188_SENSITVITY = 0x1f, +}; + +class CAP1188Channel : public binary_sensor::BinarySensor { + public: + void set_channel(uint8_t channel) { channel_ = channel; } + void process(uint8_t data) { this->publish_state(static_cast(data & (1 << this->channel_))); } + + protected: + uint8_t channel_{0}; +}; + +class CAP1188Component : public Component, public i2c::I2CDevice { + public: + void register_channel(CAP1188Channel *channel) { this->channels_.push_back(channel); } + void set_touch_threshold(uint8_t touch_threshold) { this->touch_threshold_ = touch_threshold; }; + void set_allow_multiple_touches(bool allow_multiple_touches) { + this->allow_multiple_touches_ = allow_multiple_touches ? 0x41 : 0x80; + }; + void set_reset_pin(GPIOPin *reset_pin) { this->reset_pin_ = reset_pin; } + void setup() override; + void dump_config() override; + float get_setup_priority() const override { return setup_priority::DATA; } + void loop() override; + + protected: + std::vector channels_{}; + uint8_t touch_threshold_{0x20}; + uint8_t allow_multiple_touches_{0x80}; + + GPIOPin *reset_pin_{nullptr}; + + uint8_t cap1188_product_id_{0}; + uint8_t cap1188_manufacture_id_{0}; + uint8_t cap1188_revision_{0}; + + enum ErrorCode { + NONE = 0, + COMMUNICATION_FAILED, + } error_code_{NONE}; +}; + +} // namespace cap1188 +} // namespace esphome diff --git a/tests/test2.yaml b/tests/test2.yaml index 6869eeecb1..f90e522b1e 100644 --- a/tests/test2.yaml +++ b/tests/test2.yaml @@ -504,3 +504,9 @@ interval: display: +cap1188: + id: cap1188_component + address: 0x29 + touch_threshold: 0x20 + allow_multiple_touches: true + reset_pin: 14 From 2ac232e6349e573f892e3183bb44cf87ff627c4b Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Wed, 10 Nov 2021 19:09:10 +0100 Subject: [PATCH 131/142] Add missing hal.h include in esp32_camera_web_server (#2689) --- esphome/components/esp32_camera_web_server/camera_web_server.cpp | 1 + 1 file changed, 1 insertion(+) 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 ecaef78b77..c9a684c7e5 100644 --- a/esphome/components/esp32_camera_web_server/camera_web_server.cpp +++ b/esphome/components/esp32_camera_web_server/camera_web_server.cpp @@ -2,6 +2,7 @@ #include "camera_web_server.h" #include "esphome/core/application.h" +#include "esphome/core/hal.h" #include "esphome/core/helpers.h" #include "esphome/core/log.h" #include "esphome/core/util.h" From 219b225ac08cdfb32e94a92e700cbf3d5c729fa9 Mon Sep 17 00:00:00 2001 From: Carlos Garcia Saura Date: Wed, 10 Nov 2021 19:12:57 +0100 Subject: [PATCH 132/142] [ESP32 ADC] Add option for raw uncalibrated output (#2663) --- esphome/components/adc/adc_sensor.cpp | 17 ++++++++++------- esphome/components/adc/adc_sensor.h | 4 ++-- esphome/components/adc/sensor.py | 16 ++++++++++++++-- 3 files changed, 26 insertions(+), 11 deletions(-) diff --git a/esphome/components/adc/adc_sensor.cpp b/esphome/components/adc/adc_sensor.cpp index 9b7d0437e1..c8242ce008 100644 --- a/esphome/components/adc/adc_sensor.cpp +++ b/esphome/components/adc/adc_sensor.cpp @@ -91,17 +91,21 @@ 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=%.2fV", this->get_name().c_str(), value_v); + ESP_LOGD(TAG, "'%s': Got voltage=%.4fV", this->get_name().c_str(), value_v); this->publish_state(value_v); } #ifdef USE_ESP8266 float ADCSensor::sample() { #ifdef USE_ADC_SENSOR_VCC - return ESP.getVcc() / 1024.0f; // NOLINT(readability-static-accessed-through-instance) + int raw = ESP.getVcc(); // NOLINT(readability-static-accessed-through-instance) #else - return analogRead(this->pin_->get_pin()) / 1024.0f; // NOLINT + int raw = analogRead(this->pin_->get_pin()); // NOLINT #endif + if (output_raw_) { + return raw; + } + return raw / 1024.0f; } #endif @@ -112,6 +116,9 @@ float ADCSensor::sample() { if (raw == -1) { return NAN; } + if (output_raw_) { + return raw; + } uint32_t mv = esp_adc_cal_raw_to_voltage(raw, &cal_characteristics_[(int) attenuation_]); return mv / 1000.0f; } @@ -135,10 +142,6 @@ float ADCSensor::sample() { if (raw0 == -1 || raw2 == -1 || raw6 == -1 || raw11 == -1) { return NAN; } - // prevent divide by zero - if (raw0 == 0 && raw2 == 0 && raw6 == 0 && raw11 == 0) { - return 0; - } uint32_t mv11 = esp_adc_cal_raw_to_voltage(raw11, &cal_characteristics_[(int) ADC_ATTEN_DB_11]); uint32_t mv6 = esp_adc_cal_raw_to_voltage(raw6, &cal_characteristics_[(int) ADC_ATTEN_DB_6]); diff --git a/esphome/components/adc/adc_sensor.h b/esphome/components/adc/adc_sensor.h index 9984c72819..12272a1577 100644 --- a/esphome/components/adc/adc_sensor.h +++ b/esphome/components/adc/adc_sensor.h @@ -31,6 +31,7 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage /// `HARDWARE_LATE` setup priority. float get_setup_priority() const override; void set_pin(InternalGPIOPin *pin) { this->pin_ = pin; } + void set_output_raw(bool output_raw) { output_raw_ = output_raw; } float sample() override; #ifdef USE_ESP8266 @@ -39,8 +40,7 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage protected: InternalGPIOPin *pin_; - uint16_t read_raw_(); - uint32_t raw_to_microvolts_(uint16_t raw); + bool output_raw_{false}; #ifdef USE_ESP32 adc_atten_t attenuation_{ADC_ATTEN_DB_0}; diff --git a/esphome/components/adc/sensor.py b/esphome/components/adc/sensor.py index 9fdddaa0a6..c812e67a68 100644 --- a/esphome/components/adc/sensor.py +++ b/esphome/components/adc/sensor.py @@ -4,6 +4,7 @@ from esphome import pins from esphome.components import sensor, voltage_sampler from esphome.const import ( CONF_ATTENUATION, + CONF_RAW, CONF_ID, CONF_INPUT, CONF_NUMBER, @@ -119,12 +120,18 @@ def validate_adc_pin(value): raise NotImplementedError +def validate_config(config): + if config[CONF_RAW] and config.get(CONF_ATTENUATION, None) == "auto": + raise cv.Invalid("Automatic attenuation cannot be used when raw output is set.") + return config + + adc_ns = cg.esphome_ns.namespace("adc") ADCSensor = adc_ns.class_( "ADCSensor", sensor.Sensor, cg.PollingComponent, voltage_sampler.VoltageSampler ) -CONFIG_SCHEMA = ( +CONFIG_SCHEMA = cv.All( sensor.sensor_schema( unit_of_measurement=UNIT_VOLT, accuracy_decimals=2, @@ -135,12 +142,14 @@ CONFIG_SCHEMA = ( { cv.GenerateID(): cv.declare_id(ADCSensor), cv.Required(CONF_PIN): validate_adc_pin, + cv.Optional(CONF_RAW, default=False): cv.boolean, cv.SplitDefault(CONF_ATTENUATION, esp32="0db"): cv.All( cv.only_on_esp32, cv.enum(ATTENUATION_MODES, lower=True) ), } ) - .extend(cv.polling_component_schema("60s")) + .extend(cv.polling_component_schema("60s")), + validate_config, ) @@ -155,6 +164,9 @@ async def to_code(config): pin = await cg.gpio_pin_expression(config[CONF_PIN]) cg.add(var.set_pin(pin)) + if CONF_RAW in config: + cg.add(var.set_output_raw(config[CONF_RAW])) + if CONF_ATTENUATION in config: if config[CONF_ATTENUATION] == "auto": cg.add(var.set_autorange(cg.global_ns.true)) From 15f9677d33c1bb39bbbf04795d4dc1c64089f98c Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Wed, 10 Nov 2021 19:15:06 +0100 Subject: [PATCH 133/142] Introduce parse_number() helper function (#2659) --- esphome/components/anova/anova_base.cpp | 6 +-- esphome/components/ezo/ezo.cpp | 2 +- .../sensor/homeassistant_sensor.cpp | 2 +- .../hrxl_maxsonar_wr/hrxl_maxsonar_wr.cpp | 2 +- esphome/components/mqtt/mqtt_climate.cpp | 6 +-- esphome/components/mqtt/mqtt_cover.cpp | 4 +- esphome/components/mqtt/mqtt_fan.cpp | 2 +- esphome/components/mqtt/mqtt_number.cpp | 2 +- .../sensor/mqtt_subscribe_sensor.cpp | 2 +- esphome/components/pipsolar/pipsolar.cpp | 2 +- esphome/components/sim800l/sim800l.cpp | 4 +- .../teleinfo/sensor/teleinfo_sensor.cpp | 4 +- esphome/components/web_server/web_server.cpp | 2 +- esphome/core/helpers.cpp | 14 ----- esphome/core/helpers.h | 52 ++++++++++++++++++- 15 files changed, 70 insertions(+), 36 deletions(-) diff --git a/esphome/components/anova/anova_base.cpp b/esphome/components/anova/anova_base.cpp index 811a34a27a..d55404089e 100644 --- a/esphome/components/anova/anova_base.cpp +++ b/esphome/components/anova/anova_base.cpp @@ -103,21 +103,21 @@ void AnovaCodec::decode(const uint8_t *data, uint16_t length) { break; } case READ_TARGET_TEMPERATURE: { - this->target_temp_ = strtof(this->buf_, nullptr); + this->target_temp_ = parse_number(this->buf_, sizeof(this->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_ = strtof(this->buf_, nullptr); + this->target_temp_ = parse_number(this->buf_, sizeof(this->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_ = strtof(this->buf_, nullptr); + this->current_temp_ = parse_number(this->buf_, sizeof(this->buf_)).value_or(0.0f); if (this->fahrenheit_) this->current_temp_ = ftoc(this->current_temp_); this->has_current_temp_ = true; diff --git a/esphome/components/ezo/ezo.cpp b/esphome/components/ezo/ezo.cpp index 81597f3466..7f7a41fb41 100644 --- a/esphome/components/ezo/ezo.cpp +++ b/esphome/components/ezo/ezo.cpp @@ -74,7 +74,7 @@ void EZOSensor::loop() { if (buf[0] != 1) return; - float val = strtof((char *) &buf[1], nullptr); + float val = parse_number((char *) &buf[1], sizeof(buf) - 1).value_or(0); this->publish_state(val); } diff --git a/esphome/components/homeassistant/sensor/homeassistant_sensor.cpp b/esphome/components/homeassistant/sensor/homeassistant_sensor.cpp index b6f2acdbe4..f5e73c8854 100644 --- a/esphome/components/homeassistant/sensor/homeassistant_sensor.cpp +++ b/esphome/components/homeassistant/sensor/homeassistant_sensor.cpp @@ -10,7 +10,7 @@ static const char *const TAG = "homeassistant.sensor"; void HomeassistantSensor::setup() { api::global_api_server->subscribe_home_assistant_state( this->entity_id_, this->attribute_, [this](const std::string &state) { - auto val = parse_float(state); + auto val = parse_number(state); if (!val.has_value()) { ESP_LOGW(TAG, "Can't convert '%s' to number!", state.c_str()); this->publish_state(NAN); diff --git a/esphome/components/hrxl_maxsonar_wr/hrxl_maxsonar_wr.cpp b/esphome/components/hrxl_maxsonar_wr/hrxl_maxsonar_wr.cpp index cf6c9eea65..bd1c82c96b 100644 --- a/esphome/components/hrxl_maxsonar_wr/hrxl_maxsonar_wr.cpp +++ b/esphome/components/hrxl_maxsonar_wr/hrxl_maxsonar_wr.cpp @@ -44,7 +44,7 @@ void HrxlMaxsonarWrComponent::check_buffer_() { if (this->buffer_.length() == MAX_DATA_LENGTH_BYTES && this->buffer_[0] == 'R' && this->buffer_.back() == static_cast(ASCII_CR)) { - int millimeters = strtol(this->buffer_.substr(1, MAX_DATA_LENGTH_BYTES - 2).c_str(), nullptr, 10); + int millimeters = parse_number(this->buffer_.substr(1, MAX_DATA_LENGTH_BYTES - 2)).value_or(0); float meters = float(millimeters) / 1000.0; ESP_LOGV(TAG, "Distance from sensor: %d mm, %f m", millimeters, meters); this->publish_state(meters); diff --git a/esphome/components/mqtt/mqtt_climate.cpp b/esphome/components/mqtt/mqtt_climate.cpp index a63eb9c4ff..ebc708f444 100644 --- a/esphome/components/mqtt/mqtt_climate.cpp +++ b/esphome/components/mqtt/mqtt_climate.cpp @@ -137,7 +137,7 @@ void MQTTClimateComponent::setup() { if (traits.get_supports_two_point_target_temperature()) { this->subscribe(this->get_target_temperature_low_command_topic(), [this](const std::string &topic, const std::string &payload) { - auto val = parse_float(payload); + auto val = parse_number(payload); if (!val.has_value()) { ESP_LOGW(TAG, "Can't convert '%s' to number!", payload.c_str()); return; @@ -148,7 +148,7 @@ void MQTTClimateComponent::setup() { }); this->subscribe(this->get_target_temperature_high_command_topic(), [this](const std::string &topic, const std::string &payload) { - auto val = parse_float(payload); + auto val = parse_number(payload); if (!val.has_value()) { ESP_LOGW(TAG, "Can't convert '%s' to number!", payload.c_str()); return; @@ -160,7 +160,7 @@ void MQTTClimateComponent::setup() { } else { this->subscribe(this->get_target_temperature_command_topic(), [this](const std::string &topic, const std::string &payload) { - auto val = parse_float(payload); + auto val = parse_number(payload); if (!val.has_value()) { ESP_LOGW(TAG, "Can't convert '%s' to number!", payload.c_str()); return; diff --git a/esphome/components/mqtt/mqtt_cover.cpp b/esphome/components/mqtt/mqtt_cover.cpp index 7bf3204222..7e42abcd05 100644 --- a/esphome/components/mqtt/mqtt_cover.cpp +++ b/esphome/components/mqtt/mqtt_cover.cpp @@ -24,7 +24,7 @@ void MQTTCoverComponent::setup() { }); if (traits.get_supports_position()) { this->subscribe(this->get_position_command_topic(), [this](const std::string &topic, const std::string &payload) { - auto value = parse_float(payload); + auto value = parse_number(payload); if (!value.has_value()) { ESP_LOGW(TAG, "Invalid position value: '%s'", payload.c_str()); return; @@ -36,7 +36,7 @@ void MQTTCoverComponent::setup() { } if (traits.get_supports_tilt()) { this->subscribe(this->get_tilt_command_topic(), [this](const std::string &topic, const std::string &payload) { - auto value = parse_float(payload); + auto value = parse_number(payload); if (!value.has_value()) { ESP_LOGW(TAG, "Invalid tilt value: '%s'", payload.c_str()); return; diff --git a/esphome/components/mqtt/mqtt_fan.cpp b/esphome/components/mqtt/mqtt_fan.cpp index a9d77789e1..d58e3abc88 100644 --- a/esphome/components/mqtt/mqtt_fan.cpp +++ b/esphome/components/mqtt/mqtt_fan.cpp @@ -71,7 +71,7 @@ void MQTTFanComponent::setup() { if (this->state_->get_traits().supports_speed()) { this->subscribe(this->get_speed_level_command_topic(), [this](const std::string &topic, const std::string &payload) { - optional speed_level_opt = parse_int(payload); + optional speed_level_opt = parse_number(payload); if (speed_level_opt.has_value()) { const int speed_level = speed_level_opt.value(); if (speed_level >= 0 && speed_level <= this->state_->get_traits().supported_speed_count()) { diff --git a/esphome/components/mqtt/mqtt_number.cpp b/esphome/components/mqtt/mqtt_number.cpp index 9b2292cd76..337013055a 100644 --- a/esphome/components/mqtt/mqtt_number.cpp +++ b/esphome/components/mqtt/mqtt_number.cpp @@ -17,7 +17,7 @@ MQTTNumberComponent::MQTTNumberComponent(Number *number) : MQTTComponent(), numb void MQTTNumberComponent::setup() { this->subscribe(this->get_command_topic_(), [this](const std::string &topic, const std::string &state) { - auto val = parse_float(state); + auto val = parse_number(state); if (!val.has_value()) { ESP_LOGW(TAG, "Can't convert '%s' to number!", state.c_str()); return; diff --git a/esphome/components/mqtt_subscribe/sensor/mqtt_subscribe_sensor.cpp b/esphome/components/mqtt_subscribe/sensor/mqtt_subscribe_sensor.cpp index e1accf3c70..273de10376 100644 --- a/esphome/components/mqtt_subscribe/sensor/mqtt_subscribe_sensor.cpp +++ b/esphome/components/mqtt_subscribe/sensor/mqtt_subscribe_sensor.cpp @@ -13,7 +13,7 @@ void MQTTSubscribeSensor::setup() { mqtt::global_mqtt_client->subscribe( this->topic_, [this](const std::string &topic, const std::string &payload) { - auto val = parse_float(payload); + auto val = parse_number(payload); if (!val.has_value()) { ESP_LOGW(TAG, "Can't convert '%s' to number!", payload.c_str()); this->publish_state(NAN); diff --git a/esphome/components/pipsolar/pipsolar.cpp b/esphome/components/pipsolar/pipsolar.cpp index 7dbbd798ad..9f8b57003a 100644 --- a/esphome/components/pipsolar/pipsolar.cpp +++ b/esphome/components/pipsolar/pipsolar.cpp @@ -656,7 +656,7 @@ void Pipsolar::loop() { case 32: fc = tmp[i]; fc += tmp[i + 1]; - this->value_fault_code_ = strtol(fc.c_str(), nullptr, 10); + this->value_fault_code_ = parse_number(fc).value_or(0); break; case 34: this->value_warnung_low_pv_energy_ = enabled; diff --git a/esphome/components/sim800l/sim800l.cpp b/esphome/components/sim800l/sim800l.cpp index e48b1ac9bd..eb6d62ca33 100644 --- a/esphome/components/sim800l/sim800l.cpp +++ b/esphome/components/sim800l/sim800l.cpp @@ -128,7 +128,7 @@ void Sim800LComponent::parse_cmd_(std::string message) { if (message.compare(0, 5, "+CSQ:") == 0) { size_t comma = message.find(',', 6); if (comma != 6) { - this->rssi_ = strtol(message.substr(6, comma - 6).c_str(), nullptr, 10); + this->rssi_ = parse_number(message.substr(6, comma - 6)).value_or(0); ESP_LOGD(TAG, "RSSI: %d", this->rssi_); } } @@ -146,7 +146,7 @@ void Sim800LComponent::parse_cmd_(std::string message) { while (end != start) { item++; if (item == 1) { // Slot Index - this->parse_index_ = strtol(message.substr(start, end - start).c_str(), nullptr, 10); + this->parse_index_ = parse_number(message.substr(start, end - start)).value_or(0); } // item 2 = STATUS, usually "REC UNERAD" if (item == 3) { // recipient diff --git a/esphome/components/teleinfo/sensor/teleinfo_sensor.cpp b/esphome/components/teleinfo/sensor/teleinfo_sensor.cpp index 4e4cd9f9e6..ad9c6dae00 100644 --- a/esphome/components/teleinfo/sensor/teleinfo_sensor.cpp +++ b/esphome/components/teleinfo/sensor/teleinfo_sensor.cpp @@ -6,8 +6,8 @@ namespace teleinfo { static const char *const TAG = "teleinfo_sensor"; TeleInfoSensor::TeleInfoSensor(const char *tag) { this->tag = std::string(tag); } void TeleInfoSensor::publish_val(const std::string &val) { - auto newval = parse_float(val); - publish_state(*newval); + auto newval = parse_number(val).value_or(0.0f); + publish_state(newval); } void TeleInfoSensor::dump_config() { LOG_SENSOR(" ", "Teleinfo Sensor", this); } } // namespace teleinfo diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index 44ace38990..17b17fcc3c 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -458,7 +458,7 @@ void WebServer::handle_fan_request(AsyncWebServerRequest *request, const UrlMatc } if (request->hasParam("speed_level")) { String speed_level = request->getParam("speed_level")->value(); - auto val = parse_int(speed_level.c_str()); + auto val = parse_number(speed_level.c_str()); if (!val.has_value()) { ESP_LOGW(TAG, "Can't convert '%s' to number!", speed_level.c_str()); return; diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index 8cf972e4db..3047facf45 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -279,20 +279,6 @@ std::string to_string(long double val) { sprintf(buf, "%Lf", val); return buf; } -optional parse_float(const std::string &str) { - char *end; - float value = ::strtof(str.c_str(), &end); - if (end == nullptr || end != str.end().base()) - return {}; - return value; -} -optional parse_int(const std::string &str) { - char *end; - int value = ::strtol(str.c_str(), &end, 10); - if (end == nullptr || end != str.end().base()) - return {}; - return value; -} optional parse_hex(const char chr) { int out = chr; diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index 040780e072..fde631514b 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -1,5 +1,7 @@ #pragma once +#include + #include #include #include @@ -51,8 +53,6 @@ std::string to_string(unsigned long long val); // NOLINT std::string to_string(float val); std::string to_string(double val); std::string to_string(long double val); -optional parse_float(const std::string &str); -optional parse_int(const std::string &str); optional parse_hex(const std::string &str, size_t start, size_t length); optional parse_hex(char chr); /// Sanitize the hostname by removing characters that are not in the allowlist and truncating it to 63 chars. @@ -304,4 +304,52 @@ template T *new_buffer(size_t length) { return buffer; } +// --------------------------------------------------------------------------------------------------------------------- + +/// @name Parsing & formatting +///@{ + +/// Parse a unsigned decimal number. +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()) + return {}; + return value; +} +template::value && std::is_unsigned::value), int> = 0> +optional parse_number(const std::string &str) { + return parse_number(str.c_str(), str.length()); +} +/// Parse a signed decimal number. +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()) + return {}; + return value; +} +template::value && std::is_signed::value), int> = 0> +optional parse_number(const std::string &str) { + return parse_number(str.c_str(), str.length()); +} +/// Parse a decimal floating-point number. +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) + return {}; + return value; +} +template::value), int> = 0> +optional parse_number(const std::string &str) { + return parse_number(str.c_str(), str.length()); +} + +///@} + } // namespace esphome From d8e33c5a69aa0053ae01352341e57265df7df802 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Wed, 10 Nov 2021 19:30:07 +0100 Subject: [PATCH 134/142] Add repeat action for automations (#2538) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/automation.py | 21 +++++++++++++++++++++ esphome/core/base_automation.h | 33 +++++++++++++++++++++++++++++++++ tests/test5.yaml | 8 ++++++++ 3 files changed, 62 insertions(+) diff --git a/esphome/automation.py b/esphome/automation.py index 0768bf8869..fab998527f 100644 --- a/esphome/automation.py +++ b/esphome/automation.py @@ -3,6 +3,7 @@ import esphome.config_validation as cv from esphome.const import ( CONF_AUTOMATION_ID, CONF_CONDITION, + CONF_COUNT, CONF_ELSE, CONF_ID, CONF_THEN, @@ -66,6 +67,7 @@ DelayAction = cg.esphome_ns.class_("DelayAction", Action, cg.Component) LambdaAction = cg.esphome_ns.class_("LambdaAction", Action) IfAction = cg.esphome_ns.class_("IfAction", Action) WhileAction = cg.esphome_ns.class_("WhileAction", Action) +RepeatAction = cg.esphome_ns.class_("RepeatAction", Action) WaitUntilAction = cg.esphome_ns.class_("WaitUntilAction", Action, cg.Component) UpdateComponentAction = cg.esphome_ns.class_("UpdateComponentAction", Action) Automation = cg.esphome_ns.class_("Automation") @@ -241,6 +243,25 @@ async def while_action_to_code(config, action_id, template_arg, args): return var +@register_action( + "repeat", + RepeatAction, + cv.Schema( + { + cv.Required(CONF_COUNT): cv.templatable(cv.positive_not_null_int), + cv.Required(CONF_THEN): validate_action_list, + } + ), +) +async def repeat_action_to_code(config, action_id, template_arg, args): + var = cg.new_Pvariable(action_id, template_arg) + count_template = await cg.templatable(config[CONF_COUNT], args, cg.uint32) + cg.add(var.set_count(count_template)) + actions = await build_action_list(config[CONF_THEN], template_arg, args) + cg.add(var.add_then(actions)) + return var + + def validate_wait_until(value): schema = cv.Schema( { diff --git a/esphome/core/base_automation.h b/esphome/core/base_automation.h index d97d369d33..e87a4a2765 100644 --- a/esphome/core/base_automation.h +++ b/esphome/core/base_automation.h @@ -224,6 +224,39 @@ template class WhileAction : public Action { std::tuple var_{}; }; +template class RepeatAction : public Action { + public: + TEMPLATABLE_VALUE(uint32_t, count) + + void add_then(const std::vector *> &actions) { + this->then_.add_actions(actions); + this->then_.add_action(new LambdaAction([this](Ts... x) { + this->iteration_++; + if (this->iteration_ == this->count_.value(x...)) + this->play_next_tuple_(this->var_); + else + this->then_.play_tuple(this->var_); + })); + } + + void play_complex(Ts... x) override { + this->num_running_++; + this->var_ = std::make_tuple(x...); + this->iteration_ = 0; + this->then_.play_tuple(this->var_); + } + + void play(Ts... x) override { /* ignore - see play_complex */ + } + + void stop() override { this->then_.stop(); } + + protected: + uint32_t iteration_; + ActionList then_; + std::tuple var_; +}; + template class WaitUntilAction : public Action, public Component { public: WaitUntilAction(Condition *condition) : condition_(condition) {} diff --git a/tests/test5.yaml b/tests/test5.yaml index 72df3ed212..f1fb786fe5 100644 --- a/tests/test5.yaml +++ b/tests/test5.yaml @@ -173,3 +173,11 @@ sensor: uart_id: uart2 co2: name: CO2 Sensor + +script: + - id: automation_test + then: + - repeat: + count: 5 + then: + - logger.log: "looping!" From 8aa72f4c1efeb46aaa3728bfe553713d644db8d0 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Wed, 10 Nov 2021 19:35:31 +0100 Subject: [PATCH 135/142] Neopixelbus redo method definitions (#2616) --- esphome/components/neopixelbus/_methods.py | 418 +++++++++++++++++++++ esphome/components/neopixelbus/const.py | 42 +++ esphome/components/neopixelbus/light.py | 232 ++++++------ esphome/config_validation.py | 2 +- platformio.ini | 2 +- script/clang-tidy | 1 + 6 files changed, 585 insertions(+), 112 deletions(-) create mode 100644 esphome/components/neopixelbus/_methods.py create mode 100644 esphome/components/neopixelbus/const.py diff --git a/esphome/components/neopixelbus/_methods.py b/esphome/components/neopixelbus/_methods.py new file mode 100644 index 0000000000..b03544f246 --- /dev/null +++ b/esphome/components/neopixelbus/_methods.py @@ -0,0 +1,418 @@ +from dataclasses import dataclass +from typing import Any, List +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.const import ( + CONF_CHANNEL, + CONF_CLOCK_PIN, + CONF_DATA_PIN, + CONF_METHOD, + CONF_PIN, + CONF_SPEED, +) +from esphome.components.esp32 import get_esp32_variant +from esphome.components.esp32.const import ( + VARIANT_ESP32, + VARIANT_ESP32S2, + VARIANT_ESP32C3, +) +from esphome.core import CORE +from .const import ( + CONF_ASYNC, + CONF_BUS, + CHIP_400KBPS, + CHIP_800KBPS, + CHIP_APA106, + CHIP_DOTSTAR, + CHIP_LC8812, + CHIP_LPD6803, + CHIP_LPD8806, + CHIP_P9813, + CHIP_SK6812, + CHIP_TM1814, + CHIP_TM1829, + CHIP_TM1914, + CHIP_WS2801, + CHIP_WS2811, + CHIP_WS2812, + CHIP_WS2812X, + CHIP_WS2813, + ONE_WIRE_CHIPS, + TWO_WIRE_CHIPS, +) + +METHOD_BIT_BANG = "bit_bang" +METHOD_ESP8266_UART = "esp8266_uart" +METHOD_ESP8266_DMA = "esp8266_dma" +METHOD_ESP32_RMT = "esp32_rmt" +METHOD_ESP32_I2S = "esp32_i2s" +METHOD_SPI = "spi" + +CHANNEL_DYNAMIC = "dynamic" +BUS_DYNAMIC = "dynamic" +SPI_BUS_VSPI = "vspi" +SPI_BUS_HSPI = "hspi" +SPI_SPEEDS = [40e6, 20e6, 10e6, 5e6, 2e6, 1e6, 500e3] + + +def _esp32_rmt_default_channel(): + return { + VARIANT_ESP32S2: 1, + VARIANT_ESP32C3: 1, + }.get(get_esp32_variant(), 6) + + +def _validate_esp32_rmt_channel(value): + if isinstance(value, str) and value.lower() == CHANNEL_DYNAMIC: + value = CHANNEL_DYNAMIC + else: + value = cv.int_(value) + variant_channels = { + VARIANT_ESP32: [0, 1, 2, 3, 4, 5, 6, 7, CHANNEL_DYNAMIC], + VARIANT_ESP32S2: [0, 1, 2, 3, CHANNEL_DYNAMIC], + VARIANT_ESP32C3: [0, 1, CHANNEL_DYNAMIC], + } + variant = get_esp32_variant() + if variant not in variant_channels: + raise cv.Invalid(f"{variant} does not support the rmt method") + if value not in variant_channels[variant]: + raise cv.Invalid(f"{variant} does not support rmt channel {value}") + return value + + +def _esp32_i2s_default_bus(): + return { + VARIANT_ESP32: 1, + VARIANT_ESP32S2: 0, + }.get(get_esp32_variant(), 0) + + +def _validate_esp32_i2s_bus(value): + if isinstance(value, str) and value.lower() == CHANNEL_DYNAMIC: + value = CHANNEL_DYNAMIC + else: + value = cv.int_(value) + variant_buses = { + VARIANT_ESP32: [0, 1, BUS_DYNAMIC], + VARIANT_ESP32S2: [0, BUS_DYNAMIC], + } + variant = get_esp32_variant() + if variant not in variant_buses: + raise cv.Invalid(f"{variant} does not support the i2s method") + if value not in variant_buses[variant]: + raise cv.Invalid(f"{variant} does not support i2s bus {value}") + return value + + +neo_ns = cg.global_ns + + +def _bit_bang_to_code(config, chip: str, inverted: bool): + # https://github.com/Makuna/NeoPixelBus/blob/master/src/internal/NeoEspBitBangMethod.h + # Some chips are only aliases + chip = { + CHIP_WS2813: CHIP_WS2812X, + CHIP_LC8812: CHIP_SK6812, + CHIP_TM1914: CHIP_TM1814, + CHIP_WS2812: CHIP_800KBPS, + }.get(chip, chip) + + lookup = { + CHIP_WS2811: (neo_ns.NeoEspBitBangSpeedWs2811, False), + CHIP_WS2812X: (neo_ns.NeoEspBitBangSpeedWs2812x, False), + CHIP_SK6812: (neo_ns.NeoEspBitBangSpeedSk6812, False), + CHIP_TM1814: (neo_ns.NeoEspBitBangSpeedTm1814, True), + CHIP_TM1829: (neo_ns.NeoEspBitBangSpeedTm1829, True), + CHIP_800KBPS: (neo_ns.NeoEspBitBangSpeed800Kbps, False), + CHIP_400KBPS: (neo_ns.NeoEspBitBangSpeed400Kbps, False), + CHIP_APA106: (neo_ns.NeoEspBitBangSpeedApa106, False), + } + # For tm variants opposite of inverted is needed + speed, pinset_inverted = lookup[chip] + pinset = { + False: neo_ns.NeoEspPinset, + True: neo_ns.NeoEspPinsetInverted, + }[inverted != pinset_inverted] + return neo_ns.NeoEspBitBangMethodBase.template(speed, pinset) + + +def _bit_bang_extra_validate(config): + pin = config[CONF_PIN] + if CORE.is_esp8266 and not (0 <= pin <= 15): + # Due to use of w1ts + raise cv.Invalid("Bit bang only supports pins GPIO0-GPIO15 on ESP8266") + if CORE.is_esp32 and not (0 <= pin <= 31): + raise cv.Invalid("Bit bang only supports pins GPIO0-GPIO31 on ESP32") + + +def _esp8266_uart_to_code(config, chip: str, inverted: bool): + # https://github.com/Makuna/NeoPixelBus/blob/master/src/internal/NeoEsp8266UartMethod.h + uart_context, uart_base = { + False: (neo_ns.NeoEsp8266UartContext, neo_ns.NeoEsp8266Uart), + True: (neo_ns.NeoEsp8266UartInterruptContext, neo_ns.NeoEsp8266AsyncUart), + }[config[CONF_ASYNC]] + uart_feature = { + 0: neo_ns.UartFeature0, + 1: neo_ns.UartFeature1, + }[config[CONF_BUS]] + # Some chips are only aliases + chip = { + CHIP_WS2811: CHIP_WS2812X, + CHIP_WS2813: CHIP_WS2812X, + CHIP_LC8812: CHIP_SK6812, + CHIP_TM1914: CHIP_TM1814, + CHIP_WS2812: CHIP_800KBPS, + }.get(chip, chip) + + lookup = { + CHIP_WS2812X: (neo_ns.NeoEsp8266UartSpeedWs2812x, False), + CHIP_SK6812: (neo_ns.NeoEsp8266UartSpeedSk6812, False), + CHIP_TM1814: (neo_ns.NeoEsp8266UartSpeedTm1814, True), + CHIP_TM1829: (neo_ns.NeoEsp8266UartSpeedTm1829, True), + CHIP_800KBPS: (neo_ns.NeoEsp8266UartSpeed800Kbps, False), + CHIP_400KBPS: (neo_ns.NeoEsp8266UartSpeed400Kbps, False), + CHIP_APA106: (neo_ns.NeoEsp8266UartSpeedApa106, False), + } + speed, uart_inverted = lookup[chip] + # For tm variants opposite of inverted is needed + inv = { + False: neo_ns.NeoEsp8266UartNotInverted, + True: neo_ns.NeoEsp8266UartInverted, + }[inverted != uart_inverted] + return neo_ns.NeoEsp8266UartMethodBase.template( + speed, uart_base.template(uart_feature, uart_context), inv + ) + + +def _esp8266_uart_extra_validate(config): + pin = config[CONF_PIN] + bus = config[CONF_METHOD][CONF_BUS] + right_pin = { + 0: 1, # U0TXD + 1: 2, # U1TXD + }[bus] + if pin != right_pin: + raise cv.Invalid(f"ESP8266 uart bus {bus} only supports pin GPIO{right_pin}") + + +def _esp8266_dma_to_code(config, chip: str, inverted: bool): + # https://github.com/Makuna/NeoPixelBus/blob/master/src/internal/NeoEsp8266DmaMethod.h + # Some chips are only aliases + chip = { + CHIP_WS2811: CHIP_WS2812X, + CHIP_WS2813: CHIP_WS2812X, + CHIP_LC8812: CHIP_SK6812, + CHIP_TM1914: CHIP_TM1814, + CHIP_WS2812: CHIP_800KBPS, + }.get(chip, chip) + + lookup = { + (CHIP_WS2812X, False): neo_ns.NeoEsp8266DmaSpeedWs2812x, + (CHIP_SK6812, False): neo_ns.NeoEsp8266DmaSpeedSk6812, + (CHIP_TM1814, True): neo_ns.NeoEsp8266DmaInvertedSpeedTm1814, + (CHIP_TM1829, True): neo_ns.NeoEsp8266DmaInvertedSpeedTm1829, + (CHIP_800KBPS, False): neo_ns.NeoEsp8266DmaSpeed800Kbps, + (CHIP_400KBPS, False): neo_ns.NeoEsp8266DmaSpeed400Kbps, + (CHIP_APA106, False): neo_ns.NeoEsp8266DmaSpeedApa106, + (CHIP_WS2812X, True): neo_ns.NeoEsp8266DmaInvertedSpeedWs2812x, + (CHIP_SK6812, True): neo_ns.NeoEsp8266DmaInvertedSpeedSk6812, + (CHIP_TM1814, False): neo_ns.NeoEsp8266DmaSpeedTm1814, + (CHIP_TM1829, False): neo_ns.NeoEsp8266DmaSpeedTm1829, + (CHIP_800KBPS, True): neo_ns.NeoEsp8266DmaInvertedSpeed800Kbps, + (CHIP_400KBPS, True): neo_ns.NeoEsp8266DmaInvertedSpeed400Kbps, + (CHIP_APA106, True): neo_ns.NeoEsp8266DmaInvertedSpeedApa106, + } + speed = lookup[(chip, inverted)] + return neo_ns.NeoEsp8266DmaMethodBase.template(speed) + + +def _esp8266_dma_extra_validate(config): + if config[CONF_PIN] != 3: + raise cv.Invalid("ESP8266 dma method only supports pin GPIO3") + + +def _esp32_rmt_to_code(config, chip: str, inverted: bool): + # https://github.com/Makuna/NeoPixelBus/blob/master/src/internal/NeoEsp32RmtMethod.h + channel = { + 0: neo_ns.NeoEsp32RmtChannel0, + 1: neo_ns.NeoEsp32RmtChannel1, + 2: neo_ns.NeoEsp32RmtChannel2, + 3: neo_ns.NeoEsp32RmtChannel3, + 4: neo_ns.NeoEsp32RmtChannel4, + 5: neo_ns.NeoEsp32RmtChannel5, + 6: neo_ns.NeoEsp32RmtChannel6, + 7: neo_ns.NeoEsp32RmtChannel7, + CHANNEL_DYNAMIC: neo_ns.NeoEsp32RmtChannelN, + }[config[CONF_CHANNEL]] + # Some chips are only aliases + chip = { + CHIP_WS2813: CHIP_WS2812X, + CHIP_LC8812: CHIP_SK6812, + CHIP_WS2812: CHIP_800KBPS, + }.get(chip, chip) + + lookup = { + (CHIP_WS2811, False): neo_ns.NeoEsp32RmtSpeedWs2811, + (CHIP_WS2812X, False): neo_ns.NeoEsp32RmtSpeedWs2812x, + (CHIP_SK6812, False): neo_ns.NeoEsp32RmtSpeedSk6812, + (CHIP_TM1814, False): neo_ns.NeoEsp32RmtSpeedTm1814, + (CHIP_TM1829, False): neo_ns.NeoEsp32RmtSpeedTm1829, + (CHIP_TM1914, False): neo_ns.NeoEsp32RmtSpeedTm1914, + (CHIP_800KBPS, False): neo_ns.NeoEsp32RmtSpeed800Kbps, + (CHIP_400KBPS, False): neo_ns.NeoEsp32RmtSpeed400Kbps, + (CHIP_APA106, False): neo_ns.NeoEsp32RmtSpeedApa106, + (CHIP_WS2811, True): neo_ns.NeoEsp32RmtInvertedSpeedWs2811, + (CHIP_WS2812X, True): neo_ns.NeoEsp32RmtInvertedSpeedWs2812x, + (CHIP_SK6812, True): neo_ns.NeoEsp32RmtInvertedSpeedSk6812, + (CHIP_TM1814, True): neo_ns.NeoEsp32RmtInvertedSpeedTm1814, + (CHIP_TM1829, True): neo_ns.NeoEsp32RmtInvertedSpeedTm1829, + (CHIP_TM1914, True): neo_ns.NeoEsp32RmtInvertedSpeedTm1914, + (CHIP_800KBPS, True): neo_ns.NeoEsp32RmtInvertedSpeed800Kbps, + (CHIP_400KBPS, True): neo_ns.NeoEsp32RmtInvertedSpeed400Kbps, + (CHIP_APA106, True): neo_ns.NeoEsp32RmtInvertedSpeedApa106, + } + speed = lookup[(chip, inverted)] + return neo_ns.NeoEsp32RmtMethodBase.template(speed, channel) + + +def _esp32_i2s_to_code(config, chip: str, inverted: bool): + # https://github.com/Makuna/NeoPixelBus/blob/master/src/internal/NeoEsp32I2sMethod.h + bus = { + 0: neo_ns.NeoEsp32I2sBusZero, + 1: neo_ns.NeoEsp32I2sBusOne, + BUS_DYNAMIC: neo_ns.NeoEsp32I2sBusN, + }[config[CONF_BUS]] + # Some chips are only aliases + chip = { + CHIP_WS2811: CHIP_WS2812X, + CHIP_WS2813: CHIP_WS2812X, + CHIP_LC8812: CHIP_SK6812, + CHIP_WS2812: CHIP_800KBPS, + }.get(chip, chip) + + lookup = { + CHIP_WS2812X: (neo_ns.NeoEsp32I2sSpeedWs2812x, False), + CHIP_SK6812: (neo_ns.NeoEsp32I2sSpeedSk6812, False), + CHIP_TM1814: (neo_ns.NeoEsp32I2sSpeedTm1814, True), + CHIP_TM1914: (neo_ns.NeoEsp32I2sSpeedTm1914, True), + CHIP_TM1829: (neo_ns.NeoEsp32I2sSpeedTm1829, True), + CHIP_800KBPS: (neo_ns.NeoEsp32I2sSpeed800Kbps, False), + CHIP_400KBPS: (neo_ns.NeoEsp32I2sSpeed400Kbps, False), + CHIP_APA106: (neo_ns.NeoEsp32I2sSpeedApa106, False), + } + speed, inv_inverted = lookup[chip] + # For tm variants opposite of inverted is needed + inv = { + False: neo_ns.NeoEsp32I2sNotInverted, + True: neo_ns.NeoEsp32I2sInverted, + }[inverted != inv_inverted] + return neo_ns.NeoEsp32I2sMethodBase.template(speed, bus, inv) + + +def _spi_to_code(config, chip: str, inverted: bool): + # https://github.com/Makuna/NeoPixelBus/blob/master/src/internal/TwoWireSpiImple.h + spi_imple = { + None: neo_ns.TwoWireSpiImple, + SPI_BUS_VSPI: neo_ns.TwoWireSpiImple, + SPI_BUS_HSPI: neo_ns.TwoWireHspiImple, + }[config.get(CONF_BUS)] + spi_speed = { + 40e6: neo_ns.SpiSpeed40Mhz, + 20e6: neo_ns.SpiSpeed20Mhz, + 10e6: neo_ns.SpiSpeed10Mhz, + 5e6: neo_ns.SpiSpeed5Mhz, + 2e6: neo_ns.SpiSpeed2Mhz, + 1e6: neo_ns.SpiSpeed1Mhz, + 500e3: neo_ns.SpiSpeed500Khz, + }[config[CONF_SPEED]] + chip_method_base = { + CHIP_DOTSTAR: neo_ns.DotStarMethodBase, + CHIP_LPD6803: neo_ns.Lpd6803MethodBase, + CHIP_LPD8806: neo_ns.Lpd8806MethodBase, + CHIP_WS2801: neo_ns.Ws2801MethodBase, + CHIP_P9813: neo_ns.P9813MethodBase, + }[chip] + return chip_method_base.template(spi_imple.template(spi_speed)) + + +def _spi_extra_validate(config): + if CORE.is_esp32: + return + + if config[CONF_DATA_PIN] != 13 and config[CONF_CLOCK_PIN] != 14: + raise cv.Invalid( + "SPI only supports pins GPIO13 for data and GPIO14 for clock on ESP8266" + ) + + +@dataclass +class MethodDescriptor: + method_schema: Any + to_code: Any + supported_chips: List[str] + extra_validate: Any = None + + +METHODS = { + METHOD_BIT_BANG: MethodDescriptor( + method_schema={}, + to_code=_bit_bang_to_code, + extra_validate=_bit_bang_extra_validate, + supported_chips=ONE_WIRE_CHIPS, + ), + METHOD_ESP8266_UART: MethodDescriptor( + method_schema=cv.All( + cv.only_on_esp8266, + { + cv.Optional(CONF_ASYNC, default=False): cv.boolean, + cv.Optional(CONF_BUS, default=1): cv.int_range(min=0, max=1), + }, + ), + extra_validate=_esp8266_uart_extra_validate, + to_code=_esp8266_uart_to_code, + supported_chips=ONE_WIRE_CHIPS, + ), + METHOD_ESP8266_DMA: MethodDescriptor( + method_schema=cv.All(cv.only_on_esp8266, {}), + extra_validate=_esp8266_dma_extra_validate, + to_code=_esp8266_dma_to_code, + supported_chips=ONE_WIRE_CHIPS, + ), + METHOD_ESP32_RMT: MethodDescriptor( + method_schema=cv.All( + cv.only_on_esp32, + { + cv.Optional( + CONF_CHANNEL, default=_esp32_rmt_default_channel + ): _validate_esp32_rmt_channel, + }, + ), + to_code=_esp32_rmt_to_code, + supported_chips=ONE_WIRE_CHIPS, + ), + METHOD_ESP32_I2S: MethodDescriptor( + method_schema=cv.All( + cv.only_on_esp32, + { + cv.Optional( + CONF_BUS, default=_esp32_i2s_default_bus + ): _validate_esp32_i2s_bus, + }, + ), + to_code=_esp32_i2s_to_code, + supported_chips=ONE_WIRE_CHIPS, + ), + METHOD_SPI: MethodDescriptor( + method_schema={ + cv.Optional(CONF_BUS): cv.All( + cv.only_on_esp32, cv.one_of(SPI_BUS_VSPI, SPI_BUS_HSPI, lower=True) + ), + cv.Optional(CONF_SPEED, default="10MHz"): cv.All( + cv.frequency, cv.one_of(*SPI_SPEEDS) + ), + }, + to_code=_spi_to_code, + extra_validate=_spi_extra_validate, + supported_chips=TWO_WIRE_CHIPS, + ), +} diff --git a/esphome/components/neopixelbus/const.py b/esphome/components/neopixelbus/const.py new file mode 100644 index 0000000000..ec1bd74c29 --- /dev/null +++ b/esphome/components/neopixelbus/const.py @@ -0,0 +1,42 @@ +CHIP_DOTSTAR = "dotstar" +CHIP_WS2801 = "ws2801" +CHIP_WS2811 = "ws2811" +CHIP_WS2812 = "ws2812" +CHIP_WS2812X = "ws2812x" +CHIP_WS2813 = "ws2813" +CHIP_SK6812 = "sk6812" +CHIP_TM1814 = "tm1814" +CHIP_TM1829 = "tm1829" +CHIP_TM1914 = "tm1914" +CHIP_800KBPS = "800kbps" +CHIP_400KBPS = "400kbps" +CHIP_APA106 = "apa106" +CHIP_LC8812 = "lc8812" +CHIP_LPD8806 = "lpd8806" +CHIP_LPD6803 = "lpd6803" +CHIP_P9813 = "p9813" + +ONE_WIRE_CHIPS = [ + CHIP_WS2811, + CHIP_WS2812, + CHIP_WS2812X, + CHIP_WS2813, + CHIP_SK6812, + CHIP_TM1814, + CHIP_TM1829, + CHIP_TM1914, + CHIP_800KBPS, + CHIP_400KBPS, + CHIP_APA106, + CHIP_LC8812, +] +TWO_WIRE_CHIPS = [ + CHIP_DOTSTAR, + CHIP_WS2801, + CHIP_LPD6803, + CHIP_LPD8806, + CHIP_P9813, +] +CHIP_TYPES = [*ONE_WIRE_CHIPS, *TWO_WIRE_CHIPS] +CONF_ASYNC = "async" +CONF_BUS = "bus" diff --git a/esphome/components/neopixelbus/light.py b/esphome/components/neopixelbus/light.py index 0117f1b063..6bb1bc8f99 100644 --- a/esphome/components/neopixelbus/light.py +++ b/esphome/components/neopixelbus/light.py @@ -3,6 +3,7 @@ import esphome.config_validation as cv from esphome import pins from esphome.components import light from esphome.const import ( + CONF_CHANNEL, CONF_CLOCK_PIN, CONF_DATA_PIN, CONF_METHOD, @@ -13,7 +14,26 @@ from esphome.const import ( CONF_OUTPUT_ID, CONF_INVERT, ) +from esphome.components.esp32 import get_esp32_variant +from esphome.components.esp32.const import ( + VARIANT_ESP32C3, +) from esphome.core import CORE +from ._methods import ( + METHODS, + METHOD_SPI, + METHOD_ESP8266_UART, + METHOD_BIT_BANG, + METHOD_ESP32_I2S, + METHOD_ESP32_RMT, + METHOD_ESP8266_DMA, +) +from .const import ( + CHIP_TYPES, + CONF_ASYNC, + CONF_BUS, + ONE_WIRE_CHIPS, +) neopixelbus_ns = cg.esphome_ns.namespace("neopixelbus") NeoPixelBusLightOutputBase = neopixelbus_ns.class_( @@ -46,127 +66,115 @@ def validate_type(value): return value -def validate_variant(value): - value = cv.string(value).upper() - if value == "WS2813": - value = "WS2812X" - if value == "WS2812": - value = "800KBPS" - if value == "LC8812": - value = "SK6812" - return cv.one_of(*VARIANTS)(value) +def _choose_default_method(config): + if CONF_METHOD in config: + return config + config = config.copy() + if CONF_PIN not in config: + config[CONF_METHOD] = _validate_method(METHOD_SPI) + return config - -def validate_method(value): - if value is None: - if CORE.is_esp32: - return "ESP32_I2S_1" - if CORE.is_esp8266: - return "ESP8266_DMA" - raise NotImplementedError - - if CORE.is_esp32: - return cv.one_of(*ESP32_METHODS, upper=True, space="_")(value) + pin = config[CONF_PIN] if CORE.is_esp8266: - return cv.one_of(*ESP8266_METHODS, upper=True, space="_")(value) - raise NotImplementedError - - -def validate_method_pin(value): - method = value[CONF_METHOD] - method_pins = { - "ESP8266_DMA": [3], - "ESP8266_UART0": [1], - "ESP8266_ASYNC_UART0": [1], - "ESP8266_UART1": [2], - "ESP8266_ASYNC_UART1": [2], - "ESP32_I2S_0": list(range(0, 32)), - "ESP32_I2S_1": list(range(0, 32)), - } - if CORE.is_esp8266: - method_pins["BIT_BANG"] = list(range(0, 16)) - elif CORE.is_esp32: - method_pins["BIT_BANG"] = list(range(0, 32)) - pins_ = method_pins.get(method) - if pins_ is None: - # all pins allowed for this method - return value - - for opt in (CONF_PIN, CONF_CLOCK_PIN, CONF_DATA_PIN): - if opt in value and value[opt] not in pins_: - raise cv.Invalid( - f"Method {method} only supports pin(s) {', '.join(f'GPIO{x}' for x in pins_)}", - path=[CONF_METHOD], + if pin == 3: + config[CONF_METHOD] = _validate_method(METHOD_ESP8266_DMA) + elif pin == 1: + config[CONF_METHOD] = _validate_method( + { + CONF_TYPE: METHOD_ESP8266_UART, + CONF_BUS: 0, + } + ) + elif pin == 2: + config[CONF_METHOD] = _validate_method( + { + CONF_TYPE: METHOD_ESP8266_UART, + CONF_BUS: 1, + } ) - return value - - -VARIANTS = { - "WS2812X": "Ws2812x", - "SK6812": "Sk6812", - "800KBPS": "800Kbps", - "400KBPS": "400Kbps", -} - -ESP8266_METHODS = { - "ESP8266_DMA": "NeoEsp8266Dma{}Method", - "ESP8266_UART0": "NeoEsp8266Uart0{}Method", - "ESP8266_UART1": "NeoEsp8266Uart1{}Method", - "ESP8266_ASYNC_UART0": "NeoEsp8266AsyncUart0{}Method", - "ESP8266_ASYNC_UART1": "NeoEsp8266AsyncUart1{}Method", - "BIT_BANG": "NeoEsp8266BitBang{}Method", -} -ESP32_METHODS = { - "ESP32_I2S_0": "NeoEsp32I2s0{}Method", - "ESP32_I2S_1": "NeoEsp32I2s1{}Method", - "ESP32_RMT_0": "NeoEsp32Rmt0{}Method", - "ESP32_RMT_1": "NeoEsp32Rmt1{}Method", - "ESP32_RMT_2": "NeoEsp32Rmt2{}Method", - "ESP32_RMT_3": "NeoEsp32Rmt3{}Method", - "ESP32_RMT_4": "NeoEsp32Rmt4{}Method", - "ESP32_RMT_5": "NeoEsp32Rmt5{}Method", - "ESP32_RMT_6": "NeoEsp32Rmt6{}Method", - "ESP32_RMT_7": "NeoEsp32Rmt7{}Method", - "BIT_BANG": "NeoEsp32BitBang{}Method", -} - - -def format_method(config): - variant = VARIANTS[config[CONF_VARIANT]] - method = config[CONF_METHOD] - - if config[CONF_INVERT]: - if method == "ESP8266_DMA": - variant = f"Inverted{variant}" else: - variant += "Inverted" + config[CONF_METHOD] = _validate_method(METHOD_BIT_BANG) - if CORE.is_esp8266: - return ESP8266_METHODS[method].format(variant) if CORE.is_esp32: - return ESP32_METHODS[method].format(variant) - raise NotImplementedError + if get_esp32_variant() == VARIANT_ESP32C3: + config[CONF_METHOD] = _validate_method(METHOD_ESP32_RMT) + else: + config[CONF_METHOD] = _validate_method(METHOD_ESP32_I2S) + + return config def _validate(config): - if CONF_PIN in config: + variant = config[CONF_VARIANT] + if variant in ONE_WIRE_CHIPS: + if CONF_PIN not in config: + raise cv.Invalid( + f"Chip {variant} is a 1-wire chip and needs the [pin] option." + ) if CONF_CLOCK_PIN in config or CONF_DATA_PIN in config: - raise cv.Invalid("Cannot specify both 'pin' and 'clock_pin'+'data_pin'") - return config - if CONF_CLOCK_PIN in config: - if CONF_DATA_PIN not in config: - raise cv.Invalid("If you give clock_pin, you must also specify data_pin") - return config - raise cv.Invalid("Must specify at least one of 'pin' or 'clock_pin'+'data_pin'") + raise cv.Invalid( + f"Chip {variant} is a 1-wire chip, you need to set [pin] instead of ." + ) + else: + if CONF_PIN in config: + raise cv.Invalid( + f"Chip {variant} is a 2-wire chip and needs the [data_pin]+[clock_pin] option instead of [pin]." + ) + if CONF_CLOCK_PIN not in config or CONF_DATA_PIN not in config: + raise cv.Invalid( + f"Chip {variant} is a 2-wire chip, you need to set [data_pin]+[clock_pin]." + ) + + method_type = config[CONF_METHOD][CONF_TYPE] + method_desc = METHODS[method_type] + if variant not in method_desc.supported_chips: + raise cv.Invalid(f"Method {method_type} does not support {variant}") + if method_desc.extra_validate is not None: + method_desc.extra_validate(config) + + return config + + +def _validate_method(value): + if value is None: + # default method is determined afterwards because it depends on the chip type chosen + return None + + compat_methods = {} + for bus in [0, 1]: + for is_async in [False, True]: + compat_methods[f"ESP8266{'_ASYNC' if is_async else ''}_UART{bus}"] = { + CONF_TYPE: METHOD_ESP8266_UART, + CONF_BUS: bus, + CONF_ASYNC: is_async, + } + compat_methods[f"ESP32_I2S_{bus}"] = { + CONF_TYPE: METHOD_ESP32_I2S, + CONF_BUS: bus, + } + for channel in range(8): + compat_methods[f"ESP32_RMT_{channel}"] = { + CONF_TYPE: METHOD_ESP32_RMT, + CONF_CHANNEL: channel, + } + + if isinstance(value, str): + if value.upper() in compat_methods: + return _validate_method(compat_methods[value.upper()]) + return _validate_method({CONF_TYPE: value}) + return cv.typed_schema( + {k: v.method_schema for k, v in METHODS.items()}, lower=True + )(value) CONFIG_SCHEMA = cv.All( + cv.only_with_arduino, light.ADDRESSABLE_LIGHT_SCHEMA.extend( { cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(NeoPixelBusLightOutputBase), cv.Optional(CONF_TYPE, default="GRB"): validate_type, - cv.Optional(CONF_VARIANT, default="800KBPS"): validate_variant, - cv.Optional(CONF_METHOD, default=None): validate_method, + cv.Required(CONF_VARIANT): cv.one_of(*CHIP_TYPES, lower=True), + cv.Optional(CONF_METHOD): _validate_method, cv.Optional(CONF_INVERT, default="no"): cv.boolean, cv.Optional(CONF_PIN): pins.internal_gpio_output_pin_number, cv.Optional(CONF_CLOCK_PIN): pins.internal_gpio_output_pin_number, @@ -174,19 +182,23 @@ CONFIG_SCHEMA = cv.All( cv.Required(CONF_NUM_LEDS): cv.positive_not_null_int, } ).extend(cv.COMPONENT_SCHEMA), + _choose_default_method, _validate, - validate_method_pin, - cv.only_with_arduino, ) async def to_code(config): has_white = "W" in config[CONF_TYPE] - template = cg.TemplateArguments(getattr(cg.global_ns, format_method(config))) + method = config[CONF_METHOD] + + method_template = METHODS[method[CONF_TYPE]].to_code( + method, config[CONF_VARIANT], config[CONF_INVERT] + ) + if has_white: - out_type = NeoPixelRGBWLightOutput.template(template) + out_type = NeoPixelRGBWLightOutput.template(method_template) else: - out_type = NeoPixelRGBLightOutput.template(template) + out_type = NeoPixelRGBLightOutput.template(method_template) rhs = out_type.new() var = cg.Pvariable(config[CONF_OUTPUT_ID], rhs, out_type) await light.register_light(var, config) @@ -204,4 +216,4 @@ async def to_code(config): cg.add(var.set_pixel_order(getattr(ESPNeoPixelOrder, config[CONF_TYPE]))) # https://github.com/Makuna/NeoPixelBus/blob/master/library.json - cg.add_library("makuna/NeoPixelBus", "2.6.7") + cg.add_library("makuna/NeoPixelBus", "2.6.9") diff --git a/esphome/config_validation.py b/esphome/config_validation.py index 3e5c7940a4..2bb45487fa 100644 --- a/esphome/config_validation.py +++ b/esphome/config_validation.py @@ -1399,7 +1399,7 @@ def typed_schema(schemas, **kwargs): if schema_option is None: raise Invalid(f"{key} not specified!") key_v = key_validator(schema_option) - value = schemas[key_v](value) + value = Schema(schemas[key_v])(value) value[key] = key_v return value diff --git a/platformio.ini b/platformio.ini index fa8944bf9a..0dd32268e0 100644 --- a/platformio.ini +++ b/platformio.ini @@ -27,7 +27,7 @@ build_flags = [common] lib_deps = esphome/noise-c@0.1.4 ; api - makuna/NeoPixelBus@2.6.7 ; neopixelbus + makuna/NeoPixelBus@2.6.9 ; neopixelbus build_flags = -DESPHOME_LOG_LEVEL=ESPHOME_LOG_LEVEL_VERY_VERBOSE src_filter = diff --git a/script/clang-tidy b/script/clang-tidy index 87ba1c84b5..ad5fdfeb04 100755 --- a/script/clang-tidy +++ b/script/clang-tidy @@ -32,6 +32,7 @@ def clang_options(idedata): '-D_PGMSPACE_H_', '-Dpgm_read_byte(s)=(*(const uint8_t *)(s))', '-Dpgm_read_byte_near(s)=(*(const uint8_t *)(s))', + '-Dpgm_read_word(s)=(*(const uint16_t *)(s))', '-Dpgm_read_dword(s)=(*(const uint32_t *)(s))', '-DPROGMEM=', '-DPGM_P=const char *', From c422b2fb0b6fb98641dbc6c444c17b284ef8986a Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Wed, 10 Nov 2021 19:40:18 +0100 Subject: [PATCH 136/142] Introduce byteswap helpers (#2661) * Backport std::byteswap() in helpers.h * Introduce convert_big_endian() function * Use convert_big_endian() in i2c byte swap functions --- esphome/components/i2c/i2c.h | 13 +++---------- esphome/core/helpers.h | 25 +++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 10 deletions(-) diff --git a/esphome/components/i2c/i2c.h b/esphome/components/i2c/i2c.h index 7ee4cdd811..50a0b3ae50 100644 --- a/esphome/components/i2c/i2c.h +++ b/esphome/components/i2c/i2c.h @@ -1,6 +1,7 @@ #pragma once #include "i2c_bus.h" +#include "esphome/core/helpers.h" #include "esphome/core/optional.h" #include #include @@ -32,16 +33,8 @@ class I2CRegister { // like ntohs/htons but without including networking headers. // ("i2c" byte order is big-endian) -inline uint16_t i2ctohs(uint16_t i2cshort) { - union { - uint16_t x; - uint8_t y[2]; - } conv; - conv.x = i2cshort; - return ((uint16_t) conv.y[0] << 8) | ((uint16_t) conv.y[1] << 0); -} - -inline uint16_t htoi2cs(uint16_t hostshort) { return i2ctohs(hostshort); } +inline uint16_t i2ctohs(uint16_t i2cshort) { return convert_big_endian(i2cshort); } +inline uint16_t htoi2cs(uint16_t hostshort) { return convert_big_endian(hostshort); } class I2CDevice { public: diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index fde631514b..f29af06d89 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -306,6 +306,31 @@ template T *new_buffer(size_t length) { // --------------------------------------------------------------------------------------------------------------------- +/// @name STL backports +///@{ + +// std::byteswap is from C++23 and technically should be a template, but this will do for now. +constexpr uint8_t byteswap(uint8_t n) { return n; } +constexpr uint16_t byteswap(uint16_t n) { return __builtin_bswap16(n); } +constexpr uint32_t byteswap(uint32_t n) { return __builtin_bswap32(n); } +constexpr uint64_t byteswap(uint64_t n) { return __builtin_bswap64(n); } + +///@} + +/// @name Bit manipulation +///@{ + +/// Convert a value between host byte order and big endian (most significant byte first) order. +template::value, int> = 0> constexpr T convert_big_endian(T val) { +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ + return byteswap(val); +#else + return val; +#endif +} + +///@} + /// @name Parsing & formatting ///@{ From 92321e219ab298045c0948dad3ef50f28993e426 Mon Sep 17 00:00:00 2001 From: TVDLoewe Date: Wed, 10 Nov 2021 19:41:04 +0100 Subject: [PATCH 137/142] Max7219digit multiline (#1622) Co-authored-by: Otto winter Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/max7219digit/display.py | 20 +++- .../components/max7219digit/max7219digit.cpp | 112 +++++++++++------- .../components/max7219digit/max7219digit.h | 25 +++- 3 files changed, 110 insertions(+), 47 deletions(-) diff --git a/esphome/components/max7219digit/display.py b/esphome/components/max7219digit/display.py index e1ca128699..2753f70eef 100644 --- a/esphome/components/max7219digit/display.py +++ b/esphome/components/max7219digit/display.py @@ -13,10 +13,20 @@ CONF_SCROLL_DELAY = "scroll_delay" CONF_SCROLL_ENABLE = "scroll_enable" CONF_SCROLL_MODE = "scroll_mode" CONF_REVERSE_ENABLE = "reverse_enable" +CONF_NUM_CHIP_LINES = "num_chip_lines" +CONF_CHIP_LINES_STYLE = "chip_lines_style" +integration_ns = cg.esphome_ns.namespace("max7219digit") +ChipLinesStyle = integration_ns.enum("ChipLinesStyle") +CHIP_LINES_STYLE = { + "ZIGZAG": ChipLinesStyle.ZIGZAG, + "SNAKE": ChipLinesStyle.SNAKE, +} + +ScrollMode = integration_ns.enum("ScrollMode") SCROLL_MODES = { - "CONTINUOUS": 0, - "STOP": 1, + "CONTINUOUS": ScrollMode.CONTINUOUS, + "STOP": ScrollMode.STOP, } CHIP_MODES = { @@ -37,6 +47,10 @@ CONFIG_SCHEMA = ( { cv.GenerateID(): cv.declare_id(MAX7219Component), cv.Optional(CONF_NUM_CHIPS, default=4): cv.int_range(min=1, max=255), + cv.Optional(CONF_NUM_CHIP_LINES, default=1): cv.int_range(min=1, max=255), + cv.Optional(CONF_CHIP_LINES_STYLE, default="SNAKE"): cv.enum( + CHIP_LINES_STYLE, upper=True + ), cv.Optional(CONF_INTENSITY, default=15): cv.int_range(min=0, max=15), cv.Optional(CONF_ROTATE_CHIP, default="0"): cv.enum(CHIP_MODES, upper=True), cv.Optional(CONF_SCROLL_MODE, default="CONTINUOUS"): cv.enum( @@ -67,6 +81,8 @@ async def to_code(config): await display.register_display(var, config) cg.add(var.set_num_chips(config[CONF_NUM_CHIPS])) + cg.add(var.set_num_chip_lines(config[CONF_NUM_CHIP_LINES])) + cg.add(var.set_chip_lines_style(config[CONF_CHIP_LINES_STYLE])) cg.add(var.set_intensity(config[CONF_INTENSITY])) cg.add(var.set_chip_orientation(config[CONF_ROTATE_CHIP])) cg.add(var.set_scroll_speed(config[CONF_SCROLL_SPEED])) diff --git a/esphome/components/max7219digit/max7219digit.cpp b/esphome/components/max7219digit/max7219digit.cpp index 4fedd3d312..0f86ac635c 100644 --- a/esphome/components/max7219digit/max7219digit.cpp +++ b/esphome/components/max7219digit/max7219digit.cpp @@ -26,9 +26,12 @@ void MAX7219Component::setup() { ESP_LOGCONFIG(TAG, "Setting up MAX7219_DIGITS..."); this->spi_setup(); this->stepsleft_ = 0; - this->max_displaybuffer_.reserve(500); // Create base space to write buffer - // Initialize buffer with 0 for display so all non written pixels are blank - this->max_displaybuffer_.resize(this->num_chips_ * 8, 0); + for (int chip_line = 0; chip_line < this->num_chip_lines_; chip_line++) { + std::vector vec(1); + this->max_displaybuffer_.push_back(vec); + // Initialize buffer with 0 for display so all non written pixels are blank + this->max_displaybuffer_[chip_line].resize(get_width_internal(), 0); + } // let's assume the user has all 8 digits connected, only important in daisy chained setups anyway this->send_to_all_(MAX7219_REGISTER_SCAN_LIMIT, 7); // let's use our own ASCII -> led pattern encoding @@ -46,6 +49,8 @@ void MAX7219Component::setup() { void MAX7219Component::dump_config() { ESP_LOGCONFIG(TAG, "MAX7219DIGIT:"); ESP_LOGCONFIG(TAG, " Number of Chips: %u", this->num_chips_); + ESP_LOGCONFIG(TAG, " Number of Chips Lines: %u", this->num_chip_lines_); + ESP_LOGCONFIG(TAG, " Chips Lines Style : %u", this->chip_lines_style_); ESP_LOGCONFIG(TAG, " Intensity: %u", this->intensity_); ESP_LOGCONFIG(TAG, " Scroll Mode: %u", this->scroll_mode_); ESP_LOGCONFIG(TAG, " Scroll Speed: %u", this->scroll_speed_); @@ -59,19 +64,19 @@ void MAX7219Component::loop() { uint32_t now = millis(); // check if the buffer has shrunk past the current position since last update - if ((this->max_displaybuffer_.size() >= this->old_buffer_size_ + 3) || - (this->max_displaybuffer_.size() <= this->old_buffer_size_ - 3)) { + if ((this->max_displaybuffer_[0].size() >= this->old_buffer_size_ + 3) || + (this->max_displaybuffer_[0].size() <= this->old_buffer_size_ - 3)) { this->stepsleft_ = 0; this->display(); - this->old_buffer_size_ = this->max_displaybuffer_.size(); + this->old_buffer_size_ = this->max_displaybuffer_[0].size(); } // Reset the counter back to 0 when full string has been displayed. - if (this->stepsleft_ > this->max_displaybuffer_.size()) + if (this->stepsleft_ > this->max_displaybuffer_[0].size()) this->stepsleft_ = 0; // Return if there is no need to scroll or scroll is off - if (!this->scroll_ || (this->max_displaybuffer_.size() <= this->num_chips_ * 8)) { + if (!this->scroll_ || (this->max_displaybuffer_[0].size() <= get_width_internal())) { this->display(); return; } @@ -82,8 +87,8 @@ void MAX7219Component::loop() { } // Dwell time at end of string in case of stop at end - if (this->scroll_mode_ == 1) { - if (this->stepsleft_ >= this->max_displaybuffer_.size() - this->num_chips_ * 8 + 1) { + if (this->scroll_mode_ == ScrollMode::STOP) { + if (this->stepsleft_ >= this->max_displaybuffer_[0].size() - get_width_internal() + 1) { if (now - this->last_scroll_ >= this->scroll_dwell_) { this->stepsleft_ = 0; this->last_scroll_ = now; @@ -107,30 +112,53 @@ void MAX7219Component::display() { // Run this routine for the rows of every chip 8x row 0 top to 7 bottom // Fill the pixel parameter with display data // Send the data to the chip - for (uint8_t i = 0; i < this->num_chips_; i++) { - for (uint8_t j = 0; j < 8; j++) { - if (this->reverse_) { - pixels[j] = this->max_displaybuffer_[(this->num_chips_ - i - 1) * 8 + j]; - } else { - pixels[j] = this->max_displaybuffer_[i * 8 + j]; + for (uint8_t chip = 0; chip < this->num_chips_ / this->num_chip_lines_; chip++) { + for (uint8_t chip_line = 0; chip_line < this->num_chip_lines_; chip_line++) { + for (uint8_t j = 0; j < 8; j++) { + bool reverse = + chip_line % 2 != 0 && this->chip_lines_style_ == ChipLinesStyle::SNAKE ? !this->reverse_ : this->reverse_; + if (reverse) { + pixels[j] = + this->max_displaybuffer_[chip_line][(this->num_chips_ / this->num_chip_lines_ - chip - 1) * 8 + j]; + } else { + pixels[j] = this->max_displaybuffer_[chip_line][chip * 8 + j]; + } } + if (chip_line % 2 != 0 && this->chip_lines_style_ == ChipLinesStyle::SNAKE) + this->orientation_ = orientation_180_(); + this->send64pixels(chip_line * this->num_chips_ / this->num_chip_lines_ + chip, pixels); + if (chip_line % 2 != 0 && this->chip_lines_style_ == ChipLinesStyle::SNAKE) + this->orientation_ = orientation_180_(); } - this->send64pixels(i, pixels); + } +} + +uint8_t MAX7219Component::orientation_180_() { + switch (this->orientation_) { + case 0: + return 2; + case 1: + return 3; + case 2: + return 0; + case 3: + return 1; + default: + return 0; } } int MAX7219Component::get_height_internal() { - return 8; // TO BE DONE -> STACK TWO DISPLAYS ON TOP OF EACH OTHER - // TO BE DONE -> CREATE Virtual size of screen and scroll + return 8 * this->num_chip_lines_; // TO BE DONE -> CREATE Virtual size of screen and scroll } -int MAX7219Component::get_width_internal() { return this->num_chips_ * 8; } - -size_t MAX7219Component::get_buffer_length_() { return this->num_chips_ * 8; } +int MAX7219Component::get_width_internal() { return this->num_chips_ / this->num_chip_lines_ * 8; } void HOT MAX7219Component::draw_absolute_pixel_internal(int x, int y, Color color) { - if (x + 1 > this->max_displaybuffer_.size()) { // Extend the display buffer in case required - this->max_displaybuffer_.resize(x + 1, this->bckgrnd_); + if (x + 1 > this->max_displaybuffer_[0].size()) { // Extend the display buffer in case required + for (int chip_line = 0; chip_line < this->num_chip_lines_; chip_line++) { + this->max_displaybuffer_[chip_line].resize(x + 1, this->bckgrnd_); + } } if ((y >= this->get_height_internal()) || (y < 0) || (x < 0)) // If pixel is outside display then dont draw @@ -140,9 +168,9 @@ void HOT MAX7219Component::draw_absolute_pixel_internal(int x, int y, Color colo uint8_t subpos = y; // Y is starting at 0 top left if (color.is_on()) { - this->max_displaybuffer_[pos] |= (1 << subpos); + this->max_displaybuffer_[subpos / 8][pos] |= (1 << subpos % 8); } else { - this->max_displaybuffer_[pos] &= ~(1 << subpos); + this->max_displaybuffer_[subpos / 8][pos] &= ~(1 << subpos % 8); } } @@ -158,8 +186,10 @@ void MAX7219Component::send_to_all_(uint8_t a_register, uint8_t data) { } void MAX7219Component::update() { this->update_ = true; - this->max_displaybuffer_.clear(); - this->max_displaybuffer_.resize(this->num_chips_ * 8, this->bckgrnd_); + for (int chip_line = 0; chip_line < this->num_chip_lines_; chip_line++) { + this->max_displaybuffer_[chip_line].clear(); + this->max_displaybuffer_[chip_line].resize(get_width_internal(), this->bckgrnd_); + } if (this->writer_local_.has_value()) // insert Labda function if available (*this->writer_local_)(*this); } @@ -175,7 +205,7 @@ void MAX7219Component::turn_on_off(bool on_off) { } } -void MAX7219Component::scroll(bool on_off, uint8_t mode, uint16_t speed, uint16_t delay, uint16_t dwell) { +void MAX7219Component::scroll(bool on_off, ScrollMode mode, uint16_t speed, uint16_t delay, uint16_t dwell) { this->set_scroll(on_off); this->set_scroll_mode(mode); this->set_scroll_speed(speed); @@ -183,7 +213,7 @@ void MAX7219Component::scroll(bool on_off, uint8_t mode, uint16_t speed, uint16_ this->set_scroll_delay(delay); } -void MAX7219Component::scroll(bool on_off, uint8_t mode) { +void MAX7219Component::scroll(bool on_off, ScrollMode mode) { this->set_scroll(on_off); this->set_scroll_mode(mode); } @@ -196,24 +226,26 @@ void MAX7219Component::intensity(uint8_t intensity) { void MAX7219Component::scroll(bool on_off) { this->set_scroll(on_off); } void MAX7219Component::scroll_left() { - if (this->update_) { - this->max_displaybuffer_.push_back(this->bckgrnd_); - for (uint16_t i = 0; i < this->stepsleft_; i++) { - this->max_displaybuffer_.push_back(this->max_displaybuffer_.front()); - this->max_displaybuffer_.erase(this->max_displaybuffer_.begin()); - this->update_ = false; + for (int chip_line = 0; chip_line < this->num_chip_lines_; chip_line++) { + if (this->update_) { + this->max_displaybuffer_[chip_line].push_back(this->bckgrnd_); + for (uint16_t i = 0; i < this->stepsleft_; i++) { + this->max_displaybuffer_[chip_line].push_back(this->max_displaybuffer_[chip_line].front()); + this->max_displaybuffer_[chip_line].erase(this->max_displaybuffer_[chip_line].begin()); + } + } else { + this->max_displaybuffer_[chip_line].push_back(this->max_displaybuffer_[chip_line].front()); + this->max_displaybuffer_[chip_line].erase(this->max_displaybuffer_[chip_line].begin()); } - } else { - this->max_displaybuffer_.push_back(this->max_displaybuffer_.front()); - this->max_displaybuffer_.erase(this->max_displaybuffer_.begin()); } + this->update_ = false; this->stepsleft_++; } void MAX7219Component::send_char(uint8_t chip, uint8_t data) { // get this character from PROGMEM for (uint8_t i = 0; i < 8; i++) - this->max_displaybuffer_[chip * 8 + i] = progmem_read_byte(&MAX7219_DOT_MATRIX_FONT[data][i]); + this->max_displaybuffer_[0][chip * 8 + i] = progmem_read_byte(&MAX7219_DOT_MATRIX_FONT[data][i]); } // end of send_char // send one character (data) to position (chip) diff --git a/esphome/components/max7219digit/max7219digit.h b/esphome/components/max7219digit/max7219digit.h index 02fe8b6f42..3bf934632f 100644 --- a/esphome/components/max7219digit/max7219digit.h +++ b/esphome/components/max7219digit/max7219digit.h @@ -12,6 +12,16 @@ namespace esphome { namespace max7219digit { +enum ChipLinesStyle { + ZIGZAG = 0, + SNAKE, +}; + +enum ScrollMode { + CONTINUOUS = 0, + STOP, +}; + class MAX7219Component; using max7219_writer_t = std::function; @@ -46,20 +56,22 @@ class MAX7219Component : public PollingComponent, void set_intensity(uint8_t intensity) { this->intensity_ = intensity; }; void set_num_chips(uint8_t num_chips) { this->num_chips_ = num_chips; }; + void set_num_chip_lines(uint8_t num_chip_lines) { this->num_chip_lines_ = num_chip_lines; }; + void set_chip_lines_style(ChipLinesStyle chip_lines_style) { this->chip_lines_style_ = chip_lines_style; }; void set_chip_orientation(uint8_t rotate) { this->orientation_ = rotate; }; void set_scroll_speed(uint16_t speed) { this->scroll_speed_ = speed; }; void set_scroll_dwell(uint16_t dwell) { this->scroll_dwell_ = dwell; }; void set_scroll_delay(uint16_t delay) { this->scroll_delay_ = delay; }; void set_scroll(bool on_off) { this->scroll_ = on_off; }; - void set_scroll_mode(uint8_t mode) { this->scroll_mode_ = mode; }; + void set_scroll_mode(ScrollMode mode) { this->scroll_mode_ = mode; }; void set_reverse(bool on_off) { this->reverse_ = on_off; }; void send_char(uint8_t chip, uint8_t data); void send64pixels(uint8_t chip, const uint8_t pixels[8]); void scroll_left(); - void scroll(bool on_off, uint8_t mode, uint16_t speed, uint16_t delay, uint16_t dwell); - void scroll(bool on_off, uint8_t mode); + void scroll(bool on_off, ScrollMode mode, uint16_t speed, uint16_t delay, uint16_t dwell); + void scroll(bool on_off, ScrollMode mode); void scroll(bool on_off); void intensity(uint8_t intensity); @@ -84,9 +96,12 @@ class MAX7219Component : public PollingComponent, protected: void send_byte_(uint8_t a_register, uint8_t data); void send_to_all_(uint8_t a_register, uint8_t data); + uint8_t orientation_180_(); uint8_t intensity_; /// Intensity of the display from 0 to 15 (most) uint8_t num_chips_; + uint8_t num_chip_lines_; + ChipLinesStyle chip_lines_style_; bool scroll_; bool reverse_; bool update_{false}; @@ -94,11 +109,11 @@ class MAX7219Component : public PollingComponent, uint16_t scroll_delay_; uint16_t scroll_dwell_; uint16_t old_buffer_size_ = 0; - uint8_t scroll_mode_; + ScrollMode scroll_mode_; bool invert_ = false; uint8_t orientation_; uint8_t bckgrnd_ = 0x0; - std::vector max_displaybuffer_; + std::vector> max_displaybuffer_; uint32_t last_scroll_ = 0; uint16_t stepsleft_; size_t get_buffer_length_(); From 4d433968359d54b6d48c3e1cbb6a024aeaffe08f Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Wed, 10 Nov 2021 19:42:41 +0100 Subject: [PATCH 138/142] Clean-up string sanitation helpers (#2660) --- esphome/components/mqtt/mqtt_component.cpp | 4 +- esphome/core/entity_base.cpp | 2 +- esphome/core/helpers.cpp | 47 +++++++++------------- esphome/core/helpers.h | 28 ++++++------- 4 files changed, 37 insertions(+), 44 deletions(-) diff --git a/esphome/components/mqtt/mqtt_component.cpp b/esphome/components/mqtt/mqtt_component.cpp index cebb8dd086..e3ae4dea50 100644 --- a/esphome/components/mqtt/mqtt_component.cpp +++ b/esphome/components/mqtt/mqtt_component.cpp @@ -17,7 +17,7 @@ static const char *const TAG = "mqtt.component"; void MQTTComponent::set_retain(bool retain) { this->retain_ = retain; } std::string MQTTComponent::get_discovery_topic_(const MQTTDiscoveryInfo &discovery_info) const { - std::string sanitized_name = sanitize_string_allowlist(App.get_name(), HOSTNAME_CHARACTER_ALLOWLIST); + std::string sanitized_name = str_sanitize(App.get_name()); return discovery_info.prefix + "/" + this->component_type() + "/" + sanitized_name + "/" + this->get_default_object_id_() + "/config"; } @@ -136,7 +136,7 @@ bool MQTTComponent::is_discovery_enabled() const { } std::string MQTTComponent::get_default_object_id_() const { - return sanitize_string_allowlist(to_lowercase_underscore(this->friendly_name()), HOSTNAME_CHARACTER_ALLOWLIST); + return str_sanitize(str_snake_case(this->friendly_name())); } void MQTTComponent::subscribe(const std::string &topic, mqtt_callback_t callback, uint8_t qos) { diff --git a/esphome/core/entity_base.cpp b/esphome/core/entity_base.cpp index 41f08b28a6..a9e1414018 100644 --- a/esphome/core/entity_base.cpp +++ b/esphome/core/entity_base.cpp @@ -35,7 +35,7 @@ const std::string &EntityBase::get_object_id() { return this->object_id_; } // Calculate Object ID Hash from Entity Name void EntityBase::calc_object_id_() { - this->object_id_ = sanitize_string_allowlist(to_lowercase_underscore(this->name_), HOSTNAME_CHARACTER_ALLOWLIST); + this->object_id_ = str_sanitize(str_snake_case(this->name_)); // FNV-1 hash this->object_id_hash_ = fnv1_hash(this->object_id_); } diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index 3047facf45..edd2f74c12 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -128,31 +128,6 @@ float gamma_uncorrect(float value, float gamma) { return powf(value, 1 / gamma); } -std::string to_lowercase_underscore(std::string s) { - std::transform(s.begin(), s.end(), s.begin(), ::tolower); - std::replace(s.begin(), s.end(), ' ', '_'); - return s; -} - -std::string sanitize_string_allowlist(const std::string &s, const std::string &allowlist) { - std::string out(s); - out.erase(std::remove_if(out.begin(), out.end(), - [&allowlist](const char &c) { return allowlist.find(c) == std::string::npos; }), - out.end()); - return out; -} - -std::string sanitize_hostname(const std::string &hostname) { - std::string s = sanitize_string_allowlist(hostname, HOSTNAME_CHARACTER_ALLOWLIST); - return truncate_string(s, 63); -} - -std::string truncate_string(const std::string &s, size_t length) { - if (s.length() > length) - return s.substr(0, length); - return s; -} - std::string value_accuracy_to_string(float value, int8_t accuracy_decimals) { if (accuracy_decimals < 0) { auto multiplier = powf(10.0f, accuracy_decimals); @@ -191,8 +166,6 @@ ParseOnOffState parse_on_off(const char *str, const char *on, const char *off) { return PARSE_NONE; } -const char *const HOSTNAME_CHARACTER_ALLOWLIST = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_"; - uint8_t crc8(uint8_t *data, uint8_t len) { uint8_t crc = 0; @@ -481,4 +454,24 @@ IRAM_ATTR InterruptLock::InterruptLock() { portDISABLE_INTERRUPTS(); } IRAM_ATTR InterruptLock::~InterruptLock() { portENABLE_INTERRUPTS(); } #endif +// --------------------------------------------------------------------------------------------------------------------- + +std::string str_truncate(const std::string &str, size_t length) { + return str.length() > length ? str.substr(0, length) : str; +} +std::string str_snake_case(const std::string &str) { + std::string result; + result.resize(str.length()); + std::transform(str.begin(), str.end(), result.begin(), ::tolower); + std::replace(result.begin(), result.end(), ' ', '_'); + return result; +} +std::string str_sanitize(const std::string &str) { + std::string out; + std::copy_if(str.begin(), str.end(), std::back_inserter(out), [](const char &c) { + return c == '-' || c == '_' || (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); + }); + return out; +} + } // namespace esphome diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index f29af06d89..9a60d036e4 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -25,9 +25,6 @@ namespace esphome { -/// The characters that are allowed in a hostname. -extern const char *const HOSTNAME_CHARACTER_ALLOWLIST; - /// Read the raw MAC address into the provided byte array (6 bytes). void get_mac_address_raw(uint8_t *mac); @@ -55,14 +52,6 @@ std::string to_string(double val); std::string to_string(long double val); optional parse_hex(const std::string &str, size_t start, size_t length); optional parse_hex(char chr); -/// Sanitize the hostname by removing characters that are not in the allowlist and truncating it to 63 chars. -std::string sanitize_hostname(const std::string &hostname); - -/// Truncate a string to a specific length -std::string truncate_string(const std::string &s, size_t length); - -/// Convert the string to lowercase_underscore. -std::string to_lowercase_underscore(std::string s); /// Compare string a to string b (ignoring case) and return whether they are equal. bool str_equals_case_insensitive(const std::string &a, const std::string &b); @@ -145,9 +134,6 @@ std::string uint64_to_string(uint64_t num); /// Convert a uint32_t to a hex string std::string uint32_to_string(uint32_t num); -/// Sanitizes the input string with the allowlist. -std::string sanitize_string_allowlist(const std::string &s, const std::string &allowlist); - uint8_t reverse_bits_8(uint8_t x); uint16_t reverse_bits_16(uint16_t x); uint32_t reverse_bits_32(uint32_t x); @@ -331,6 +317,20 @@ template::value, int> = 0> constexpr ///@} +/// @name Strings +///@{ + +/// Truncate a string to a specific length. +std::string str_truncate(const std::string &str, size_t length); + +/// Convert the string to snake case (lowercase with underscores). +std::string str_snake_case(const std::string &str); + +/// Sanitizes the input string by removing all characters but alphanumerics, dashes and underscores. +std::string str_sanitize(const std::string &str); + +///@} + /// @name Parsing & formatting ///@{ From 99c775d8cbad9afe3be4ae6c9a446dde34c1f3cd Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Wed, 10 Nov 2021 19:44:01 +0100 Subject: [PATCH 139/142] Introduce encode_value/decode_value() template functions (#2662) --- esphome/components/ccs811/ccs811.cpp | 2 +- esphome/core/helpers.cpp | 11 -------- esphome/core/helpers.h | 42 +++++++++++++++++++++++----- 3 files changed, 36 insertions(+), 19 deletions(-) diff --git a/esphome/components/ccs811/ccs811.cpp b/esphome/components/ccs811/ccs811.cpp index 11a66f5100..f8cee79c55 100644 --- a/esphome/components/ccs811/ccs811.cpp +++ b/esphome/components/ccs811/ccs811.cpp @@ -52,7 +52,7 @@ void CCS811Component::setup() { if (this->baseline_.has_value()) { // baseline available, write to sensor - this->write_bytes(0x11, decode_uint16(*this->baseline_)); + this->write_bytes(0x11, decode_value(*this->baseline_)); } auto hardware_version_data = this->read_bytes<1>(0x21); diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index edd2f74c12..daca3ffd32 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -350,17 +350,6 @@ std::string str_sprintf(const char *fmt, ...) { return str; } -uint16_t encode_uint16(uint8_t msb, uint8_t lsb) { return (uint16_t(msb) << 8) | uint16_t(lsb); } -std::array decode_uint16(uint16_t value) { - uint8_t msb = (value >> 8) & 0xFF; - uint8_t lsb = (value >> 0) & 0xFF; - return {msb, lsb}; -} - -uint32_t encode_uint32(uint8_t msb, uint8_t byte2, uint8_t byte3, uint8_t lsb) { - return (uint32_t(msb) << 24) | (uint32_t(byte2) << 16) | (uint32_t(byte3) << 8) | uint32_t(lsb); -} - std::string hexencode(const uint8_t *data, uint32_t len) { char buf[20]; std::string res; diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index 9a60d036e4..c67ad8eea3 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -138,13 +138,6 @@ uint8_t reverse_bits_8(uint8_t x); uint16_t reverse_bits_16(uint16_t x); uint32_t reverse_bits_32(uint32_t x); -/// Encode a 16-bit unsigned integer given a most and least-significant byte. -uint16_t encode_uint16(uint8_t msb, uint8_t lsb); -/// Decode a 16-bit unsigned integer into an array of two values: most significant byte, least significant byte. -std::array decode_uint16(uint16_t value); -/// Encode a 32-bit unsigned integer given four bytes in MSB -> LSB order -uint32_t encode_uint32(uint8_t msb, uint8_t byte2, uint8_t byte3, uint8_t lsb); - /// Convert RGB floats (0-1) to hue (0-360) & saturation/value percentage (0-1) void rgb_to_hsv(float red, float green, float blue, int &hue, float &saturation, float &value); /// Convert hue (0-360) & saturation/value percentage (0-1) to RGB floats (0-1) @@ -306,6 +299,41 @@ constexpr uint64_t byteswap(uint64_t n) { return __builtin_bswap64(n); } /// @name Bit manipulation ///@{ +/// Encode a 16-bit value given the most and least significant byte. +constexpr uint16_t encode_uint16(uint8_t msb, uint8_t lsb) { + return (static_cast(msb) << 8) | (static_cast(lsb)); +} +/// Encode a 32-bit value given four bytes in most to least significant byte order. +constexpr uint32_t encode_uint32(uint8_t byte1, uint8_t byte2, uint8_t byte3, uint8_t byte4) { + return (static_cast(byte1) << 24) | (static_cast(byte2) << 16) | + (static_cast(byte3) << 8) | (static_cast(byte4)); +} + +/// Encode a value from its constituent bytes (from most to least significant) in an array with length sizeof(T). +template::value, int> = 0> inline T encode_value(const uint8_t *bytes) { + T val = 0; + for (size_t i = 0; i < sizeof(T); i++) { + val <<= 8; + val |= bytes[i]; + } + return val; +} +/// Encode a value from its constituent bytes (from most to least significant) in an std::array with length sizeof(T). +template::value, int> = 0> +inline T encode_value(const std::array bytes) { + return encode_value(bytes.data()); +} +/// Decode a value into its constituent bytes (from most to least significant). +template::value, int> = 0> +inline std::array decode_value(T val) { + std::array ret{}; + for (size_t i = sizeof(T); i > 0; i--) { + ret[i - 1] = val & 0xFF; + val >>= 8; + } + return ret; +} + /// Convert a value between host byte order and big endian (most significant byte first) order. template::value, int> = 0> constexpr T convert_big_endian(T val) { #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ From 0bdb48bcac9244df6700410b9cf4ec581dbbda38 Mon Sep 17 00:00:00 2001 From: Laszlo Gazdag Date: Wed, 10 Nov 2021 20:31:22 +0100 Subject: [PATCH 140/142] Make OTA function switchable in web_server component (#2685) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/web_server/__init__.py | 3 +++ esphome/components/web_server/web_server.cpp | 16 +++++++++++----- esphome/components/web_server/web_server.h | 7 +++++++ tests/test1.yaml | 1 + tests/test4.yaml | 1 + 5 files changed, 23 insertions(+), 5 deletions(-) diff --git a/esphome/components/web_server/__init__.py b/esphome/components/web_server/__init__.py index ba2d866593..61b1fa5ad6 100644 --- a/esphome/components/web_server/__init__.py +++ b/esphome/components/web_server/__init__.py @@ -12,6 +12,7 @@ from esphome.const import ( CONF_AUTH, CONF_USERNAME, CONF_PASSWORD, + CONF_OTA, ) from esphome.core import CORE, coroutine_with_priority @@ -41,6 +42,7 @@ CONFIG_SCHEMA = cv.Schema( cv.GenerateID(CONF_WEB_SERVER_BASE_ID): cv.use_id( web_server_base.WebServerBase ), + cv.Optional(CONF_OTA, default=True): cv.boolean, } ).extend(cv.COMPONENT_SCHEMA) @@ -57,6 +59,7 @@ async def to_code(config): cg.add_define("USE_WEBSERVER") cg.add(var.set_css_url(config[CONF_CSS_URL])) cg.add(var.set_js_url(config[CONF_JS_URL])) + cg.add(var.set_allow_ota(config[CONF_OTA])) if CONF_AUTH in config: cg.add(paren.set_auth_username(config[CONF_AUTH][CONF_USERNAME])) cg.add(paren.set_auth_password(config[CONF_AUTH][CONF_PASSWORD])) diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index 17b17fcc3c..6f47f460af 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -152,7 +152,9 @@ void WebServer::setup() { #endif this->base_->add_handler(&this->events_); this->base_->add_handler(this); - this->base_->add_ota_handler(); + + if (this->allow_ota_) + this->base_->add_ota_handler(); this->set_interval(10000, [this]() { this->events_.send("", "ping", millis(), 30000); }); } @@ -240,10 +242,14 @@ void WebServer::handle_index_request(AsyncWebServerRequest *request) { #endif stream->print(F("

See ESPHome Web API for " - "REST API documentation.

" - "

OTA Update

" - "

Debug Log

"));
+                  "REST API documentation.

")); + if (this->allow_ota_) { + stream->print( + F("

OTA Update

")); + } + stream->print(F("

Debug Log

"));
+
 #ifdef WEBSERVER_JS_INCLUDE
   if (this->js_include_ != nullptr) {
     stream->print(F(""));
diff --git a/esphome/components/web_server/web_server.h b/esphome/components/web_server/web_server.h
index 021d5a0646..cdfec51cf1 100644
--- a/esphome/components/web_server/web_server.h
+++ b/esphome/components/web_server/web_server.h
@@ -58,6 +58,12 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
    */
   void set_js_include(const char *js_include);
 
+  /** Set whether or not the webserver should expose the OTA form and handler.
+   *
+   * @param allow_ota.
+   */
+  void set_allow_ota(bool allow_ota) { this->allow_ota_ = allow_ota; }
+
   // ========== INTERNAL METHODS ==========
   // (In most use cases you won't need these)
   /// Setup the internal web server and register handlers.
@@ -182,6 +188,7 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
   const char *css_include_{nullptr};
   const char *js_url_{nullptr};
   const char *js_include_{nullptr};
+  bool allow_ota_{true};
 };
 
 }  // namespace web_server
diff --git a/tests/test1.yaml b/tests/test1.yaml
index 585e01635f..dee7493bf2 100644
--- a/tests/test1.yaml
+++ b/tests/test1.yaml
@@ -234,6 +234,7 @@ logger:
 
 web_server:
   port: 8080
+  ota: true
   css_url: https://esphome.io/_static/webserver-v1.min.css
   js_url: https://esphome.io/_static/webserver-v1.min.js
 
diff --git a/tests/test4.yaml b/tests/test4.yaml
index 4228c7494c..938145235a 100644
--- a/tests/test4.yaml
+++ b/tests/test4.yaml
@@ -45,6 +45,7 @@ logger:
   level: DEBUG
 
 web_server:
+  ota: false
   auth:
     username: admin
     password: admin

From 5ff7c8418c584fc10f49caaaf9736ffffb321c00 Mon Sep 17 00:00:00 2001
From: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
Date: Thu, 11 Nov 2021 08:55:45 +1300
Subject: [PATCH 141/142] Implement Improv via Serial component (#2423)

Co-authored-by: Paulus Schoutsen 
---
 CODEOWNERS                                    |   1 +
 .../captive_portal/captive_portal.cpp         |   1 +
 esphome/components/esp32/__init__.py          |   2 +
 esphome/components/esp32/const.py             |   8 +
 esphome/components/esp8266/__init__.py        |   1 +
 esphome/components/improv/improv.cpp          |  12 +-
 esphome/components/improv/improv.h            |  10 +-
 esphome/components/improv_serial/__init__.py  |  33 +++
 .../improv_serial/improv_serial_component.cpp | 250 ++++++++++++++++++
 .../improv_serial/improv_serial_component.h   |  69 +++++
 esphome/components/logger/logger.cpp          |   2 +-
 esphome/components/web_server/__init__.py     |   2 +
 esphome/components/wifi/__init__.py           |   3 +-
 esphome/components/wifi/wifi_component.cpp    |   2 -
 esphome/core/defines.h                        |   1 +
 script/ci-custom.py                           |   6 +-
 tests/test3.yaml                              |   2 +
 17 files changed, 391 insertions(+), 14 deletions(-)
 create mode 100644 esphome/components/improv_serial/__init__.py
 create mode 100644 esphome/components/improv_serial/improv_serial_component.cpp
 create mode 100644 esphome/components/improv_serial/improv_serial_component.h

diff --git a/CODEOWNERS b/CODEOWNERS
index 8f98fe1f7f..18b4564280 100644
--- a/CODEOWNERS
+++ b/CODEOWNERS
@@ -73,6 +73,7 @@ esphome/components/homeassistant/* @OttoWinter
 esphome/components/hrxl_maxsonar_wr/* @netmikey
 esphome/components/i2c/* @esphome/core
 esphome/components/improv/* @jesserockz
+esphome/components/improv_serial/* @esphome/core
 esphome/components/inkbird_ibsth1_mini/* @fkirill
 esphome/components/inkplate6/* @jesserockz
 esphome/components/integration/* @OttoWinter
diff --git a/esphome/components/captive_portal/captive_portal.cpp b/esphome/components/captive_portal/captive_portal.cpp
index 9e00adae3d..ad4c32bb1f 100644
--- a/esphome/components/captive_portal/captive_portal.cpp
+++ b/esphome/components/captive_portal/captive_portal.cpp
@@ -67,6 +67,7 @@ void CaptivePortal::handle_wifisave(AsyncWebServerRequest *request) {
   ESP_LOGI(TAG, "  SSID='%s'", ssid.c_str());
   ESP_LOGI(TAG, "  Password=" LOG_SECRET("'%s'"), psk.c_str());
   wifi::global_wifi_component->save_wifi_sta(ssid, psk);
+  wifi::global_wifi_component->start_scanning();
   request->redirect("/?save=true");
 }
 
diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py
index d84663b2d6..1c249476e7 100644
--- a/esphome/components/esp32/__init__.py
+++ b/esphome/components/esp32/__init__.py
@@ -28,6 +28,7 @@ from .const import (  # noqa
     KEY_SDKCONFIG_OPTIONS,
     KEY_VARIANT,
     VARIANT_ESP32C3,
+    VARIANT_FRIENDLY,
     VARIANTS,
 )
 from .boards import BOARD_TO_VARIANT
@@ -287,6 +288,7 @@ async def to_code(config):
     cg.add_build_flag("-DUSE_ESP32")
     cg.add_define("ESPHOME_BOARD", config[CONF_BOARD])
     cg.add_build_flag(f"-DUSE_ESP32_VARIANT_{config[CONF_VARIANT]}")
+    cg.add_define("ESPHOME_VARIANT", VARIANT_FRIENDLY[config[CONF_VARIANT]])
 
     cg.add_platformio_option("lib_ldf_mode", "off")
 
diff --git a/esphome/components/esp32/const.py b/esphome/components/esp32/const.py
index b82f03bf68..d92b449ee9 100644
--- a/esphome/components/esp32/const.py
+++ b/esphome/components/esp32/const.py
@@ -18,4 +18,12 @@ VARIANTS = [
     VARIANT_ESP32H2,
 ]
 
+VARIANT_FRIENDLY = {
+    VARIANT_ESP32: "ESP32",
+    VARIANT_ESP32S2: "ESP32-S2",
+    VARIANT_ESP32S3: "ESP32-S3",
+    VARIANT_ESP32C3: "ESP32-C3",
+    VARIANT_ESP32H2: "ESP32-H2",
+}
+
 esp32_ns = cg.esphome_ns.namespace("esp32")
diff --git a/esphome/components/esp8266/__init__.py b/esphome/components/esp8266/__init__.py
index 5b97d2d9d5..34c792499d 100644
--- a/esphome/components/esp8266/__init__.py
+++ b/esphome/components/esp8266/__init__.py
@@ -156,6 +156,7 @@ async def to_code(config):
     cg.add_platformio_option("board", config[CONF_BOARD])
     cg.add_build_flag("-DUSE_ESP8266")
     cg.add_define("ESPHOME_BOARD", config[CONF_BOARD])
+    cg.add_define("ESPHOME_VARIANT", "ESP8266")
 
     conf = config[CONF_FRAMEWORK]
     cg.add_platformio_option("framework", "arduino")
diff --git a/esphome/components/improv/improv.cpp b/esphome/components/improv/improv.cpp
index 4f6ed7702d..94068bc626 100644
--- a/esphome/components/improv/improv.cpp
+++ b/esphome/components/improv/improv.cpp
@@ -7,11 +7,13 @@ ImprovCommand parse_improv_data(const std::vector &data) {
 }
 
 ImprovCommand parse_improv_data(const uint8_t *data, size_t length) {
+  ImprovCommand improv_command;
   Command command = (Command) data[0];
   uint8_t data_length = data[1];
 
   if (data_length != length - 3) {
-    return {.command = UNKNOWN};
+    improv_command.command = UNKNOWN;
+    return improv_command;
   }
 
   uint8_t checksum = data[length - 1];
@@ -22,7 +24,8 @@ ImprovCommand parse_improv_data(const uint8_t *data, size_t length) {
   }
 
   if ((uint8_t) calculated_checksum != checksum) {
-    return {.command = BAD_CHECKSUM};
+    improv_command.command = BAD_CHECKSUM;
+    return improv_command;
   }
 
   if (command == WIFI_SETTINGS) {
@@ -39,9 +42,8 @@ ImprovCommand parse_improv_data(const uint8_t *data, size_t length) {
     return {.command = command, .ssid = ssid, .password = password};
   }
 
-  return {
-      .command = command,
-  };
+  improv_command.command = command;
+  return improv_command;
 }
 
 std::vector build_rpc_response(Command command, const std::vector &datum) {
diff --git a/esphome/components/improv/improv.h b/esphome/components/improv/improv.h
index 0ead80e2cf..542eb82bd3 100644
--- a/esphome/components/improv/improv.h
+++ b/esphome/components/improv/improv.h
@@ -1,8 +1,8 @@
 #pragma once
 
-#ifdef USE_ARDUINO
+#ifdef ARDUINO
 #include "WString.h"
-#endif  // USE_ARDUINO
+#endif  // ARDUINO
 
 #include 
 #include 
@@ -38,6 +38,8 @@ enum Command : uint8_t {
   UNKNOWN = 0x00,
   WIFI_SETTINGS = 0x01,
   IDENTIFY = 0x02,
+  GET_CURRENT_STATE = 0x02,
+  GET_DEVICE_INFO = 0x03,
   BAD_CHECKSUM = 0xFF,
 };
 
@@ -53,8 +55,8 @@ ImprovCommand parse_improv_data(const std::vector &data);
 ImprovCommand parse_improv_data(const uint8_t *data, size_t length);
 
 std::vector build_rpc_response(Command command, const std::vector &datum);
-#ifdef USE_ARDUINO
+#ifdef ARDUINO
 std::vector build_rpc_response(Command command, const std::vector &datum);
-#endif  // USE_ARDUINO
+#endif  // ARDUINO
 
 }  // namespace improv
diff --git a/esphome/components/improv_serial/__init__.py b/esphome/components/improv_serial/__init__.py
new file mode 100644
index 0000000000..b1cdc2d93e
--- /dev/null
+++ b/esphome/components/improv_serial/__init__.py
@@ -0,0 +1,33 @@
+from esphome.const import CONF_BAUD_RATE, CONF_ID, CONF_LOGGER
+import esphome.codegen as cg
+import esphome.config_validation as cv
+import esphome.final_validate as fv
+
+CODEOWNERS = ["@esphome/core"]
+DEPENDENCIES = ["logger", "wifi"]
+AUTO_LOAD = ["improv"]
+
+improv_serial_ns = cg.esphome_ns.namespace("improv_serial")
+
+ImprovSerialComponent = improv_serial_ns.class_("ImprovSerialComponent", cg.Component)
+
+CONFIG_SCHEMA = cv.Schema(
+    {
+        cv.GenerateID(): cv.declare_id(ImprovSerialComponent),
+    }
+).extend(cv.COMPONENT_SCHEMA)
+
+
+def validate_logger_baud_rate(config):
+    logger_conf = fv.full_config.get()[CONF_LOGGER]
+    if logger_conf[CONF_BAUD_RATE] == 0:
+        raise cv.Invalid("improv_serial requires the logger baud_rate to be not 0")
+    return config
+
+
+FINAL_VALIDATE_SCHEMA = validate_logger_baud_rate
+
+
+async def to_code(config):
+    var = cg.new_Pvariable(config[CONF_ID])
+    await cg.register_component(var, config)
diff --git a/esphome/components/improv_serial/improv_serial_component.cpp b/esphome/components/improv_serial/improv_serial_component.cpp
new file mode 100644
index 0000000000..a12f1bd83b
--- /dev/null
+++ b/esphome/components/improv_serial/improv_serial_component.cpp
@@ -0,0 +1,250 @@
+#include "improv_serial_component.h"
+
+#include "esphome/core/application.h"
+#include "esphome/core/defines.h"
+#include "esphome/core/hal.h"
+#include "esphome/core/log.h"
+#include "esphome/core/version.h"
+
+#include "esphome/components/logger/logger.h"
+
+namespace esphome {
+namespace improv_serial {
+
+static const char *const TAG = "improv_serial";
+
+void ImprovSerialComponent::setup() {
+  global_improv_serial_component = this;
+#ifdef USE_ARDUINO
+  this->hw_serial_ = logger::global_logger->get_hw_serial();
+#endif
+#ifdef USE_ESP_IDF
+  this->uart_num_ = logger::global_logger->get_uart_num();
+#endif
+
+  if (wifi::global_wifi_component->has_sta()) {
+    this->state_ = improv::STATE_PROVISIONED;
+  }
+}
+
+void ImprovSerialComponent::dump_config() { ESP_LOGCONFIG(TAG, "Improv Serial:"); }
+
+int ImprovSerialComponent::available_() {
+#ifdef USE_ARDUINO
+  return this->hw_serial_->available();
+#endif
+#ifdef USE_ESP_IDF
+  size_t available;
+  uart_get_buffered_data_len(this->uart_num_, &available);
+  return available;
+#endif
+}
+
+uint8_t ImprovSerialComponent::read_byte_() {
+  uint8_t data;
+#ifdef USE_ARDUINO
+  this->hw_serial_->readBytes(&data, 1);
+#endif
+#ifdef USE_ESP_IDF
+  uart_read_bytes(this->uart_num_, &data, 1, 20 / portTICK_RATE_MS);
+#endif
+  return data;
+}
+
+void ImprovSerialComponent::write_data_(std::vector &data) {
+  data.push_back('\n');
+#ifdef USE_ARDUINO
+  this->hw_serial_->write(data.data(), data.size());
+#endif
+#ifdef USE_ESP_IDF
+  uart_write_bytes(this->uart_num_, data.data(), data.size());
+#endif
+}
+
+void ImprovSerialComponent::loop() {
+  const uint32_t now = millis();
+  if (now - this->last_read_byte_ > 50) {
+    this->rx_buffer_.clear();
+    this->last_read_byte_ = now;
+  }
+
+  while (this->available_()) {
+    uint8_t byte = this->read_byte_();
+    if (this->parse_improv_serial_byte_(byte)) {
+      this->last_read_byte_ = now;
+    } else {
+      this->rx_buffer_.clear();
+    }
+  }
+
+  if (this->state_ == improv::STATE_PROVISIONING) {
+    if (wifi::global_wifi_component->is_connected()) {
+      wifi::global_wifi_component->save_wifi_sta(this->connecting_sta_.get_ssid(),
+                                                 this->connecting_sta_.get_password());
+      this->connecting_sta_ = {};
+      this->cancel_timeout("wifi-connect-timeout");
+      this->set_state_(improv::STATE_PROVISIONED);
+
+      std::vector url = this->build_rpc_settings_response_(improv::WIFI_SETTINGS);
+      this->send_response_(url);
+    }
+  }
+}
+
+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};
+#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(command, urls);
+  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);
+  return data;
+};
+
+bool ImprovSerialComponent::parse_improv_serial_byte_(uint8_t byte) {
+  size_t at = this->rx_buffer_.size();
+  this->rx_buffer_.push_back(byte);
+  ESP_LOGD(TAG, "Improv Serial byte: 0x%02X", byte);
+  const uint8_t *raw = &this->rx_buffer_[0];
+  if (at == 0)
+    return byte == 'I';
+  if (at == 1)
+    return byte == 'M';
+  if (at == 2)
+    return byte == 'P';
+  if (at == 3)
+    return byte == 'R';
+  if (at == 4)
+    return byte == 'O';
+  if (at == 5)
+    return byte == 'V';
+
+  if (at == 6)
+    return byte == IMPROV_SERIAL_VERSION;
+
+  if (at == 7)
+    return true;
+  uint8_t type = raw[7];
+
+  if (at == 8)
+    return true;
+  uint8_t data_len = raw[8];
+
+  if (at < 8 + data_len)
+    return true;
+
+  if (at == 8 + data_len) {
+    if (type == TYPE_RPC) {
+      this->set_error_(improv::ERROR_NONE);
+      auto command = improv::parse_improv_data(&raw[9], data_len);
+      return this->parse_improv_payload_(command);
+    }
+  }
+  return true;
+}
+
+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);
+      sta.set_password(command.password);
+      this->connecting_sta_ = sta;
+
+      wifi::global_wifi_component->set_sta(sta);
+      wifi::global_wifi_component->start_scanning();
+      this->set_state_(improv::STATE_PROVISIONING);
+      ESP_LOGD(TAG, "Received Improv wifi settings ssid=%s, password=" LOG_SECRET("%s"), command.ssid.c_str(),
+               command.password.c_str());
+
+      auto f = std::bind(&ImprovSerialComponent::on_wifi_connect_timeout_, this);
+      this->set_timeout("wifi-connect-timeout", 30000, f);
+      return true;
+    }
+    case improv::GET_CURRENT_STATE:
+      this->set_state_(this->state_);
+      if (this->state_ == improv::STATE_PROVISIONED) {
+        std::vector url = this->build_rpc_settings_response_(improv::GET_CURRENT_STATE);
+        this->send_response_(url);
+      }
+      return true;
+    case improv::GET_DEVICE_INFO: {
+      std::vector info = this->build_version_info_();
+      this->send_response_(info);
+      return true;
+    }
+    default: {
+      ESP_LOGW(TAG, "Unknown Improv payload");
+      this->set_error_(improv::ERROR_UNKNOWN_RPC);
+      return false;
+    }
+  }
+}
+
+void ImprovSerialComponent::set_state_(improv::State state) {
+  this->state_ = state;
+
+  std::vector data = {'I', 'M', 'P', 'R', 'O', 'V'};
+  data.resize(11);
+  data[6] = IMPROV_SERIAL_VERSION;
+  data[7] = TYPE_CURRENT_STATE;
+  data[8] = 1;
+  data[9] = state;
+
+  uint8_t checksum = 0x00;
+  for (uint8_t d : data)
+    checksum += d;
+  data[10] = checksum;
+
+  this->write_data_(data);
+}
+
+void ImprovSerialComponent::set_error_(improv::Error error) {
+  std::vector data = {'I', 'M', 'P', 'R', 'O', 'V'};
+  data.resize(11);
+  data[6] = IMPROV_SERIAL_VERSION;
+  data[7] = TYPE_ERROR_STATE;
+  data[8] = 1;
+  data[9] = error;
+
+  uint8_t checksum = 0x00;
+  for (uint8_t d : data)
+    checksum += d;
+  data[10] = checksum;
+  this->write_data_(data);
+}
+
+void ImprovSerialComponent::send_response_(std::vector &response) {
+  std::vector data = {'I', 'M', 'P', 'R', 'O', 'V'};
+  data.resize(9);
+  data[6] = IMPROV_SERIAL_VERSION;
+  data[7] = TYPE_RPC_RESPONSE;
+  data[8] = response.size();
+  data.insert(data.end(), response.begin(), response.end());
+  this->write_data_(data);
+}
+
+void ImprovSerialComponent::on_wifi_connect_timeout_() {
+  this->set_error_(improv::ERROR_UNABLE_TO_CONNECT);
+  this->set_state_(improv::STATE_AUTHORIZED);
+  ESP_LOGW(TAG, "Timed out trying to connect to given WiFi network");
+  wifi::global_wifi_component->clear_sta();
+}
+
+ImprovSerialComponent *global_improv_serial_component =  // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
+    nullptr;                                             // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
+
+}  // namespace improv_serial
+}  // namespace esphome
diff --git a/esphome/components/improv_serial/improv_serial_component.h b/esphome/components/improv_serial/improv_serial_component.h
new file mode 100644
index 0000000000..539674e2d3
--- /dev/null
+++ b/esphome/components/improv_serial/improv_serial_component.h
@@ -0,0 +1,69 @@
+#pragma once
+
+#include "esphome/components/improv/improv.h"
+#include "esphome/components/wifi/wifi_component.h"
+#include "esphome/core/component.h"
+#include "esphome/core/defines.h"
+#include "esphome/core/helpers.h"
+
+#ifdef USE_ARDUINO
+#include 
+#endif
+#ifdef USE_ESP_IDF
+#include 
+#endif
+
+namespace esphome {
+namespace improv_serial {
+
+enum ImprovSerialType : uint8_t {
+  TYPE_CURRENT_STATE = 0x01,
+  TYPE_ERROR_STATE = 0x02,
+  TYPE_RPC = 0x03,
+  TYPE_RPC_RESPONSE = 0x04
+};
+
+static const uint8_t IMPROV_SERIAL_VERSION = 1;
+
+class ImprovSerialComponent : public Component {
+ public:
+  void setup() override;
+  void loop() override;
+  void dump_config() override;
+
+  float get_setup_priority() const override { return setup_priority::HARDWARE; }
+
+ protected:
+  bool parse_improv_serial_byte_(uint8_t byte);
+  bool parse_improv_payload_(improv::ImprovCommand &command);
+
+  void set_state_(improv::State state);
+  void set_error_(improv::Error error);
+  void send_response_(std::vector &response);
+  void on_wifi_connect_timeout_();
+
+  std::vector build_rpc_settings_response_(improv::Command command);
+  std::vector build_version_info_();
+
+  int available_();
+  uint8_t read_byte_();
+  void write_data_(std::vector &data);
+
+#ifdef USE_ARDUINO
+  HardwareSerial *hw_serial_{nullptr};
+#endif
+#ifdef USE_ESP_IDF
+  uart_port_t uart_num_;
+#endif
+
+  std::vector rx_buffer_;
+  uint32_t last_read_byte_{0};
+  wifi::WiFiAP connecting_sta_;
+  improv::State state_{improv::STATE_AUTHORIZED};
+};
+
+extern ImprovSerialComponent
+    *global_improv_serial_component;  // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
+
+}  // namespace improv_serial
+}  // namespace esphome
diff --git a/esphome/components/logger/logger.cpp b/esphome/components/logger/logger.cpp
index 97ad4c2cb9..11c0733701 100644
--- a/esphome/components/logger/logger.cpp
+++ b/esphome/components/logger/logger.cpp
@@ -221,7 +221,7 @@ UARTSelection Logger::get_uart() const { return this->uart_; }
 void Logger::add_on_log_callback(std::function &&callback) {
   this->log_callback_.add(std::move(callback));
 }
-float Logger::get_setup_priority() const { return setup_priority::HARDWARE - 1.0f; }
+float Logger::get_setup_priority() const { return setup_priority::BUS + 500.0f; }
 const char *const LOG_LEVELS[] = {"NONE", "ERROR", "WARN", "INFO", "CONFIG", "DEBUG", "VERBOSE", "VERY_VERBOSE"};
 #ifdef USE_ESP32
 const char *const UART_SELECTIONS[] = {"UART0", "UART1", "UART2"};
diff --git a/esphome/components/web_server/__init__.py b/esphome/components/web_server/__init__.py
index 61b1fa5ad6..dc652e0312 100644
--- a/esphome/components/web_server/__init__.py
+++ b/esphome/components/web_server/__init__.py
@@ -54,6 +54,8 @@ async def to_code(config):
     var = cg.new_Pvariable(config[CONF_ID], paren)
     await cg.register_component(var, config)
 
+    cg.add_define("USE_WEBSERVER")
+
     cg.add(paren.set_port(config[CONF_PORT]))
     cg.add_define("WEBSERVER_PORT", config[CONF_PORT])
     cg.add_define("USE_WEBSERVER")
diff --git a/esphome/components/wifi/__init__.py b/esphome/components/wifi/__init__.py
index faf3cca280..7a9319f5e0 100644
--- a/esphome/components/wifi/__init__.py
+++ b/esphome/components/wifi/__init__.py
@@ -140,7 +140,8 @@ def final_validate(config):
     has_sta = bool(config.get(CONF_NETWORKS, True))
     has_ap = CONF_AP in config
     has_improv = "esp32_improv" in fv.full_config.get()
-    if (not has_sta) and (not has_ap) and (not has_improv):
+    has_improv_serial = "improv_serial" in fv.full_config.get()
+    if not (has_sta or has_ap or has_improv or has_improv_serial):
         raise cv.Invalid(
             "Please specify at least an SSID or an Access Point to create."
         )
diff --git a/esphome/components/wifi/wifi_component.cpp b/esphome/components/wifi/wifi_component.cpp
index 703afa99bc..36944e3633 100644
--- a/esphome/components/wifi/wifi_component.cpp
+++ b/esphome/components/wifi/wifi_component.cpp
@@ -239,8 +239,6 @@ void WiFiComponent::save_wifi_sta(const std::string &ssid, const std::string &pa
   sta.set_ssid(ssid);
   sta.set_password(password);
   this->set_sta(sta);
-
-  this->start_scanning();
 }
 
 void WiFiComponent::start_connecting(const WiFiAP &ap, bool two) {
diff --git a/esphome/core/defines.h b/esphome/core/defines.h
index dc07bde196..94fac73906 100644
--- a/esphome/core/defines.h
+++ b/esphome/core/defines.h
@@ -9,6 +9,7 @@
 #define ESPHOME_BOARD "dummy_board"
 #define ESPHOME_PROJECT_NAME "dummy project"
 #define ESPHOME_PROJECT_VERSION "v2"
+#define ESPHOME_VARIANT "ESP32"
 
 // Feature flags
 #define USE_API
diff --git a/script/ci-custom.py b/script/ci-custom.py
index 8e9ca487a6..89550afd3d 100755
--- a/script/ci-custom.py
+++ b/script/ci-custom.py
@@ -263,7 +263,11 @@ def highlight(s):
 @lint_re_check(
     r"^#define\s+([a-zA-Z0-9_]+)\s+([0-9bx]+)" + CPP_RE_EOL,
     include=cpp_include,
-    exclude=["esphome/core/log.h", "esphome/components/socket/headers.h"],
+    exclude=[
+        "esphome/core/log.h",
+        "esphome/components/socket/headers.h",
+        "esphome/core/defines.h",
+    ],
 )
 def lint_no_defines(fname, match):
     s = highlight(
diff --git a/tests/test3.yaml b/tests/test3.yaml
index 0b7f1ad71e..cf80c06aa8 100644
--- a/tests/test3.yaml
+++ b/tests/test3.yaml
@@ -268,6 +268,8 @@ logger:
   level: DEBUG
   esp8266_store_log_strings_in_flash: true
 
+improv_serial:
+
 deep_sleep:
   run_duration: 20s
   sleep_duration: 50s

From f310cacd411c743b0850b6bc26fcc3ada7078a1d Mon Sep 17 00:00:00 2001
From: anatoly-savchenkov
 <48646998+anatoly-savchenkov@users.noreply.github.com>
Date: Thu, 11 Nov 2021 00:01:47 +0300
Subject: [PATCH 142/142] [ms5611] Re-implement conversion from ADC readings to
 sensor values (#2665)

---
 esphome/components/ms5611/ms5611.cpp | 52 +++++++++++++++++++---------
 1 file changed, 35 insertions(+), 17 deletions(-)

diff --git a/esphome/components/ms5611/ms5611.cpp b/esphome/components/ms5611/ms5611.cpp
index 1d7516dbe8..4b34e1d71a 100644
--- a/esphome/components/ms5611/ms5611.cpp
+++ b/esphome/components/ms5611/ms5611.cpp
@@ -75,30 +75,48 @@ void MS5611Component::read_pressure_(uint32_t raw_temperature) {
   const uint32_t raw_pressure = (uint32_t(bytes[0]) << 16) | (uint32_t(bytes[1]) << 8) | (uint32_t(bytes[2]));
   this->calculate_values_(raw_temperature, raw_pressure);
 }
+
+// Calculations are taken from the datasheet which can be found here:
+// https://www.te.com/commerce/DocumentDelivery/DDEController?Action=showdoc&DocId=Data+Sheet%7FMS5611-01BA03%7FB3%7Fpdf%7FEnglish%7FENG_DS_MS5611-01BA03_B3.pdf%7FCAT-BLPS0036
+// Sections PRESSURE AND TEMPERATURE CALCULATION and SECOND ORDER TEMPERATURE COMPENSATION
+// Variable names below match variable names from the datasheet but lowercased
 void MS5611Component::calculate_values_(uint32_t raw_temperature, uint32_t raw_pressure) {
-  const int32_t d_t = int32_t(raw_temperature) - (uint32_t(this->prom_[4]) << 8);
-  float temperature = (2000 + (int64_t(d_t) * this->prom_[5]) / 8388608.0f) / 100.0f;
+  const uint32_t c1 = uint32_t(this->prom_[0]);
+  const uint32_t c2 = uint32_t(this->prom_[1]);
+  const uint16_t c3 = uint16_t(this->prom_[2]);
+  const uint16_t c4 = uint16_t(this->prom_[3]);
+  const int32_t c5 = int32_t(this->prom_[4]);
+  const uint16_t c6 = uint16_t(this->prom_[5]);
+  const uint32_t d1 = raw_pressure;
+  const int32_t d2 = raw_temperature;
 
-  float pressure_offset = (uint32_t(this->prom_[1]) << 16) + ((this->prom_[3] * d_t) >> 7);
-  float pressure_sensitivity = (uint32_t(this->prom_[0]) << 15) + ((this->prom_[2] * d_t) >> 8);
+  // Promote dt to 64 bit here to make the math below cleaner
+  const int64_t dt = d2 - (c5 << 8);
+  int32_t temp = (2000 + ((dt * c6) >> 23));
 
-  if (temperature < 20.0f) {
-    const float t2 = (d_t * d_t) / 2147483648.0f;
-    const float temp20 = (temperature - 20.0f) * 100.0f;
-    float pressure_offset_2 = 2.5f * temp20 * temp20;
-    float pressure_sensitivity_2 = 1.25f * temp20 * temp20;
-    if (temp20 < -15.0f) {
-      const float temp15 = (temperature + 15.0f) * 100.0f;
-      pressure_offset_2 += 7.0f * temp15;
-      pressure_sensitivity_2 += 5.5f * temp15;
+  int64_t off = (c2 << 16) + ((dt * c4) >> 7);
+  int64_t sens = (c1 << 15) + ((dt * c3) >> 8);
+
+  if (temp < 2000) {
+    const int32_t t2 = (dt * dt) >> 31;
+    int32_t off2 = ((5 * (temp - 2000) * (temp - 2000)) >> 1);
+    int32_t sens2 = ((5 * (temp - 2000) * (temp - 2000)) >> 2);
+    if (temp < -1500) {
+      off2 = (off2 + 7 * (temp + 1500) * (temp + 1500));
+      sens2 = sens2 + ((11 * (temp + 1500) * (temp + 1500)) >> 1);
     }
-    temperature -= t2;
-    pressure_offset -= pressure_offset_2;
-    pressure_sensitivity -= pressure_sensitivity_2;
+    temp = temp - t2;
+    off = off - off2;
+    sens = sens - sens2;
   }
 
-  const float pressure = ((raw_pressure * pressure_sensitivity) / 2097152.0f - pressure_offset) / 3276800.0f;
+  // Here we multiply unsigned 32-bit by signed 64-bit using signed 64-bit math.
+  // Possible ranges of D1 and SENS from the datasheet guarantee
+  // that this multiplication does not overflow
+  const int32_t p = ((((d1 * sens) >> 21) - off) >> 15);
 
+  const float temperature = temp / 100.0f;
+  const float pressure = p / 100.0f;
   ESP_LOGD(TAG, "Got temperature=%0.02f°C pressure=%0.01fhPa", temperature, pressure);
 
   if (this->temperature_sensor_ != nullptr)