diff --git a/.clang-tidy b/.clang-tidy index 946f2950d8..5878028f48 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -7,28 +7,39 @@ Checks: >- -boost-*, -bugprone-easily-swappable-parameters, -bugprone-implicit-widening-of-multiplication-result, + -bugprone-multi-level-implicit-pointer-conversion, -bugprone-narrowing-conversions, -bugprone-signed-char-misuse, + -bugprone-switch-missing-default-case, -cert-dcl50-cpp, -cert-err33-c, -cert-err58-cpp, -cert-oop57-cpp, -cert-str34-c, + -clang-analyzer-optin.core.EnumCastOutOfRange, -clang-analyzer-optin.cplusplus.UninitializedObject, -clang-analyzer-osx.*, -clang-diagnostic-delete-abstract-non-virtual-dtor, -clang-diagnostic-delete-non-abstract-non-virtual-dtor, + -clang-diagnostic-deprecated-declarations, -clang-diagnostic-ignored-optimization-argument, + -clang-diagnostic-missing-field-initializers, -clang-diagnostic-shadow-field, -clang-diagnostic-unused-const-variable, -clang-diagnostic-unused-parameter, + -clang-diagnostic-vla-cxx-extension, -concurrency-*, -cppcoreguidelines-avoid-c-arrays, + -cppcoreguidelines-avoid-const-or-ref-data-members, + -cppcoreguidelines-avoid-do-while, -cppcoreguidelines-avoid-magic-numbers, -cppcoreguidelines-init-variables, + -cppcoreguidelines-macro-to-enum, -cppcoreguidelines-macro-usage, + -cppcoreguidelines-missing-std-forward, -cppcoreguidelines-narrowing-conversions, -cppcoreguidelines-non-private-member-variables-in-classes, + -cppcoreguidelines-owning-memory, -cppcoreguidelines-prefer-member-initializer, -cppcoreguidelines-pro-bounds-array-to-pointer-decay, -cppcoreguidelines-pro-bounds-constant-array-index, @@ -40,7 +51,9 @@ Checks: >- -cppcoreguidelines-pro-type-static-cast-downcast, -cppcoreguidelines-pro-type-union-access, -cppcoreguidelines-pro-type-vararg, + -cppcoreguidelines-rvalue-reference-param-not-moved, -cppcoreguidelines-special-member-functions, + -cppcoreguidelines-use-default-member-init, -cppcoreguidelines-virtual-class-destructor, -fuchsia-multiple-inheritance, -fuchsia-overloaded-operator, @@ -60,20 +73,32 @@ Checks: >- -llvm-include-order, -llvm-qualified-auto, -llvmlibc-*, - -misc-non-private-member-variables-in-classes, + -misc-const-correctness, + -misc-include-cleaner, -misc-no-recursion, + -misc-non-private-member-variables-in-classes, -misc-unused-parameters, - -modernize-avoid-c-arrays, + -misc-use-anonymous-namespace, -modernize-avoid-bind, + -modernize-avoid-c-arrays, -modernize-concat-nested-namespaces, + -modernize-macro-to-enum, -modernize-return-braced-init-list, + -modernize-type-traits, -modernize-use-auto, + -modernize-use-constraints, -modernize-use-default-member-init, -modernize-use-equals-default, - -modernize-use-trailing-return-type, -modernize-use-nodiscard, + -modernize-use-nullptr, + -modernize-use-nodiscard, + -modernize-use-nullptr, + -modernize-use-trailing-return-type, -mpi-*, -objc-*, + -performance-enum-size, + -readability-avoid-nested-conditional-operator, + -readability-container-contains, -readability-container-data-pointer, -readability-convert-member-functions-to-static, -readability-else-after-return, @@ -82,11 +107,14 @@ Checks: >- -readability-isolate-declaration, -readability-magic-numbers, -readability-make-member-function-const, + -readability-named-parameter, + -readability-redundant-casting, + -readability-redundant-inline-specifier, + -readability-redundant-member-init, -readability-redundant-string-init, -readability-uppercase-literal-suffix, -readability-use-anyofallof, WarningsAsErrors: '*' -AnalyzeTemporaryDtors: false FormatStyle: google CheckOptions: - key: google-readability-function-size.StatementThreshold diff --git a/.github/actions/build-image/action.yaml b/.github/actions/build-image/action.yaml index 5c686605c3..cc9894a657 100644 --- a/.github/actions/build-image/action.yaml +++ b/.github/actions/build-image/action.yaml @@ -46,7 +46,7 @@ runs: - name: Build and push to ghcr by digest id: build-ghcr - uses: docker/build-push-action@v6.9.0 + uses: docker/build-push-action@v6.10.0 env: DOCKER_BUILD_SUMMARY: false DOCKER_BUILD_RECORD_UPLOAD: false @@ -72,7 +72,7 @@ runs: - name: Build and push to dockerhub by digest id: build-dockerhub - uses: docker/build-push-action@v6.9.0 + uses: docker/build-push-action@v6.10.0 env: DOCKER_BUILD_SUMMARY: false DOCKER_BUILD_RECORD_UPLOAD: false diff --git a/.github/actions/restore-python/action.yml b/.github/actions/restore-python/action.yml index 06c54578f5..6b87cf0170 100644 --- a/.github/actions/restore-python/action.yml +++ b/.github/actions/restore-python/action.yml @@ -22,7 +22,7 @@ runs: python-version: ${{ inputs.python-version }} - name: Restore Python virtual environment id: cache-venv - uses: actions/cache/restore@v4.1.2 + uses: actions/cache/restore@v4.2.0 with: path: venv # yamllint disable-line rule:line-length diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 82a55d0e2a..6ce4159da0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,7 +30,7 @@ concurrency: jobs: common: name: Create common environment - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 outputs: cache-key: ${{ steps.cache-key.outputs.key }} steps: @@ -46,7 +46,7 @@ jobs: python-version: ${{ env.DEFAULT_PYTHON }} - name: Restore Python virtual environment id: cache-venv - uses: actions/cache@v4.1.2 + uses: actions/cache@v4.2.0 with: path: venv # yamllint disable-line rule:line-length @@ -62,7 +62,7 @@ jobs: black: name: Check black - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 needs: - common steps: @@ -83,7 +83,7 @@ jobs: flake8: name: Check flake8 - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 needs: - common steps: @@ -104,7 +104,7 @@ jobs: pylint: name: Check pylint - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 needs: - common steps: @@ -125,7 +125,7 @@ jobs: pyupgrade: name: Check pyupgrade - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 needs: - common steps: @@ -146,7 +146,7 @@ jobs: ci-custom: name: Run script/ci-custom - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 needs: - common steps: @@ -219,13 +219,13 @@ jobs: . venv/bin/activate pytest -vv --cov-report=xml --tb=native tests - name: Upload coverage to Codecov - uses: codecov/codecov-action@v4 + uses: codecov/codecov-action@v5 with: token: ${{ secrets.CODECOV_TOKEN }} clang-format: name: Check clang-format - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 needs: - common steps: @@ -251,7 +251,7 @@ jobs: clang-tidy: name: ${{ matrix.name }} - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 needs: - common - black @@ -302,23 +302,18 @@ jobs: - name: Cache platformio if: github.ref == 'refs/heads/dev' - uses: actions/cache@v4.1.2 + uses: actions/cache@v4.2.0 with: path: ~/.platformio key: platformio-${{ matrix.pio_cache_key }} - name: Cache platformio if: github.ref != 'refs/heads/dev' - uses: actions/cache/restore@v4.1.2 + uses: actions/cache/restore@v4.2.0 with: path: ~/.platformio key: platformio-${{ matrix.pio_cache_key }} - - name: Install clang-tidy - run: | - sudo apt-get update - sudo apt-get install clang-tidy-14 - - name: Register problem matchers run: | echo "::add-matcher::.github/workflows/matchers/gcc.json" @@ -345,7 +340,7 @@ jobs: if: always() list-components: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 needs: - common if: github.event_name == 'pull_request' @@ -387,7 +382,7 @@ jobs: test-build-components: name: Component test ${{ matrix.file }} - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 needs: - common - list-components @@ -421,7 +416,7 @@ jobs: test-build-components-splitter: name: Split components for testing into 20 groups maximum - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 needs: - common - list-components @@ -439,7 +434,7 @@ jobs: test-build-components-split: name: Test split components - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 needs: - common - list-components @@ -483,7 +478,7 @@ jobs: ci-status: name: CI Status - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 needs: - common - black diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 096b00f0f1..a4e4305207 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -65,7 +65,7 @@ jobs: pip3 install build python3 -m build - name: Publish - uses: pypa/gh-action-pypi-publish@v1.12.2 + uses: pypa/gh-action-pypi-publish@v1.12.3 deploy-docker: name: Build ESPHome ${{ matrix.platform }} diff --git a/CODEOWNERS b/CODEOWNERS index 8fbbacef59..404ad35efc 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -179,6 +179,7 @@ esphome/components/haier/text_sensor/* @paveldn esphome/components/havells_solar/* @sourabhjaiswal esphome/components/hbridge/fan/* @WeekendWarrior esphome/components/hbridge/light/* @DotNetDann +esphome/components/hbridge/switch/* @dwmw2 esphome/components/he60r/* @clydebarrow esphome/components/heatpumpir/* @rob-deutsch esphome/components/hitachi_ac424/* @sourabhjaiswal @@ -354,6 +355,8 @@ esphome/components/sdl/* @clydebarrow esphome/components/sdm_meter/* @jesserockz @polyfaces esphome/components/sdp3x/* @Azimath esphome/components/seeed_mr24hpc1/* @limengdu +esphome/components/seeed_mr60bha2/* @limengdu +esphome/components/seeed_mr60fda2/* @limengdu esphome/components/selec_meter/* @sourabhjaiswal esphome/components/select/* @esphome/core esphome/components/sen0321/* @notjj @@ -408,6 +411,7 @@ esphome/components/substitutions/* @esphome/core esphome/components/sun/* @OttoWinter esphome/components/sun_gtil2/* @Mat931 esphome/components/switch/* @esphome/core +esphome/components/switch/binary_sensor/* @ssieb esphome/components/t6615/* @tylermenezes esphome/components/tc74/* @sethgirvan esphome/components/tca9548a/* @andreashergert1984 diff --git a/docker/Dockerfile b/docker/Dockerfile index c2902a9dd1..0bb558d35e 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -163,6 +163,18 @@ ENTRYPOINT ["/entrypoint.sh"] CMD ["dashboard", "/config"] +ARG BUILD_VERSION=dev + +# Labels +LABEL \ + org.opencontainers.image.authors="The ESPHome Authors" \ + org.opencontainers.image.title="ESPHome" \ + org.opencontainers.image.description="ESPHome is a system to configure your microcontrollers by simple yet powerful configuration files and control them remotely through Home Automation systems" \ + org.opencontainers.image.url="https://esphome.io/" \ + org.opencontainers.image.documentation="https://esphome.io/" \ + org.opencontainers.image.source="https://github.com/esphome/esphome" \ + org.opencontainers.image.licenses="ESPHome" \ + org.opencontainers.image.version=${BUILD_VERSION} # ======================= hassio-type image ======================= @@ -194,7 +206,7 @@ RUN if [ "$TARGETARCH$TARGETVARIANT" = "armv7" ]; then \ # Labels LABEL \ io.hass.name="ESPHome" \ - io.hass.description="Manage and program ESP8266/ESP32 microcontrollers through YAML configuration files" \ + io.hass.description="ESPHome is a system to configure your microcontrollers by simple yet powerful configuration files and control them remotely through Home Automation systems" \ io.hass.type="addon" \ io.hass.version="${BUILD_VERSION}" # io.hass.arch is inherited from addon-debian-base @@ -209,17 +221,22 @@ ENV \ PLATFORMIO_CORE_DIR=/esphome/.temp/platformio RUN \ - apt-get update \ + curl -L https://apt.llvm.org/llvm-snapshot.gpg.key -o /etc/apt/trusted.gpg.d/apt.llvm.org.asc \ + && echo "deb http://apt.llvm.org/bookworm/ llvm-toolchain-bookworm-18 main" > /etc/apt/sources.list.d/llvm.sources.list \ + && apt-get update \ # Use pinned versions so that we get updates with build caching && apt-get install -y --no-install-recommends \ clang-format-13=1:13.0.1-11+b2 \ - clang-tidy-14=1:14.0.6-12 \ patch=2.7.6-7 \ software-properties-common=0.99.30-4.1~deb12u1 \ nano=7.2-1+deb12u1 \ build-essential=12.9 \ python3-dev=3.11.2-1+b1 \ - && rm -rf \ + && if [ "$TARGETARCH$TARGETVARIANT" != "armv7" ]; then \ + # move this up after armv7 is retired + apt-get install -y --no-install-recommends clang-tidy-18=1:18.1.8~++20240731024826+3b5b5c1ec4a3-1~exp1~20240731144843.145 ; \ + fi; \ + rm -rf \ /tmp/* \ /var/{cache,log}/* \ /var/lib/apt/lists/* diff --git a/esphome/components/adc/adc_sensor.h b/esphome/components/adc/adc_sensor.h index b697d6dd7e..7a3e1c8da7 100644 --- a/esphome/components/adc/adc_sensor.h +++ b/esphome/components/adc/adc_sensor.h @@ -3,13 +3,12 @@ #include "esphome/components/sensor/sensor.h" #include "esphome/components/voltage_sampler/voltage_sampler.h" #include "esphome/core/component.h" -#include "esphome/core/defines.h" #include "esphome/core/hal.h" #ifdef USE_ESP32 #include #include "driver/adc.h" -#endif +#endif // USE_ESP32 namespace esphome { namespace adc { @@ -43,7 +42,7 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage this->channel1_ = ADC1_CHANNEL_MAX; } void set_autorange(bool autorange) { this->autorange_ = autorange; } -#endif +#endif // USE_ESP32 /// Update ADC values void update() override; @@ -59,11 +58,11 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage #ifdef USE_ESP8266 std::string unique_id() override; -#endif +#endif // USE_ESP8266 #ifdef USE_RP2040 void set_is_temperature() { this->is_temperature_ = true; } -#endif +#endif // USE_RP2040 protected: InternalGPIOPin *pin_; @@ -72,7 +71,7 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage #ifdef USE_RP2040 bool is_temperature_{false}; -#endif +#endif // USE_RP2040 #ifdef USE_ESP32 adc_atten_t attenuation_{ADC_ATTEN_DB_0}; @@ -83,8 +82,8 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage esp_adc_cal_characteristics_t cal_characteristics_[SOC_ADC_ATTEN_NUM] = {}; #else esp_adc_cal_characteristics_t cal_characteristics_[ADC_ATTEN_MAX] = {}; -#endif -#endif +#endif // ESP_IDF_VERSION_MAJOR +#endif // USE_ESP32 }; } // namespace adc diff --git a/esphome/components/adc/adc_sensor_common.cpp b/esphome/components/adc/adc_sensor_common.cpp new file mode 100644 index 0000000000..2dccd55fcd --- /dev/null +++ b/esphome/components/adc/adc_sensor_common.cpp @@ -0,0 +1,24 @@ +#include "adc_sensor.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace adc { + +static const char *const TAG = "adc.common"; + +void ADCSensor::update() { + float value_v = this->sample(); + ESP_LOGV(TAG, "'%s': Got voltage=%.4fV", this->get_name().c_str(), value_v); + this->publish_state(value_v); +} + +void ADCSensor::set_sample_count(uint8_t sample_count) { + if (sample_count != 0) { + this->sample_count_ = sample_count; + } +} + +float ADCSensor::get_setup_priority() const { return setup_priority::DATA; } + +} // namespace adc +} // namespace esphome diff --git a/esphome/components/adc/adc_sensor.cpp b/esphome/components/adc/adc_sensor_esp32.cpp similarity index 53% rename from esphome/components/adc/adc_sensor.cpp rename to esphome/components/adc/adc_sensor_esp32.cpp index 7257793016..24e3750091 100644 --- a/esphome/components/adc/adc_sensor.cpp +++ b/esphome/components/adc/adc_sensor_esp32.cpp @@ -1,30 +1,13 @@ +#ifdef USE_ESP32 + #include "adc_sensor.h" -#include "esphome/core/helpers.h" #include "esphome/core/log.h" -#ifdef USE_ESP8266 -#ifdef USE_ADC_SENSOR_VCC -#include -ADC_MODE(ADC_VCC) -#else -#include -#endif -#endif - -#ifdef USE_RP2040 -#ifdef CYW43_USES_VSYS_PIN -#include "pico/cyw43_arch.h" -#endif -#include -#endif - namespace esphome { namespace adc { -static const char *const TAG = "adc"; +static const char *const TAG = "adc.esp32"; -// 13-bit for S2, 12-bit for all other ESP32 variants -#ifdef USE_ESP32 static const adc_bits_width_t ADC_WIDTH_MAX_SOC_BITS = static_cast(ADC_WIDTH_MAX - 1); #ifndef SOC_ADC_RTC_MAX_BITWIDTH @@ -32,24 +15,15 @@ static const adc_bits_width_t ADC_WIDTH_MAX_SOC_BITS = static_cast> 1; // 2048 (12 bit) or 4096 (13 bit) -#endif +static const int ADC_MAX = (1 << SOC_ADC_RTC_MAX_BITWIDTH) - 1; +static const int ADC_HALF = (1 << SOC_ADC_RTC_MAX_BITWIDTH) >> 1; -#ifdef USE_RP2040 -extern "C" -#endif - void - ADCSensor::setup() { +void ADCSensor::setup() { ESP_LOGCONFIG(TAG, "Setting up ADC '%s'...", this->get_name().c_str()); -#if !defined(USE_ADC_SENSOR_VCC) && !defined(USE_RP2040) - this->pin_->setup(); -#endif -#ifdef USE_ESP32 if (this->channel1_ != ADC1_CHANNEL_MAX) { adc1_config_width(ADC_WIDTH_MAX_SOC_BITS); if (!this->autorange_) { @@ -61,7 +35,6 @@ extern "C" } } - // load characteristics for each attenuation for (int32_t i = 0; i <= ADC_ATTEN_DB_12_COMPAT; i++) { auto adc_unit = this->channel1_ != ADC1_CHANNEL_MAX ? ADC_UNIT_1 : ADC_UNIT_2; auto cal_value = esp_adc_cal_characterize(adc_unit, (adc_atten_t) i, ADC_WIDTH_MAX_SOC_BITS, @@ -79,31 +52,10 @@ extern "C" break; } } - -#endif // USE_ESP32 - -#ifdef USE_RP2040 - static bool initialized = false; - if (!initialized) { - adc_init(); - initialized = true; - } -#endif - - ESP_LOGCONFIG(TAG, "ADC '%s' setup finished!", this->get_name().c_str()); } void ADCSensor::dump_config() { LOG_SENSOR("", "ADC Sensor", this); -#if defined(USE_ESP8266) || defined(USE_LIBRETINY) -#ifdef USE_ADC_SENSOR_VCC - ESP_LOGCONFIG(TAG, " Pin: VCC"); -#else - LOG_PIN(" Pin: ", this->pin_); -#endif -#endif // USE_ESP8266 || USE_LIBRETINY - -#ifdef USE_ESP32 LOG_PIN(" Pin: ", this->pin_); if (this->autorange_) { ESP_LOGCONFIG(TAG, " Attenuation: auto"); @@ -125,55 +77,10 @@ void ADCSensor::dump_config() { break; } } -#endif // USE_ESP32 - -#ifdef USE_RP2040 - if (this->is_temperature_) { - ESP_LOGCONFIG(TAG, " Pin: Temperature"); - } else { -#ifdef USE_ADC_SENSOR_VCC - ESP_LOGCONFIG(TAG, " Pin: VCC"); -#else - LOG_PIN(" Pin: ", this->pin_); -#endif // USE_ADC_SENSOR_VCC - } -#endif // USE_RP2040 ESP_LOGCONFIG(TAG, " Samples: %i", this->sample_count_); LOG_UPDATE_INTERVAL(this); } -float ADCSensor::get_setup_priority() const { return setup_priority::DATA; } -void ADCSensor::update() { - float value_v = this->sample(); - ESP_LOGV(TAG, "'%s': Got voltage=%.4fV", this->get_name().c_str(), value_v); - this->publish_state(value_v); -} - -void ADCSensor::set_sample_count(uint8_t sample_count) { - if (sample_count != 0) { - this->sample_count_ = sample_count; - } -} - -#ifdef USE_ESP8266 -float ADCSensor::sample() { - uint32_t raw = 0; - for (uint8_t sample = 0; sample < this->sample_count_; sample++) { -#ifdef USE_ADC_SENSOR_VCC - raw += ESP.getVcc(); // NOLINT(readability-static-accessed-through-instance) -#else - raw += analogRead(this->pin_->get_pin()); // NOLINT -#endif - } - raw = (raw + (this->sample_count_ >> 1)) / this->sample_count_; // NOLINT(clang-analyzer-core.DivideZero) - if (this->output_raw_) { - return raw; - } - return raw / 1024.0f; -} -#endif - -#ifdef USE_ESP32 float ADCSensor::sample() { if (!this->autorange_) { uint32_t sum = 0; @@ -240,93 +147,17 @@ float ADCSensor::sample() { uint32_t mv2 = esp_adc_cal_raw_to_voltage(raw2, &this->cal_characteristics_[(int32_t) ADC_ATTEN_DB_2_5]); uint32_t mv0 = esp_adc_cal_raw_to_voltage(raw0, &this->cal_characteristics_[(int32_t) ADC_ATTEN_DB_0]); - // Contribution of each value, in range 0-2048 (12 bit ADC) or 0-4096 (13 bit ADC) uint32_t c12 = std::min(raw12, ADC_HALF); uint32_t c6 = ADC_HALF - std::abs(raw6 - ADC_HALF); uint32_t c2 = ADC_HALF - std::abs(raw2 - ADC_HALF); uint32_t c0 = std::min(ADC_MAX - raw0, ADC_HALF); - // max theoretical csum value is 4096*4 = 16384 uint32_t csum = c12 + c6 + c2 + c0; - // each mv is max 3900; so max value is 3900*4096*4, fits in unsigned32 uint32_t mv_scaled = (mv12 * c12) + (mv6 * c6) + (mv2 * c2) + (mv0 * c0); return mv_scaled / (float) (csum * 1000U); } -#endif // USE_ESP32 - -#ifdef USE_RP2040 -float ADCSensor::sample() { - if (this->is_temperature_) { - adc_set_temp_sensor_enabled(true); - delay(1); - adc_select_input(4); - uint32_t raw = 0; - for (uint8_t sample = 0; sample < this->sample_count_; sample++) { - raw += adc_read(); - } - raw = (raw + (this->sample_count_ >> 1)) / this->sample_count_; // NOLINT(clang-analyzer-core.DivideZero) - adc_set_temp_sensor_enabled(false); - if (this->output_raw_) { - return raw; - } - return raw * 3.3f / 4096.0f; - } else { - uint8_t pin = this->pin_->get_pin(); -#ifdef CYW43_USES_VSYS_PIN - if (pin == PICO_VSYS_PIN) { - // Measuring VSYS on Raspberry Pico W needs to be wrapped with - // `cyw43_thread_enter()`/`cyw43_thread_exit()` as discussed in - // https://github.com/raspberrypi/pico-sdk/issues/1222, since Wifi chip and - // VSYS ADC both share GPIO29 - cyw43_thread_enter(); - } -#endif // CYW43_USES_VSYS_PIN - - adc_gpio_init(pin); - adc_select_input(pin - 26); - - uint32_t raw = 0; - for (uint8_t sample = 0; sample < this->sample_count_; sample++) { - raw += adc_read(); - } - raw = (raw + (this->sample_count_ >> 1)) / this->sample_count_; // NOLINT(clang-analyzer-core.DivideZero) - -#ifdef CYW43_USES_VSYS_PIN - if (pin == PICO_VSYS_PIN) { - cyw43_thread_exit(); - } -#endif // CYW43_USES_VSYS_PIN - - if (this->output_raw_) { - return raw; - } - float coeff = pin == PICO_VSYS_PIN ? 3.0 : 1.0; - return raw * 3.3f / 4096.0f * coeff; - } -} -#endif - -#ifdef USE_LIBRETINY -float ADCSensor::sample() { - uint32_t raw = 0; - if (this->output_raw_) { - for (uint8_t sample = 0; sample < this->sample_count_; sample++) { - raw += analogRead(this->pin_->get_pin()); // NOLINT - } - raw = (raw + (this->sample_count_ >> 1)) / this->sample_count_; // NOLINT(clang-analyzer-core.DivideZero) - return raw; - } - for (uint8_t sample = 0; sample < this->sample_count_; sample++) { - raw += analogReadVoltage(this->pin_->get_pin()); // NOLINT - } - raw = (raw + (this->sample_count_ >> 1)) / this->sample_count_; // NOLINT(clang-analyzer-core.DivideZero) - return raw / 1000.0f; -} -#endif // USE_LIBRETINY - -#ifdef USE_ESP8266 -std::string ADCSensor::unique_id() { return get_mac_address() + "-adc"; } -#endif } // namespace adc } // namespace esphome + +#endif // USE_ESP32 diff --git a/esphome/components/adc/adc_sensor_esp8266.cpp b/esphome/components/adc/adc_sensor_esp8266.cpp new file mode 100644 index 0000000000..c9b6f8b652 --- /dev/null +++ b/esphome/components/adc/adc_sensor_esp8266.cpp @@ -0,0 +1,58 @@ +#ifdef USE_ESP8266 + +#include "adc_sensor.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" + +#ifdef USE_ADC_SENSOR_VCC +#include +ADC_MODE(ADC_VCC) +#else +#include +#endif // USE_ADC_SENSOR_VCC + +namespace esphome { +namespace adc { + +static const char *const TAG = "adc.esp8266"; + +void ADCSensor::setup() { + ESP_LOGCONFIG(TAG, "Setting up ADC '%s'...", this->get_name().c_str()); +#ifndef USE_ADC_SENSOR_VCC + this->pin_->setup(); +#endif +} + +void ADCSensor::dump_config() { + LOG_SENSOR("", "ADC Sensor", this); +#ifdef USE_ADC_SENSOR_VCC + ESP_LOGCONFIG(TAG, " Pin: VCC"); +#else + LOG_PIN(" Pin: ", this->pin_); +#endif // USE_ADC_SENSOR_VCC + ESP_LOGCONFIG(TAG, " Samples: %i", this->sample_count_); + LOG_UPDATE_INTERVAL(this); +} + +float ADCSensor::sample() { + uint32_t raw = 0; + for (uint8_t sample = 0; sample < this->sample_count_; sample++) { +#ifdef USE_ADC_SENSOR_VCC + raw += ESP.getVcc(); // NOLINT(readability-static-accessed-through-instance) +#else + raw += analogRead(this->pin_->get_pin()); // NOLINT +#endif // USE_ADC_SENSOR_VCC + } + raw = (raw + (this->sample_count_ >> 1)) / this->sample_count_; // NOLINT(clang-analyzer-core.DivideZero) + if (this->output_raw_) { + return raw; + } + return raw / 1024.0f; +} + +std::string ADCSensor::unique_id() { return get_mac_address() + "-adc"; } + +} // namespace adc +} // namespace esphome + +#endif // USE_ESP8266 diff --git a/esphome/components/adc/adc_sensor_libretiny.cpp b/esphome/components/adc/adc_sensor_libretiny.cpp new file mode 100644 index 0000000000..cd04477b3f --- /dev/null +++ b/esphome/components/adc/adc_sensor_libretiny.cpp @@ -0,0 +1,48 @@ +#ifdef USE_LIBRETINY + +#include "adc_sensor.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace adc { + +static const char *const TAG = "adc.libretiny"; + +void ADCSensor::setup() { + ESP_LOGCONFIG(TAG, "Setting up ADC '%s'...", this->get_name().c_str()); +#ifndef USE_ADC_SENSOR_VCC + this->pin_->setup(); +#endif // !USE_ADC_SENSOR_VCC +} + +void ADCSensor::dump_config() { + LOG_SENSOR("", "ADC Sensor", this); +#ifdef USE_ADC_SENSOR_VCC + ESP_LOGCONFIG(TAG, " Pin: VCC"); +#else // USE_ADC_SENSOR_VCC + LOG_PIN(" Pin: ", this->pin_); +#endif // USE_ADC_SENSOR_VCC + ESP_LOGCONFIG(TAG, " Samples: %i", this->sample_count_); + LOG_UPDATE_INTERVAL(this); +} + +float ADCSensor::sample() { + uint32_t raw = 0; + if (this->output_raw_) { + for (uint8_t sample = 0; sample < this->sample_count_; sample++) { + raw += analogRead(this->pin_->get_pin()); // NOLINT + } + raw = (raw + (this->sample_count_ >> 1)) / this->sample_count_; // NOLINT(clang-analyzer-core.DivideZero) + return raw; + } + for (uint8_t sample = 0; sample < this->sample_count_; sample++) { + raw += analogReadVoltage(this->pin_->get_pin()); // NOLINT + } + raw = (raw + (this->sample_count_ >> 1)) / this->sample_count_; // NOLINT(clang-analyzer-core.DivideZero) + return raw / 1000.0f; +} + +} // namespace adc +} // namespace esphome + +#endif // USE_LIBRETINY diff --git a/esphome/components/adc/adc_sensor_rp2040.cpp b/esphome/components/adc/adc_sensor_rp2040.cpp new file mode 100644 index 0000000000..520ff3bacc --- /dev/null +++ b/esphome/components/adc/adc_sensor_rp2040.cpp @@ -0,0 +1,93 @@ +#ifdef USE_RP2040 + +#include "adc_sensor.h" +#include "esphome/core/log.h" + +#ifdef CYW43_USES_VSYS_PIN +#include "pico/cyw43_arch.h" +#endif // CYW43_USES_VSYS_PIN +#include + +namespace esphome { +namespace adc { + +static const char *const TAG = "adc.rp2040"; + +void ADCSensor::setup() { + ESP_LOGCONFIG(TAG, "Setting up ADC '%s'...", this->get_name().c_str()); + static bool initialized = false; + if (!initialized) { + adc_init(); + initialized = true; + } +} + +void ADCSensor::dump_config() { + LOG_SENSOR("", "ADC Sensor", this); + if (this->is_temperature_) { + ESP_LOGCONFIG(TAG, " Pin: Temperature"); + } else { +#ifdef USE_ADC_SENSOR_VCC + ESP_LOGCONFIG(TAG, " Pin: VCC"); +#else + LOG_PIN(" Pin: ", this->pin_); +#endif // USE_ADC_SENSOR_VCC + } + ESP_LOGCONFIG(TAG, " Samples: %i", this->sample_count_); + LOG_UPDATE_INTERVAL(this); +} + +float ADCSensor::sample() { + if (this->is_temperature_) { + adc_set_temp_sensor_enabled(true); + delay(1); + adc_select_input(4); + uint32_t raw = 0; + for (uint8_t sample = 0; sample < this->sample_count_; sample++) { + raw += adc_read(); + } + raw = (raw + (this->sample_count_ >> 1)) / this->sample_count_; // NOLINT(clang-analyzer-core.DivideZero) + adc_set_temp_sensor_enabled(false); + if (this->output_raw_) { + return raw; + } + return raw * 3.3f / 4096.0f; + } + + uint8_t pin = this->pin_->get_pin(); +#ifdef CYW43_USES_VSYS_PIN + if (pin == PICO_VSYS_PIN) { + // Measuring VSYS on Raspberry Pico W needs to be wrapped with + // `cyw43_thread_enter()`/`cyw43_thread_exit()` as discussed in + // https://github.com/raspberrypi/pico-sdk/issues/1222, since Wifi chip and + // VSYS ADC both share GPIO29 + cyw43_thread_enter(); + } +#endif // CYW43_USES_VSYS_PIN + + adc_gpio_init(pin); + adc_select_input(pin - 26); + + uint32_t raw = 0; + for (uint8_t sample = 0; sample < this->sample_count_; sample++) { + raw += adc_read(); + } + raw = (raw + (this->sample_count_ >> 1)) / this->sample_count_; // NOLINT(clang-analyzer-core.DivideZero) + +#ifdef CYW43_USES_VSYS_PIN + if (pin == PICO_VSYS_PIN) { + cyw43_thread_exit(); + } +#endif // CYW43_USES_VSYS_PIN + + if (this->output_raw_) { + return raw; + } + float coeff = pin == PICO_VSYS_PIN ? 3.0f : 1.0f; + return raw * 3.3f / 4096.0f * coeff; +} + +} // namespace adc +} // namespace esphome + +#endif // USE_RP2040 diff --git a/esphome/components/alarm_control_panel/alarm_control_panel_call.cpp b/esphome/components/alarm_control_panel/alarm_control_panel_call.cpp index b1d2b2a097..7bb9b9989c 100644 --- a/esphome/components/alarm_control_panel/alarm_control_panel_call.cpp +++ b/esphome/components/alarm_control_panel/alarm_control_panel_call.cpp @@ -72,10 +72,9 @@ void AlarmControlPanelCall::validate_() { this->state_.reset(); return; } - if (state == ACP_STATE_DISARMED && - !(this->parent_->is_state_armed(this->parent_->get_state()) || - this->parent_->get_state() == ACP_STATE_PENDING || this->parent_->get_state() == ACP_STATE_ARMING || - this->parent_->get_state() == ACP_STATE_TRIGGERED)) { + if (state == ACP_STATE_DISARMED && !this->parent_->is_state_armed(this->parent_->get_state()) && + this->parent_->get_state() != ACP_STATE_PENDING && this->parent_->get_state() != ACP_STATE_ARMING && + this->parent_->get_state() != ACP_STATE_TRIGGERED) { ESP_LOGW(TAG, "Cannot disarm when not armed"); this->state_.reset(); return; diff --git a/esphome/components/animation/__init__.py b/esphome/components/animation/__init__.py index 5a308855de..21a82649f0 100644 --- a/esphome/components/animation/__init__.py +++ b/esphome/components/animation/__init__.py @@ -2,7 +2,6 @@ import logging from esphome import automation, core import esphome.codegen as cg -from esphome.components import font import esphome.components.image as espImage from esphome.components.image import ( CONF_USE_TRANSPARENCY, @@ -131,7 +130,7 @@ ANIMATION_SCHEMA = cv.Schema( ) ) -CONFIG_SCHEMA = cv.All(font.validate_pillow_installed, ANIMATION_SCHEMA) +CONFIG_SCHEMA = ANIMATION_SCHEMA NEXT_FRAME_SCHEMA = automation.maybe_simple_id( { diff --git a/esphome/components/apds9306/apds9306.cpp b/esphome/components/apds9306/apds9306.cpp index 7b79b0964c..bbb3ba1910 100644 --- a/esphome/components/apds9306/apds9306.cpp +++ b/esphome/components/apds9306/apds9306.cpp @@ -122,7 +122,8 @@ void APDS9306::update() { this->status_clear_warning(); - if (!(status &= 0b00001000)) { // No new data + status &= 0b00001000; + if (!status) { // No new data return; } diff --git a/esphome/components/audio/audio.h b/esphome/components/audio/audio.h index b0968dc8da..caf325cf54 100644 --- a/esphome/components/audio/audio.h +++ b/esphome/components/audio/audio.h @@ -1,7 +1,7 @@ #pragma once +#include #include -#include namespace esphome { namespace audio { diff --git a/esphome/components/bk72xx/__init__.py b/esphome/components/bk72xx/__init__.py index 6737631ac7..b5122de956 100644 --- a/esphome/components/bk72xx/__init__.py +++ b/esphome/components/bk72xx/__init__.py @@ -15,7 +15,7 @@ from esphome.components.libretiny.const import ( ) from esphome.core import CORE -from .boards import BK72XX_BOARDS, BK72XX_BOARD_PINS +from .boards import BK72XX_BOARD_PINS, BK72XX_BOARDS CODEOWNERS = ["@kuba2k2"] AUTO_LOAD = ["libretiny"] diff --git a/esphome/components/cse7766/cse7766.cpp b/esphome/components/cse7766/cse7766.cpp index 48240464b3..3907c195d0 100644 --- a/esphome/components/cse7766/cse7766.cpp +++ b/esphome/components/cse7766/cse7766.cpp @@ -43,7 +43,7 @@ bool CSE7766Component::check_byte_() { uint8_t index = this->raw_data_index_; uint8_t byte = this->raw_data_[index]; if (index == 0) { - return !((byte != 0x55) && ((byte & 0xF0) != 0xF0) && (byte != 0xAA)); + return (byte == 0x55) || ((byte & 0xF0) == 0xF0) || (byte == 0xAA); } if (index == 1) { diff --git a/esphome/components/deep_sleep/deep_sleep_esp32.cpp b/esphome/components/deep_sleep/deep_sleep_esp32.cpp index d54046bc11..d647140865 100644 --- a/esphome/components/deep_sleep/deep_sleep_esp32.cpp +++ b/esphome/components/deep_sleep/deep_sleep_esp32.cpp @@ -52,11 +52,11 @@ void DeepSleepComponent::dump_config_platform_() { bool DeepSleepComponent::prepare_to_sleep_() { if (this->wakeup_pin_mode_ == WAKEUP_PIN_MODE_KEEP_AWAKE && this->wakeup_pin_ != nullptr && - !this->sleep_duration_.has_value() && this->wakeup_pin_->digital_read()) { + this->wakeup_pin_->digital_read()) { // Defer deep sleep until inactive if (!this->next_enter_deep_sleep_) { this->status_set_warning(); - ESP_LOGW(TAG, "Waiting for pin_ to switch state to enter deep sleep..."); + ESP_LOGW(TAG, "Waiting wakeup pin state change to enter deep sleep..."); } this->next_enter_deep_sleep_ = true; return false; diff --git a/esphome/components/dht/dht.cpp b/esphome/components/dht/dht.cpp index db1c851d5f..3f9f9c57f4 100644 --- a/esphome/components/dht/dht.cpp +++ b/esphome/components/dht/dht.cpp @@ -135,7 +135,8 @@ bool HOT IRAM_ATTR DHT::read_sensor_(float *temperature, float *humidity, bool r // Wait for falling edge while (this->pin_->digital_read()) { - if ((end_time = micros()) - start_time > 90) { + end_time = micros(); + if (end_time - start_time > 90) { if (i < 0) { error_code = 3; } else { diff --git a/esphome/components/display/display.cpp b/esphome/components/display/display.cpp index 145a4f5278..f00c2936a8 100644 --- a/esphome/components/display/display.cpp +++ b/esphome/components/display/display.cpp @@ -1,6 +1,6 @@ #include "display.h" -#include "display_color_utils.h" #include +#include "display_color_utils.h" #include "esphome/core/hal.h" #include "esphome/core/log.h" @@ -662,20 +662,24 @@ void DisplayOnPageChangeTrigger::process(DisplayPage *from, DisplayPage *to) { if ((this->from_ == nullptr || this->from_ == from) && (this->to_ == nullptr || this->to_ == to)) this->trigger(from, to); } -void Display::strftime(int x, int y, BaseFont *font, Color color, TextAlign align, const char *format, ESPTime time) { +void Display::strftime(int x, int y, BaseFont *font, Color color, Color background, TextAlign align, const char *format, + ESPTime time) { char buffer[64]; size_t ret = time.strftime(buffer, sizeof(buffer), format); if (ret > 0) - this->print(x, y, font, color, align, buffer); + this->print(x, y, font, color, align, buffer, background); +} +void Display::strftime(int x, int y, BaseFont *font, Color color, TextAlign align, const char *format, ESPTime time) { + this->strftime(x, y, font, color, COLOR_OFF, align, format, time); } void Display::strftime(int x, int y, BaseFont *font, Color color, const char *format, ESPTime time) { - this->strftime(x, y, font, color, TextAlign::TOP_LEFT, format, time); + this->strftime(x, y, font, color, COLOR_OFF, TextAlign::TOP_LEFT, format, time); } void Display::strftime(int x, int y, BaseFont *font, TextAlign align, const char *format, ESPTime time) { - this->strftime(x, y, font, COLOR_ON, align, format, time); + this->strftime(x, y, font, COLOR_ON, COLOR_OFF, align, format, time); } void Display::strftime(int x, int y, BaseFont *font, const char *format, ESPTime time) { - this->strftime(x, y, font, COLOR_ON, TextAlign::TOP_LEFT, format, time); + this->strftime(x, y, font, COLOR_ON, COLOR_OFF, TextAlign::TOP_LEFT, format, time); } void Display::start_clipping(Rect rect) { diff --git a/esphome/components/display/display.h b/esphome/components/display/display.h index 54e897cdec..43da08f4ac 100644 --- a/esphome/components/display/display.h +++ b/esphome/components/display/display.h @@ -437,6 +437,20 @@ class Display : public PollingComponent { */ void printf(int x, int y, BaseFont *font, const char *format, ...) __attribute__((format(printf, 5, 6))); + /** Evaluate the strftime-format `format` and print the result with the anchor point at [x,y] with `font`. + * + * @param x The x coordinate of the text alignment anchor point. + * @param y The y coordinate of the text alignment anchor point. + * @param font The font to draw the text with. + * @param color The color to draw the text with. + * @param background The background color to draw the text with. + * @param align The alignment of the text. + * @param format The format to use. + * @param ... The arguments to use for the text formatting. + */ + void strftime(int x, int y, BaseFont *font, Color color, Color background, TextAlign align, const char *format, + ESPTime time) __attribute__((format(strftime, 8, 0))); + /** Evaluate the strftime-format `format` and print the result with the anchor point at [x,y] with `font`. * * @param x The x coordinate of the text alignment anchor point. diff --git a/esphome/components/display_menu_base/__init__.py b/esphome/components/display_menu_base/__init__.py index 8ae9cbc2a4..f9c0424104 100644 --- a/esphome/components/display_menu_base/__init__.py +++ b/esphome/components/display_menu_base/__init__.py @@ -68,8 +68,6 @@ IsActiveCondition = display_menu_base_ns.class_( "IsActiveCondition", automation.Condition ) -MULTI_CONF = True - MenuItemType = display_menu_base_ns.enum("MenuItemType") MENU_ITEM_TYPES = { diff --git a/esphome/components/display_menu_base/display_menu_base.cpp b/esphome/components/display_menu_base/display_menu_base.cpp index 5502623607..2d8e6ae5fc 100644 --- a/esphome/components/display_menu_base/display_menu_base.cpp +++ b/esphome/components/display_menu_base/display_menu_base.cpp @@ -280,7 +280,7 @@ bool DisplayMenuComponent::cursor_down_() { bool DisplayMenuComponent::enter_menu_() { this->displayed_item_->on_leave(); this->displayed_item_ = static_cast(this->get_selected_item_()); - this->selection_stack_.push_front({this->top_index_, this->cursor_index_}); + this->selection_stack_.emplace_front(this->top_index_, this->cursor_index_); this->cursor_index_ = this->top_index_ = 0; this->displayed_item_->on_enter(); diff --git a/esphome/components/dsmr/dsmr.cpp b/esphome/components/dsmr/dsmr.cpp index 193ea1d4e5..c0a2883d79 100644 --- a/esphome/components/dsmr/dsmr.cpp +++ b/esphome/components/dsmr/dsmr.cpp @@ -296,7 +296,7 @@ void Dsmr::dump_config() { } void Dsmr::set_decryption_key(const std::string &decryption_key) { - if (decryption_key.length() == 0) { + if (decryption_key.empty()) { ESP_LOGI(TAG, "Disabling decryption"); this->decryption_key_.clear(); if (this->crypt_telegram_ != nullptr) { diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index aaef68fa27..b0bde75451 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -65,6 +65,8 @@ _LOGGER = logging.getLogger(__name__) CODEOWNERS = ["@esphome/core"] AUTO_LOAD = ["preferences"] +CONF_RELEASE = "release" + def set_core_data(config): CORE.data[KEY_ESP32] = {} @@ -216,11 +218,17 @@ 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: +def _format_framework_espidf_version( + ver: cv.Version, release: str, for_platformio: bool +) -> 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" + if for_platformio: + return f"platformio/framework-espidf@~3.{ver.major}{ver.minor:02d}{ver.patch:02d}.0" + if release: + return f"pioarduino/framework-espidf@https://github.com/pioarduino/esp-idf/releases/download/v{str(ver)}.{release}/esp-idf-v{str(ver)}.zip" + return f"pioarduino/framework-espidf@https://github.com/pioarduino/esp-idf/releases/download/v{str(ver)}/esp-idf-v{str(ver)}.zip" # NOTE: Keep this in mind when updating the recommended version: @@ -241,11 +249,33 @@ ARDUINO_PLATFORM_VERSION = cv.Version(5, 4, 0) # The default/recommended esp-idf framework version # - https://github.com/espressif/esp-idf/releases # - https://api.registry.platformio.org/v3/packages/platformio/tool/framework-espidf -RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION = cv.Version(4, 4, 8) +RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION = cv.Version(5, 1, 5) # The platformio/espressif32 version to use for esp-idf frameworks # - https://github.com/platformio/platform-espressif32/releases # - https://api.registry.platformio.org/v3/packages/platformio/platform/espressif32 -ESP_IDF_PLATFORM_VERSION = cv.Version(5, 4, 0) +ESP_IDF_PLATFORM_VERSION = cv.Version(51, 3, 7) + +# List based on https://registry.platformio.org/tools/platformio/framework-espidf/versions +SUPPORTED_PLATFORMIO_ESP_IDF_5X = [ + cv.Version(5, 3, 1), + cv.Version(5, 3, 0), + cv.Version(5, 2, 2), + cv.Version(5, 2, 1), + cv.Version(5, 1, 2), + cv.Version(5, 1, 1), + cv.Version(5, 1, 0), + cv.Version(5, 0, 2), + cv.Version(5, 0, 1), + cv.Version(5, 0, 0), +] + +# pioarduino versions that don't require a release number +# List based on https://github.com/pioarduino/esp-idf/releases +SUPPORTED_PIOARDUINO_ESP_IDF_5X = [ + cv.Version(5, 3, 1), + cv.Version(5, 3, 0), + cv.Version(5, 1, 5), +] def _arduino_check_versions(value): @@ -286,8 +316,8 @@ def _arduino_check_versions(value): def _esp_idf_check_versions(value): value = value.copy() lookups = { - "dev": (cv.Version(5, 1, 2), "https://github.com/espressif/esp-idf.git"), - "latest": (cv.Version(5, 1, 2), None), + "dev": (cv.Version(5, 1, 5), "https://github.com/espressif/esp-idf.git"), + "latest": (cv.Version(5, 1, 5), None), "recommended": (RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION, None), } @@ -305,13 +335,51 @@ def _esp_idf_check_versions(value): if version < cv.Version(4, 0, 0): raise cv.Invalid("Only ESP-IDF 4.0+ is supported.") - value[CONF_VERSION] = str(version) - value[CONF_SOURCE] = source or _format_framework_espidf_version(version) + # flag this for later *before* we set value[CONF_PLATFORM_VERSION] below + has_platform_ver = CONF_PLATFORM_VERSION in value value[CONF_PLATFORM_VERSION] = value.get( CONF_PLATFORM_VERSION, _parse_platform_version(str(ESP_IDF_PLATFORM_VERSION)) ) + if ( + (is_platformio := _platform_is_platformio(value[CONF_PLATFORM_VERSION])) + and version.major >= 5 + and version not in SUPPORTED_PLATFORMIO_ESP_IDF_5X + ): + raise cv.Invalid( + f"ESP-IDF {str(version)} not supported by platformio/espressif32" + ) + + if ( + version.major < 5 + or ( + version in SUPPORTED_PLATFORMIO_ESP_IDF_5X + and version not in SUPPORTED_PIOARDUINO_ESP_IDF_5X + ) + ) and not has_platform_ver: + raise cv.Invalid( + f"ESP-IDF {value[CONF_VERSION]} may be supported by platformio/espressif32; please specify '{CONF_PLATFORM_VERSION}'" + ) + + if ( + not is_platformio + and CONF_RELEASE not in value + and version not in SUPPORTED_PIOARDUINO_ESP_IDF_5X + ): + raise cv.Invalid( + f"ESP-IDF {value[CONF_VERSION]} is not available with pioarduino; you may need to specify '{CONF_RELEASE}'" + ) + + value[CONF_VERSION] = str(version) + value[CONF_SOURCE] = source or _format_framework_espidf_version( + version, value.get(CONF_RELEASE, None), is_platformio + ) + + if value[CONF_SOURCE].startswith("http"): + # prefix is necessary or platformio will complain with a cryptic error + value[CONF_SOURCE] = f"framework-espidf@{value[CONF_SOURCE]}" + if version != RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION: _LOGGER.warning( "The selected ESP-IDF framework version is not the recommended one. " @@ -323,6 +391,12 @@ def _esp_idf_check_versions(value): def _parse_platform_version(value): try: + ver = cv.Version.parse(cv.version_number(value)) + if ver.major >= 50: # a pioarduino version + if "-" in value: + # maybe a release candidate?...definitely not our default, just use it as-is... + return f"https://github.com/pioarduino/platform-espressif32.git#{value}" + return f"https://github.com/pioarduino/platform-espressif32.git#{ver.major}.{ver.minor:02d}.{ver.patch:02d}" # if platform version is a valid version constraint, prefix the default package cv.platformio_version_constraint(value) return f"platformio/espressif32@{value}" @@ -330,6 +404,14 @@ def _parse_platform_version(value): return value +def _platform_is_platformio(value): + try: + ver = cv.Version.parse(cv.version_number(value)) + return ver.major < 50 + except cv.Invalid: + return "platformio" in value + + def _detect_variant(value): board = value[CONF_BOARD] if board in BOARDS: @@ -408,6 +490,7 @@ ESP_IDF_FRAMEWORK_SCHEMA = cv.All( cv.Schema( { cv.Optional(CONF_VERSION, default="recommended"): cv.string_strict, + cv.Optional(CONF_RELEASE): cv.string_strict, cv.Optional(CONF_SOURCE): cv.string_strict, cv.Optional(CONF_PLATFORM_VERSION): _parse_platform_version, cv.Optional(CONF_SDKCONFIG_OPTIONS, default={}): { @@ -511,10 +594,9 @@ async def to_code(config): cg.add_build_flag("-DUSE_ESP_IDF") cg.add_build_flag("-DUSE_ESP32_FRAMEWORK_ESP_IDF") cg.add_build_flag("-Wno-nonnull-compare") - cg.add_platformio_option( - "platform_packages", - [f"platformio/framework-espidf@{conf[CONF_SOURCE]}"], - ) + + cg.add_platformio_option("platform_packages", [conf[CONF_SOURCE]]) + # platformio/toolchain-esp32ulp does not support linux_aarch64 yet and has not been updated for over 2 years # This is espressif's own published version which is more up to date. cg.add_platformio_option( diff --git a/esphome/components/esp32/boards.py b/esphome/components/esp32/boards.py index 02744ecb6f..81400eb9c3 100644 --- a/esphome/components/esp32/boards.py +++ b/esphome/components/esp32/boards.py @@ -1,4 +1,12 @@ -from .const import VARIANT_ESP32, VARIANT_ESP32C3, VARIANT_ESP32S2, VARIANT_ESP32S3 +from .const import ( + VARIANT_ESP32, + VARIANT_ESP32C2, + VARIANT_ESP32C3, + VARIANT_ESP32C6, + VARIANT_ESP32H2, + VARIANT_ESP32S2, + VARIANT_ESP32S3, +) ESP32_BASE_PINS = { "TX": 1, @@ -1344,6 +1352,26 @@ done | sort """ BOARDS = { + "4d_systems_esp32s3_gen4_r8n16": { + "name": "4D Systems GEN4-ESP32 16MB (ESP32S3-R8N16)", + "variant": VARIANT_ESP32S3, + }, + "adafruit_camera_esp32s3": { + "name": "Adafruit pyCamera S3", + "variant": VARIANT_ESP32S3, + }, + "adafruit_feather_esp32c6": { + "name": "Adafruit Feather ESP32-C6", + "variant": VARIANT_ESP32C6, + }, + "adafruit_feather_esp32s2": { + "name": "Adafruit Feather ESP32-S2", + "variant": VARIANT_ESP32S2, + }, + "adafruit_feather_esp32s2_reversetft": { + "name": "Adafruit Feather ESP32-S2 Reverse TFT", + "variant": VARIANT_ESP32S2, + }, "adafruit_feather_esp32s2_tft": { "name": "Adafruit Feather ESP32-S2 TFT", "variant": VARIANT_ESP32S2, @@ -1356,6 +1384,10 @@ BOARDS = { "name": "Adafruit Feather ESP32-S3 No PSRAM", "variant": VARIANT_ESP32S3, }, + "adafruit_feather_esp32s3_reversetft": { + "name": "Adafruit Feather ESP32-S3 Reverse TFT", + "variant": VARIANT_ESP32S3, + }, "adafruit_feather_esp32s3_tft": { "name": "Adafruit Feather ESP32-S3 TFT", "variant": VARIANT_ESP32S3, @@ -1376,10 +1408,18 @@ BOARDS = { "name": "Adafruit MagTag 2.9", "variant": VARIANT_ESP32S2, }, + "adafruit_matrixportal_esp32s3": { + "name": "Adafruit MatrixPortal ESP32-S3", + "variant": VARIANT_ESP32S3, + }, "adafruit_metro_esp32s2": { "name": "Adafruit Metro ESP32-S2", "variant": VARIANT_ESP32S2, }, + "adafruit_metro_esp32s3": { + "name": "Adafruit Metro ESP32-S3", + "variant": VARIANT_ESP32S3, + }, "adafruit_qtpy_esp32c3": { "name": "Adafruit QT Py ESP32-C3", "variant": VARIANT_ESP32C3, @@ -1392,10 +1432,18 @@ BOARDS = { "name": "Adafruit QT Py ESP32-S2", "variant": VARIANT_ESP32S2, }, + "adafruit_qtpy_esp32s3_n4r2": { + "name": "Adafruit QT Py ESP32-S3 (4M Flash 2M PSRAM)", + "variant": VARIANT_ESP32S3, + }, "adafruit_qtpy_esp32s3_nopsram": { "name": "Adafruit QT Py ESP32-S3 No PSRAM", "variant": VARIANT_ESP32S3, }, + "adafruit_qualia_s3_rgb666": { + "name": "Adafruit Qualia ESP32-S3 RGB666", + "variant": VARIANT_ESP32S3, + }, "airm2m_core_esp32c3": { "name": "AirM2M CORE ESP32C3", "variant": VARIANT_ESP32C3, @@ -1404,14 +1452,30 @@ BOARDS = { "name": "ALKS ESP32", "variant": VARIANT_ESP32, }, + "arduino_nano_esp32": { + "name": "Arduino Nano ESP32", + "variant": VARIANT_ESP32S3, + }, + "atd147_s3": { + "name": "ArtronShop ATD1.47-S3", + "variant": VARIANT_ESP32S3, + }, "atmegazero_esp32s2": { "name": "EspinalLab ATMegaZero ESP32-S2", "variant": VARIANT_ESP32S2, }, + "aventen_s3_sync": { + "name": "Aventen S3 Sync", + "variant": VARIANT_ESP32S3, + }, "az-delivery-devkit-v4": { "name": "AZ-Delivery ESP-32 Dev Kit C V4", "variant": VARIANT_ESP32, }, + "bee_data_logger": { + "name": "Smart Bee Data Logger", + "variant": VARIANT_ESP32S3, + }, "bee_motion_mini": { "name": "Smart Bee Motion Mini", "variant": VARIANT_ESP32C3, @@ -1436,14 +1500,6 @@ BOARDS = { "name": "BPI-Leaf-S3", "variant": VARIANT_ESP32S3, }, - "briki_abc_esp32": { - "name": "Briki ABC (MBC-WB) - ESP32", - "variant": VARIANT_ESP32, - }, - "briki_mbc-wb_esp32": { - "name": "Briki MBC-WB - ESP32", - "variant": VARIANT_ESP32, - }, "cnrs_aw2eth": { "name": "CNRS AW2ETH", "variant": VARIANT_ESP32, @@ -1496,18 +1552,38 @@ BOARDS = { "name": "DFRobot Beetle ESP32-C3", "variant": VARIANT_ESP32C3, }, + "dfrobot_firebeetle2_esp32e": { + "name": "DFRobot Firebeetle 2 ESP32-E", + "variant": VARIANT_ESP32, + }, "dfrobot_firebeetle2_esp32s3": { "name": "DFRobot Firebeetle 2 ESP32-S3", "variant": VARIANT_ESP32S3, }, + "dfrobot_romeo_esp32s3": { + "name": "DFRobot Romeo ESP32-S3", + "variant": VARIANT_ESP32S3, + }, "dpu_esp32": { "name": "TAMC DPU ESP32", "variant": VARIANT_ESP32, }, + "edgebox-esp-100": { + "name": "Seeed Studio Edgebox-ESP-100", + "variant": VARIANT_ESP32S3, + }, "esp320": { "name": "Electronic SweetPeas ESP320", "variant": VARIANT_ESP32, }, + "esp32-c2-devkitm-1": { + "name": "Espressif ESP32-C2-DevKitM-1", + "variant": VARIANT_ESP32C2, + }, + "esp32-c3-devkitc-02": { + "name": "Espressif ESP32-C3-DevKitC-02", + "variant": VARIANT_ESP32C3, + }, "esp32-c3-devkitm-1": { "name": "Espressif ESP32-C3-DevKitM-1", "variant": VARIANT_ESP32C3, @@ -1516,6 +1592,14 @@ BOARDS = { "name": "Ai-Thinker ESP-C3-M1-I-Kit", "variant": VARIANT_ESP32C3, }, + "esp32-c6-devkitc-1": { + "name": "Espressif ESP32-C6-DevKitC-1", + "variant": VARIANT_ESP32C6, + }, + "esp32-c6-devkitm-1": { + "name": "Espressif ESP32-C6-DevKitM-1", + "variant": VARIANT_ESP32C6, + }, "esp32cam": { "name": "AI Thinker ESP32-CAM", "variant": VARIANT_ESP32, @@ -1544,6 +1628,14 @@ BOARDS = { "name": "OLIMEX ESP32-GATEWAY", "variant": VARIANT_ESP32, }, + "esp32-h2-devkitm-1": { + "name": "Espressif ESP32-H2-DevKit", + "variant": VARIANT_ESP32H2, + }, + "esp32-pico-devkitm-2": { + "name": "Espressif ESP32-PICO-DevKitM-2", + "variant": VARIANT_ESP32, + }, "esp32-poe-iso": { "name": "OLIMEX ESP32-PoE-ISO", "variant": VARIANT_ESP32, @@ -1580,10 +1672,22 @@ BOARDS = { "name": "Espressif ESP32-S3-DevKitC-1-N8 (8 MB QD, No PSRAM)", "variant": VARIANT_ESP32S3, }, - "esp32-s3-korvo-2": { - "name": "Espressif ESP32-S3-Korvo-2", + "esp32-s3-devkitm-1": { + "name": "Espressif ESP32-S3-DevKitM-1", "variant": VARIANT_ESP32S3, }, + "esp32s3_powerfeather": { + "name": "ESP32-S3 PowerFeather", + "variant": VARIANT_ESP32S3, + }, + "esp32s3usbotg": { + "name": "Espressif ESP32-S3-USB-OTG", + "variant": VARIANT_ESP32S3, + }, + "esp32-solo1": { + "name": "Espressif Generic ESP32-solo1 4M Flash", + "variant": VARIANT_ESP32, + }, "esp32thing": { "name": "SparkFun ESP32 Thing", "variant": VARIANT_ESP32, @@ -1652,9 +1756,9 @@ BOARDS = { "name": "Heltec WiFi Kit 32", "variant": VARIANT_ESP32, }, - "heltec_wifi_kit_32_v2": { - "name": "Heltec WiFi Kit 32 (V2)", - "variant": VARIANT_ESP32, + "heltec_wifi_kit_32_V3": { + "name": "Heltec WiFi Kit 32 (V3)", + "variant": VARIANT_ESP32S3, }, "heltec_wifi_lora_32": { "name": "Heltec WiFi LoRa 32", @@ -1664,6 +1768,10 @@ BOARDS = { "name": "Heltec WiFi LoRa 32 (V2)", "variant": VARIANT_ESP32, }, + "heltec_wifi_lora_32_V3": { + "name": "Heltec WiFi LoRa 32 (V3)", + "variant": VARIANT_ESP32S3, + }, "heltec_wireless_stick_lite": { "name": "Heltec Wireless Stick Lite", "variant": VARIANT_ESP32, @@ -1708,6 +1816,14 @@ BOARDS = { "name": "oddWires IoT-Bus Proteus", "variant": VARIANT_ESP32, }, + "ioxesp32": { + "name": "ArtronShop IOXESP32", + "variant": VARIANT_ESP32, + }, + "ioxesp32ps": { + "name": "ArtronShop IOXESP32PS", + "variant": VARIANT_ESP32, + }, "kb32-ft": { "name": "MakerAsia KB32-FT", "variant": VARIANT_ESP32, @@ -1720,10 +1836,26 @@ BOARDS = { "name": "Labplus mPython", "variant": VARIANT_ESP32, }, + "lilka_v2": { + "name": "Lilka v2", + "variant": VARIANT_ESP32S3, + }, + "lilygo-t-display": { + "name": "LilyGo T-Display", + "variant": VARIANT_ESP32, + }, + "lilygo-t-display-s3": { + "name": "LilyGo T-Display-S3", + "variant": VARIANT_ESP32S3, + }, "lionbit": { "name": "Lion:Bit Dev Board", "variant": VARIANT_ESP32, }, + "lionbits3": { + "name": "Lion:Bit S3 STEM Dev Board", + "variant": VARIANT_ESP32S3, + }, "lolin32_lite": { "name": "WEMOS LOLIN32 Lite", "variant": VARIANT_ESP32, @@ -1752,10 +1884,18 @@ BOARDS = { "name": "WEMOS LOLIN S2 PICO", "variant": VARIANT_ESP32S2, }, + "lolin_s3_mini": { + "name": "WEMOS LOLIN S3 Mini", + "variant": VARIANT_ESP32S3, + }, "lolin_s3": { "name": "WEMOS LOLIN S3", "variant": VARIANT_ESP32S3, }, + "lolin_s3_pro": { + "name": "WEMOS LOLIN S3 PRO", + "variant": VARIANT_ESP32S3, + }, "lopy4": { "name": "Pycom LoPy4", "variant": VARIANT_ESP32, @@ -1768,10 +1908,18 @@ BOARDS = { "name": "M5Stack-ATOM", "variant": VARIANT_ESP32, }, + "m5stack-atoms3": { + "name": "M5Stack AtomS3", + "variant": VARIANT_ESP32S3, + }, "m5stack-core2": { "name": "M5Stack Core2", "variant": VARIANT_ESP32, }, + "m5stack-core-esp32-16M": { + "name": "M5Stack Core ESP32 16M", + "variant": VARIANT_ESP32, + }, "m5stack-core-esp32": { "name": "M5Stack Core ESP32", "variant": VARIANT_ESP32, @@ -1780,6 +1928,10 @@ BOARDS = { "name": "M5Stack-Core Ink", "variant": VARIANT_ESP32, }, + "m5stack-cores3": { + "name": "M5Stack CoreS3", + "variant": VARIANT_ESP32S3, + }, "m5stack-fire": { "name": "M5Stack FIRE", "variant": VARIANT_ESP32, @@ -1788,6 +1940,14 @@ BOARDS = { "name": "M5Stack GREY ESP32", "variant": VARIANT_ESP32, }, + "m5stack_paper": { + "name": "M5Stack Paper", + "variant": VARIANT_ESP32, + }, + "m5stack-stamps3": { + "name": "M5Stack StampS3", + "variant": VARIANT_ESP32S3, + }, "m5stack-station": { "name": "M5Stack Station", "variant": VARIANT_ESP32, @@ -1796,6 +1956,10 @@ BOARDS = { "name": "M5Stack Timer CAM", "variant": VARIANT_ESP32, }, + "m5stamp-pico": { + "name": "M5Stamp-Pico", + "variant": VARIANT_ESP32, + }, "m5stick-c": { "name": "M5Stick-C", "variant": VARIANT_ESP32, @@ -1832,10 +1996,26 @@ BOARDS = { "name": "Deparment of Alchemy MiniMain ESP32-S2", "variant": VARIANT_ESP32S2, }, + "motorgo_mini_1": { + "name": "MotorGo Mini 1 (ESP32-S3)", + "variant": VARIANT_ESP32S3, + }, + "namino_arancio": { + "name": "Namino Arancio", + "variant": VARIANT_ESP32S3, + }, + "namino_rosso": { + "name": "Namino Rosso", + "variant": VARIANT_ESP32S3, + }, "nano32": { "name": "MakerAsia Nano32", "variant": VARIANT_ESP32, }, + "nebulas3": { + "name": "Kinetic Dynamics Nebula S3", + "variant": VARIANT_ESP32S3, + }, "nina_w10": { "name": "u-blox NINA-W10 series", "variant": VARIANT_ESP32, @@ -1896,10 +2076,22 @@ BOARDS = { "name": "Munich Labs RedPill ESP32-S3", "variant": VARIANT_ESP32S3, }, + "roboheart_hercules": { + "name": "RoboHeart Hercules", + "variant": VARIANT_ESP32, + }, "seeed_xiao_esp32c3": { "name": "Seeed Studio XIAO ESP32C3", "variant": VARIANT_ESP32C3, }, + "seeed_xiao_esp32s3": { + "name": "Seeed Studio XIAO ESP32S3", + "variant": VARIANT_ESP32S3, + }, + "sensebox_mcu_esp32s2": { + "name": "senseBox MCU-S2 ESP32-S2", + "variant": VARIANT_ESP32S2, + }, "sensesiot_weizen": { "name": "LOGISENSES Senses Weizen", "variant": VARIANT_ESP32, @@ -1912,6 +2104,10 @@ BOARDS = { "name": "S.ODI Ultra v1", "variant": VARIANT_ESP32, }, + "sparkfun_esp32c6_thing_plus": { + "name": "Sparkfun ESP32-C6 Thing Plus", + "variant": VARIANT_ESP32C6, + }, "sparkfun_esp32_iot_redboard": { "name": "SparkFun ESP32 IoT RedBoard", "variant": VARIANT_ESP32, @@ -2004,6 +2200,10 @@ BOARDS = { "name": "Unexpected Maker FeatherS3", "variant": VARIANT_ESP32S3, }, + "um_nanos3": { + "name": "Unexpected Maker NanoS3", + "variant": VARIANT_ESP32S3, + }, "um_pros3": { "name": "Unexpected Maker PROS3", "variant": VARIANT_ESP32S3, @@ -2040,6 +2240,14 @@ BOARDS = { "name": "uPesy ESP32 Wrover DevKit", "variant": VARIANT_ESP32, }, + "valtrack_v4_mfw_esp32_c3": { + "name": "Valetron Systems VALTRACK-V4MVF", + "variant": VARIANT_ESP32C3, + }, + "valtrack_v4_vts_esp32_c3": { + "name": "Valetron Systems VALTRACK-V4VTS", + "variant": VARIANT_ESP32C3, + }, "vintlabs-devkit-v1": { "name": "VintLabs ESP32 Devkit", "variant": VARIANT_ESP32, diff --git a/esphome/components/esp32_ble/__init__.py b/esphome/components/esp32_ble/__init__.py index 75cf9d707d..08e30c9247 100644 --- a/esphome/components/esp32_ble/__init__.py +++ b/esphome/components/esp32_ble/__init__.py @@ -2,8 +2,10 @@ from esphome import automation import esphome.codegen as cg from esphome.components.esp32 import add_idf_sdkconfig_option, const, get_esp32_variant import esphome.config_validation as cv -from esphome.const import CONF_ENABLE_ON_BOOT, CONF_ID +from esphome.const import CONF_ENABLE_ON_BOOT, CONF_ESPHOME, CONF_ID, CONF_NAME from esphome.core import CORE +from esphome.core.config import CONF_NAME_ADD_MAC_SUFFIX +import esphome.final_validate as fv DEPENDENCIES = ["esp32"] CODEOWNERS = ["@jesserockz", "@Rapsssito"] @@ -50,6 +52,7 @@ TX_POWER_LEVELS = { CONFIG_SCHEMA = cv.Schema( { cv.GenerateID(): cv.declare_id(ESP32BLE), + cv.Optional(CONF_NAME): cv.All(cv.string, cv.Length(max=20)), cv.Optional(CONF_IO_CAPABILITY, default="none"): cv.enum( IO_CAPABILITY, lower=True ), @@ -67,7 +70,22 @@ def validate_variant(_): raise cv.Invalid(f"{variant} does not support Bluetooth") -FINAL_VALIDATE_SCHEMA = validate_variant +def final_validation(config): + validate_variant(config) + if (name := config.get(CONF_NAME)) is not None: + full_config = fv.full_config.get() + max_length = 20 + if full_config[CONF_ESPHOME][CONF_NAME_ADD_MAC_SUFFIX]: + max_length -= 7 # "-AABBCC" is appended when add mac suffix option is used + if len(name) > max_length: + raise cv.Invalid( + f"Name '{name}' is too long, maximum length is {max_length} characters" + ) + + return config + + +FINAL_VALIDATE_SCHEMA = final_validation async def to_code(config): @@ -75,6 +93,8 @@ async def to_code(config): cg.add(var.set_enable_on_boot(config[CONF_ENABLE_ON_BOOT])) cg.add(var.set_io_capability(config[CONF_IO_CAPABILITY])) cg.add(var.set_advertising_cycle_time(config[CONF_ADVERTISING_CYCLE_TIME])) + if (name := config.get(CONF_NAME)) is not None: + cg.add(var.set_name(name)) await cg.register_component(var, config) if CORE.using_esp_idf: diff --git a/esphome/components/esp32_ble/ble.cpp b/esphome/components/esp32_ble/ble.cpp index 5d08b6e973..b10e454c21 100644 --- a/esphome/components/esp32_ble/ble.cpp +++ b/esphome/components/esp32_ble/ble.cpp @@ -27,6 +27,9 @@ namespace esp32_ble { static const char *const TAG = "esp32_ble"; +static RAMAllocator EVENT_ALLOCATOR( // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) + RAMAllocator::ALLOW_FAILURE | RAMAllocator::ALLOC_INTERNAL); + void ESP32BLE::setup() { global_ble = this; ESP_LOGCONFIG(TAG, "Setting up BLE..."); @@ -188,12 +191,20 @@ bool ESP32BLE::ble_setup_() { } } - std::string name = App.get_name(); - if (name.length() > 20) { + std::string name; + if (this->name_.has_value()) { + name = this->name_.value(); if (App.is_name_add_mac_suffix_enabled()) { - name.erase(name.begin() + 13, name.end() - 7); // Remove characters between 13 and the mac address - } else { - name = name.substr(0, 20); + name += "-" + get_mac_address().substr(6); + } + } else { + name = App.get_name(); + if (name.length() > 20) { + if (App.is_name_add_mac_suffix_enabled()) { + name.erase(name.begin() + 13, name.end() - 7); // Remove characters between 13 and the mac address + } else { + name = name.substr(0, 20); + } } } @@ -314,7 +325,8 @@ void ESP32BLE::loop() { default: break; } - delete ble_event; // NOLINT(cppcoreguidelines-owning-memory) + ble_event->~BLEEvent(); + EVENT_ALLOCATOR.deallocate(ble_event, 1); ble_event = this->ble_events_.pop(); } if (this->advertising_ != nullptr) { @@ -323,9 +335,14 @@ void ESP32BLE::loop() { } void ESP32BLE::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) { - BLEEvent *new_event = new BLEEvent(event, param); // NOLINT(cppcoreguidelines-owning-memory) + BLEEvent *new_event = EVENT_ALLOCATOR.allocate(1); + if (new_event == nullptr) { + // Memory too fragmented to allocate new event. Can only drop it until memory comes back + return; + } + new (new_event) BLEEvent(event, param); global_ble->ble_events_.push(new_event); -} // NOLINT(clang-analyzer-cplusplus.NewDeleteLeaks) +} // NOLINT(clang-analyzer-unix.Malloc) void ESP32BLE::real_gap_event_handler_(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) { ESP_LOGV(TAG, "(BLE) gap_event_handler - %d", event); @@ -336,9 +353,14 @@ void ESP32BLE::real_gap_event_handler_(esp_gap_ble_cb_event_t event, esp_ble_gap void ESP32BLE::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param) { - BLEEvent *new_event = new BLEEvent(event, gatts_if, param); // NOLINT(cppcoreguidelines-owning-memory) + BLEEvent *new_event = EVENT_ALLOCATOR.allocate(1); + if (new_event == nullptr) { + // Memory too fragmented to allocate new event. Can only drop it until memory comes back + return; + } + new (new_event) BLEEvent(event, gatts_if, param); global_ble->ble_events_.push(new_event); -} // NOLINT(clang-analyzer-cplusplus.NewDeleteLeaks) +} // NOLINT(clang-analyzer-unix.Malloc) void ESP32BLE::real_gatts_event_handler_(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param) { @@ -350,9 +372,14 @@ void ESP32BLE::real_gatts_event_handler_(esp_gatts_cb_event_t event, esp_gatt_if void ESP32BLE::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) { - BLEEvent *new_event = new BLEEvent(event, gattc_if, param); // NOLINT(cppcoreguidelines-owning-memory) + BLEEvent *new_event = EVENT_ALLOCATOR.allocate(1); + if (new_event == nullptr) { + // Memory too fragmented to allocate new event. Can only drop it until memory comes back + return; + } + new (new_event) BLEEvent(event, gattc_if, param); global_ble->ble_events_.push(new_event); -} // NOLINT(clang-analyzer-cplusplus.NewDeleteLeaks) +} // NOLINT(clang-analyzer-unix.Malloc) void ESP32BLE::real_gattc_event_handler_(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) { diff --git a/esphome/components/esp32_ble/ble.h b/esphome/components/esp32_ble/ble.h index 7c55583852..ed7575f128 100644 --- a/esphome/components/esp32_ble/ble.h +++ b/esphome/components/esp32_ble/ble.h @@ -90,6 +90,7 @@ class ESP32BLE : public Component { void loop() override; void dump_config() override; float get_setup_priority() const override; + void set_name(const std::string &name) { this->name_ = name; } void advertising_start(); void advertising_set_service_data(const std::vector &data); @@ -131,6 +132,7 @@ class ESP32BLE : public Component { esp_ble_io_cap_t io_cap_{ESP_IO_CAP_NONE}; uint32_t advertising_cycle_time_; bool enable_on_boot_; + optional name_; }; // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) diff --git a/esphome/components/esp32_ble/ble_advertising.cpp b/esphome/components/esp32_ble/ble_advertising.cpp index 1d340c76d9..92b7c60368 100644 --- a/esphome/components/esp32_ble/ble_advertising.cpp +++ b/esphome/components/esp32_ble/ble_advertising.cpp @@ -83,7 +83,7 @@ esp_err_t BLEAdvertising::services_advertisement_() { esp_err_t err; this->advertising_data_.set_scan_rsp = false; - this->advertising_data_.include_name = !this->scan_response_; + this->advertising_data_.include_name = true; this->advertising_data_.include_txpower = !this->scan_response_; err = esp_ble_gap_config_adv_data(&this->advertising_data_); if (err != ESP_OK) { diff --git a/esphome/components/esp32_ble/ble_uuid.cpp b/esphome/components/esp32_ble/ble_uuid.cpp index 07ac719434..aa1edd96b2 100644 --- a/esphome/components/esp32_ble/ble_uuid.cpp +++ b/esphome/components/esp32_ble/ble_uuid.cpp @@ -34,7 +34,7 @@ ESPBTUUID ESPBTUUID::from_raw(const uint8_t *data) { ESPBTUUID ESPBTUUID::from_raw_reversed(const uint8_t *data) { ESPBTUUID ret; ret.uuid_.len = ESP_UUID_LEN_128; - for (int i = 0; i < ESP_UUID_LEN_128; i++) + for (uint8_t i = 0; i < ESP_UUID_LEN_128; i++) ret.uuid_.uuid.uuid128[ESP_UUID_LEN_128 - 1 - i] = data[i]; return ret; } @@ -43,30 +43,30 @@ ESPBTUUID ESPBTUUID::from_raw(const std::string &data) { if (data.length() == 4) { ret.uuid_.len = ESP_UUID_LEN_16; ret.uuid_.uuid.uuid16 = 0; - for (int i = 0; i < data.length();) { + for (uint i = 0; i < data.length(); i += 2) { uint8_t msb = data.c_str()[i]; uint8_t lsb = data.c_str()[i + 1]; + uint8_t lsb_shift = i <= 2 ? (2 - i) * 4 : 0; if (msb > '9') msb -= 7; if (lsb > '9') lsb -= 7; - ret.uuid_.uuid.uuid16 += (((msb & 0x0F) << 4) | (lsb & 0x0F)) << (2 - i) * 4; - i += 2; + ret.uuid_.uuid.uuid16 += (((msb & 0x0F) << 4) | (lsb & 0x0F)) << lsb_shift; } } else if (data.length() == 8) { ret.uuid_.len = ESP_UUID_LEN_32; ret.uuid_.uuid.uuid32 = 0; - for (int i = 0; i < data.length();) { + for (uint i = 0; i < data.length(); i += 2) { uint8_t msb = data.c_str()[i]; uint8_t lsb = data.c_str()[i + 1]; + uint8_t lsb_shift = i <= 6 ? (6 - i) * 4 : 0; if (msb > '9') msb -= 7; if (lsb > '9') lsb -= 7; - ret.uuid_.uuid.uuid32 += (((msb & 0x0F) << 4) | (lsb & 0x0F)) << (6 - i) * 4; - i += 2; + ret.uuid_.uuid.uuid32 += (((msb & 0x0F) << 4) | (lsb & 0x0F)) << lsb_shift; } } else if (data.length() == 16) { // how we can have 16 byte length string reprezenting 128 bit uuid??? needs to be // investigated (lack of time) @@ -77,7 +77,7 @@ ESPBTUUID ESPBTUUID::from_raw(const std::string &data) { // UUID format. ret.uuid_.len = ESP_UUID_LEN_128; int n = 0; - for (int i = 0; i < data.length();) { + for (uint i = 0; i < data.length(); i += 2) { if (data.c_str()[i] == '-') i++; uint8_t msb = data.c_str()[i]; @@ -88,7 +88,6 @@ ESPBTUUID ESPBTUUID::from_raw(const std::string &data) { if (lsb > '9') lsb -= 7; ret.uuid_.uuid.uuid128[15 - n++] = ((msb & 0x0F) << 4) | (lsb & 0x0F); - i += 2; } } else { ESP_LOGE(TAG, "ERROR: UUID value not 2, 4, 16 or 36 bytes - %s", data.c_str()); @@ -155,7 +154,7 @@ bool ESPBTUUID::operator==(const ESPBTUUID &uuid) const { } break; case ESP_UUID_LEN_128: - for (int i = 0; i < ESP_UUID_LEN_128; i++) { + for (uint8_t i = 0; i < ESP_UUID_LEN_128; i++) { if (uuid.uuid_.uuid.uuid128[i] != this->uuid_.uuid.uuid128[i]) { return false; } diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp index b86d32ee61..6d051e3d4a 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp @@ -432,7 +432,7 @@ void ESPBTDevice::parse_scan_rst(const esp_ble_gap_cb_param_t::ble_scan_result_e #ifdef ESPHOME_LOG_HAS_VERY_VERBOSE ESP_LOGVV(TAG, "Parse Result:"); - const char *address_type = ""; + const char *address_type; switch (this->address_type_) { case BLE_ADDR_TYPE_PUBLIC: address_type = "PUBLIC"; @@ -446,6 +446,9 @@ void ESPBTDevice::parse_scan_rst(const esp_ble_gap_cb_param_t::ble_scan_result_e case BLE_ADDR_TYPE_RPA_RANDOM: address_type = "RPA_RANDOM"; break; + default: + address_type = "UNKNOWN"; + break; } ESP_LOGVV(TAG, " Address: %02X:%02X:%02X:%02X:%02X:%02X (%s)", this->address_[0], this->address_[1], this->address_[2], this->address_[3], this->address_[4], this->address_[5], address_type); diff --git a/esphome/components/esp32_camera_web_server/camera_web_server.h b/esphome/components/esp32_camera_web_server/camera_web_server.h index f65625554c..3ba8f31dd7 100644 --- a/esphome/components/esp32_camera_web_server/camera_web_server.h +++ b/esphome/components/esp32_camera_web_server/camera_web_server.h @@ -11,7 +11,7 @@ #include "esphome/core/helpers.h" #include "esphome/core/preferences.h" -struct httpd_req; +struct httpd_req; // NOLINT(readability-identifier-naming) namespace esphome { namespace esp32_camera_web_server { diff --git a/esphome/components/esp32_rmt_led_strip/light.py b/esphome/components/esp32_rmt_led_strip/light.py index 79f339e248..976f70e858 100644 --- a/esphome/components/esp32_rmt_led_strip/light.py +++ b/esphome/components/esp32_rmt_led_strip/light.py @@ -1,9 +1,9 @@ from dataclasses import dataclass -import esphome.codegen as cg -import esphome.config_validation as cv from esphome import pins +import esphome.codegen as cg from esphome.components import esp32_rmt, light +import esphome.config_validation as cv from esphome.const import ( CONF_CHIPSET, CONF_IS_RGBW, @@ -103,7 +103,7 @@ CONFIG_SCHEMA = cv.All( default="0 us", ): cv.positive_time_period_nanoseconds, } - ), + ).extend(cv.COMPONENT_SCHEMA), cv.has_exactly_one_key(CONF_CHIPSET, CONF_BIT0_HIGH), ) diff --git a/esphome/components/esp8266/__init__.py b/esphome/components/esp8266/__init__.py index 64b127bda3..c73027fe1b 100644 --- a/esphome/components/esp8266/__init__.py +++ b/esphome/components/esp8266/__init__.py @@ -1,10 +1,13 @@ import logging import os +import esphome.codegen as cg +import esphome.config_validation as cv from esphome.const import ( CONF_BOARD, CONF_BOARD_FLASH_MODE, CONF_FRAMEWORK, + CONF_PLATFORM_VERSION, CONF_SOURCE, CONF_VERSION, KEY_CORE, @@ -12,27 +15,22 @@ from esphome.const import ( KEY_TARGET_FRAMEWORK, KEY_TARGET_PLATFORM, PLATFORM_ESP8266, - CONF_PLATFORM_VERSION, ) from esphome.core import CORE, coroutine_with_priority -import esphome.config_validation as cv -import esphome.codegen as cg from esphome.helpers import copy_file_if_changed +from .boards import BOARDS, ESP8266_LD_SCRIPTS from .const import ( - CONF_RESTORE_FROM_FLASH, CONF_EARLY_PIN_INIT, + CONF_RESTORE_FROM_FLASH, KEY_BOARD, KEY_ESP8266, KEY_FLASH_SIZE, KEY_PIN_INITIAL_STATES, esp8266_ns, ) -from .boards import BOARDS, ESP8266_LD_SCRIPTS - from .gpio import PinInitialState, add_pin_initial_states_array - CODEOWNERS = ["@esphome/core"] _LOGGER = logging.getLogger(__name__) AUTO_LOAD = ["preferences"] diff --git a/esphome/components/ezo/ezo.cpp b/esphome/components/ezo/ezo.cpp index 8e4486dbf2..10f3d530ce 100644 --- a/esphome/components/ezo/ezo.cpp +++ b/esphome/components/ezo/ezo.cpp @@ -111,11 +111,11 @@ void EZOSensor::loop() { if (buf[0] == 1) { std::string payload = reinterpret_cast(&buf[1]); if (!payload.empty()) { + auto start_location = payload.find(','); switch (to_run->command_type) { case EzoCommandType::EZO_READ: { // some sensors return multiple comma-separated values, terminate string after first one - int start_location = 0; - if ((start_location = payload.find(',')) != std::string::npos) { + if (start_location != std::string::npos) { payload.erase(start_location); } auto val = parse_number(payload); @@ -126,49 +126,37 @@ void EZOSensor::loop() { } break; } - case EzoCommandType::EZO_LED: { + case EzoCommandType::EZO_LED: this->led_callback_.call(payload.back() == '1'); break; - } - case EzoCommandType::EZO_DEVICE_INFORMATION: { - int start_location = 0; - if ((start_location = payload.find(',')) != std::string::npos) { + case EzoCommandType::EZO_DEVICE_INFORMATION: + if (start_location != std::string::npos) { this->device_infomation_callback_.call(payload.substr(start_location + 1)); } break; - } - case EzoCommandType::EZO_SLOPE: { - int start_location = 0; - if ((start_location = payload.find(',')) != std::string::npos) { + case EzoCommandType::EZO_SLOPE: + if (start_location != std::string::npos) { this->slope_callback_.call(payload.substr(start_location + 1)); } break; - } - case EzoCommandType::EZO_CALIBRATION: { - int start_location = 0; - if ((start_location = payload.find(',')) != std::string::npos) { + case EzoCommandType::EZO_CALIBRATION: + if (start_location != std::string::npos) { this->calibration_callback_.call(payload.substr(start_location + 1)); } break; - } - case EzoCommandType::EZO_T: { - int start_location = 0; - if ((start_location = payload.find(',')) != std::string::npos) { + case EzoCommandType::EZO_T: + if (start_location != std::string::npos) { this->t_callback_.call(payload.substr(start_location + 1)); } break; - } - case EzoCommandType::EZO_CUSTOM: { + case EzoCommandType::EZO_CUSTOM: this->custom_callback_.call(payload); break; - } - default: { + default: break; - } } } } - this->commands_.pop_front(); } @@ -178,7 +166,7 @@ void EZOSensor::add_command_(const std::string &command, EzoCommandType command_ ezo_command->command_type = command_type; ezo_command->delay_ms = delay_ms; this->commands_.push_back(std::move(ezo_command)); -}; +} void EZOSensor::set_calibration_point_(EzoCalibrationType type, float value) { std::string payload = str_sprintf("Cal,%s,%0.2f", EZO_CALIBRATION_TYPE_STRINGS[type], value); diff --git a/esphome/components/font/__init__.py b/esphome/components/font/__init__.py index 6fd2d7c310..e2051298fe 100644 --- a/esphome/components/font/__init__.py +++ b/esphome/components/font/__init__.py @@ -1,4 +1,3 @@ -from collections.abc import Iterable import functools import hashlib import logging @@ -8,7 +7,6 @@ import re import freetype import glyphsets -from packaging import version import requests from esphome import core, external_files @@ -53,8 +51,11 @@ CONF_IGNORE_MISSING_GLYPHS = "ignore_missing_glyphs" # Cache loaded freetype fonts class FontCache(dict): def __missing__(self, key): - res = self[key] = freetype.Face(key) - return res + try: + res = self[key] = freetype.Face(key) + return res + except freetype.FT_Exception as e: + raise cv.Invalid(f"Could not load Font file {key}: {e}") from e FONT_CACHE = FontCache() @@ -88,7 +89,7 @@ def flatten(lists) -> list: return list(chain.from_iterable(lists)) -def check_missing_glyphs(file, codepoints: Iterable, warning: bool = False): +def check_missing_glyphs(file, codepoints, warning: bool = False): """ Check that the given font file actually contains the requested glyphs :param file: A Truetype font file @@ -177,24 +178,6 @@ def validate_glyphs(config): return config -def validate_pillow_installed(value): - try: - import PIL - except ImportError as err: - raise cv.Invalid( - "Please install the pillow python package to use this feature. " - '(pip install "pillow==10.4.0")' - ) from err - - if version.parse(PIL.__version__) != version.parse("10.4.0"): - raise cv.Invalid( - "Please update your pillow installation to 10.4.0. " - '(pip install "pillow==10.4.0")' - ) - - return value - - FONT_EXTENSIONS = (".ttf", ".woff", ".otf") @@ -393,7 +376,9 @@ def font_file_schema(value): # Default if no glyphs or glyphsets are provided DEFAULT_GLYPHSET = "GF_Latin_Kernel" # default for bitmap fonts -DEFAULT_GLYPHS = ' !"%()+=,-.:/?0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz' +DEFAULT_GLYPHS = ( + ' !"%()+=,-.:/?0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz°' +) CONF_RAW_GLYPH_ID = "raw_glyph_id" @@ -421,7 +406,7 @@ FONT_SCHEMA = cv.Schema( }, ) -CONFIG_SCHEMA = cv.All(validate_pillow_installed, FONT_SCHEMA, validate_glyphs) +CONFIG_SCHEMA = cv.All(FONT_SCHEMA, validate_glyphs) # PIL doesn't provide a consistent interface for both TrueType and bitmap diff --git a/esphome/components/font/font.cpp b/esphome/components/font/font.cpp index aeca0f5cc0..8c4cba34b3 100644 --- a/esphome/components/font/font.cpp +++ b/esphome/components/font/font.cpp @@ -133,9 +133,11 @@ void Font::print(int x_start, int y_start, display::Display *display, Color colo auto diff_r = (float) color.r - (float) background.r; auto diff_g = (float) color.g - (float) background.g; auto diff_b = (float) color.b - (float) background.b; + auto diff_w = (float) color.w - (float) background.w; auto b_r = (float) background.r; auto b_g = (float) background.g; - auto b_b = (float) background.g; + auto b_b = (float) background.b; + auto b_w = (float) background.w; for (int glyph_y = y_start + scan_y1; glyph_y != max_y; glyph_y++) { for (int glyph_x = x_at + scan_x1; glyph_x != max_x; glyph_x++) { uint8_t pixel = 0; @@ -153,8 +155,8 @@ void Font::print(int x_start, int y_start, display::Display *display, Color colo display->draw_pixel_at(glyph_x, glyph_y, color); } else if (pixel != 0) { auto on = (float) pixel / (float) bpp_max; - auto blended = - Color((uint8_t) (diff_r * on + b_r), (uint8_t) (diff_g * on + b_g), (uint8_t) (diff_b * on + b_b)); + auto blended = Color((uint8_t) (diff_r * on + b_r), (uint8_t) (diff_g * on + b_g), + (uint8_t) (diff_b * on + b_b), (uint8_t) (diff_w * on + b_w)); display->draw_pixel_at(glyph_x, glyph_y, blended); } } diff --git a/esphome/components/graphical_display_menu/__init__.py b/esphome/components/graphical_display_menu/__init__.py index f4d59b22b8..56b720e75c 100644 --- a/esphome/components/graphical_display_menu/__init__.py +++ b/esphome/components/graphical_display_menu/__init__.py @@ -36,6 +36,8 @@ CODEOWNERS = ["@MrMDavidson"] AUTO_LOAD = ["display_menu_base"] +MULTI_CONF = True + CONFIG_SCHEMA = DISPLAY_MENU_BASE_SCHEMA.extend( cv.Schema( { diff --git a/esphome/components/haier/hon_climate.cpp b/esphome/components/haier/hon_climate.cpp index e7be1fa418..c95a87223d 100644 --- a/esphome/components/haier/hon_climate.cpp +++ b/esphome/components/haier/hon_climate.cpp @@ -35,7 +35,9 @@ void HonClimate::set_beeper_state(bool state) { if (state != this->settings_.beeper_state) { this->settings_.beeper_state = state; #ifdef USE_SWITCH - this->beeper_switch_->publish_state(state); + if (this->beeper_switch_ != nullptr) { + this->beeper_switch_->publish_state(state); + } #endif this->hon_rtc_.save(&this->settings_); } @@ -45,10 +47,17 @@ bool HonClimate::get_beeper_state() const { return this->settings_.beeper_state; void HonClimate::set_quiet_mode_state(bool state) { if (state != this->get_quiet_mode_state()) { - this->quiet_mode_state_ = state ? SwitchState::PENDING_ON : SwitchState::PENDING_OFF; + if ((this->mode != ClimateMode::CLIMATE_MODE_OFF) && (this->mode != ClimateMode::CLIMATE_MODE_FAN_ONLY)) { + this->quiet_mode_state_ = state ? SwitchState::PENDING_ON : SwitchState::PENDING_OFF; + this->force_send_control_ = true; + } else { + this->quiet_mode_state_ = state ? SwitchState::ON : SwitchState::OFF; + } this->settings_.quiet_mode_state = state; #ifdef USE_SWITCH - this->quiet_mode_switch_->publish_state(state); + if (this->quiet_mode_switch_ != nullptr) { + this->quiet_mode_switch_->publish_state(state); + } #endif this->hon_rtc_.save(&this->settings_); } @@ -509,7 +518,7 @@ void HonClimate::initialization() { } this->current_vertical_swing_ = this->settings_.last_vertiacal_swing; this->current_horizontal_swing_ = this->settings_.last_horizontal_swing; - this->quiet_mode_state_ = this->settings_.quiet_mode_state ? SwitchState::PENDING_ON : SwitchState::PENDING_OFF; + this->quiet_mode_state_ = this->settings_.quiet_mode_state ? SwitchState::ON : SwitchState::OFF; } haier_protocol::HaierMessage HonClimate::get_control_message() { @@ -932,7 +941,7 @@ haier_protocol::HandlerError HonClimate::process_status_message_(const uint8_t * if (this->mode == CLIMATE_MODE_OFF) { // AC just turned on from remote need to turn off display this->force_send_control_ = true; - } else if ((((uint8_t) this->health_mode_) & 0b10) == 0) { + } else if ((((uint8_t) this->display_status_) & 0b10) == 0) { this->display_status_ = disp_status ? SwitchState::ON : SwitchState::OFF; } } @@ -1004,6 +1013,11 @@ haier_protocol::HandlerError HonClimate::process_status_message_(const uint8_t * if (new_quiet_mode != this->get_quiet_mode_state()) { this->quiet_mode_state_ = new_quiet_mode ? SwitchState::ON : SwitchState::OFF; this->settings_.quiet_mode_state = new_quiet_mode; +#ifdef USE_SWITCH + if (this->quiet_mode_switch_ != nullptr) { + this->quiet_mode_switch_->publish_state(new_quiet_mode); + } +#endif // USE_SWITCH this->hon_rtc_.save(&this->settings_); } } @@ -1069,19 +1083,17 @@ void HonClimate::fill_control_messages_queue_() { climate_control = this->current_hvac_settings_; // Beeper command { - this->control_messages_queue_.push( - haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL, - (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + - (uint8_t) hon_protocol::DataParameters::BEEPER_STATUS, - this->get_beeper_state() ? ZERO_BUF : ONE_BUF, 2)); + this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL, + (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + + (uint8_t) hon_protocol::DataParameters::BEEPER_STATUS, + this->get_beeper_state() ? ZERO_BUF : ONE_BUF, 2); } // Health mode { - this->control_messages_queue_.push( - haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL, - (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + - (uint8_t) hon_protocol::DataParameters::HEALTH_MODE, - this->get_health_mode() ? ONE_BUF : ZERO_BUF, 2)); + this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL, + (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + + (uint8_t) hon_protocol::DataParameters::HEALTH_MODE, + this->get_health_mode() ? ONE_BUF : ZERO_BUF, 2); this->health_mode_ = (SwitchState) ((uint8_t) this->health_mode_ & 0b01); } // Climate mode @@ -1099,51 +1111,46 @@ void HonClimate::fill_control_messages_queue_() { case CLIMATE_MODE_HEAT_COOL: new_power = true; buffer[1] = (uint8_t) hon_protocol::ConditioningMode::AUTO; - this->control_messages_queue_.push( - haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL, - (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + - (uint8_t) hon_protocol::DataParameters::AC_MODE, - buffer, 2)); + this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL, + (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + + (uint8_t) hon_protocol::DataParameters::AC_MODE, + buffer, 2); fan_mode_buf[1] = this->other_modes_fan_speed_; break; case CLIMATE_MODE_HEAT: new_power = true; buffer[1] = (uint8_t) hon_protocol::ConditioningMode::HEAT; - this->control_messages_queue_.push( - haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL, - (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + - (uint8_t) hon_protocol::DataParameters::AC_MODE, - buffer, 2)); + this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL, + (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + + (uint8_t) hon_protocol::DataParameters::AC_MODE, + buffer, 2); fan_mode_buf[1] = this->other_modes_fan_speed_; break; case CLIMATE_MODE_DRY: new_power = true; buffer[1] = (uint8_t) hon_protocol::ConditioningMode::DRY; - this->control_messages_queue_.push( - haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL, - (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + - (uint8_t) hon_protocol::DataParameters::AC_MODE, - buffer, 2)); + this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL, + (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + + (uint8_t) hon_protocol::DataParameters::AC_MODE, + buffer, 2); fan_mode_buf[1] = this->other_modes_fan_speed_; break; case CLIMATE_MODE_FAN_ONLY: new_power = true; buffer[1] = (uint8_t) hon_protocol::ConditioningMode::FAN; - this->control_messages_queue_.push( - haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL, - (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + - (uint8_t) hon_protocol::DataParameters::AC_MODE, - buffer, 2)); + this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL, + (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + + (uint8_t) hon_protocol::DataParameters::AC_MODE, + buffer, 2); fan_mode_buf[1] = this->other_modes_fan_speed_; // Auto doesn't work in fan only mode break; case CLIMATE_MODE_COOL: new_power = true; buffer[1] = (uint8_t) hon_protocol::ConditioningMode::COOL; - this->control_messages_queue_.push( - haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL, - (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + - (uint8_t) hon_protocol::DataParameters::AC_MODE, - buffer, 2)); + this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL, + (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + + (uint8_t) hon_protocol::DataParameters::AC_MODE, + buffer, 2); fan_mode_buf[1] = this->other_modes_fan_speed_; break; default: @@ -1153,11 +1160,10 @@ void HonClimate::fill_control_messages_queue_() { } // Climate power { - this->control_messages_queue_.push( - haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL, - (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + - (uint8_t) hon_protocol::DataParameters::AC_POWER, - new_power ? ONE_BUF : ZERO_BUF, 2)); + this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL, + (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + + (uint8_t) hon_protocol::DataParameters::AC_POWER, + new_power ? ONE_BUF : ZERO_BUF, 2); } // CLimate preset { @@ -1199,36 +1205,32 @@ void HonClimate::fill_control_messages_queue_() { } auto presets = this->traits_.get_supported_presets(); if (quiet_mode_buf[1] != 0xFF) { - this->control_messages_queue_.push( - haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL, - (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + - (uint8_t) hon_protocol::DataParameters::QUIET_MODE, - quiet_mode_buf, 2)); + this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL, + (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + + (uint8_t) hon_protocol::DataParameters::QUIET_MODE, + quiet_mode_buf, 2); } if ((fast_mode_buf[1] != 0xFF) && ((presets.find(climate::ClimatePreset::CLIMATE_PRESET_BOOST) != presets.end()))) { - this->control_messages_queue_.push( - haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL, - (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + - (uint8_t) hon_protocol::DataParameters::FAST_MODE, - fast_mode_buf, 2)); + this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL, + (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + + (uint8_t) hon_protocol::DataParameters::FAST_MODE, + fast_mode_buf, 2); } if ((away_mode_buf[1] != 0xFF) && ((presets.find(climate::ClimatePreset::CLIMATE_PRESET_AWAY) != presets.end()))) { - this->control_messages_queue_.push( - haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL, - (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + - (uint8_t) hon_protocol::DataParameters::TEN_DEGREE, - away_mode_buf, 2)); + this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL, + (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + + (uint8_t) hon_protocol::DataParameters::TEN_DEGREE, + away_mode_buf, 2); } } // Target temperature if (climate_control.target_temperature.has_value() && (this->mode != ClimateMode::CLIMATE_MODE_FAN_ONLY)) { uint8_t buffer[2] = {0x00, 0x00}; buffer[1] = ((uint8_t) climate_control.target_temperature.value()) - 16; - this->control_messages_queue_.push( - haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL, - (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + - (uint8_t) hon_protocol::DataParameters::SET_POINT, - buffer, 2)); + this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL, + (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + + (uint8_t) hon_protocol::DataParameters::SET_POINT, + buffer, 2); } // Vertical swing mode if (climate_control.swing_mode.has_value()) { @@ -1248,16 +1250,14 @@ void HonClimate::fill_control_messages_queue_() { case CLIMATE_SWING_BOTH: break; } - this->control_messages_queue_.push( - haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL, - (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + - (uint8_t) hon_protocol::DataParameters::HORIZONTAL_SWING_MODE, - horizontal_swing_buf, 2)); - this->control_messages_queue_.push( - haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL, - (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + - (uint8_t) hon_protocol::DataParameters::VERTICAL_SWING_MODE, - vertical_swing_buf, 2)); + this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL, + (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + + (uint8_t) hon_protocol::DataParameters::HORIZONTAL_SWING_MODE, + horizontal_swing_buf, 2); + this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL, + (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + + (uint8_t) hon_protocol::DataParameters::VERTICAL_SWING_MODE, + vertical_swing_buf, 2); } // Fan mode if (climate_control.fan_mode.has_value()) { @@ -1280,11 +1280,10 @@ void HonClimate::fill_control_messages_queue_() { break; } if (fan_mode_buf[1] != 0xFF) { - this->control_messages_queue_.push( - haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL, - (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + - (uint8_t) hon_protocol::DataParameters::FAN_MODE, - fan_mode_buf, 2)); + this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL, + (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + + (uint8_t) hon_protocol::DataParameters::FAN_MODE, + fan_mode_buf, 2); } } } diff --git a/esphome/components/hbridge/switch/__init__.py b/esphome/components/hbridge/switch/__init__.py new file mode 100644 index 0000000000..e26bd6b1d8 --- /dev/null +++ b/esphome/components/hbridge/switch/__init__.py @@ -0,0 +1,44 @@ +from esphome import pins +import esphome.codegen as cg +from esphome.components import switch +import esphome.config_validation as cv +from esphome.const import CONF_OPTIMISTIC, CONF_PULSE_LENGTH, CONF_WAIT_TIME + +from .. import hbridge_ns + +HBridgeSwitch = hbridge_ns.class_("HBridgeSwitch", switch.Switch, cg.Component) + +CODEOWNERS = ["@dwmw2"] + +CONF_OFF_PIN = "off_pin" +CONF_ON_PIN = "on_pin" + +CONFIG_SCHEMA = ( + switch.switch_schema(HBridgeSwitch) + .extend( + { + cv.Required(CONF_ON_PIN): pins.gpio_output_pin_schema, + cv.Required(CONF_OFF_PIN): pins.gpio_output_pin_schema, + cv.Optional( + CONF_PULSE_LENGTH, default="100ms" + ): cv.positive_time_period_milliseconds, + cv.Optional(CONF_WAIT_TIME): cv.positive_time_period_milliseconds, + cv.Optional(CONF_OPTIMISTIC, default=False): cv.boolean, + } + ) + .extend(cv.COMPONENT_SCHEMA) +) + + +async def to_code(config): + var = await switch.new_switch(config) + await cg.register_component(var, config) + + on_pin = await cg.gpio_pin_expression(config[CONF_ON_PIN]) + cg.add(var.set_on_pin(on_pin)) + off_pin = await cg.gpio_pin_expression(config[CONF_OFF_PIN]) + cg.add(var.set_off_pin(off_pin)) + cg.add(var.set_pulse_length(config[CONF_PULSE_LENGTH])) + cg.add(var.set_optimistic(config[CONF_OPTIMISTIC])) + if wait_time := config.get(CONF_WAIT_TIME): + cg.add(var.set_wait_time(wait_time)) diff --git a/esphome/components/hbridge/switch/hbridge_switch.cpp b/esphome/components/hbridge/switch/hbridge_switch.cpp new file mode 100644 index 0000000000..12d1c01bca --- /dev/null +++ b/esphome/components/hbridge/switch/hbridge_switch.cpp @@ -0,0 +1,95 @@ +#include "hbridge_switch.h" +#include "esphome/core/log.h" + +#include + +namespace esphome { +namespace hbridge { + +static const char *const TAG = "switch.hbridge"; + +float HBridgeSwitch::get_setup_priority() const { return setup_priority::HARDWARE; } +void HBridgeSwitch::setup() { + ESP_LOGCONFIG(TAG, "Setting up H-Bridge Switch '%s'...", this->name_.c_str()); + + optional initial_state = this->get_initial_state_with_restore_mode().value_or(false); + + // Like GPIOSwitch does, set the pin state both before and after pin setup() + this->on_pin_->digital_write(false); + this->on_pin_->setup(); + this->on_pin_->digital_write(false); + + this->off_pin_->digital_write(false); + this->off_pin_->setup(); + this->off_pin_->digital_write(false); + + if (initial_state.has_value()) + this->write_state(initial_state); +} + +void HBridgeSwitch::dump_config() { + LOG_SWITCH("", "H-Bridge Switch", this); + LOG_PIN(" On Pin: ", this->on_pin_); + LOG_PIN(" Off Pin: ", this->off_pin_); + ESP_LOGCONFIG(TAG, " Pulse length: %" PRId32 " ms", this->pulse_length_); + if (this->wait_time_) + ESP_LOGCONFIG(TAG, " Wait time %" PRId32 " ms", this->wait_time_); +} + +void HBridgeSwitch::write_state(bool state) { + this->desired_state_ = state; + if (!this->timer_running_) + this->timer_fn_(); +} + +void HBridgeSwitch::timer_fn_() { + uint32_t next_timeout = 0; + + while ((uint8_t) this->desired_state_ != this->relay_state_) { + switch (this->relay_state_) { + case RELAY_STATE_ON: + case RELAY_STATE_OFF: + case RELAY_STATE_UNKNOWN: + if (this->desired_state_) { + this->on_pin_->digital_write(true); + this->relay_state_ = RELAY_STATE_SWITCHING_ON; + } else { + this->off_pin_->digital_write(true); + this->relay_state_ = RELAY_STATE_SWITCHING_OFF; + } + next_timeout = this->pulse_length_; + if (!this->optimistic_) + this->publish_state(this->desired_state_); + break; + + case RELAY_STATE_SWITCHING_ON: + this->on_pin_->digital_write(false); + this->relay_state_ = RELAY_STATE_ON; + if (this->optimistic_) + this->publish_state(true); + next_timeout = this->wait_time_; + break; + + case RELAY_STATE_SWITCHING_OFF: + this->off_pin_->digital_write(false); + this->relay_state_ = RELAY_STATE_OFF; + if (this->optimistic_) + this->publish_state(false); + next_timeout = this->wait_time_; + break; + } + + if (next_timeout) { + this->timer_running_ = true; + this->set_timeout(next_timeout, [this]() { this->timer_fn_(); }); + return; + } + + // In the case where ON/OFF state has been reached but we need to + // immediately change back again to reach desired_state_, we loop. + } + this->timer_running_ = false; +} + +} // namespace hbridge +} // namespace esphome diff --git a/esphome/components/hbridge/switch/hbridge_switch.h b/esphome/components/hbridge/switch/hbridge_switch.h new file mode 100644 index 0000000000..ce00c6baa2 --- /dev/null +++ b/esphome/components/hbridge/switch/hbridge_switch.h @@ -0,0 +1,50 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/hal.h" +#include "esphome/components/switch/switch.h" + +#include + +namespace esphome { +namespace hbridge { + +enum RelayState : uint8_t { + RELAY_STATE_OFF = 0, + RELAY_STATE_ON = 1, + RELAY_STATE_SWITCHING_ON = 2, + RELAY_STATE_SWITCHING_OFF = 3, + RELAY_STATE_UNKNOWN = 4, +}; + +class HBridgeSwitch : public switch_::Switch, public Component { + public: + void set_on_pin(GPIOPin *pin) { this->on_pin_ = pin; } + void set_off_pin(GPIOPin *pin) { this->off_pin_ = pin; } + void set_pulse_length(uint32_t pulse_length) { this->pulse_length_ = pulse_length; } + void set_wait_time(uint32_t wait_time) { this->wait_time_ = wait_time; } + void set_optimistic(bool optimistic) { this->optimistic_ = optimistic; } + + // ========== INTERNAL METHODS ========== + // (In most use cases you won't need these) + float get_setup_priority() const override; + + void setup() override; + void dump_config() override; + + protected: + void write_state(bool state) override; + void timer_fn_(); + + bool timer_running_{false}; + bool desired_state_{false}; + RelayState relay_state_{RELAY_STATE_UNKNOWN}; + GPIOPin *on_pin_{nullptr}; + GPIOPin *off_pin_{nullptr}; + uint32_t pulse_length_{0}; + uint32_t wait_time_{0}; + bool optimistic_{false}; +}; + +} // namespace hbridge +} // namespace esphome diff --git a/esphome/components/hitachi_ac344/hitachi_ac344.cpp b/esphome/components/hitachi_ac344/hitachi_ac344.cpp index 2825e4f04c..2bcb205644 100644 --- a/esphome/components/hitachi_ac344/hitachi_ac344.cpp +++ b/esphome/components/hitachi_ac344/hitachi_ac344.cpp @@ -133,8 +133,10 @@ bool HitachiClimate::get_swing_v_() { } void HitachiClimate::set_swing_h_(uint8_t position) { - if (position > HITACHI_AC344_SWINGH_LEFT_MAX) - return set_swing_h_(HITACHI_AC344_SWINGH_MIDDLE); + if (position > HITACHI_AC344_SWINGH_LEFT_MAX) { + set_swing_h_(HITACHI_AC344_SWINGH_MIDDLE); + return; + } set_bits(&remote_state_[HITACHI_AC344_SWINGH_BYTE], HITACHI_AC344_SWINGH_OFFSET, HITACHI_AC344_SWINGH_SIZE, position); set_button_(HITACHI_AC344_BUTTON_SWINGH); } diff --git a/esphome/components/hitachi_ac424/hitachi_ac424.cpp b/esphome/components/hitachi_ac424/hitachi_ac424.cpp index 0bfc3ae564..64f23dfc17 100644 --- a/esphome/components/hitachi_ac424/hitachi_ac424.cpp +++ b/esphome/components/hitachi_ac424/hitachi_ac424.cpp @@ -133,8 +133,10 @@ bool HitachiClimate::get_swing_v_() { } void HitachiClimate::set_swing_h_(uint8_t position) { - if (position > HITACHI_AC424_SWINGH_LEFT_MAX) - return set_swing_h_(HITACHI_AC424_SWINGH_MIDDLE); + if (position > HITACHI_AC424_SWINGH_LEFT_MAX) { + set_swing_h_(HITACHI_AC424_SWINGH_MIDDLE); + return; + } set_bits(&remote_state_[HITACHI_AC424_SWINGH_BYTE], HITACHI_AC424_SWINGH_OFFSET, HITACHI_AC424_SWINGH_SIZE, position); set_button_(HITACHI_AC424_BUTTON_SWINGH); } diff --git a/esphome/components/homeassistant/number/homeassistant_number.cpp b/esphome/components/homeassistant/number/homeassistant_number.cpp index d3e285f4ac..a7f71c3244 100644 --- a/esphome/components/homeassistant/number/homeassistant_number.cpp +++ b/esphome/components/homeassistant/number/homeassistant_number.cpp @@ -27,6 +27,7 @@ void HomeassistantNumber::min_retrieved_(const std::string &min) { auto min_value = parse_number(min); if (!min_value.has_value()) { ESP_LOGE(TAG, "'%s': Can't convert 'min' value '%s' to number!", this->entity_id_.c_str(), min.c_str()); + return; } ESP_LOGD(TAG, "'%s': Min retrieved: %s", get_name().c_str(), min.c_str()); this->traits.set_min_value(min_value.value()); @@ -36,6 +37,7 @@ void HomeassistantNumber::max_retrieved_(const std::string &max) { auto max_value = parse_number(max); if (!max_value.has_value()) { ESP_LOGE(TAG, "'%s': Can't convert 'max' value '%s' to number!", this->entity_id_.c_str(), max.c_str()); + return; } ESP_LOGD(TAG, "'%s': Max retrieved: %s", get_name().c_str(), max.c_str()); this->traits.set_max_value(max_value.value()); @@ -45,6 +47,7 @@ void HomeassistantNumber::step_retrieved_(const std::string &step) { auto step_value = parse_number(step); if (!step_value.has_value()) { ESP_LOGE(TAG, "'%s': Can't convert 'step' value '%s' to number!", this->entity_id_.c_str(), step.c_str()); + return; } ESP_LOGD(TAG, "'%s': Step Retrieved %s", get_name().c_str(), step.c_str()); this->traits.set_step(step_value.value()); diff --git a/esphome/components/hx711/hx711.cpp b/esphome/components/hx711/hx711.cpp index 1a7169eed7..9643d0c411 100644 --- a/esphome/components/hx711/hx711.cpp +++ b/esphome/components/hx711/hx711.cpp @@ -53,7 +53,7 @@ bool HX711Sensor::read_sensor_(uint32_t *result) { } // Cycle clock pin for gain setting - for (uint8_t i = 0; i < this->gain_; i++) { + for (uint8_t i = 0; i < static_cast(this->gain_); i++) { this->sck_pin_->digital_write(true); delayMicroseconds(1); this->sck_pin_->digital_write(false); diff --git a/esphome/components/hx711/hx711.h b/esphome/components/hx711/hx711.h index 0cb6868ab5..a92bb9945d 100644 --- a/esphome/components/hx711/hx711.h +++ b/esphome/components/hx711/hx711.h @@ -9,7 +9,7 @@ namespace esphome { namespace hx711 { -enum HX711Gain { +enum HX711Gain : uint8_t { HX711_GAIN_128 = 1, HX711_GAIN_32 = 2, HX711_GAIN_64 = 3, diff --git a/esphome/components/i2c/i2c_bus_esp_idf.cpp b/esphome/components/i2c/i2c_bus_esp_idf.cpp index 3a9c229778..ac3a754024 100644 --- a/esphome/components/i2c/i2c_bus_esp_idf.cpp +++ b/esphome/components/i2c/i2c_bus_esp_idf.cpp @@ -17,14 +17,14 @@ void IDFI2CBus::setup() { ESP_LOGCONFIG(TAG, "Setting up I2C bus..."); static i2c_port_t next_port = I2C_NUM_0; port_ = next_port; -#if I2C_NUM_MAX > 1 +#if SOC_I2C_NUM > 1 next_port = (next_port == I2C_NUM_0) ? I2C_NUM_1 : I2C_NUM_MAX; #else next_port = I2C_NUM_MAX; #endif if (port_ == I2C_NUM_MAX) { - ESP_LOGE(TAG, "Too many I2C buses configured"); + ESP_LOGE(TAG, "Too many I2C buses configured. Max %u supported.", SOC_I2C_NUM); this->mark_failed(); return; } diff --git a/esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp b/esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp index 53b3cc8dc0..46f1b00d05 100644 --- a/esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp +++ b/esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp @@ -33,14 +33,15 @@ enum SpeakerEventGroupBits : uint32_t { STATE_RUNNING = (1 << 11), STATE_STOPPING = (1 << 12), STATE_STOPPED = (1 << 13), - ERR_INVALID_FORMAT = (1 << 14), - ERR_TASK_FAILED_TO_START = (1 << 15), - ERR_ESP_INVALID_STATE = (1 << 16), + ERR_TASK_FAILED_TO_START = (1 << 14), + ERR_ESP_INVALID_STATE = (1 << 15), + ERR_ESP_NOT_SUPPORTED = (1 << 16), ERR_ESP_INVALID_ARG = (1 << 17), ERR_ESP_INVALID_SIZE = (1 << 18), ERR_ESP_NO_MEM = (1 << 19), ERR_ESP_FAIL = (1 << 20), - ALL_ERR_ESP_BITS = ERR_ESP_INVALID_STATE | ERR_ESP_INVALID_ARG | ERR_ESP_INVALID_SIZE | ERR_ESP_NO_MEM | ERR_ESP_FAIL, + ALL_ERR_ESP_BITS = ERR_ESP_INVALID_STATE | ERR_ESP_NOT_SUPPORTED | ERR_ESP_INVALID_ARG | ERR_ESP_INVALID_SIZE | + ERR_ESP_NO_MEM | ERR_ESP_FAIL, ALL_BITS = 0x00FFFFFF, // All valid FreeRTOS event group bits }; @@ -55,6 +56,8 @@ static esp_err_t err_bit_to_esp_err(uint32_t bit) { return ESP_ERR_INVALID_SIZE; case SpeakerEventGroupBits::ERR_ESP_NO_MEM: return ESP_ERR_NO_MEM; + case SpeakerEventGroupBits::ERR_ESP_NOT_SUPPORTED: + return ESP_ERR_NOT_SUPPORTED; default: return ESP_FAIL; } @@ -135,19 +138,19 @@ void I2SAudioSpeaker::loop() { xEventGroupClearBits(this->event_group_, SpeakerEventGroupBits::ERR_TASK_FAILED_TO_START); } - if (event_group_bits & SpeakerEventGroupBits::ERR_INVALID_FORMAT) { + if (event_group_bits & SpeakerEventGroupBits::ALL_ERR_ESP_BITS) { + uint32_t error_bits = event_group_bits & SpeakerEventGroupBits::ALL_ERR_ESP_BITS; + ESP_LOGW(TAG, "Error writing to I2S: %s", esp_err_to_name(err_bit_to_esp_err(error_bits))); + this->status_set_warning(); + } + + if (event_group_bits & SpeakerEventGroupBits::ERR_ESP_NOT_SUPPORTED) { this->status_set_error("Failed to adjust I2S bus to match the incoming audio"); ESP_LOGE(TAG, "Incompatible audio format: sample rate = %" PRIu32 ", channels = %" PRIu8 ", bits per sample = %" PRIu8, this->audio_stream_info_.sample_rate, this->audio_stream_info_.channels, this->audio_stream_info_.bits_per_sample); } - - if (event_group_bits & SpeakerEventGroupBits::ALL_ERR_ESP_BITS) { - uint32_t error_bits = event_group_bits & SpeakerEventGroupBits::ALL_ERR_ESP_BITS; - ESP_LOGW(TAG, "Error writing to I2S: %s", esp_err_to_name(err_bit_to_esp_err(error_bits))); - this->status_set_warning(); - } } void I2SAudioSpeaker::set_volume(float volume) { @@ -236,13 +239,15 @@ void I2SAudioSpeaker::speaker_task(void *params) { xEventGroupSetBits(this_speaker->event_group_, SpeakerEventGroupBits::STATE_STARTING); audio::AudioStreamInfo audio_stream_info = this_speaker->audio_stream_info_; - const ssize_t bytes_per_sample = audio_stream_info.get_bytes_per_sample(); - const uint8_t number_of_channels = audio_stream_info.channels; - const size_t dma_buffers_size = DMA_BUFFERS_COUNT * DMA_BUFFER_DURATION_MS * this_speaker->sample_rate_ / 1000 * - bytes_per_sample * number_of_channels; + const uint32_t bytes_per_ms = + audio_stream_info.channels * audio_stream_info.get_bytes_per_sample() * audio_stream_info.sample_rate / 1000; + + const size_t dma_buffers_size = DMA_BUFFERS_COUNT * DMA_BUFFER_DURATION_MS * bytes_per_ms; + + // Ensure ring buffer is at least as large as the total size of the DMA buffers const size_t ring_buffer_size = - this_speaker->buffer_duration_ms_ * this_speaker->sample_rate_ / 1000 * bytes_per_sample * number_of_channels; + std::max((uint32_t) dma_buffers_size, this_speaker->buffer_duration_ms_ * bytes_per_ms); if (this_speaker->send_esp_err_to_event_group_(this_speaker->allocate_buffers_(dma_buffers_size, ring_buffer_size))) { // Failed to allocate buffers @@ -250,14 +255,7 @@ void I2SAudioSpeaker::speaker_task(void *params) { this_speaker->delete_task_(dma_buffers_size); } - if (this_speaker->send_esp_err_to_event_group_(this_speaker->start_i2s_driver_())) { - // Failed to start I2S driver - this_speaker->delete_task_(dma_buffers_size); - } - - if (!this_speaker->send_esp_err_to_event_group_(this_speaker->reconfigure_i2s_stream_info_(audio_stream_info))) { - // Successfully set the I2S stream info, ready to write audio data to the I2S port - + if (!this_speaker->send_esp_err_to_event_group_(this_speaker->start_i2s_driver_(audio_stream_info))) { xEventGroupSetBits(this_speaker->event_group_, SpeakerEventGroupBits::STATE_RUNNING); bool stop_gracefully = false; @@ -275,6 +273,12 @@ void I2SAudioSpeaker::speaker_task(void *params) { stop_gracefully = true; } + if (this_speaker->audio_stream_info_ != audio_stream_info) { + // Audio stream info has changed, stop the speaker task so it will restart with the proper settings. + + break; + } + i2s_event_t i2s_event; while (xQueueReceive(this_speaker->i2s_event_queue_, &i2s_event, 0)) { if (i2s_event.type == I2S_EVENT_TX_Q_OVF) { @@ -316,17 +320,14 @@ void I2SAudioSpeaker::speaker_task(void *params) { } } } - } else { - // Couldn't configure the I2S port to be compatible with the incoming audio - xEventGroupSetBits(this_speaker->event_group_, SpeakerEventGroupBits::ERR_INVALID_FORMAT); + + xEventGroupSetBits(this_speaker->event_group_, SpeakerEventGroupBits::STATE_STOPPING); + + i2s_driver_uninstall(this_speaker->parent_->get_port()); + + this_speaker->parent_->unlock(); } - i2s_zero_dma_buffer(this_speaker->parent_->get_port()); - xEventGroupSetBits(this_speaker->event_group_, SpeakerEventGroupBits::STATE_STOPPING); - - i2s_driver_uninstall(this_speaker->parent_->get_port()); - - this_speaker->parent_->unlock(); this_speaker->delete_task_(dma_buffers_size); } @@ -382,6 +383,9 @@ bool I2SAudioSpeaker::send_esp_err_to_event_group_(esp_err_t err) { case ESP_ERR_NO_MEM: xEventGroupSetBits(this->event_group_, SpeakerEventGroupBits::ERR_ESP_NO_MEM); return true; + case ESP_ERR_NOT_SUPPORTED: + xEventGroupSetBits(this->event_group_, SpeakerEventGroupBits::ERR_ESP_NOT_SUPPORTED); + return true; default: xEventGroupSetBits(this->event_group_, SpeakerEventGroupBits::ERR_ESP_FAIL); return true; @@ -411,18 +415,40 @@ esp_err_t I2SAudioSpeaker::allocate_buffers_(size_t data_buffer_size, size_t rin return ESP_OK; } -esp_err_t I2SAudioSpeaker::start_i2s_driver_() { +esp_err_t I2SAudioSpeaker::start_i2s_driver_(audio::AudioStreamInfo &audio_stream_info) { + if ((this->i2s_mode_ & I2S_MODE_SLAVE) && (this->sample_rate_ != audio_stream_info.sample_rate)) { // NOLINT + // Can't reconfigure I2S bus, so the sample rate must match the configured value + return ESP_ERR_NOT_SUPPORTED; + } + + if ((i2s_bits_per_sample_t) audio_stream_info.bits_per_sample > this->bits_per_sample_) { + // Currently can't handle the case when the incoming audio has more bits per sample than the configured value + return ESP_ERR_NOT_SUPPORTED; + } + if (!this->parent_->try_lock()) { return ESP_ERR_INVALID_STATE; } + i2s_channel_fmt_t channel = this->channel_; + + if (audio_stream_info.channels == 1) { + if (this->channel_ == I2S_CHANNEL_FMT_ONLY_LEFT) { + channel = I2S_CHANNEL_FMT_ONLY_LEFT; + } else { + channel = I2S_CHANNEL_FMT_ONLY_RIGHT; + } + } else if (audio_stream_info.channels == 2) { + channel = I2S_CHANNEL_FMT_RIGHT_LEFT; + } + int dma_buffer_length = DMA_BUFFER_DURATION_MS * this->sample_rate_ / 1000; i2s_driver_config_t config = { .mode = (i2s_mode_t) (this->i2s_mode_ | I2S_MODE_TX), - .sample_rate = this->sample_rate_, + .sample_rate = audio_stream_info.sample_rate, .bits_per_sample = this->bits_per_sample_, - .channel_format = this->channel_, + .channel_format = channel, .communication_format = this->i2s_comm_fmt_, .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, .dma_buf_count = DMA_BUFFERS_COUNT, @@ -477,30 +503,6 @@ esp_err_t I2SAudioSpeaker::start_i2s_driver_() { return err; } -esp_err_t I2SAudioSpeaker::reconfigure_i2s_stream_info_(audio::AudioStreamInfo &audio_stream_info) { - if (this->i2s_mode_ & I2S_MODE_MASTER) { - // ESP controls for the the I2S bus, so adjust the sample rate and bits per sample to match the incoming audio - this->sample_rate_ = audio_stream_info.sample_rate; - this->bits_per_sample_ = (i2s_bits_per_sample_t) audio_stream_info.bits_per_sample; - } else if (this->sample_rate_ != audio_stream_info.sample_rate) { - // Can't reconfigure I2S bus, so the sample rate must match the configured value - return ESP_ERR_INVALID_ARG; - } - - if ((i2s_bits_per_sample_t) audio_stream_info.bits_per_sample > this->bits_per_sample_) { - // Currently can't handle the case when the incoming audio has more bits per sample than the configured value - return ESP_ERR_INVALID_ARG; - } - - if (audio_stream_info.channels == 1) { - return i2s_set_clk(this->parent_->get_port(), this->sample_rate_, this->bits_per_sample_, I2S_CHANNEL_MONO); - } else if (audio_stream_info.channels == 2) { - return i2s_set_clk(this->parent_->get_port(), this->sample_rate_, this->bits_per_sample_, I2S_CHANNEL_STEREO); - } - - return ESP_ERR_INVALID_ARG; -} - void I2SAudioSpeaker::delete_task_(size_t buffer_size) { this->audio_ring_buffer_.reset(); // Releases onwership of the shared_ptr diff --git a/esphome/components/i2s_audio/speaker/i2s_audio_speaker.h b/esphome/components/i2s_audio/speaker/i2s_audio_speaker.h index 2b90f39399..d706deb0f4 100644 --- a/esphome/components/i2s_audio/speaker/i2s_audio_speaker.h +++ b/esphome/components/i2s_audio/speaker/i2s_audio_speaker.h @@ -91,24 +91,15 @@ class I2SAudioSpeaker : public I2SAudioOut, public speaker::Speaker, public Comp esp_err_t allocate_buffers_(size_t data_buffer_size, size_t ring_buffer_size); /// @brief Starts the ESP32 I2S driver. - /// Attempts to lock the I2S port, starts the I2S driver, and sets the data out pin. If it fails, it will unlock - /// the I2S port and uninstall the driver, if necessary. - /// @return ESP_ERR_INVALID_STATE if the I2S port is already locked. - /// ESP_ERR_INVALID_ARG if installing the driver or setting the data out pin fails due to a parameter error. + /// Attempts to lock the I2S port, starts the I2S driver using the passed in stream information, and sets the data out + /// pin. If it fails, it will unlock the I2S port and uninstall the driver, if necessary. + /// @param audio_stream_info Stream information for the I2S driver. + /// @return ESP_ERR_NOT_ALLOWED if the I2S port can't play the incoming audio stream. + /// ESP_ERR_INVALID_STATE if the I2S port is already locked. + /// ESP_ERR_INVALID_ARG if nstalling the driver or setting the data outpin fails due to a parameter error. /// ESP_ERR_NO_MEM if the driver fails to install due to a memory allocation error. - /// ESP_FAIL if setting the data out pin fails due to an IO error - /// ESP_OK if successful - esp_err_t start_i2s_driver_(); - - /// @brief Adjusts the I2S driver configuration to match the incoming audio stream. - /// Modifies I2S driver's sample rate, bits per sample, and number of channel settings. If the I2S is in secondary - /// mode, it only modifies the number of channels. - /// @param audio_stream_info Describes the incoming audio stream - /// @return ESP_ERR_INVALID_ARG if there is a parameter error, if there is more than 2 channels in the stream, or if - /// the audio settings are incompatible with the configuration. - /// ESP_ERR_NO_MEM if the driver fails to reconfigure due to a memory allocation error. - /// ESP_OK if successful. - esp_err_t reconfigure_i2s_stream_info_(audio::AudioStreamInfo &audio_stream_info); + /// ESP_FAIL if setting the data out pin fails due to an IO error ESP_OK if successful + esp_err_t start_i2s_driver_(audio::AudioStreamInfo &audio_stream_info); /// @brief Deletes the speaker's task. /// Deallocates the data_buffer_ and audio_ring_buffer_, if necessary, and deletes the task. Should only be called by diff --git a/esphome/components/ili9xxx/display.py b/esphome/components/ili9xxx/display.py index 739ad07843..3c9dd2dab9 100644 --- a/esphome/components/ili9xxx/display.py +++ b/esphome/components/ili9xxx/display.py @@ -1,6 +1,6 @@ from esphome import core, pins import esphome.codegen as cg -from esphome.components import display, font, spi +from esphome.components import display, spi from esphome.components.display import validate_rotation import esphome.config_validation as cv from esphome.const import ( @@ -147,7 +147,6 @@ def _validate(config): CONFIG_SCHEMA = cv.All( - font.validate_pillow_installed, display.FULL_DISPLAY_SCHEMA.extend( { cv.GenerateID(): cv.declare_id(ILI9XXXDisplay), diff --git a/esphome/components/ili9xxx/ili9xxx_display.cpp b/esphome/components/ili9xxx/ili9xxx_display.cpp index 81976dd2c9..b9664067a9 100644 --- a/esphome/components/ili9xxx/ili9xxx_display.cpp +++ b/esphome/components/ili9xxx/ili9xxx_display.cpp @@ -313,8 +313,9 @@ void ILI9XXXDisplay::draw_pixels_at(int x_start, int y_start, int w, int h, cons // do color conversion pixel-by-pixel into the buffer and draw it later. If this is happening the user has not // configured the renderer well. if (this->rotation_ != display::DISPLAY_ROTATION_0_DEGREES || bitness != display::COLOR_BITNESS_565 || !big_endian) { - return display::Display::draw_pixels_at(x_start, y_start, w, h, ptr, order, bitness, big_endian, x_offset, y_offset, - x_pad); + display::Display::draw_pixels_at(x_start, y_start, w, h, ptr, order, bitness, big_endian, x_offset, y_offset, + x_pad); + return; } this->set_addr_window_(x_start, y_start, x_start + w - 1, y_start + h - 1); // x_ and y_offset are offsets into the source buffer, unrelated to our own offsets into the display. diff --git a/esphome/components/image/__init__.py b/esphome/components/image/__init__.py index 8742540067..4669a3418a 100644 --- a/esphome/components/image/__init__.py +++ b/esphome/components/image/__init__.py @@ -10,7 +10,6 @@ import puremagic from esphome import core, external_files import esphome.codegen as cg -from esphome.components import font import esphome.config_validation as cv from esphome.const import ( CONF_DITHER, @@ -233,7 +232,7 @@ IMAGE_SCHEMA = cv.Schema( ) ) -CONFIG_SCHEMA = cv.All(font.validate_pillow_installed, IMAGE_SCHEMA) +CONFIG_SCHEMA = IMAGE_SCHEMA def load_svg_image(file: bytes, resize: tuple[int, int]): diff --git a/esphome/components/logger/logger.cpp b/esphome/components/logger/logger.cpp index dac08fbbce..36934c7459 100644 --- a/esphome/components/logger/logger.cpp +++ b/esphome/components/logger/logger.cpp @@ -47,7 +47,7 @@ void Logger::write_header_(int level, const char *tag, int line) { if (current_task == main_task_) { this->printf_to_buffer_("%s[%s][%s:%03u]: ", color, letter, tag, line); } else { - const char *thread_name = ""; + const char *thread_name = ""; // NOLINT(clang-analyzer-deadcode.DeadStores) #if defined(USE_ESP32) thread_name = pcTaskGetName(current_task); #elif defined(USE_LIBRETINY) diff --git a/esphome/components/ltr501/ltr501.cpp b/esphome/components/ltr501/ltr501.cpp index 4f4e26f44f..b30e520f2b 100644 --- a/esphome/components/ltr501/ltr501.cpp +++ b/esphome/components/ltr501/ltr501.cpp @@ -23,7 +23,7 @@ bool operator==(const GainTimePair &lhs, const GainTimePair &rhs) { } bool operator!=(const GainTimePair &lhs, const GainTimePair &rhs) { - return !(lhs.gain == rhs.gain && lhs.time == rhs.time); + return lhs.gain != rhs.gain || lhs.time != rhs.time; } template T get_next(const T (&array)[size], const T val) { diff --git a/esphome/components/lvgl/defines.py b/esphome/components/lvgl/defines.py index bb7d25c2c7..02323f9655 100644 --- a/esphome/components/lvgl/defines.py +++ b/esphome/components/lvgl/defines.py @@ -8,7 +8,7 @@ import logging from esphome import codegen as cg, config_validation as cv from esphome.const import CONF_ITEMS -from esphome.core import Lambda +from esphome.core import ID, Lambda from esphome.cpp_generator import LambdaExpression, MockObj from esphome.cpp_types import uint32 from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor @@ -72,6 +72,12 @@ class LValidator: ) if self.retmapper is not None: return self.retmapper(value) + if isinstance(value, ID): + return await cg.get_variable(value) + if isinstance(value, list): + value = [ + await cg.get_variable(x) if isinstance(x, ID) else x for x in value + ] return cg.safe_exp(value) @@ -162,6 +168,7 @@ LV_EVENT_MAP = { "READY": "READY", "CANCEL": "CANCEL", "ALL_EVENTS": "ALL", + "CHANGE": "VALUE_CHANGED", } LV_EVENT_TRIGGERS = tuple(f"on_{x.lower()}" for x in LV_EVENT_MAP) diff --git a/esphome/components/lvgl/lv_validation.py b/esphome/components/lvgl/lv_validation.py index 766c010244..f91ed893f2 100644 --- a/esphome/components/lvgl/lv_validation.py +++ b/esphome/components/lvgl/lv_validation.py @@ -1,6 +1,7 @@ from typing import Union import esphome.codegen as cg +from esphome.components import image from esphome.components.color import CONF_HEX, ColorStruct, from_rgbw from esphome.components.font import Font from esphome.components.image import Image_ @@ -31,7 +32,7 @@ from .defines import ( literal, ) from .helpers import add_lv_use, esphome_fonts_used, lv_fonts_used, requires_component -from .types import lv_font_t, lv_gradient_t, lv_img_t +from .types import lv_font_t, lv_gradient_t opacity_consts = LvConstant("LV_OPA_", "TRANSP", "COVER") @@ -332,8 +333,12 @@ def image_validator(value): lv_image = LValidator( image_validator, - lv_img_t, - retmapper=lambda x: MockObj(x, "->").get_lv_img_dsc(), + image.Image_.operator("ptr"), + requires="image", +) +lv_image_list = LValidator( + cv.ensure_list(image_validator), + cg.std_vector.template(image.Image_.operator("ptr")), requires="image", ) lv_bool = LValidator(cv.boolean, cg.bool_, retmapper=literal) diff --git a/esphome/components/lvgl/lvgl_esphome.cpp b/esphome/components/lvgl/lvgl_esphome.cpp index 41346bc732..61bdfe9755 100644 --- a/esphome/components/lvgl/lvgl_esphome.cpp +++ b/esphome/components/lvgl/lvgl_esphome.cpp @@ -279,7 +279,7 @@ std::string LvSelectable::get_selected_text() { static std::string join_string(std::vector options) { return std::accumulate( options.begin(), options.end(), std::string(), - [](const std::string &a, const std::string &b) -> std::string { return a + (a.length() > 0 ? "\n" : "") + b; }); + [](const std::string &a, const std::string &b) -> std::string { return a + (!a.empty() ? "\n" : "") + b; }); } void LvSelectable::set_selected_text(const std::string &text, lv_anim_enable_t anim) { diff --git a/esphome/components/lvgl/lvgl_esphome.h b/esphome/components/lvgl/lvgl_esphome.h index 7bc6b00cf5..56413ad77e 100644 --- a/esphome/components/lvgl/lvgl_esphome.h +++ b/esphome/components/lvgl/lvgl_esphome.h @@ -60,6 +60,22 @@ inline void lv_disp_set_bg_image(lv_disp_t *disp, esphome::image::Image *image) lv_disp_set_bg_image(disp, image->get_lv_img_dsc()); } #endif // USE_LVGL_IMAGE +#ifdef USE_LVGL_ANIMIMG +inline void lv_animimg_set_src(lv_obj_t *img, std::vector images) { + auto *dsc = static_cast *>(lv_obj_get_user_data(img)); + if (dsc == nullptr) { + // object will be lazily allocated but never freed. + dsc = new std::vector(images.size()); // NOLINT + lv_obj_set_user_data(img, dsc); + } + dsc->clear(); + for (auto &image : images) { + dsc->push_back(image->get_lv_img_dsc()); + } + lv_animimg_set_src(img, (const void **) dsc->data(), dsc->size()); +} + +#endif // USE_LVGL_ANIMIMG // Parent class for things that wrap an LVGL object class LvCompound { diff --git a/esphome/components/lvgl/widgets/animimg.py b/esphome/components/lvgl/widgets/animimg.py index 8adea72ad3..b824d28fb8 100644 --- a/esphome/components/lvgl/widgets/animimg.py +++ b/esphome/components/lvgl/widgets/animimg.py @@ -1,20 +1,18 @@ from esphome import automation -import esphome.codegen as cg import esphome.config_validation as cv from esphome.const import CONF_DURATION, CONF_ID from ..automation import action_to_code from ..defines import CONF_AUTO_START, CONF_MAIN, CONF_REPEAT_COUNT, CONF_SRC from ..helpers import lvgl_components_required -from ..lv_validation import lv_image, lv_milliseconds +from ..lv_validation import lv_image_list, lv_milliseconds from ..lvcode import lv -from ..types import LvType, ObjUpdateAction, void_ptr +from ..types import LvType, ObjUpdateAction from . import Widget, WidgetType, get_widgets from .img import CONF_IMAGE from .label import CONF_LABEL CONF_ANIMIMG = "animimg" -CONF_SRC_LIST_ID = "src_list_id" def lv_repeat_count(value): @@ -32,14 +30,14 @@ ANIMIMG_BASE_SCHEMA = cv.Schema( ANIMIMG_SCHEMA = ANIMIMG_BASE_SCHEMA.extend( { cv.Required(CONF_DURATION): lv_milliseconds, - cv.Required(CONF_SRC): cv.ensure_list(lv_image), - cv.GenerateID(CONF_SRC_LIST_ID): cv.declare_id(void_ptr), + cv.Required(CONF_SRC): lv_image_list, } ) ANIMIMG_MODIFY_SCHEMA = ANIMIMG_BASE_SCHEMA.extend( { cv.Optional(CONF_DURATION): lv_milliseconds, + cv.Optional(CONF_SRC): lv_image_list, } ) @@ -59,17 +57,14 @@ class AnimimgType(WidgetType): async def to_code(self, w: Widget, config): lvgl_components_required.add(CONF_IMAGE) lvgl_components_required.add(CONF_ANIMIMG) - if CONF_SRC in config: - srcs = [ - await lv_image.process(await cg.get_variable(x)) - for x in config[CONF_SRC] - ] - src_id = cg.static_const_array(config[CONF_SRC_LIST_ID], srcs) - count = len(config[CONF_SRC]) - lv.animimg_set_src(w.obj, src_id, count) - lv.animimg_set_repeat_count(w.obj, config[CONF_REPEAT_COUNT]) - lv.animimg_set_duration(w.obj, config[CONF_DURATION]) - if config.get(CONF_AUTO_START): + if srcs := config.get(CONF_SRC): + srcs = await lv_image_list.process(srcs) + lv.animimg_set_src(w.obj, srcs) + if repeat_count := config.get(CONF_REPEAT_COUNT): + lv.animimg_set_repeat_count(w.obj, repeat_count) + if duration := config.get(CONF_DURATION): + lv.animimg_set_duration(w.obj, duration) + if config[CONF_AUTO_START]: lv.animimg_start(w.obj) def get_uses(self): diff --git a/esphome/components/lvgl/widgets/img.py b/esphome/components/lvgl/widgets/img.py index 931d0c0b5b..46077190d0 100644 --- a/esphome/components/lvgl/widgets/img.py +++ b/esphome/components/lvgl/widgets/img.py @@ -1,4 +1,3 @@ -import esphome.codegen as cg import esphome.config_validation as cv from esphome.const import CONF_ANGLE, CONF_MODE @@ -65,7 +64,6 @@ class ImgType(WidgetType): async def to_code(self, w: Widget, config): if src := config.get(CONF_SRC): - src = await cg.get_variable(src) lv.img_set_src(w.obj, await lv_image.process(src)) if (cf_angle := config.get(CONF_ANGLE)) is not None: pivot_x = config[CONF_PIVOT_X] @@ -81,7 +79,7 @@ class ImgType(WidgetType): if CONF_ANTIALIAS in config: lv.img_set_antialias(w.obj, config[CONF_ANTIALIAS]) if mode := config.get(CONF_MODE): - lv.img_set_mode(w.obj, mode) + await w.set_property("size_mode", mode) img_spec = ImgType() diff --git a/esphome/components/matrix_keypad/__init__.py b/esphome/components/matrix_keypad/__init__.py index 5250a45732..b2bcde98ec 100644 --- a/esphome/components/matrix_keypad/__init__.py +++ b/esphome/components/matrix_keypad/__init__.py @@ -1,8 +1,8 @@ +from esphome import automation, pins import esphome.codegen as cg -import esphome.config_validation as cv -from esphome import pins from esphome.components import key_provider -from esphome.const import CONF_ID, CONF_PIN +import esphome.config_validation as cv +from esphome.const import CONF_ID, CONF_ON_KEY, CONF_PIN, CONF_TRIGGER_ID CODEOWNERS = ["@ssieb"] @@ -14,6 +14,9 @@ matrix_keypad_ns = cg.esphome_ns.namespace("matrix_keypad") MatrixKeypad = matrix_keypad_ns.class_( "MatrixKeypad", key_provider.KeyProvider, cg.Component ) +MatrixKeyTrigger = matrix_keypad_ns.class_( + "MatrixKeyTrigger", automation.Trigger.template(cg.uint8) +) CONF_KEYPAD_ID = "keypad_id" CONF_ROWS = "rows" @@ -47,6 +50,11 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_DEBOUNCE_TIME, default=1): cv.int_range(min=1, max=100), cv.Optional(CONF_HAS_DIODES): cv.boolean, cv.Optional(CONF_HAS_PULLDOWNS): cv.boolean, + cv.Optional(CONF_ON_KEY): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(MatrixKeyTrigger), + } + ), } ), check_keys, @@ -73,3 +81,7 @@ async def to_code(config): cg.add(var.set_has_diodes(config[CONF_HAS_DIODES])) if CONF_HAS_PULLDOWNS in config: cg.add(var.set_has_pulldowns(config[CONF_HAS_PULLDOWNS])) + for conf in config.get(CONF_ON_KEY, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID]) + cg.add(var.register_key_trigger(trigger)) + await automation.build_automation(trigger, [(cg.uint8, "x")], conf) diff --git a/esphome/components/matrix_keypad/matrix_keypad.cpp b/esphome/components/matrix_keypad/matrix_keypad.cpp index f62c75c869..8537997935 100644 --- a/esphome/components/matrix_keypad/matrix_keypad.cpp +++ b/esphome/components/matrix_keypad/matrix_keypad.cpp @@ -86,6 +86,8 @@ void MatrixKeypad::loop() { if (!this->keys_.empty()) { uint8_t keycode = this->keys_[key]; ESP_LOGD(TAG, "key '%c' pressed", keycode); + for (auto &trigger : this->key_triggers_) + trigger->trigger(keycode); for (auto &listener : this->listeners_) listener->key_pressed(keycode); this->send_key_(keycode); @@ -107,5 +109,7 @@ void MatrixKeypad::dump_config() { void MatrixKeypad::register_listener(MatrixKeypadListener *listener) { this->listeners_.push_back(listener); } +void MatrixKeypad::register_key_trigger(MatrixKeyTrigger *trig) { this->key_triggers_.push_back(trig); } + } // namespace matrix_keypad } // namespace esphome diff --git a/esphome/components/matrix_keypad/matrix_keypad.h b/esphome/components/matrix_keypad/matrix_keypad.h index d506040b7c..8b309b42c2 100644 --- a/esphome/components/matrix_keypad/matrix_keypad.h +++ b/esphome/components/matrix_keypad/matrix_keypad.h @@ -1,6 +1,7 @@ #pragma once #include "esphome/components/key_provider/key_provider.h" +#include "esphome/core/automation.h" #include "esphome/core/component.h" #include "esphome/core/hal.h" #include "esphome/core/helpers.h" @@ -18,6 +19,8 @@ class MatrixKeypadListener { virtual void key_released(uint8_t key){}; }; +class MatrixKeyTrigger : public Trigger {}; + class MatrixKeypad : public key_provider::KeyProvider, public Component { public: void setup() override; @@ -31,6 +34,7 @@ class MatrixKeypad : public key_provider::KeyProvider, public Component { void set_has_pulldowns(int has_pulldowns) { has_pulldowns_ = has_pulldowns; }; void register_listener(MatrixKeypadListener *listener); + void register_key_trigger(MatrixKeyTrigger *trig); protected: std::vector rows_; @@ -42,6 +46,7 @@ class MatrixKeypad : public key_provider::KeyProvider, public Component { int pressed_key_ = -1; std::vector listeners_{}; + std::vector key_triggers_; }; } // namespace matrix_keypad diff --git a/esphome/components/max31865/max31865.cpp b/esphome/components/max31865/max31865.cpp index b48aa2fdd3..4749874ac7 100644 --- a/esphome/components/max31865/max31865.cpp +++ b/esphome/components/max31865/max31865.cpp @@ -106,7 +106,8 @@ void MAX31865Sensor::read_data_() { // Check faults const uint8_t faults = this->read_register_(FAULT_STATUS_REG); - if ((has_fault_ = faults & 0b00111100)) { + has_fault_ = faults & 0b00111100; + if (has_fault_) { if (faults & (1 << 2)) { ESP_LOGE(TAG, "Overvoltage/undervoltage fault"); } @@ -125,7 +126,8 @@ void MAX31865Sensor::read_data_() { } else { this->status_clear_error(); } - if ((has_warn_ = faults & 0b11000000)) { + has_warn_ = faults & 0b11000000; + if (has_warn_) { if (faults & (1 << 6)) { ESP_LOGW(TAG, "RTD Low Threshold"); } diff --git a/esphome/components/modbus_controller/modbus_controller.cpp b/esphome/components/modbus_controller/modbus_controller.cpp index f8b72af817..3f487abc94 100644 --- a/esphome/components/modbus_controller/modbus_controller.cpp +++ b/esphome/components/modbus_controller/modbus_controller.cpp @@ -152,11 +152,11 @@ void ModbusController::on_modbus_read_registers(uint8_t function_code, uint16_t } SensorSet ModbusController::find_sensors_(ModbusRegisterType register_type, uint16_t start_address) const { - auto reg_it = find_if(begin(register_ranges_), end(register_ranges_), [=](RegisterRange const &r) { - return (r.start_address == start_address && r.register_type == register_type); - }); + auto reg_it = std::find_if( + std::begin(this->register_ranges_), std::end(this->register_ranges_), + [=](RegisterRange const &r) { return (r.start_address == start_address && r.register_type == register_type); }); - if (reg_it == register_ranges_.end()) { + if (reg_it == this->register_ranges_.end()) { ESP_LOGE(TAG, "No matching range for sensor found - start_address : 0x%X", start_address); } else { return reg_it->sensors; @@ -240,18 +240,18 @@ void ModbusController::update() { // walk through the sensors and determine the register ranges to read size_t ModbusController::create_register_ranges_() { - register_ranges_.clear(); - if (this->parent_->role == modbus::ModbusRole::CLIENT && sensorset_.empty()) { + this->register_ranges_.clear(); + if (this->parent_->role == modbus::ModbusRole::CLIENT && this->sensorset_.empty()) { ESP_LOGW(TAG, "No sensors registered"); return 0; } // iterator is sorted see SensorItemsComparator for details - auto ix = sensorset_.begin(); + auto ix = this->sensorset_.begin(); RegisterRange r = {}; uint8_t buffer_offset = 0; SensorItem *prev = nullptr; - while (ix != sensorset_.end()) { + while (ix != this->sensorset_.end()) { SensorItem *curr = *ix; ESP_LOGV(TAG, "Register: 0x%X %d %d %d offset=%u skip=%u addr=%p", curr->start_address, curr->register_count, @@ -278,12 +278,12 @@ size_t ModbusController::create_register_ranges_() { // this register can re-use the data from the previous register // remove this sensore because start_address is changed (sort-order) - ix = sensorset_.erase(ix); + ix = this->sensorset_.erase(ix); curr->start_address = r.start_address; curr->offset += prev->offset; - sensorset_.insert(curr); + this->sensorset_.insert(curr); // move iterator backwards because it will be incremented later ix--; @@ -293,14 +293,14 @@ size_t ModbusController::create_register_ranges_() { // this register can extend the current range // remove this sensore because start_address is changed (sort-order) - ix = sensorset_.erase(ix); + ix = this->sensorset_.erase(ix); curr->start_address = r.start_address; curr->offset += buffer_offset; buffer_offset += curr->get_register_size(); r.register_count += curr->register_count; - sensorset_.insert(curr); + this->sensorset_.insert(curr); // move iterator backwards because it will be incremented later ix--; @@ -327,7 +327,7 @@ size_t ModbusController::create_register_ranges_() { ix++; } else { ESP_LOGV(TAG, "Add range 0x%X %d skip:%d", r.start_address, r.register_count, r.skip_updates); - register_ranges_.push_back(r); + this->register_ranges_.push_back(r); r = {}; buffer_offset = 0; // do not increment the iterator here because the current sensor has to be re-evaluated @@ -339,10 +339,10 @@ size_t ModbusController::create_register_ranges_() { if (r.register_count > 0) { // Add the last range ESP_LOGV(TAG, "Add last range 0x%X %d skip:%d", r.start_address, r.register_count, r.skip_updates); - register_ranges_.push_back(r); + this->register_ranges_.push_back(r); } - return register_ranges_.size(); + return this->register_ranges_.size(); } void ModbusController::dump_config() { @@ -352,18 +352,18 @@ void ModbusController::dump_config() { ESP_LOGCONFIG(TAG, " Offline Skip Updates: %d", this->offline_skip_updates_); #if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE ESP_LOGCONFIG(TAG, "sensormap"); - for (auto &it : sensorset_) { + for (auto &it : this->sensorset_) { ESP_LOGCONFIG(TAG, " Sensor type=%zu start=0x%X offset=0x%X count=%d size=%d", static_cast(it->register_type), it->start_address, it->offset, it->register_count, it->get_register_size()); } ESP_LOGCONFIG(TAG, "ranges"); - for (auto &it : register_ranges_) { + for (auto &it : this->register_ranges_) { ESP_LOGCONFIG(TAG, " Range type=%zu start=0x%X count=%d skip_updates=%d", static_cast(it.register_type), it.start_address, it.register_count, it.skip_updates); } ESP_LOGCONFIG(TAG, "server registers"); - for (auto &r : server_registers_) { + for (auto &r : this->server_registers_) { ESP_LOGCONFIG(TAG, " Address=0x%02X value_type=%zu register_count=%u", r->address, static_cast(r->value_type), r->register_count); } @@ -372,15 +372,15 @@ void ModbusController::dump_config() { void ModbusController::loop() { // Incoming data to process? - if (!incoming_queue_.empty()) { - auto &message = incoming_queue_.front(); + if (!this->incoming_queue_.empty()) { + auto &message = this->incoming_queue_.front(); if (message != nullptr) - process_modbus_data_(message.get()); - incoming_queue_.pop(); + this->process_modbus_data_(message.get()); + this->incoming_queue_.pop(); } else { // all messages processed send pending commands - send_next_command_(); + this->send_next_command_(); } } @@ -391,7 +391,7 @@ void ModbusController::on_write_register_response(ModbusRegisterType register_ty void ModbusController::dump_sensors_() { ESP_LOGV(TAG, "sensors"); - for (auto &it : sensorset_) { + for (auto &it : this->sensorset_) { ESP_LOGV(TAG, " Sensor start=0x%X count=%d size=%d offset=%d", it->start_address, it->register_count, it->get_register_size(), it->offset); } diff --git a/esphome/components/modbus_controller/modbus_controller.h b/esphome/components/modbus_controller/modbus_controller.h index 2a0b936bf5..dfd52e44bc 100644 --- a/esphome/components/modbus_controller/modbus_controller.h +++ b/esphome/components/modbus_controller/modbus_controller.h @@ -240,14 +240,14 @@ class SensorItem { } // Override register size for modbus devices not using 1 register for one dword void set_register_size(uint8_t register_size) { response_bytes = register_size; } - ModbusRegisterType register_type; - SensorValueType sensor_value_type; - uint16_t start_address; - uint32_t bitmask; - uint8_t offset; - uint8_t register_count; + ModbusRegisterType register_type{ModbusRegisterType::CUSTOM}; + SensorValueType sensor_value_type{SensorValueType::RAW}; + uint16_t start_address{0}; + uint32_t bitmask{0}; + uint8_t offset{0}; + uint8_t register_count{0}; uint8_t response_bytes{0}; - uint16_t skip_updates; + uint16_t skip_updates{0}; std::vector custom_data{}; bool force_new_range{false}; }; @@ -261,9 +261,9 @@ class ServerRegister { this->register_count = register_count; this->read_lambda = std::move(read_lambda); } - uint16_t address; - SensorValueType value_type; - uint8_t register_count; + uint16_t address{0}; + SensorValueType value_type{SensorValueType::RAW}; + uint8_t register_count{0}; std::function read_lambda; }; @@ -312,11 +312,11 @@ struct RegisterRange { class ModbusCommandItem { public: static const size_t MAX_PAYLOAD_BYTES = 240; - ModbusController *modbusdevice; - uint16_t register_address; - uint16_t register_count; - ModbusFunctionCode function_code; - ModbusRegisterType register_type; + ModbusController *modbusdevice{nullptr}; + uint16_t register_address{0}; + uint16_t register_count{0}; + ModbusFunctionCode function_code{ModbusFunctionCode::CUSTOM}; + ModbusRegisterType register_type{ModbusRegisterType::CUSTOM}; std::function &data)> on_data_func; std::vector payload = {}; @@ -493,23 +493,23 @@ class ModbusController : public PollingComponent, public modbus::ModbusDevice { /// Collection of all sensors for this component SensorSet sensorset_; /// Collection of all server registers for this component - std::vector server_registers_; + std::vector server_registers_{}; /// Continuous range of modbus registers - std::vector register_ranges_; + std::vector register_ranges_{}; /// Hold the pending requests to be sent std::list> command_queue_; /// modbus response data waiting to get processed std::queue> incoming_queue_; /// if duplicate commands can be sent - bool allow_duplicate_commands_; + bool allow_duplicate_commands_{false}; /// when was the last send operation - uint32_t last_command_timestamp_; + uint32_t last_command_timestamp_{0}; /// min time in ms between sending modbus commands - uint16_t command_throttle_; + uint16_t command_throttle_{0}; /// if module didn't respond the last command - bool module_offline_; + bool module_offline_{false}; /// how many updates to skip if module is offline - uint16_t offline_skip_updates_; + uint16_t offline_skip_updates_{0}; /// How many times we will retry a command if we get no response uint8_t max_cmd_retries_{4}; /// Command sent callback diff --git a/esphome/components/modbus_controller/number/modbus_number.cpp b/esphome/components/modbus_controller/number/modbus_number.cpp index 001cfb5787..ea8467d5a3 100644 --- a/esphome/components/modbus_controller/number/modbus_number.cpp +++ b/esphome/components/modbus_controller/number/modbus_number.cpp @@ -8,7 +8,7 @@ namespace modbus_controller { static const char *const TAG = "modbus.number"; void ModbusNumber::parse_and_publish(const std::vector &data) { - float result = payload_to_float(data, *this) / multiply_by_; + float result = payload_to_float(data, *this) / this->multiply_by_; // Is there a lambda registered // call it with the pre converted value and the raw data array @@ -43,7 +43,7 @@ void ModbusNumber::control(float value) { return; } } else { - write_value = multiply_by_ * write_value; + write_value = this->multiply_by_ * write_value; } if (!data.empty()) { @@ -63,21 +63,21 @@ void ModbusNumber::control(float value) { // Create and send the write command if (this->register_count == 1 && !this->use_write_multiple_) { // since offset is in bytes and a register is 16 bits we get the start by adding offset/2 - write_cmd = - ModbusCommandItem::create_write_single_command(parent_, this->start_address + this->offset / 2, data[0]); + write_cmd = ModbusCommandItem::create_write_single_command(this->parent_, this->start_address + this->offset / 2, + data[0]); } else { - write_cmd = ModbusCommandItem::create_write_multiple_command(parent_, this->start_address + this->offset / 2, - this->register_count, data); + write_cmd = ModbusCommandItem::create_write_multiple_command( + this->parent_, this->start_address + this->offset / 2, this->register_count, data); } // publish new value write_cmd.on_data_func = [this, write_cmd, value](ModbusRegisterType register_type, uint16_t start_address, const std::vector &data) { // gets called when the write command is ack'd from the device - parent_->on_write_register_response(write_cmd.register_type, start_address, data); + this->parent_->on_write_register_response(write_cmd.register_type, start_address, data); this->publish_state(value); }; } - parent_->queue_command(write_cmd); + this->parent_->queue_command(write_cmd); this->publish_state(value); } void ModbusNumber::dump_config() { LOG_NUMBER(TAG, "Modbus Number", this); } diff --git a/esphome/components/modbus_controller/number/modbus_number.h b/esphome/components/modbus_controller/number/modbus_number.h index 544d161cbc..8f77b2e014 100644 --- a/esphome/components/modbus_controller/number/modbus_number.h +++ b/esphome/components/modbus_controller/number/modbus_number.h @@ -29,7 +29,7 @@ class ModbusNumber : public number::Number, public Component, public SensorItem void parse_and_publish(const std::vector &data) override; float get_setup_priority() const override { return setup_priority::HARDWARE; } void set_parent(ModbusController *parent) { this->parent_ = parent; } - void set_write_multiply(float factor) { multiply_by_ = factor; } + void set_write_multiply(float factor) { this->multiply_by_ = factor; } using transform_func_t = std::function(ModbusNumber *, float, const std::vector &)>; using write_transform_func_t = std::function(ModbusNumber *, float, std::vector &)>; @@ -39,9 +39,9 @@ class ModbusNumber : public number::Number, public Component, public SensorItem protected: void control(float value) override; - optional transform_func_; - optional write_transform_func_; - ModbusController *parent_; + optional transform_func_{nullopt}; + optional write_transform_func_{nullopt}; + ModbusController *parent_{nullptr}; float multiply_by_{1.0}; bool use_write_multiple_{false}; }; diff --git a/esphome/components/modbus_controller/output/modbus_output.cpp b/esphome/components/modbus_controller/output/modbus_output.cpp index 79cd2d49c2..f0f6e64f10 100644 --- a/esphome/components/modbus_controller/output/modbus_output.cpp +++ b/esphome/components/modbus_controller/output/modbus_output.cpp @@ -27,7 +27,7 @@ void ModbusFloatOutput::write_state(float value) { return; } } else { - value = multiply_by_ * value; + value = this->multiply_by_ * value; } // lambda didn't set payload if (data.empty()) { @@ -40,12 +40,13 @@ void ModbusFloatOutput::write_state(float value) { // Create and send the write command ModbusCommandItem write_cmd; if (this->register_count == 1 && !this->use_write_multiple_) { - write_cmd = ModbusCommandItem::create_write_single_command(parent_, this->start_address + this->offset, data[0]); + write_cmd = + ModbusCommandItem::create_write_single_command(this->parent_, this->start_address + this->offset, data[0]); } else { - write_cmd = ModbusCommandItem::create_write_multiple_command(parent_, this->start_address + this->offset, + write_cmd = ModbusCommandItem::create_write_multiple_command(this->parent_, this->start_address + this->offset, this->register_count, data); } - parent_->queue_command(write_cmd); + this->parent_->queue_command(write_cmd); } void ModbusFloatOutput::dump_config() { @@ -90,9 +91,9 @@ void ModbusBinaryOutput::write_state(bool state) { // offset for coil and discrete inputs is the coil/register number not bytes if (this->use_write_multiple_) { std::vector states{state}; - cmd = ModbusCommandItem::create_write_multiple_coils(parent_, this->start_address + this->offset, states); + cmd = ModbusCommandItem::create_write_multiple_coils(this->parent_, this->start_address + this->offset, states); } else { - cmd = ModbusCommandItem::create_write_single_coil(parent_, this->start_address + this->offset, state); + cmd = ModbusCommandItem::create_write_single_coil(this->parent_, this->start_address + this->offset, state); } } this->parent_->queue_command(cmd); diff --git a/esphome/components/modbus_controller/output/modbus_output.h b/esphome/components/modbus_controller/output/modbus_output.h index f424671cd1..bceb97affb 100644 --- a/esphome/components/modbus_controller/output/modbus_output.h +++ b/esphome/components/modbus_controller/output/modbus_output.h @@ -25,7 +25,7 @@ class ModbusFloatOutput : public output::FloatOutput, public Component, public S void dump_config() override; void set_parent(ModbusController *parent) { this->parent_ = parent; } - void set_write_multiply(float factor) { multiply_by_ = factor; } + void set_write_multiply(float factor) { this->multiply_by_ = factor; } // Do nothing void parse_and_publish(const std::vector &data) override{}; @@ -37,9 +37,9 @@ class ModbusFloatOutput : public output::FloatOutput, public Component, public S void write_state(float value) override; optional write_transform_func_{nullopt}; - ModbusController *parent_; + ModbusController *parent_{nullptr}; float multiply_by_{1.0}; - bool use_write_multiple_; + bool use_write_multiple_{false}; }; class ModbusBinaryOutput : public output::BinaryOutput, public Component, public SensorItem { @@ -68,8 +68,8 @@ class ModbusBinaryOutput : public output::BinaryOutput, public Component, public void write_state(bool state) override; optional write_transform_func_{nullopt}; - ModbusController *parent_; - bool use_write_multiple_; + ModbusController *parent_{nullptr}; + bool use_write_multiple_{false}; }; } // namespace modbus_controller diff --git a/esphome/components/modbus_controller/select/modbus_select.cpp b/esphome/components/modbus_controller/select/modbus_select.cpp index 33cef39a18..56b8c783ed 100644 --- a/esphome/components/modbus_controller/select/modbus_select.cpp +++ b/esphome/components/modbus_controller/select/modbus_select.cpp @@ -74,12 +74,13 @@ void ModbusSelect::control(const std::string &value) { const uint16_t write_address = this->start_address + this->offset / 2; ModbusCommandItem write_cmd; if ((this->register_count == 1) && (!this->use_write_multiple_)) { - write_cmd = ModbusCommandItem::create_write_single_command(parent_, write_address, data[0]); + write_cmd = ModbusCommandItem::create_write_single_command(this->parent_, write_address, data[0]); } else { - write_cmd = ModbusCommandItem::create_write_multiple_command(parent_, write_address, this->register_count, data); + write_cmd = + ModbusCommandItem::create_write_multiple_command(this->parent_, write_address, this->register_count, data); } - parent_->queue_command(write_cmd); + this->parent_->queue_command(write_cmd); if (this->optimistic_) this->publish_state(value); diff --git a/esphome/components/modbus_controller/select/modbus_select.h b/esphome/components/modbus_controller/select/modbus_select.h index 1c046b11d0..55fb2107dd 100644 --- a/esphome/components/modbus_controller/select/modbus_select.h +++ b/esphome/components/modbus_controller/select/modbus_select.h @@ -42,12 +42,12 @@ class ModbusSelect : public Component, public select::Select, public SensorItem void control(const std::string &value) override; protected: - std::vector mapping_; - ModbusController *parent_; + std::vector mapping_{}; + ModbusController *parent_{nullptr}; bool use_write_multiple_{false}; bool optimistic_{false}; - optional transform_func_; - optional write_transform_func_; + optional transform_func_{nullopt}; + optional write_transform_func_{nullopt}; }; } // namespace modbus_controller diff --git a/esphome/components/modbus_controller/switch/modbus_switch.cpp b/esphome/components/modbus_controller/switch/modbus_switch.cpp index 3a679fbeb8..b729e2659f 100644 --- a/esphome/components/modbus_controller/switch/modbus_switch.cpp +++ b/esphome/components/modbus_controller/switch/modbus_switch.cpp @@ -80,24 +80,24 @@ void ModbusSwitch::write_state(bool state) { // offset for coil and discrete inputs is the coil/register number not bytes if (this->use_write_multiple_) { std::vector states{state}; - cmd = ModbusCommandItem::create_write_multiple_coils(parent_, this->start_address + this->offset, states); + cmd = ModbusCommandItem::create_write_multiple_coils(this->parent_, this->start_address + this->offset, states); } else { - cmd = ModbusCommandItem::create_write_single_coil(parent_, this->start_address + this->offset, state); + cmd = ModbusCommandItem::create_write_single_coil(this->parent_, this->start_address + this->offset, state); } } else { // since offset is in bytes and a register is 16 bits we get the start by adding offset/2 if (this->use_write_multiple_) { std::vector bool_states(1, state ? (0xFFFF & this->bitmask) : 0); - cmd = ModbusCommandItem::create_write_multiple_command(parent_, this->start_address + this->offset / 2, 1, + cmd = ModbusCommandItem::create_write_multiple_command(this->parent_, this->start_address + this->offset / 2, 1, bool_states); } else { - cmd = ModbusCommandItem::create_write_single_command(parent_, this->start_address + this->offset / 2, + cmd = ModbusCommandItem::create_write_single_command(this->parent_, this->start_address + this->offset / 2, state ? 0xFFFF & this->bitmask : 0u); } } } this->parent_->queue_command(cmd); - publish_state(state); + this->publish_state(state); } // ModbusSwitch end } // namespace modbus_controller diff --git a/esphome/components/modbus_controller/switch/modbus_switch.h b/esphome/components/modbus_controller/switch/modbus_switch.h index bfe46f3ac8..fe4b7c1ad5 100644 --- a/esphome/components/modbus_controller/switch/modbus_switch.h +++ b/esphome/components/modbus_controller/switch/modbus_switch.h @@ -40,8 +40,8 @@ class ModbusSwitch : public Component, public switch_::Switch, public SensorItem void set_use_write_mutiple(bool use_write_multiple) { this->use_write_multiple_ = use_write_multiple; } protected: - ModbusController *parent_; - bool use_write_multiple_; + ModbusController *parent_{nullptr}; + bool use_write_multiple_{false}; optional publish_transform_func_{nullopt}; optional write_transform_func_{nullopt}; }; diff --git a/esphome/components/mqtt/__init__.py b/esphome/components/mqtt/__init__.py index 86d163e61d..2b0d941220 100644 --- a/esphome/components/mqtt/__init__.py +++ b/esphome/components/mqtt/__init__.py @@ -49,6 +49,7 @@ from esphome.const import ( CONF_USE_ABBREVIATIONS, CONF_USERNAME, CONF_WILL_MESSAGE, + CONF_PUBLISH_NAN_AS_NONE, PLATFORM_BK72XX, PLATFORM_ESP32, PLATFORM_ESP8266, @@ -296,6 +297,7 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_QOS, default=0): cv.mqtt_qos, } ), + cv.Optional(CONF_PUBLISH_NAN_AS_NONE, default=False): cv.boolean, } ), validate_config, @@ -449,6 +451,8 @@ async def to_code(config): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) await automation.build_automation(trigger, [], conf) + cg.add(var.set_publish_nan_as_none(config[CONF_PUBLISH_NAN_AS_NONE])) + MQTT_PUBLISH_ACTION_SCHEMA = cv.Schema( { diff --git a/esphome/components/mqtt/mqtt_alarm_control_panel.cpp b/esphome/components/mqtt/mqtt_alarm_control_panel.cpp index 660a030d11..4cc4773bd3 100644 --- a/esphome/components/mqtt/mqtt_alarm_control_panel.cpp +++ b/esphome/components/mqtt/mqtt_alarm_control_panel.cpp @@ -80,8 +80,7 @@ const EntityBase *MQTTAlarmControlPanelComponent::get_entity() const { return th bool MQTTAlarmControlPanelComponent::send_initial_state() { return this->publish_state(); } bool MQTTAlarmControlPanelComponent::publish_state() { - bool success = true; - const char *state_s = ""; + const char *state_s; switch (this->alarm_control_panel_->get_state()) { case ACP_STATE_DISARMED: state_s = "disarmed"; @@ -116,9 +115,7 @@ bool MQTTAlarmControlPanelComponent::publish_state() { default: state_s = "unknown"; } - if (!this->publish(this->get_state_topic_(), state_s)) - success = false; - return success; + return this->publish(this->get_state_topic_(), state_s); } } // namespace mqtt diff --git a/esphome/components/mqtt/mqtt_backend_esp32.cpp b/esphome/components/mqtt/mqtt_backend_esp32.cpp index ed500c6d44..2cccb957eb 100644 --- a/esphome/components/mqtt/mqtt_backend_esp32.cpp +++ b/esphome/components/mqtt/mqtt_backend_esp32.cpp @@ -151,11 +151,11 @@ void MQTTBackendESP32::mqtt_event_handler_(const Event &event) { break; case MQTT_EVENT_DATA: { static std::string topic; - if (event.topic.length() > 0) { + if (!event.topic.empty()) { topic = event.topic; } ESP_LOGV(TAG, "MQTT_EVENT_DATA %s", topic.c_str()); - this->on_message_.call(event.topic.length() > 0 ? topic.c_str() : nullptr, event.data.data(), event.data.size(), + this->on_message_.call(!event.topic.empty() ? topic.c_str() : nullptr, event.data.data(), event.data.size(), event.current_data_offset, event.total_data_len); } break; case MQTT_EVENT_ERROR: @@ -184,7 +184,7 @@ void MQTTBackendESP32::mqtt_event_handler(void *handler_args, esp_event_base_t b // queue event to decouple processing if (instance) { auto event = *static_cast(event_data); - instance->mqtt_events_.push(Event(event)); + instance->mqtt_events_.emplace(event); } } diff --git a/esphome/components/mqtt/mqtt_client.cpp b/esphome/components/mqtt/mqtt_client.cpp index 106192c0e3..c7ace505a8 100644 --- a/esphome/components/mqtt/mqtt_client.cpp +++ b/esphome/components/mqtt/mqtt_client.cpp @@ -608,6 +608,10 @@ void MQTTClientComponent::set_log_message_template(MQTTMessage &&message) { this const MQTTDiscoveryInfo &MQTTClientComponent::get_discovery_info() const { return this->discovery_info_; } void MQTTClientComponent::set_topic_prefix(const std::string &topic_prefix) { this->topic_prefix_ = topic_prefix; } const std::string &MQTTClientComponent::get_topic_prefix() const { return this->topic_prefix_; } +void MQTTClientComponent::set_publish_nan_as_none(bool publish_nan_as_none) { + this->publish_nan_as_none_ = publish_nan_as_none; +} +bool MQTTClientComponent::is_publish_nan_as_none() const { return this->publish_nan_as_none_; } void MQTTClientComponent::disable_birth_message() { this->birth_message_.topic = ""; this->recalculate_availability_(); diff --git a/esphome/components/mqtt/mqtt_client.h b/esphome/components/mqtt/mqtt_client.h index 7ae3a6c5e8..34eac29464 100644 --- a/esphome/components/mqtt/mqtt_client.h +++ b/esphome/components/mqtt/mqtt_client.h @@ -263,6 +263,10 @@ class MQTTClientComponent : public Component { void set_on_connect(mqtt_on_connect_callback_t &&callback); void set_on_disconnect(mqtt_on_disconnect_callback_t &&callback); + // Publish None state instead of NaN for Home Assistant + void set_publish_nan_as_none(bool publish_nan_as_none); + bool is_publish_nan_as_none() const; + protected: void send_device_info_(); @@ -328,6 +332,8 @@ class MQTTClientComponent : public Component { uint32_t connect_begin_; uint32_t last_connected_{0}; optional disconnect_reason_{}; + + bool publish_nan_as_none_{false}; }; extern MQTTClientComponent *global_mqtt_client; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) diff --git a/esphome/components/mqtt/mqtt_climate.cpp b/esphome/components/mqtt/mqtt_climate.cpp index 773d863835..f06574fa26 100644 --- a/esphome/components/mqtt/mqtt_climate.cpp +++ b/esphome/components/mqtt/mqtt_climate.cpp @@ -257,7 +257,7 @@ const EntityBase *MQTTClimateComponent::get_entity() const { return this->device bool MQTTClimateComponent::publish_state_() { auto traits = this->device_->get_traits(); // mode - const char *mode_s = ""; + const char *mode_s; switch (this->device_->mode) { case CLIMATE_MODE_OFF: mode_s = "off"; @@ -280,6 +280,8 @@ bool MQTTClimateComponent::publish_state_() { case CLIMATE_MODE_HEAT_COOL: mode_s = "heat_cool"; break; + default: + mode_s = "unknown"; } bool success = true; if (!this->publish(this->get_mode_state_topic(), mode_s)) @@ -343,6 +345,8 @@ bool MQTTClimateComponent::publish_state_() { case CLIMATE_PRESET_ACTIVITY: payload = "activity"; break; + default: + payload = "unknown"; } } if (this->device_->custom_preset.has_value()) @@ -352,7 +356,7 @@ bool MQTTClimateComponent::publish_state_() { } if (traits.get_supports_action()) { - const char *payload = "unknown"; + const char *payload; switch (this->device_->action) { case CLIMATE_ACTION_OFF: payload = "off"; @@ -372,6 +376,8 @@ bool MQTTClimateComponent::publish_state_() { case CLIMATE_ACTION_FAN: payload = "fan"; break; + default: + payload = "unknown"; } if (!this->publish(this->get_action_state_topic(), payload)) success = false; @@ -411,6 +417,8 @@ bool MQTTClimateComponent::publish_state_() { case CLIMATE_FAN_QUIET: payload = "quiet"; break; + default: + payload = "unknown"; } } if (this->device_->custom_fan_mode.has_value()) @@ -420,7 +428,7 @@ bool MQTTClimateComponent::publish_state_() { } if (traits.get_supports_swing_modes()) { - const char *payload = ""; + const char *payload; switch (this->device_->swing_mode) { case CLIMATE_SWING_OFF: payload = "off"; @@ -434,6 +442,8 @@ bool MQTTClimateComponent::publish_state_() { case CLIMATE_SWING_HORIZONTAL: payload = "horizontal"; break; + default: + payload = "unknown"; } if (!this->publish(this->get_swing_mode_state_topic(), payload)) success = false; diff --git a/esphome/components/mqtt/mqtt_sensor.cpp b/esphome/components/mqtt/mqtt_sensor.cpp index fff75a3c00..2cbc291ccf 100644 --- a/esphome/components/mqtt/mqtt_sensor.cpp +++ b/esphome/components/mqtt/mqtt_sensor.cpp @@ -69,6 +69,8 @@ bool MQTTSensorComponent::send_initial_state() { } } bool MQTTSensorComponent::publish_state(float value) { + if (mqtt::global_mqtt_client->is_publish_nan_as_none() && std::isnan(value)) + return this->publish(this->get_state_topic_(), "None"); int8_t accuracy = this->sensor_->get_accuracy_decimals(); return this->publish(this->get_state_topic_(), value_accuracy_to_string(value, accuracy)); } diff --git a/esphome/components/network/ip_address.h b/esphome/components/network/ip_address.h index 941934cf0a..69d3788ca5 100644 --- a/esphome/components/network/ip_address.h +++ b/esphome/components/network/ip_address.h @@ -116,7 +116,7 @@ struct IPAddress { operator arduino_ns::IPAddress() const { return ip_addr_get_ip4_u32(&ip_addr_); } #endif - bool is_set() { return !ip_addr_isany(&ip_addr_); } + bool is_set() { return !ip_addr_isany(&ip_addr_); } // NOLINT(readability-simplify-boolean-expr) bool is_ip4() { return IP_IS_V4(&ip_addr_); } bool is_ip6() { return IP_IS_V6(&ip_addr_); } std::string str() const { return str_lower_case(ipaddr_ntoa(&ip_addr_)); } diff --git a/esphome/components/nextion/__init__.py b/esphome/components/nextion/__init__.py index 924d58198d..fb75daf4ba 100644 --- a/esphome/components/nextion/__init__.py +++ b/esphome/components/nextion/__init__.py @@ -6,3 +6,5 @@ Nextion = nextion_ns.class_("Nextion", cg.PollingComponent, uart.UARTDevice) nextion_ref = Nextion.operator("ref") CONF_NEXTION_ID = "nextion_id" +CONF_PUBLISH_STATE = "publish_state" +CONF_SEND_TO_NEXTION = "send_to_nextion" diff --git a/esphome/components/nextion/automation.h b/esphome/components/nextion/automation.h index f51fe6b4f8..65f1fd0058 100644 --- a/esphome/components/nextion/automation.h +++ b/esphome/components/nextion/automation.h @@ -5,6 +5,13 @@ namespace esphome { namespace nextion { +class BufferOverflowTrigger : public Trigger<> { + public: + explicit BufferOverflowTrigger(Nextion *nextion) { + nextion->add_buffer_overflow_event_callback([this]() { this->trigger(); }); + } +}; + class SetupTrigger : public Trigger<> { public: explicit SetupTrigger(Nextion *nextion) { @@ -42,5 +49,74 @@ class TouchTrigger : public Trigger { } }; +template class NextionPublishFloatAction : public Action { + public: + explicit NextionPublishFloatAction(NextionComponent *component) : component_(component) {} + + TEMPLATABLE_VALUE(float, state) + TEMPLATABLE_VALUE(bool, publish_state) + TEMPLATABLE_VALUE(bool, send_to_nextion) + + void play(Ts... x) override { + this->component_->set_state(this->state_.value(x...), this->publish_state_.value(x...), + this->send_to_nextion_.value(x...)); + } + + void set_state(std::function state) { this->state_ = state; } + void set_publish_state(std::function publish_state) { this->publish_state_ = publish_state; } + void set_send_to_nextion(std::function send_to_nextion) { + this->send_to_nextion_ = send_to_nextion; + } + + protected: + NextionComponent *component_; +}; + +template class NextionPublishTextAction : public Action { + public: + explicit NextionPublishTextAction(NextionComponent *component) : component_(component) {} + + TEMPLATABLE_VALUE(const char *, state) + TEMPLATABLE_VALUE(bool, publish_state) + TEMPLATABLE_VALUE(bool, send_to_nextion) + + void play(Ts... x) override { + this->component_->set_state(this->state_.value(x...), this->publish_state_.value(x...), + this->send_to_nextion_.value(x...)); + } + + void set_state(std::function state) { this->state_ = state; } + void set_publish_state(std::function publish_state) { this->publish_state_ = publish_state; } + void set_send_to_nextion(std::function send_to_nextion) { + this->send_to_nextion_ = send_to_nextion; + } + + protected: + NextionComponent *component_; +}; + +template class NextionPublishBoolAction : public Action { + public: + explicit NextionPublishBoolAction(NextionComponent *component) : component_(component) {} + + TEMPLATABLE_VALUE(bool, state) + TEMPLATABLE_VALUE(bool, publish_state) + TEMPLATABLE_VALUE(bool, send_to_nextion) + + void play(Ts... x) override { + this->component_->set_state(this->state_.value(x...), this->publish_state_.value(x...), + this->send_to_nextion_.value(x...)); + } + + void set_state(std::function state) { this->state_ = state; } + void set_publish_state(std::function publish_state) { this->publish_state_ = publish_state; } + void set_send_to_nextion(std::function send_to_nextion) { + this->send_to_nextion_ = send_to_nextion; + } + + protected: + NextionComponent *component_; +}; + } // namespace nextion } // namespace esphome diff --git a/esphome/components/nextion/base_component.py b/esphome/components/nextion/base_component.py index 2924f66d3c..9708379861 100644 --- a/esphome/components/nextion/base_component.py +++ b/esphome/components/nextion/base_component.py @@ -18,6 +18,7 @@ CONF_ON_SLEEP = "on_sleep" CONF_ON_WAKE = "on_wake" CONF_ON_SETUP = "on_setup" CONF_ON_PAGE = "on_page" +CONF_ON_BUFFER_OVERFLOW = "on_buffer_overflow" CONF_TOUCH_SLEEP_TIMEOUT = "touch_sleep_timeout" CONF_WAKE_UP_PAGE = "wake_up_page" CONF_START_UP_PAGE = "start_up_page" diff --git a/esphome/components/nextion/binary_sensor/__init__.py b/esphome/components/nextion/binary_sensor/__init__.py index 8b4a45cc60..a257587e13 100644 --- a/esphome/components/nextion/binary_sensor/__init__.py +++ b/esphome/components/nextion/binary_sensor/__init__.py @@ -1,9 +1,16 @@ +from esphome import automation import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import binary_sensor -from esphome.const import CONF_COMPONENT_ID, CONF_PAGE_ID, CONF_ID -from .. import nextion_ns, CONF_NEXTION_ID +from esphome.const import ( + CONF_ID, + CONF_STATE, + CONF_COMPONENT_ID, + CONF_PAGE_ID, +) + +from .. import nextion_ns, CONF_NEXTION_ID, CONF_PUBLISH_STATE, CONF_SEND_TO_NEXTION from ..base_component import ( @@ -19,6 +26,10 @@ NextionBinarySensor = nextion_ns.class_( "NextionBinarySensor", binary_sensor.BinarySensor, cg.PollingComponent ) +NextionPublishBoolAction = nextion_ns.class_( + "NextionPublishBoolAction", automation.Action +) + CONFIG_SCHEMA = cv.All( binary_sensor.binary_sensor_schema(NextionBinarySensor) .extend( @@ -52,3 +63,33 @@ async def to_code(config): if CONF_COMPONENT_NAME in config or CONF_VARIABLE_NAME in config: await setup_component_core_(var, config, ".val") cg.add(hub.register_binarysensor_component(var)) + + +@automation.register_action( + "binary_sensor.nextion.publish", + NextionPublishBoolAction, + cv.Schema( + { + cv.Required(CONF_ID): cv.use_id(NextionBinarySensor), + cv.Required(CONF_STATE): cv.templatable(cv.boolean), + cv.Optional(CONF_PUBLISH_STATE, default="true"): cv.templatable(cv.boolean), + cv.Optional(CONF_SEND_TO_NEXTION, default="true"): cv.templatable( + cv.boolean + ), + } + ), +) +async def sensor_nextion_publish_to_code(config, action_id, template_arg, args): + paren = await cg.get_variable(config[CONF_ID]) + var = cg.new_Pvariable(action_id, template_arg, paren) + + template_ = await cg.templatable(config[CONF_STATE], args, bool) + cg.add(var.set_state(template_)) + + template_ = await cg.templatable(config[CONF_PUBLISH_STATE], args, bool) + cg.add(var.set_publish_state(template_)) + + template_ = await cg.templatable(config[CONF_SEND_TO_NEXTION], args, bool) + cg.add(var.set_send_to_nextion(template_)) + + return var diff --git a/esphome/components/nextion/display.py b/esphome/components/nextion/display.py index e403ba7ae8..6f284376af 100644 --- a/esphome/components/nextion/display.py +++ b/esphome/components/nextion/display.py @@ -13,6 +13,7 @@ from esphome.const import ( from esphome.core import CORE from . import Nextion, nextion_ns, nextion_ref from .base_component import ( + CONF_ON_BUFFER_OVERFLOW, CONF_ON_SLEEP, CONF_ON_WAKE, CONF_ON_SETUP, @@ -36,6 +37,9 @@ SleepTrigger = nextion_ns.class_("SleepTrigger", automation.Trigger.template()) WakeTrigger = nextion_ns.class_("WakeTrigger", automation.Trigger.template()) PageTrigger = nextion_ns.class_("PageTrigger", automation.Trigger.template()) TouchTrigger = nextion_ns.class_("TouchTrigger", automation.Trigger.template()) +BufferOverflowTrigger = nextion_ns.class_( + "BufferOverflowTrigger", automation.Trigger.template() +) CONFIG_SCHEMA = ( display.BASIC_DISPLAY_SCHEMA.extend( @@ -68,6 +72,13 @@ CONFIG_SCHEMA = ( cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(TouchTrigger), } ), + cv.Optional(CONF_ON_BUFFER_OVERFLOW): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( + BufferOverflowTrigger + ), + } + ), cv.Optional(CONF_TOUCH_SLEEP_TIMEOUT): cv.int_range(min=3, max=65535), cv.Optional(CONF_WAKE_UP_PAGE): cv.uint8_t, cv.Optional(CONF_START_UP_PAGE): cv.uint8_t, @@ -151,3 +162,7 @@ async def to_code(config): ], conf, ) + + for conf in config.get(CONF_ON_BUFFER_OVERFLOW, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [], conf) diff --git a/esphome/components/nextion/nextion.cpp b/esphome/components/nextion/nextion.cpp index a80f6efc91..50a5834347 100644 --- a/esphome/components/nextion/nextion.cpp +++ b/esphome/components/nextion/nextion.cpp @@ -190,6 +190,10 @@ void Nextion::add_touch_event_callback(std::functiontouch_callback_.add(std::move(callback)); } +void Nextion::add_buffer_overflow_event_callback(std::function &&callback) { + this->buffer_overflow_callback_.add(std::move(callback)); +} + void Nextion::update_all_components() { if ((!this->is_setup() && !this->ignore_is_setup_) || this->is_sleeping()) return; @@ -339,7 +343,7 @@ void Nextion::process_serial_() { } // nextion.tech/instruction-set/ void Nextion::process_nextion_commands_() { - if (this->command_data_.length() == 0) { + if (this->command_data_.empty()) { return; } @@ -458,7 +462,9 @@ void Nextion::process_nextion_commands_() { this->remove_from_q_(); break; case 0x24: // Serial Buffer overflow occurs - ESP_LOGW(TAG, "Nextion reported Serial Buffer overflow!"); + // Buffer will continue to receive the current instruction, all previous instructions are lost. + ESP_LOGE(TAG, "Nextion reported Serial Buffer overflow!"); + this->buffer_overflow_callback_.call(); break; case 0x65: { // touch event return data if (to_process_length != 3) { @@ -557,13 +563,10 @@ void Nextion::process_nextion_commands_() { break; } - int dataindex = 0; - int value = 0; for (int i = 0; i < 4; ++i) { value += to_process[i] << (8 * i); - ++dataindex; } NextionQueue *nb = this->nextion_queue_.front(); diff --git a/esphome/components/nextion/nextion.h b/esphome/components/nextion/nextion.h index 732ee9b455..f539c79718 100644 --- a/esphome/components/nextion/nextion.h +++ b/esphome/components/nextion/nextion.h @@ -1134,6 +1134,12 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe */ void add_touch_event_callback(std::function &&callback); + /** Add a callback to be notified when the nextion reports a buffer overflow. + * + * @param callback The void() callback. + */ + void add_buffer_overflow_event_callback(std::function &&callback); + void update_all_components(); /** @@ -1323,6 +1329,7 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe CallbackManager wake_callback_{}; CallbackManager page_callback_{}; CallbackManager touch_callback_{}; + CallbackManager buffer_overflow_callback_{}; optional writer_; float brightness_{1.0}; diff --git a/esphome/components/nextion/nextion_upload_idf.cpp b/esphome/components/nextion/nextion_upload_idf.cpp index b5bb5478c1..7541a57d56 100644 --- a/esphome/components/nextion/nextion_upload_idf.cpp +++ b/esphome/components/nextion/nextion_upload_idf.cpp @@ -36,8 +36,8 @@ int Nextion::upload_by_chunks_(esp_http_client_handle_t http_client, uint32_t &r ESP_LOGV(TAG, "Requesting range: %s", range_header); esp_http_client_set_header(http_client, "Range", range_header); ESP_LOGV(TAG, "Opening HTTP connetion"); - esp_err_t err; - if ((err = esp_http_client_open(http_client, 0)) != ESP_OK) { + esp_err_t err = esp_http_client_open(http_client, 0); + if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to open HTTP connection: %s", esp_err_to_name(err)); return -1; } diff --git a/esphome/components/nextion/sensor/__init__.py b/esphome/components/nextion/sensor/__init__.py index eefbe34d58..1058c2a04b 100644 --- a/esphome/components/nextion/sensor/__init__.py +++ b/esphome/components/nextion/sensor/__init__.py @@ -1,12 +1,11 @@ +from esphome import automation import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import sensor -from esphome.const import ( - CONF_ID, - CONF_COMPONENT_ID, -) -from .. import nextion_ns, CONF_NEXTION_ID +from esphome.const import CONF_ID, CONF_COMPONENT_ID, CONF_STATE + +from .. import nextion_ns, CONF_NEXTION_ID, CONF_PUBLISH_STATE, CONF_SEND_TO_NEXTION from ..base_component import ( setup_component_core_, @@ -25,6 +24,10 @@ CODEOWNERS = ["@senexcrenshaw"] NextionSensor = nextion_ns.class_("NextionSensor", sensor.Sensor, cg.PollingComponent) +NextionPublishFloatAction = nextion_ns.class_( + "NextionPublishFloatAction", automation.Action +) + def CheckWaveID(value): value = cv.int_(value) @@ -95,3 +98,33 @@ async def to_code(config): if CONF_WAVE_MAX_LENGTH in config: cg.add(var.set_wave_max_length(config[CONF_WAVE_MAX_LENGTH])) + + +@automation.register_action( + "sensor.nextion.publish", + NextionPublishFloatAction, + cv.Schema( + { + cv.Required(CONF_ID): cv.use_id(NextionSensor), + cv.Required(CONF_STATE): cv.templatable(cv.float_), + cv.Optional(CONF_PUBLISH_STATE, default="true"): cv.templatable(cv.boolean), + cv.Optional(CONF_SEND_TO_NEXTION, default="true"): cv.templatable( + cv.boolean + ), + } + ), +) +async def sensor_nextion_publish_to_code(config, action_id, template_arg, args): + paren = await cg.get_variable(config[CONF_ID]) + var = cg.new_Pvariable(action_id, template_arg, paren) + + template_ = await cg.templatable(config[CONF_STATE], args, float) + cg.add(var.set_state(template_)) + + template_ = await cg.templatable(config[CONF_PUBLISH_STATE], args, bool) + cg.add(var.set_publish_state(template_)) + + template_ = await cg.templatable(config[CONF_SEND_TO_NEXTION], args, bool) + cg.add(var.set_send_to_nextion(template_)) + + return var diff --git a/esphome/components/nextion/switch/__init__.py b/esphome/components/nextion/switch/__init__.py index 91ab0cc81f..de1a061478 100644 --- a/esphome/components/nextion/switch/__init__.py +++ b/esphome/components/nextion/switch/__init__.py @@ -1,9 +1,11 @@ +from esphome import automation import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import switch -from esphome.const import CONF_ID -from .. import nextion_ns, CONF_NEXTION_ID +from esphome.const import CONF_ID, CONF_STATE + +from .. import nextion_ns, CONF_NEXTION_ID, CONF_PUBLISH_STATE, CONF_SEND_TO_NEXTION from ..base_component import ( setup_component_core_, @@ -16,6 +18,10 @@ CODEOWNERS = ["@senexcrenshaw"] NextionSwitch = nextion_ns.class_("NextionSwitch", switch.Switch, cg.PollingComponent) +NextionPublishBoolAction = nextion_ns.class_( + "NextionPublishBoolAction", automation.Action +) + CONFIG_SCHEMA = cv.All( switch.switch_schema(NextionSwitch) .extend(CONFIG_SWITCH_COMPONENT_SCHEMA) @@ -33,3 +39,33 @@ async def to_code(config): cg.add(hub.register_switch_component(var)) await setup_component_core_(var, config, ".val") + + +@automation.register_action( + "switch.nextion.publish", + NextionPublishBoolAction, + cv.Schema( + { + cv.Required(CONF_ID): cv.use_id(NextionSwitch), + cv.Required(CONF_STATE): cv.templatable(cv.boolean), + cv.Optional(CONF_PUBLISH_STATE, default="true"): cv.templatable(cv.boolean), + cv.Optional(CONF_SEND_TO_NEXTION, default="true"): cv.templatable( + cv.boolean + ), + } + ), +) +async def sensor_nextion_publish_to_code(config, action_id, template_arg, args): + paren = await cg.get_variable(config[CONF_ID]) + var = cg.new_Pvariable(action_id, template_arg, paren) + + template_ = await cg.templatable(config[CONF_STATE], args, bool) + cg.add(var.set_state(template_)) + + template_ = await cg.templatable(config[CONF_PUBLISH_STATE], args, bool) + cg.add(var.set_publish_state(template_)) + + template_ = await cg.templatable(config[CONF_SEND_TO_NEXTION], args, bool) + cg.add(var.set_send_to_nextion(template_)) + + return var diff --git a/esphome/components/nextion/text_sensor/__init__.py b/esphome/components/nextion/text_sensor/__init__.py index 826ff2354e..793397b1f4 100644 --- a/esphome/components/nextion/text_sensor/__init__.py +++ b/esphome/components/nextion/text_sensor/__init__.py @@ -1,9 +1,10 @@ +from esphome import automation from esphome.components import text_sensor import esphome.config_validation as cv import esphome.codegen as cg -from esphome.const import CONF_ID +from esphome.const import CONF_ID, CONF_STATE -from .. import nextion_ns, CONF_NEXTION_ID +from .. import nextion_ns, CONF_NEXTION_ID, CONF_PUBLISH_STATE, CONF_SEND_TO_NEXTION from ..base_component import ( setup_component_core_, @@ -16,6 +17,10 @@ NextionTextSensor = nextion_ns.class_( "NextionTextSensor", text_sensor.TextSensor, cg.PollingComponent ) +NextionPublishTextAction = nextion_ns.class_( + "NextionPublishTextAction", automation.Action +) + CONFIG_SCHEMA = ( text_sensor.text_sensor_schema(NextionTextSensor) .extend(CONFIG_TEXT_COMPONENT_SCHEMA) @@ -32,3 +37,33 @@ async def to_code(config): cg.add(hub.register_textsensor_component(var)) await setup_component_core_(var, config, ".txt") + + +@automation.register_action( + "text_sensor.nextion.publish", + NextionPublishTextAction, + cv.Schema( + { + cv.Required(CONF_ID): cv.use_id(NextionTextSensor), + cv.Required(CONF_STATE): cv.templatable(cv.string_strict), + cv.Optional(CONF_PUBLISH_STATE, default="true"): cv.templatable(cv.boolean), + cv.Optional(CONF_SEND_TO_NEXTION, default="true"): cv.templatable( + cv.boolean + ), + } + ), +) +async def sensor_nextion_publish_to_code(config, action_id, template_arg, args): + paren = await cg.get_variable(config[CONF_ID]) + var = cg.new_Pvariable(action_id, template_arg, paren) + + template_ = await cg.templatable(config[CONF_STATE], args, cg.const_char_ptr) + cg.add(var.set_state(template_)) + + template_ = await cg.templatable(config[CONF_PUBLISH_STATE], args, cg.bool_) + cg.add(var.set_publish_state(template_)) + + template_ = await cg.templatable(config[CONF_SEND_TO_NEXTION], args, cg.bool_) + cg.add(var.set_send_to_nextion(template_)) + + return var diff --git a/esphome/components/nfc/ndef_record.cpp b/esphome/components/nfc/ndef_record.cpp index 8eb0c3b901..540ba62940 100644 --- a/esphome/components/nfc/ndef_record.cpp +++ b/esphome/components/nfc/ndef_record.cpp @@ -30,13 +30,13 @@ std::vector NdefRecord::encode(bool first, bool last) { data.push_back(payload_length & 0xFF); } - if (this->id_.length()) { + if (!this->id_.empty()) { data.push_back(this->id_.length()); } data.insert(data.end(), this->type_.begin(), this->type_.end()); - if (this->id_.length()) { + if (!this->id_.empty()) { data.insert(data.end(), this->id_.begin(), this->id_.end()); } @@ -55,7 +55,7 @@ uint8_t NdefRecord::create_flag_byte(bool first, bool last, size_t payload_size) if (payload_size <= 255) { value = value | 0x10; // Set SR bit } - if (this->id_.length()) { + if (!this->id_.empty()) { value = value | 0x08; // Set IL bit } return value; diff --git a/esphome/components/online_image/__init__.py b/esphome/components/online_image/__init__.py index dfb10137aa..be1bfb4a00 100644 --- a/esphome/components/online_image/__init__.py +++ b/esphome/components/online_image/__init__.py @@ -98,6 +98,7 @@ CONFIG_SCHEMA = cv.Schema( # esp8266_arduino=cv.Version(2, 7, 0), esp32_arduino=cv.Version(0, 0, 0), esp_idf=cv.Version(4, 0, 0), + rp2040_arduino=cv.Version(0, 0, 0), ), ) ) diff --git a/esphome/components/opentherm/opentherm.cpp b/esphome/components/opentherm/opentherm.cpp index c56b49ccb8..62ab1d3860 100644 --- a/esphome/components/opentherm/opentherm.cpp +++ b/esphome/components/opentherm/opentherm.cpp @@ -25,7 +25,7 @@ using std::to_string; static const char *const TAG = "opentherm"; #ifdef ESP8266 -OpenTherm *OpenTherm::instance_ = nullptr; +OpenTherm *OpenTherm::instance = nullptr; #endif OpenTherm::OpenTherm(InternalGPIOPin *in_pin, InternalGPIOPin *out_pin, int32_t device_timeout) @@ -49,7 +49,7 @@ OpenTherm::OpenTherm(InternalGPIOPin *in_pin, InternalGPIOPin *out_pin, int32_t bool OpenTherm::initialize() { #ifdef ESP8266 - OpenTherm::instance_ = this; + OpenTherm::instance = this; #endif this->in_pin_->pin_mode(gpio::FLAG_INPUT); this->out_pin_->pin_mode(gpio::FLAG_OUTPUT); @@ -212,7 +212,7 @@ bool IRAM_ATTR OpenTherm::timer_isr(OpenTherm *arg) { } #ifdef ESP8266 -void IRAM_ATTR OpenTherm::esp8266_timer_isr() { OpenTherm::timer_isr(OpenTherm::instance_); } +void IRAM_ATTR OpenTherm::esp8266_timer_isr() { OpenTherm::timer_isr(OpenTherm::instance); } #endif void IRAM_ATTR OpenTherm::bit_read_(uint8_t value) { diff --git a/esphome/components/opentherm/opentherm.h b/esphome/components/opentherm/opentherm.h index 76710af5f5..3be0191c63 100644 --- a/esphome/components/opentherm/opentherm.h +++ b/esphome/components/opentherm/opentherm.h @@ -370,7 +370,7 @@ class OpenTherm { #ifdef ESP8266 // ESP8266 timer can accept callback with no parameters, so we have this hack to save a static instance of OpenTherm - static OpenTherm *instance_; + static OpenTherm *instance; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) #endif }; diff --git a/esphome/components/ota/automation.h b/esphome/components/ota/automation.h index 4605193480..7e1a60f3ce 100644 --- a/esphome/components/ota/automation.h +++ b/esphome/components/ota/automation.h @@ -12,7 +12,7 @@ class OTAStateChangeTrigger : public Trigger { explicit OTAStateChangeTrigger(OTAComponent *parent) { parent->add_on_state_callback([this, parent](OTAState state, float progress, uint8_t error) { if (!parent->is_failed()) { - return trigger(state); + trigger(state); } }); } diff --git a/esphome/components/output/float_output.cpp b/esphome/components/output/float_output.cpp index f120f86f1f..e7dba1d81d 100644 --- a/esphome/components/output/float_output.cpp +++ b/esphome/components/output/float_output.cpp @@ -32,7 +32,7 @@ void FloatOutput::set_level(float state) { } #endif - if (!(state == 0.0f && this->zero_means_zero_)) // regardless of min_power_, 0.0 means off + if (state != 0.0f || !this->zero_means_zero_) // regardless of min_power_, 0.0 means off state = (state * (this->max_power_ - this->min_power_)) + this->min_power_; if (this->is_inverted()) diff --git a/esphome/components/pca6416a/pca6416a.cpp b/esphome/components/pca6416a/pca6416a.cpp index 1f4e315644..53c0dcaf76 100644 --- a/esphome/components/pca6416a/pca6416a.cpp +++ b/esphome/components/pca6416a/pca6416a.cpp @@ -34,7 +34,7 @@ void PCA6416AComponent::setup() { } // Test to see if the device supports pull-up resistors - if (this->read_register(PCAL6416A_PULL_EN0, &value, 1, true) == esphome::i2c::ERROR_OK) { + if (this->read_register(PCAL6416A_PULL_EN0, &value, 1, true) == i2c::ERROR_OK) { this->has_pullup_ = true; } @@ -106,7 +106,8 @@ bool PCA6416AComponent::read_register_(uint8_t reg, uint8_t *value) { return false; } - if ((this->last_error_ = this->read_register(reg, value, 1, true)) != esphome::i2c::ERROR_OK) { + this->last_error_ = this->read_register(reg, value, 1, true); + if (this->last_error_ != i2c::ERROR_OK) { this->status_set_warning(); ESP_LOGE(TAG, "read_register_(): I2C I/O error: %d", (int) this->last_error_); return false; @@ -122,7 +123,8 @@ bool PCA6416AComponent::write_register_(uint8_t reg, uint8_t value) { return false; } - if ((this->last_error_ = this->write_register(reg, &value, 1, true)) != esphome::i2c::ERROR_OK) { + this->last_error_ = this->write_register(reg, &value, 1, true); + if (this->last_error_ != i2c::ERROR_OK) { this->status_set_warning(); ESP_LOGE(TAG, "write_register_(): I2C I/O error: %d", (int) this->last_error_); return false; diff --git a/esphome/components/pca9554/pca9554.cpp b/esphome/components/pca9554/pca9554.cpp index c5a4bcfb09..78b877072a 100644 --- a/esphome/components/pca9554/pca9554.cpp +++ b/esphome/components/pca9554/pca9554.cpp @@ -95,8 +95,8 @@ bool PCA9554Component::read_inputs_() { return false; } - if ((this->last_error_ = this->read_register(INPUT_REG * this->reg_width_, inputs, this->reg_width_, true)) != - esphome::i2c::ERROR_OK) { + this->last_error_ = this->read_register(INPUT_REG * this->reg_width_, inputs, this->reg_width_, true); + if (this->last_error_ != i2c::ERROR_OK) { this->status_set_warning(); ESP_LOGE(TAG, "read_register_(): I2C I/O error: %d", (int) this->last_error_); return false; @@ -113,8 +113,8 @@ bool PCA9554Component::write_register_(uint8_t reg, uint16_t value) { uint8_t outputs[2]; outputs[0] = (uint8_t) value; outputs[1] = (uint8_t) (value >> 8); - if ((this->last_error_ = this->write_register(reg * this->reg_width_, outputs, this->reg_width_, true)) != - esphome::i2c::ERROR_OK) { + this->last_error_ = this->write_register(reg * this->reg_width_, outputs, this->reg_width_, true); + if (this->last_error_ != i2c::ERROR_OK) { this->status_set_warning(); ESP_LOGE(TAG, "write_register_(): I2C I/O error: %d", (int) this->last_error_); return false; diff --git a/esphome/components/pipsolar/pipsolar.cpp b/esphome/components/pipsolar/pipsolar.cpp index c4bc018b75..03c699e7d4 100644 --- a/esphome/components/pipsolar/pipsolar.cpp +++ b/esphome/components/pipsolar/pipsolar.cpp @@ -790,7 +790,7 @@ uint8_t Pipsolar::check_incoming_crc_() { // send next command used uint8_t Pipsolar::send_next_command_() { uint16_t crc16; - if (this->command_queue_[this->command_queue_position_].length() != 0) { + if (!this->command_queue_[this->command_queue_position_].empty()) { const char *command = this->command_queue_[this->command_queue_position_].c_str(); uint8_t byte_command[16]; uint8_t length = this->command_queue_[this->command_queue_position_].length(); @@ -846,7 +846,7 @@ void Pipsolar::queue_command_(const char *command, uint8_t length) { uint8_t next_position = command_queue_position_; for (uint8_t i = 0; i < COMMAND_QUEUE_LENGTH; i++) { uint8_t testposition = (next_position + i) % COMMAND_QUEUE_LENGTH; - if (command_queue_[testposition].length() == 0) { + if (command_queue_[testposition].empty()) { command_queue_[testposition] = command; ESP_LOGD(TAG, "Command queued successfully: %s with length %u at position %d", command, command_queue_[testposition].length(), testposition); diff --git a/esphome/components/pipsolar/switch/pipsolar_switch.cpp b/esphome/components/pipsolar/switch/pipsolar_switch.cpp index 7eaeac1c2d..be7763226b 100644 --- a/esphome/components/pipsolar/switch/pipsolar_switch.cpp +++ b/esphome/components/pipsolar/switch/pipsolar_switch.cpp @@ -10,11 +10,11 @@ static const char *const TAG = "pipsolar.switch"; void PipsolarSwitch::dump_config() { LOG_SWITCH("", "Pipsolar Switch", this); } void PipsolarSwitch::write_state(bool state) { if (state) { - if (this->on_command_.length() > 0) { + if (!this->on_command_.empty()) { this->parent_->switch_command(this->on_command_); } } else { - if (this->off_command_.length() > 0) { + if (!this->off_command_.empty()) { this->parent_->switch_command(this->off_command_); } } diff --git a/esphome/components/pn532/pn532_mifare_ultralight.cpp b/esphome/components/pn532/pn532_mifare_ultralight.cpp index b08a7336c7..f823829a6c 100644 --- a/esphome/components/pn532/pn532_mifare_ultralight.cpp +++ b/esphome/components/pn532/pn532_mifare_ultralight.cpp @@ -80,8 +80,8 @@ bool PN532::is_mifare_ultralight_formatted_(const std::vector &page_3_t const uint8_t p4_offset = nfc::MIFARE_ULTRALIGHT_PAGE_SIZE; // page 4 will begin 4 bytes into the vector return (page_3_to_6.size() > p4_offset + 3) && - !((page_3_to_6[p4_offset + 0] == 0xFF) && (page_3_to_6[p4_offset + 1] == 0xFF) && - (page_3_to_6[p4_offset + 2] == 0xFF) && (page_3_to_6[p4_offset + 3] == 0xFF)); + ((page_3_to_6[p4_offset + 0] != 0xFF) || (page_3_to_6[p4_offset + 1] != 0xFF) || + (page_3_to_6[p4_offset + 2] != 0xFF) || (page_3_to_6[p4_offset + 3] != 0xFF)); } uint16_t PN532::read_mifare_ultralight_capacity_() { diff --git a/esphome/components/pn7150/pn7150_mifare_ultralight.cpp b/esphome/components/pn7150/pn7150_mifare_ultralight.cpp index 791b0634d6..b107f6f79e 100644 --- a/esphome/components/pn7150/pn7150_mifare_ultralight.cpp +++ b/esphome/components/pn7150/pn7150_mifare_ultralight.cpp @@ -81,8 +81,8 @@ bool PN7150::is_mifare_ultralight_formatted_(const std::vector &page_3_ const uint8_t p4_offset = nfc::MIFARE_ULTRALIGHT_PAGE_SIZE; // page 4 will begin 4 bytes into the vector return (page_3_to_6.size() > p4_offset + 3) && - !((page_3_to_6[p4_offset + 0] == 0xFF) && (page_3_to_6[p4_offset + 1] == 0xFF) && - (page_3_to_6[p4_offset + 2] == 0xFF) && (page_3_to_6[p4_offset + 3] == 0xFF)); + ((page_3_to_6[p4_offset + 0] != 0xFF) || (page_3_to_6[p4_offset + 1] != 0xFF) || + (page_3_to_6[p4_offset + 2] != 0xFF) || (page_3_to_6[p4_offset + 3] != 0xFF)); } uint16_t PN7150::read_mifare_ultralight_capacity_() { diff --git a/esphome/components/pn7160/pn7160_mifare_ultralight.cpp b/esphome/components/pn7160/pn7160_mifare_ultralight.cpp index a74f23d4f2..65daac494f 100644 --- a/esphome/components/pn7160/pn7160_mifare_ultralight.cpp +++ b/esphome/components/pn7160/pn7160_mifare_ultralight.cpp @@ -81,8 +81,8 @@ bool PN7160::is_mifare_ultralight_formatted_(const std::vector &page_3_ const uint8_t p4_offset = nfc::MIFARE_ULTRALIGHT_PAGE_SIZE; // page 4 will begin 4 bytes into the vector return (page_3_to_6.size() > p4_offset + 3) && - !((page_3_to_6[p4_offset + 0] == 0xFF) && (page_3_to_6[p4_offset + 1] == 0xFF) && - (page_3_to_6[p4_offset + 2] == 0xFF) && (page_3_to_6[p4_offset + 3] == 0xFF)); + ((page_3_to_6[p4_offset + 0] != 0xFF) || (page_3_to_6[p4_offset + 1] != 0xFF) || + (page_3_to_6[p4_offset + 2] != 0xFF) || (page_3_to_6[p4_offset + 3] != 0xFF)); } uint16_t PN7160::read_mifare_ultralight_capacity_() { diff --git a/esphome/components/qmc5883l/qmc5883l.cpp b/esphome/components/qmc5883l/qmc5883l.cpp index 49a67d4e09..36286244fb 100644 --- a/esphome/components/qmc5883l/qmc5883l.cpp +++ b/esphome/components/qmc5883l/qmc5883l.cpp @@ -81,16 +81,39 @@ void QMC5883LComponent::dump_config() { } float QMC5883LComponent::get_setup_priority() const { return setup_priority::DATA; } void QMC5883LComponent::update() { + i2c::ErrorCode err; uint8_t status = false; - this->read_byte(QMC5883L_REGISTER_STATUS, &status); + // Status byte gets cleared when data is read, so we have to read this first. + // If status and two axes are desired, it's possible to save one byte of traffic by enabling + // ROL_PNT in setup and reading 7 bytes starting at the status register. + // If status and all three axes are desired, using ROL_PNT saves you 3 bytes. + // But simply not reading status saves you 4 bytes always and is much simpler. + if (ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_DEBUG) { + err = this->read_register(QMC5883L_REGISTER_STATUS, &status, 1); + if (err != i2c::ERROR_OK) { + this->status_set_warning(str_sprintf("status read failed (%d)", err).c_str()); + return; + } + } - // Always request X,Y,Z regardless if there are sensors for them - // to avoid https://github.com/esphome/issues/issues/5731 - uint16_t raw_x, raw_y, raw_z; - if (!this->read_byte_16_(QMC5883L_REGISTER_DATA_X_LSB, &raw_x) || - !this->read_byte_16_(QMC5883L_REGISTER_DATA_Y_LSB, &raw_y) || - !this->read_byte_16_(QMC5883L_REGISTER_DATA_Z_LSB, &raw_z)) { - this->status_set_warning(); + uint16_t raw[3] = {0}; + // Z must always be requested, otherwise the data registers will remain locked against updates. + // Skipping the Y axis if X and Z are needed actually requires an additional byte of comms. + // Starting partway through the axes does save you traffic. + uint8_t start, dest; + if (this->heading_sensor_ != nullptr || this->x_sensor_ != nullptr) { + start = QMC5883L_REGISTER_DATA_X_LSB; + dest = 0; + } else if (this->y_sensor_ != nullptr) { + start = QMC5883L_REGISTER_DATA_Y_LSB; + dest = 1; + } else { + start = QMC5883L_REGISTER_DATA_Z_LSB; + dest = 2; + } + err = this->read_bytes_16_le_(start, &raw[dest], 3 - dest); + if (err != i2c::ERROR_OK) { + this->status_set_warning(str_sprintf("mag read failed (%d)", err).c_str()); return; } @@ -107,17 +130,18 @@ void QMC5883LComponent::update() { } // in µT - const float x = int16_t(raw_x) * mg_per_bit * 0.1f; - const float y = int16_t(raw_y) * mg_per_bit * 0.1f; - const float z = int16_t(raw_z) * mg_per_bit * 0.1f; + const float x = int16_t(raw[0]) * mg_per_bit * 0.1f; + const float y = int16_t(raw[1]) * mg_per_bit * 0.1f; + const float z = int16_t(raw[2]) * mg_per_bit * 0.1f; float heading = atan2f(0.0f - x, y) * 180.0f / M_PI; float temp = NAN; if (this->temperature_sensor_ != nullptr) { uint16_t raw_temp; - if (!this->read_byte_16_(QMC5883L_REGISTER_TEMPERATURE_LSB, &raw_temp)) { - this->status_set_warning(); + err = this->read_bytes_16_le_(QMC5883L_REGISTER_TEMPERATURE_LSB, &raw_temp); + if (err != i2c::ERROR_OK) { + this->status_set_warning(str_sprintf("temp read failed (%d)", err).c_str()); return; } temp = int16_t(raw_temp) * 0.01f; @@ -138,11 +162,13 @@ void QMC5883LComponent::update() { this->temperature_sensor_->publish_state(temp); } -bool QMC5883LComponent::read_byte_16_(uint8_t a_register, uint16_t *data) { - if (!this->read_byte_16(a_register, data)) - return false; - *data = (*data & 0x00FF) << 8 | (*data & 0xFF00) >> 8; // Flip Byte order, LSB first; - return true; +i2c::ErrorCode QMC5883LComponent::read_bytes_16_le_(uint8_t a_register, uint16_t *data, uint8_t len) { + i2c::ErrorCode err = this->read_register(a_register, reinterpret_cast(data), len * 2); + if (err != i2c::ERROR_OK) + return err; + for (size_t i = 0; i < len; i++) + data[i] = convert_little_endian(data[i]); + return err; } } // namespace qmc5883l diff --git a/esphome/components/qmc5883l/qmc5883l.h b/esphome/components/qmc5883l/qmc5883l.h index dd2008d453..3202e37780 100644 --- a/esphome/components/qmc5883l/qmc5883l.h +++ b/esphome/components/qmc5883l/qmc5883l.h @@ -55,7 +55,7 @@ class QMC5883LComponent : public PollingComponent, public i2c::I2CDevice { NONE = 0, COMMUNICATION_FAILED, } error_code_; - bool read_byte_16_(uint8_t a_register, uint16_t *data); + i2c::ErrorCode read_bytes_16_le_(uint8_t a_register, uint16_t *data, uint8_t len = 1); HighFrequencyLoopRequester high_freq_; }; diff --git a/esphome/components/qspi_dbi/qspi_dbi.cpp b/esphome/components/qspi_dbi/qspi_dbi.cpp index 785885d4ec..f8fd5dd374 100644 --- a/esphome/components/qspi_dbi/qspi_dbi.cpp +++ b/esphome/components/qspi_dbi/qspi_dbi.cpp @@ -146,7 +146,8 @@ void QspiDbi::draw_pixels_at(int x_start, int y_start, int w, int h, const uint8 return; if (bitness != display::COLOR_BITNESS_565 || order != this->color_mode_ || big_endian != (this->bit_order_ == spi::BIT_ORDER_MSB_FIRST)) { - return Display::draw_pixels_at(x_start, y_start, w, h, ptr, order, bitness, big_endian, x_offset, y_offset, x_pad); + Display::draw_pixels_at(x_start, y_start, w, h, ptr, order, bitness, big_endian, x_offset, y_offset, x_pad); + return; } else if (this->draw_from_origin_) { auto stride = x_offset + w + x_pad; for (int y = 0; y != h; y++) { diff --git a/esphome/components/remote_base/raw_protocol.cpp b/esphome/components/remote_base/raw_protocol.cpp index bdeb935dc4..ef0cb8454e 100644 --- a/esphome/components/remote_base/raw_protocol.cpp +++ b/esphome/components/remote_base/raw_protocol.cpp @@ -28,7 +28,7 @@ bool RawDumper::dump(RemoteReceiveData src) { ESP_LOGI(TAG, "%s", buffer); buffer_offset = 0; written = sprintf(buffer, " "); - if (i + 1 < src.size()) { + if (i + 1 < src.size() - 1) { written += sprintf(buffer + written, "%" PRId32 ", ", value); } else { written += sprintf(buffer + written, "%" PRId32, value); diff --git a/esphome/components/remote_receiver/__init__.py b/esphome/components/remote_receiver/__init__.py index e5085bb33c..d3f61977c6 100644 --- a/esphome/components/remote_receiver/__init__.py +++ b/esphome/components/remote_receiver/__init__.py @@ -1,24 +1,23 @@ -import esphome.codegen as cg -import esphome.config_validation as cv from esphome import pins -from esphome.components import remote_base, esp32_rmt +import esphome.codegen as cg +from esphome.components import esp32_rmt, remote_base +import esphome.config_validation as cv from esphome.const import ( CONF_BUFFER_SIZE, + CONF_CLOCK_DIVIDER, CONF_DUMP, CONF_FILTER, CONF_ID, CONF_IDLE, + CONF_MEMORY_BLOCKS, CONF_PIN, + CONF_RMT_CHANNEL, CONF_TOLERANCE, CONF_TYPE, - CONF_MEMORY_BLOCKS, - CONF_RMT_CHANNEL, CONF_VALUE, ) from esphome.core import CORE, TimePeriod -CONF_CLOCK_DIVIDER = "clock_divider" - AUTO_LOAD = ["remote_base"] remote_receiver_ns = cg.esphome_ns.namespace("remote_receiver") remote_base_ns = cg.esphome_ns.namespace("remote_base") diff --git a/esphome/components/rotary_encoder/rotary_encoder.cpp b/esphome/components/rotary_encoder/rotary_encoder.cpp index a3631ffe27..e9a0eac3f5 100644 --- a/esphome/components/rotary_encoder/rotary_encoder.cpp +++ b/esphome/components/rotary_encoder/rotary_encoder.cpp @@ -162,7 +162,7 @@ void RotaryEncoderSensor::dump_config() { LOG_PIN(" Pin B: ", this->pin_b_); LOG_PIN(" Pin I: ", this->pin_i_); - const LogString *restore_mode = LOG_STR(""); + const LogString *restore_mode; switch (this->restore_mode_) { case ROTARY_ENCODER_RESTORE_DEFAULT_ZERO: restore_mode = LOG_STR("Restore (Defaults to zero)"); @@ -170,6 +170,8 @@ void RotaryEncoderSensor::dump_config() { case ROTARY_ENCODER_ALWAYS_ZERO: restore_mode = LOG_STR("Always zero"); break; + default: + restore_mode = LOG_STR(""); } ESP_LOGCONFIG(TAG, " Restore Mode: %s", LOG_STR_ARG(restore_mode)); diff --git a/esphome/components/rp2040/__init__.py b/esphome/components/rp2040/__init__.py index d612631a4c..b04e539182 100644 --- a/esphome/components/rp2040/__init__.py +++ b/esphome/components/rp2040/__init__.py @@ -17,7 +17,7 @@ from esphome.const import ( PLATFORM_RP2040, ) from esphome.core import CORE, EsphomeError, coroutine_with_priority -from esphome.helpers import copy_file_if_changed, mkdir_p, write_file, read_file +from esphome.helpers import copy_file_if_changed, mkdir_p, read_file, write_file from .const import KEY_BOARD, KEY_PIO_FILES, KEY_RP2040, rp2040_ns diff --git a/esphome/components/rtl87xx/__init__.py b/esphome/components/rtl87xx/__init__.py index 9060a7c4a6..4c1956f0f4 100644 --- a/esphome/components/rtl87xx/__init__.py +++ b/esphome/components/rtl87xx/__init__.py @@ -15,7 +15,7 @@ from esphome.components.libretiny.const import ( ) from esphome.core import CORE -from .boards import RTL87XX_BOARDS, RTL87XX_BOARD_PINS +from .boards import RTL87XX_BOARD_PINS, RTL87XX_BOARDS CODEOWNERS = ["@kuba2k2"] AUTO_LOAD = ["libretiny"] diff --git a/esphome/components/safe_mode/automation.h b/esphome/components/safe_mode/automation.h index d1388449ee..1ffa86a588 100644 --- a/esphome/components/safe_mode/automation.h +++ b/esphome/components/safe_mode/automation.h @@ -9,7 +9,7 @@ namespace safe_mode { class SafeModeTrigger : public Trigger<> { public: explicit SafeModeTrigger(SafeModeComponent *parent) { - parent->add_on_safe_mode_callback([this, parent]() { trigger(); }); + parent->add_on_safe_mode_callback([this]() { trigger(); }); } }; diff --git a/esphome/components/seeed_mr60bha2/__init__.py b/esphome/components/seeed_mr60bha2/__init__.py new file mode 100644 index 0000000000..87bdbbd003 --- /dev/null +++ b/esphome/components/seeed_mr60bha2/__init__.py @@ -0,0 +1,41 @@ +import esphome.codegen as cg +from esphome.components import uart +import esphome.config_validation as cv +from esphome.const import CONF_ID + +CODEOWNERS = ["@limengdu"] +DEPENDENCIES = ["uart"] +MULTI_CONF = True + +mr60bha2_ns = cg.esphome_ns.namespace("seeed_mr60bha2") + +MR60BHA2Component = mr60bha2_ns.class_( + "MR60BHA2Component", cg.Component, uart.UARTDevice +) + +CONF_MR60BHA2_ID = "mr60bha2_id" + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(MR60BHA2Component), + } + ) + .extend(uart.UART_DEVICE_SCHEMA) + .extend(cv.COMPONENT_SCHEMA) +) + +FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema( + "seeed_mr60bha2", + require_tx=True, + require_rx=True, + baud_rate=115200, + parity="NONE", + stop_bits=1, +) + + +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) diff --git a/esphome/components/seeed_mr60bha2/seeed_mr60bha2.cpp b/esphome/components/seeed_mr60bha2/seeed_mr60bha2.cpp new file mode 100644 index 0000000000..50d709c3b0 --- /dev/null +++ b/esphome/components/seeed_mr60bha2/seeed_mr60bha2.cpp @@ -0,0 +1,173 @@ +#include "seeed_mr60bha2.h" +#include "esphome/core/log.h" + +#include + +namespace esphome { +namespace seeed_mr60bha2 { + +static const char *const TAG = "seeed_mr60bha2"; + +// Prints the component's configuration data. dump_config() prints all of the component's configuration +// items in an easy-to-read format, including the configuration key-value pairs. +void MR60BHA2Component::dump_config() { + ESP_LOGCONFIG(TAG, "MR60BHA2:"); +#ifdef USE_SENSOR + LOG_SENSOR(" ", "Breath Rate Sensor", this->breath_rate_sensor_); + LOG_SENSOR(" ", "Heart Rate Sensor", this->heart_rate_sensor_); + LOG_SENSOR(" ", "Distance Sensor", this->distance_sensor_); +#endif +} + +// main loop +void MR60BHA2Component::loop() { + uint8_t byte; + + // Is there data on the serial port + while (this->available()) { + this->read_byte(&byte); + this->rx_message_.push_back(byte); + if (!this->validate_message_()) { + this->rx_message_.clear(); + } + } +} + +/** + * @brief Calculate the checksum for a byte array. + * + * This function calculates the checksum for the provided byte array using an + * XOR-based checksum algorithm. + * + * @param data The byte array to calculate the checksum for. + * @param len The length of the byte array. + * @return The calculated checksum. + */ +static uint8_t calculate_checksum(const uint8_t *data, size_t len) { + uint8_t checksum = 0; + for (size_t i = 0; i < len; i++) { + checksum ^= data[i]; + } + checksum = ~checksum; + return checksum; +} + +/** + * @brief Validate the checksum of a byte array. + * + * This function validates the checksum of the provided byte array by comparing + * it to the expected checksum. + * + * @param data The byte array to validate. + * @param len The length of the byte array. + * @param expected_checksum The expected checksum. + * @return True if the checksum is valid, false otherwise. + */ +static bool validate_checksum(const uint8_t *data, size_t len, uint8_t expected_checksum) { + return calculate_checksum(data, len) == expected_checksum; +} + +bool MR60BHA2Component::validate_message_() { + size_t at = this->rx_message_.size() - 1; + auto *data = &this->rx_message_[0]; + uint8_t new_byte = data[at]; + + if (at == 0) { + return new_byte == FRAME_HEADER_BUFFER; + } + + if (at <= 2) { + return true; + } + uint16_t frame_id = encode_uint16(data[1], data[2]); + + if (at <= 4) { + return true; + } + + uint16_t length = encode_uint16(data[3], data[4]); + + if (at <= 6) { + return true; + } + + uint16_t frame_type = encode_uint16(data[5], data[6]); + + if (frame_type != BREATH_RATE_TYPE_BUFFER && frame_type != HEART_RATE_TYPE_BUFFER && + frame_type != DISTANCE_TYPE_BUFFER) { + return false; + } + + uint8_t header_checksum = new_byte; + + if (at == 7) { + if (!validate_checksum(data, 7, header_checksum)) { + ESP_LOGE(TAG, "HEAD_CKSUM_FRAME ERROR: 0x%02x", header_checksum); + ESP_LOGV(TAG, "GET FRAME: %s", format_hex_pretty(data, 8).c_str()); + return false; + } + return true; + } + + // Wait until all data is read + if (at - 8 < length) { + return true; + } + + uint8_t data_checksum = new_byte; + if (at == 8 + length) { + if (!validate_checksum(data + 8, length, data_checksum)) { + ESP_LOGE(TAG, "DATA_CKSUM_FRAME ERROR: 0x%02x", data_checksum); + ESP_LOGV(TAG, "GET FRAME: %s", format_hex_pretty(data, 8 + length).c_str()); + return false; + } + } + + const uint8_t *frame_data = data + 8; + ESP_LOGV(TAG, "Received Frame: ID: 0x%04x, Type: 0x%04x, Data: [%s] Raw Data: [%s]", frame_id, frame_type, + format_hex_pretty(frame_data, length).c_str(), format_hex_pretty(this->rx_message_).c_str()); + this->process_frame_(frame_id, frame_type, data + 8, length); + + // Return false to reset rx buffer + return false; +} + +void MR60BHA2Component::process_frame_(uint16_t frame_id, uint16_t frame_type, const uint8_t *data, size_t length) { + switch (frame_type) { + case BREATH_RATE_TYPE_BUFFER: + if (this->breath_rate_sensor_ != nullptr && length >= 4) { + uint32_t current_breath_rate_int = encode_uint32(data[3], data[2], data[1], data[0]); + if (current_breath_rate_int != 0) { + float breath_rate_float; + memcpy(&breath_rate_float, ¤t_breath_rate_int, sizeof(float)); + this->breath_rate_sensor_->publish_state(breath_rate_float); + } + } + break; + case HEART_RATE_TYPE_BUFFER: + if (this->heart_rate_sensor_ != nullptr && length >= 4) { + uint32_t current_heart_rate_int = encode_uint32(data[3], data[2], data[1], data[0]); + if (current_heart_rate_int != 0) { + float heart_rate_float; + memcpy(&heart_rate_float, ¤t_heart_rate_int, sizeof(float)); + this->heart_rate_sensor_->publish_state(heart_rate_float); + } + } + break; + case DISTANCE_TYPE_BUFFER: + if (!data[0]) { + if (this->distance_sensor_ != nullptr && length >= 8) { + uint32_t current_distance_int = encode_uint32(data[7], data[6], data[5], data[4]); + float distance_float; + memcpy(&distance_float, ¤t_distance_int, sizeof(float)); + this->distance_sensor_->publish_state(distance_float); + } + } + break; + default: + break; + } +} + +} // namespace seeed_mr60bha2 +} // namespace esphome diff --git a/esphome/components/seeed_mr60bha2/seeed_mr60bha2.h b/esphome/components/seeed_mr60bha2/seeed_mr60bha2.h new file mode 100644 index 0000000000..0a4f21f1ad --- /dev/null +++ b/esphome/components/seeed_mr60bha2/seeed_mr60bha2.h @@ -0,0 +1,61 @@ +#pragma once +#include "esphome/core/component.h" +#include "esphome/core/defines.h" +#ifdef USE_SENSOR +#include "esphome/components/sensor/sensor.h" +#endif +#include "esphome/components/uart/uart.h" +#include "esphome/core/automation.h" +#include "esphome/core/helpers.h" + +#include + +namespace esphome { +namespace seeed_mr60bha2 { + +static const uint8_t DATA_BUF_MAX_SIZE = 12; +static const uint8_t FRAME_BUF_MAX_SIZE = 21; +static const uint8_t LEN_TO_HEAD_CKSUM = 8; +static const uint8_t LEN_TO_DATA_FRAME = 9; + +static const uint8_t FRAME_HEADER_BUFFER = 0x01; +static const uint16_t BREATH_RATE_TYPE_BUFFER = 0x0A14; +static const uint16_t HEART_RATE_TYPE_BUFFER = 0x0A15; +static const uint16_t DISTANCE_TYPE_BUFFER = 0x0A16; + +enum FrameLocation { + LOCATE_FRAME_HEADER, + LOCATE_ID_FRAME1, + LOCATE_ID_FRAME2, + LOCATE_LENGTH_FRAME_H, + LOCATE_LENGTH_FRAME_L, + LOCATE_TYPE_FRAME1, + LOCATE_TYPE_FRAME2, + LOCATE_HEAD_CKSUM_FRAME, // Header checksum: [from the first byte to the previous byte of the HEAD_CKSUM bit] + LOCATE_DATA_FRAME, + LOCATE_DATA_CKSUM_FRAME, // Data checksum: [from the first to the previous byte of the DATA_CKSUM bit] + LOCATE_PROCESS_FRAME, +}; + +class MR60BHA2Component : public Component, + public uart::UARTDevice { // The class name must be the name defined by text_sensor.py +#ifdef USE_SENSOR + SUB_SENSOR(breath_rate); + SUB_SENSOR(heart_rate); + SUB_SENSOR(distance); +#endif + + public: + float get_setup_priority() const override { return esphome::setup_priority::LATE; } + void dump_config() override; + void loop() override; + + protected: + bool validate_message_(); + void process_frame_(uint16_t frame_id, uint16_t frame_type, const uint8_t *data, size_t length); + + std::vector rx_message_; +}; + +} // namespace seeed_mr60bha2 +} // namespace esphome diff --git a/esphome/components/seeed_mr60bha2/sensor.py b/esphome/components/seeed_mr60bha2/sensor.py new file mode 100644 index 0000000000..5f30b363bf --- /dev/null +++ b/esphome/components/seeed_mr60bha2/sensor.py @@ -0,0 +1,57 @@ +import esphome.codegen as cg +from esphome.components import sensor +import esphome.config_validation as cv +from esphome.const import ( + CONF_DISTANCE, + DEVICE_CLASS_DISTANCE, + ICON_HEART_PULSE, + ICON_PULSE, + ICON_SIGNAL, + STATE_CLASS_MEASUREMENT, + UNIT_BEATS_PER_MINUTE, + UNIT_CENTIMETER, +) + +from . import CONF_MR60BHA2_ID, MR60BHA2Component + +DEPENDENCIES = ["seeed_mr60bha2"] + +CONF_BREATH_RATE = "breath_rate" +CONF_HEART_RATE = "heart_rate" + +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(CONF_MR60BHA2_ID): cv.use_id(MR60BHA2Component), + cv.Optional(CONF_BREATH_RATE): sensor.sensor_schema( + accuracy_decimals=2, + state_class=STATE_CLASS_MEASUREMENT, + icon=ICON_PULSE, + ), + cv.Optional(CONF_HEART_RATE): sensor.sensor_schema( + accuracy_decimals=0, + icon=ICON_HEART_PULSE, + state_class=STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_BEATS_PER_MINUTE, + ), + cv.Optional(CONF_DISTANCE): sensor.sensor_schema( + device_class=DEVICE_CLASS_DISTANCE, + state_class=STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_CENTIMETER, + accuracy_decimals=2, + icon=ICON_SIGNAL, + ), + } +) + + +async def to_code(config): + mr60bha2_component = await cg.get_variable(config[CONF_MR60BHA2_ID]) + if breath_rate_config := config.get(CONF_BREATH_RATE): + sens = await sensor.new_sensor(breath_rate_config) + cg.add(mr60bha2_component.set_breath_rate_sensor(sens)) + if heart_rate_config := config.get(CONF_HEART_RATE): + sens = await sensor.new_sensor(heart_rate_config) + cg.add(mr60bha2_component.set_heart_rate_sensor(sens)) + if distance_config := config.get(CONF_DISTANCE): + sens = await sensor.new_sensor(distance_config) + cg.add(mr60bha2_component.set_distance_sensor(sens)) diff --git a/esphome/components/seeed_mr60fda2/__init__.py b/esphome/components/seeed_mr60fda2/__init__.py new file mode 100644 index 0000000000..e79134deec --- /dev/null +++ b/esphome/components/seeed_mr60fda2/__init__.py @@ -0,0 +1,41 @@ +import esphome.codegen as cg +from esphome.components import uart +import esphome.config_validation as cv +from esphome.const import CONF_ID + +CODEOWNERS = ["@limengdu"] +DEPENDENCIES = ["uart"] +MULTI_CONF = True + +mr60fda2_ns = cg.esphome_ns.namespace("seeed_mr60fda2") + +MR60FDA2Component = mr60fda2_ns.class_( + "MR60FDA2Component", cg.Component, uart.UARTDevice +) + +CONF_MR60FDA2_ID = "mr60fda2_id" + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(MR60FDA2Component), + } + ) + .extend(uart.UART_DEVICE_SCHEMA) + .extend(cv.COMPONENT_SCHEMA) +) + +FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema( + "seeed_mr60fda2", + require_tx=True, + require_rx=True, + baud_rate=115200, + parity="NONE", + stop_bits=1, +) + + +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) diff --git a/esphome/components/seeed_mr60fda2/binary_sensor.py b/esphome/components/seeed_mr60fda2/binary_sensor.py new file mode 100644 index 0000000000..2860ac0100 --- /dev/null +++ b/esphome/components/seeed_mr60fda2/binary_sensor.py @@ -0,0 +1,33 @@ +import esphome.codegen as cg +from esphome.components import binary_sensor +import esphome.config_validation as cv +from esphome.const import DEVICE_CLASS_OCCUPANCY, DEVICE_CLASS_SAFETY + +from . import CONF_MR60FDA2_ID, MR60FDA2Component + +DEPENDENCIES = ["seeed_mr60fda2"] + +CONF_PEOPLE_EXIST = "people_exist" +CONF_FALL_DETECTED = "fall_detected" + +CONFIG_SCHEMA = { + cv.GenerateID(CONF_MR60FDA2_ID): cv.use_id(MR60FDA2Component), + cv.Optional(CONF_PEOPLE_EXIST): binary_sensor.binary_sensor_schema( + device_class=DEVICE_CLASS_OCCUPANCY, icon="mdi:motion-sensor" + ), + cv.Optional(CONF_FALL_DETECTED): binary_sensor.binary_sensor_schema( + device_class=DEVICE_CLASS_SAFETY, icon="mdi:emergency" + ), +} + + +async def to_code(config): + mr60fda2_component = await cg.get_variable(config[CONF_MR60FDA2_ID]) + + if people_exist_config := config.get(CONF_PEOPLE_EXIST): + sens = await binary_sensor.new_binary_sensor(people_exist_config) + cg.add(mr60fda2_component.set_people_exist_binary_sensor(sens)) + + if is_fall_config := config.get(CONF_FALL_DETECTED): + sens = await binary_sensor.new_binary_sensor(is_fall_config) + cg.add(mr60fda2_component.set_fall_detected_binary_sensor(sens)) diff --git a/esphome/components/seeed_mr60fda2/button/__init__.py b/esphome/components/seeed_mr60fda2/button/__init__.py new file mode 100644 index 0000000000..1415dc27ca --- /dev/null +++ b/esphome/components/seeed_mr60fda2/button/__init__.py @@ -0,0 +1,45 @@ +import esphome.codegen as cg +from esphome.components import button +import esphome.config_validation as cv +from esphome.const import ( + DEVICE_CLASS_RESTART, + DEVICE_CLASS_UPDATE, + ENTITY_CATEGORY_DIAGNOSTIC, + ENTITY_CATEGORY_NONE, + CONF_FACTORY_RESET, +) + +from .. import CONF_MR60FDA2_ID, MR60FDA2Component, mr60fda2_ns + +DEPENDENCIES = ["seeed_mr60fda2"] + +GetRadarParametersButton = mr60fda2_ns.class_("GetRadarParametersButton", button.Button) +ResetRadarButton = mr60fda2_ns.class_("ResetRadarButton", button.Button) + +CONF_GET_RADAR_PARAMETERS = "get_radar_parameters" + +CONFIG_SCHEMA = { + cv.GenerateID(CONF_MR60FDA2_ID): cv.use_id(MR60FDA2Component), + cv.Optional(CONF_GET_RADAR_PARAMETERS): button.button_schema( + GetRadarParametersButton, + device_class=DEVICE_CLASS_UPDATE, + entity_category=ENTITY_CATEGORY_NONE, + ), + cv.Optional(CONF_FACTORY_RESET): button.button_schema( + ResetRadarButton, + device_class=DEVICE_CLASS_RESTART, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ), +} + + +async def to_code(config): + mr60fda2_component = await cg.get_variable(config[CONF_MR60FDA2_ID]) + if get_radar_parameters_config := config.get(CONF_GET_RADAR_PARAMETERS): + b = await button.new_button(get_radar_parameters_config) + await cg.register_parented(b, config[CONF_MR60FDA2_ID]) + cg.add(mr60fda2_component.set_get_radar_parameters_button(b)) + if factory_reset_config := config.get(CONF_FACTORY_RESET): + b = await button.new_button(factory_reset_config) + await cg.register_parented(b, config[CONF_MR60FDA2_ID]) + cg.add(mr60fda2_component.set_factory_reset_button(b)) diff --git a/esphome/components/seeed_mr60fda2/button/get_radar_parameters_button.cpp b/esphome/components/seeed_mr60fda2/button/get_radar_parameters_button.cpp new file mode 100644 index 0000000000..88be6dfe7c --- /dev/null +++ b/esphome/components/seeed_mr60fda2/button/get_radar_parameters_button.cpp @@ -0,0 +1,9 @@ +#include "get_radar_parameters_button.h" + +namespace esphome { +namespace seeed_mr60fda2 { + +void GetRadarParametersButton::press_action() { this->parent_->get_radar_parameters(); } + +} // namespace seeed_mr60fda2 +} // namespace esphome diff --git a/esphome/components/seeed_mr60fda2/button/get_radar_parameters_button.h b/esphome/components/seeed_mr60fda2/button/get_radar_parameters_button.h new file mode 100644 index 0000000000..9d6d507383 --- /dev/null +++ b/esphome/components/seeed_mr60fda2/button/get_radar_parameters_button.h @@ -0,0 +1,18 @@ +#pragma once + +#include "esphome/components/button/button.h" +#include "../seeed_mr60fda2.h" + +namespace esphome { +namespace seeed_mr60fda2 { + +class GetRadarParametersButton : public button::Button, public Parented { + public: + GetRadarParametersButton() = default; + + protected: + void press_action() override; +}; + +} // namespace seeed_mr60fda2 +} // namespace esphome diff --git a/esphome/components/seeed_mr60fda2/button/reset_radar_button.cpp b/esphome/components/seeed_mr60fda2/button/reset_radar_button.cpp new file mode 100644 index 0000000000..0a5833a18c --- /dev/null +++ b/esphome/components/seeed_mr60fda2/button/reset_radar_button.cpp @@ -0,0 +1,9 @@ +#include "reset_radar_button.h" + +namespace esphome { +namespace seeed_mr60fda2 { + +void ResetRadarButton::press_action() { this->parent_->factory_reset(); } + +} // namespace seeed_mr60fda2 +} // namespace esphome diff --git a/esphome/components/seeed_mr60fda2/button/reset_radar_button.h b/esphome/components/seeed_mr60fda2/button/reset_radar_button.h new file mode 100644 index 0000000000..66780fb8af --- /dev/null +++ b/esphome/components/seeed_mr60fda2/button/reset_radar_button.h @@ -0,0 +1,18 @@ +#pragma once + +#include "esphome/components/button/button.h" +#include "../seeed_mr60fda2.h" + +namespace esphome { +namespace seeed_mr60fda2 { + +class ResetRadarButton : public button::Button, public Parented { + public: + ResetRadarButton() = default; + + protected: + void press_action() override; +}; + +} // namespace seeed_mr60fda2 +} // namespace esphome diff --git a/esphome/components/seeed_mr60fda2/seeed_mr60fda2.cpp b/esphome/components/seeed_mr60fda2/seeed_mr60fda2.cpp new file mode 100644 index 0000000000..d183a1f77f --- /dev/null +++ b/esphome/components/seeed_mr60fda2/seeed_mr60fda2.cpp @@ -0,0 +1,368 @@ +#include "seeed_mr60fda2.h" +#include "esphome/core/log.h" + +#include +#include + +namespace esphome { +namespace seeed_mr60fda2 { + +static const char *const TAG = "seeed_mr60fda2"; + +// Prints the component's configuration data. dump_config() prints all of the component's configuration +// items in an easy-to-read format, including the configuration key-value pairs. +void MR60FDA2Component::dump_config() { + ESP_LOGCONFIG(TAG, "MR60FDA2:"); +#ifdef USE_BINARY_SENSOR + LOG_BINARY_SENSOR(" ", "People Exist Binary Sensor", this->people_exist_binary_sensor_); + LOG_BINARY_SENSOR(" ", "Is Fall Binary Sensor", this->fall_detected_binary_sensor_); +#endif +#ifdef USE_BUTTON + LOG_BUTTON(" ", "Get Radar Parameters Button", this->get_radar_parameters_button_); + LOG_BUTTON(" ", "Reset Radar Button", this->factory_reset_button_); +#endif +#ifdef USE_SELECT + LOG_SELECT(" ", "Install Height Select", this->install_height_select_); + LOG_SELECT(" ", "Height Threshold Select", this->height_threshold_select_); + LOG_SELECT(" ", "Sensitivity Select", this->sensitivity_select_); +#endif +} + +// Initialisation functions +void MR60FDA2Component::setup() { + ESP_LOGCONFIG(TAG, "Setting up MR60FDA2..."); + this->check_uart_settings(115200); + + this->current_frame_locate_ = LOCATE_FRAME_HEADER; + this->current_frame_id_ = 0; + this->current_frame_len_ = 0; + this->current_data_frame_len_ = 0; + this->current_frame_type_ = 0; + this->get_radar_parameters(); + + memset(this->current_frame_buf_, 0, FRAME_BUF_MAX_SIZE); + memset(this->current_data_buf_, 0, DATA_BUF_MAX_SIZE); + + ESP_LOGCONFIG(TAG, "Set up MR60FDA2 complete"); +} + +// main loop +void MR60FDA2Component::loop() { + uint8_t byte; + + // Is there data on the serial port + while (this->available()) { + this->read_byte(&byte); + this->split_frame_(byte); // split data frame + } +} + +/** + * @brief Calculate the checksum for a byte array. + * + * This function calculates the checksum for the provided byte array using an + * XOR-based checksum algorithm. + * + * @param data The byte array to calculate the checksum for. + * @param len The length of the byte array. + * @return The calculated checksum. + */ +static uint8_t calculate_checksum(const uint8_t *data, size_t len) { + uint8_t checksum = 0; + for (size_t i = 0; i < len; i++) { + checksum ^= data[i]; + } + checksum = ~checksum; + return checksum; +} + +/** + * @brief Validate the checksum of a byte array. + * + * This function validates the checksum of the provided byte array by comparing + * it to the expected checksum. + * + * @param data The byte array to validate. + * @param len The length of the byte array. + * @param expected_checksum The expected checksum. + * @return True if the checksum is valid, false otherwise. + */ +static bool validate_checksum(const uint8_t *data, size_t len, uint8_t expected_checksum) { + return calculate_checksum(data, len) == expected_checksum; +} + +static uint8_t find_nearest_index(float value, const float *arr, int size) { + int nearest_index = 0; + float min_diff = std::abs(value - arr[0]); + for (int i = 1; i < size; ++i) { + float diff = std::abs(value - arr[i]); + if (diff < min_diff) { + min_diff = diff; + nearest_index = i; + } + } + return nearest_index; +} + +/** + * @brief Convert a float value to a byte array. + * + * This function converts a float value to a byte array. + * + * @param value The float value to convert. + * @param bytes The byte array to store the converted value. + */ +static void float_to_bytes(float value, unsigned char *bytes) { + union { + float float_value; + unsigned char byte_array[4]; + } u; + + u.float_value = value; + memcpy(bytes, u.byte_array, 4); +} + +/** + * @brief Convert a 32-bit unsigned integer to a byte array. + * + * This function converts a 32-bit unsigned integer to a byte array. + * + * @param value The 32-bit unsigned integer to convert. + * @param bytes The byte array to store the converted value. + */ +static void int_to_bytes(uint32_t value, unsigned char *bytes) { + bytes[0] = value & 0xFF; + bytes[1] = (value >> 8) & 0xFF; + bytes[2] = (value >> 16) & 0xFF; + bytes[3] = (value >> 24) & 0xFF; +} + +void MR60FDA2Component::split_frame_(uint8_t buffer) { + switch (this->current_frame_locate_) { + case LOCATE_FRAME_HEADER: // starting buffer + if (buffer == FRAME_HEADER_BUFFER) { + this->current_frame_len_ = 1; + this->current_frame_buf_[this->current_frame_len_ - 1] = buffer; + this->current_frame_locate_++; + } + break; + case LOCATE_ID_FRAME1: + this->current_frame_id_ = buffer << 8; + this->current_frame_len_++; + this->current_frame_buf_[this->current_frame_len_ - 1] = buffer; + this->current_frame_locate_++; + break; + case LOCATE_ID_FRAME2: + this->current_frame_id_ += buffer; + this->current_frame_len_++; + this->current_frame_buf_[this->current_frame_len_ - 1] = buffer; + this->current_frame_locate_++; + break; + case LOCATE_LENGTH_FRAME_H: + this->current_data_frame_len_ = buffer << 8; + if (this->current_data_frame_len_ == 0x00) { + this->current_frame_len_++; + this->current_frame_buf_[this->current_frame_len_ - 1] = buffer; + this->current_frame_locate_++; + } else { + this->current_frame_locate_ = LOCATE_FRAME_HEADER; + } + break; + case LOCATE_LENGTH_FRAME_L: + this->current_data_frame_len_ += buffer; + if (this->current_data_frame_len_ > DATA_BUF_MAX_SIZE) { + this->current_frame_locate_ = LOCATE_FRAME_HEADER; + } else { + this->current_frame_len_++; + this->current_frame_buf_[this->current_frame_len_ - 1] = buffer; + this->current_frame_locate_++; + } + break; + case LOCATE_TYPE_FRAME1: + this->current_frame_type_ = buffer << 8; + this->current_frame_len_++; + this->current_frame_buf_[this->current_frame_len_ - 1] = buffer; + this->current_frame_locate_++; + break; + case LOCATE_TYPE_FRAME2: + this->current_frame_type_ += buffer; + if ((this->current_frame_type_ == IS_FALL_TYPE_BUFFER) || + (this->current_frame_type_ == PEOPLE_EXIST_TYPE_BUFFER) || + (this->current_frame_type_ == RESULT_INSTALL_HEIGHT) || (this->current_frame_type_ == RESULT_PARAMETERS) || + (this->current_frame_type_ == RESULT_HEIGHT_THRESHOLD) || (this->current_frame_type_ == RESULT_SENSITIVITY)) { + this->current_frame_len_++; + this->current_frame_buf_[this->current_frame_len_ - 1] = buffer; + this->current_frame_locate_++; + } else { + this->current_frame_locate_ = LOCATE_FRAME_HEADER; + } + break; + case LOCATE_HEAD_CKSUM_FRAME: + if (validate_checksum(this->current_frame_buf_, this->current_frame_len_, buffer)) { + this->current_frame_len_++; + this->current_frame_buf_[this->current_frame_len_ - 1] = buffer; + this->current_frame_locate_++; + } else { + ESP_LOGD(TAG, "HEAD_CKSUM_FRAME ERROR: 0x%02x", buffer); + ESP_LOGV(TAG, "CURRENT_FRAME: %s %s", + format_hex_pretty(this->current_frame_buf_, this->current_frame_len_).c_str(), + format_hex_pretty(&buffer, 1).c_str()); + this->current_frame_locate_ = LOCATE_FRAME_HEADER; + } + break; + case LOCATE_DATA_FRAME: + this->current_frame_len_++; + this->current_frame_buf_[this->current_frame_len_ - 1] = buffer; + this->current_data_buf_[this->current_frame_len_ - LEN_TO_DATA_FRAME] = buffer; + if (this->current_frame_len_ - LEN_TO_HEAD_CKSUM == this->current_data_frame_len_) { + this->current_frame_locate_++; + } + if (this->current_frame_len_ > FRAME_BUF_MAX_SIZE) { + ESP_LOGD(TAG, "PRACTICE_DATA_FRAME_LEN ERROR: %d", this->current_frame_len_ - LEN_TO_HEAD_CKSUM); + this->current_frame_locate_ = LOCATE_FRAME_HEADER; + } + break; + case LOCATE_DATA_CKSUM_FRAME: + if (validate_checksum(this->current_data_buf_, this->current_data_frame_len_, buffer)) { + this->current_frame_len_++; + this->current_frame_buf_[this->current_frame_len_ - 1] = buffer; + this->current_frame_locate_++; + this->process_frame_(); + } else { + ESP_LOGD(TAG, "DATA_CKSUM_FRAME ERROR: 0x%02x", buffer); + ESP_LOGV(TAG, "GET CURRENT_FRAME: %s %s", + format_hex_pretty(this->current_frame_buf_, this->current_frame_len_).c_str(), + format_hex_pretty(&buffer, 1).c_str()); + + this->current_frame_locate_ = LOCATE_FRAME_HEADER; + } + break; + default: + break; + } +} + +void MR60FDA2Component::process_frame_() { + switch (this->current_frame_type_) { + case IS_FALL_TYPE_BUFFER: + if (this->fall_detected_binary_sensor_ != nullptr) { + this->fall_detected_binary_sensor_->publish_state(this->current_frame_buf_[LEN_TO_HEAD_CKSUM]); + } + this->current_frame_locate_ = LOCATE_FRAME_HEADER; + break; + + case PEOPLE_EXIST_TYPE_BUFFER: + if (this->people_exist_binary_sensor_ != nullptr) + this->people_exist_binary_sensor_->publish_state(this->current_frame_buf_[LEN_TO_HEAD_CKSUM]); + this->current_frame_locate_ = LOCATE_FRAME_HEADER; + break; + + case RESULT_INSTALL_HEIGHT: + if (this->current_data_buf_[0]) { + ESP_LOGD(TAG, "Successfully set the mounting height"); + } else { + ESP_LOGD(TAG, "Failed to set the mounting height"); + } + this->current_frame_locate_ = LOCATE_FRAME_HEADER; + break; + + case RESULT_HEIGHT_THRESHOLD: + if (this->current_data_buf_[0]) { + ESP_LOGD(TAG, "Successfully set the height threshold"); + } else { + ESP_LOGD(TAG, "Failed to set the height threshold"); + } + this->current_frame_locate_ = LOCATE_FRAME_HEADER; + break; + + case RESULT_SENSITIVITY: + if (this->current_data_buf_[0]) { + ESP_LOGD(TAG, "Successfully set the sensitivity"); + } else { + ESP_LOGD(TAG, "Failed to set the sensitivity"); + } + this->current_frame_locate_ = LOCATE_FRAME_HEADER; + break; + + case RESULT_PARAMETERS: { + float install_height_float = 0; + float height_threshold_float = 0; + uint32_t current_sensitivity = 0; + if (this->install_height_select_ != nullptr) { + uint32_t current_install_height_int = + encode_uint32(current_data_buf_[3], current_data_buf_[2], current_data_buf_[1], current_data_buf_[0]); + + install_height_float = bit_cast(current_install_height_int); + uint32_t select_index = find_nearest_index(install_height_float, INSTALL_HEIGHT, 7); + this->install_height_select_->publish_state(this->install_height_select_->at(select_index).value()); + } + + if (this->height_threshold_select_ != nullptr) { + uint32_t current_height_threshold_int = + encode_uint32(current_data_buf_[7], current_data_buf_[6], current_data_buf_[5], current_data_buf_[4]); + + height_threshold_float = bit_cast(current_height_threshold_int); + size_t select_index = find_nearest_index(height_threshold_float, HEIGHT_THRESHOLD, 7); + this->height_threshold_select_->publish_state(this->height_threshold_select_->at(select_index).value()); + } + + if (this->sensitivity_select_ != nullptr) { + current_sensitivity = + encode_uint32(current_data_buf_[11], current_data_buf_[10], current_data_buf_[9], current_data_buf_[8]); + + uint32_t select_index = find_nearest_index(current_sensitivity, SENSITIVITY, 3); + this->sensitivity_select_->publish_state(this->sensitivity_select_->at(select_index).value()); + } + + ESP_LOGD(TAG, "Mounting height: %.2f, Height threshold: %.2f, Sensitivity: %" PRIu32, install_height_float, + height_threshold_float, current_sensitivity); + this->current_frame_locate_ = LOCATE_FRAME_HEADER; + break; + } + default: + break; + } +} + +// Send Heartbeat Packet Command +void MR60FDA2Component::set_install_height(uint8_t index) { + uint8_t send_data[13] = {0x01, 0x00, 0x00, 0x00, 0x04, 0x0E, 0x04, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00}; + float_to_bytes(INSTALL_HEIGHT[index], &send_data[8]); + send_data[12] = calculate_checksum(send_data + 8, 4); + this->write_array(send_data, 13); + ESP_LOGV(TAG, "SEND INSTALL HEIGHT FRAME: %s", format_hex_pretty(send_data, 13).c_str()); +} + +void MR60FDA2Component::set_height_threshold(uint8_t index) { + uint8_t send_data[13] = {0x01, 0x00, 0x00, 0x00, 0x04, 0x0E, 0x08, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00}; + float_to_bytes(INSTALL_HEIGHT[index], &send_data[8]); + send_data[12] = calculate_checksum(send_data + 8, 4); + this->write_array(send_data, 13); + ESP_LOGV(TAG, "SEND HEIGHT THRESHOLD: %s", format_hex_pretty(send_data, 13).c_str()); +} + +void MR60FDA2Component::set_sensitivity(uint8_t index) { + uint8_t send_data[13] = {0x01, 0x00, 0x00, 0x00, 0x04, 0x0E, 0x0A, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00}; + + int_to_bytes(SENSITIVITY[index], &send_data[8]); + + send_data[12] = calculate_checksum(send_data + 8, 4); + this->write_array(send_data, 13); + ESP_LOGV(TAG, "SEND SET SENSITIVITY: %s", format_hex_pretty(send_data, 13).c_str()); +} + +void MR60FDA2Component::get_radar_parameters() { + uint8_t send_data[8] = {0x01, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x06, 0xF6}; + this->write_array(send_data, 8); + ESP_LOGV(TAG, "SEND GET PARAMETERS: %s", format_hex_pretty(send_data, 8).c_str()); +} + +void MR60FDA2Component::factory_reset() { + uint8_t send_data[8] = {0x01, 0x00, 0x00, 0x00, 0x00, 0x21, 0x10, 0xCF}; + this->write_array(send_data, 8); + ESP_LOGV(TAG, "SEND RESET: %s", format_hex_pretty(send_data, 8).c_str()); + this->get_radar_parameters(); +} + +} // namespace seeed_mr60fda2 +} // namespace esphome diff --git a/esphome/components/seeed_mr60fda2/seeed_mr60fda2.h b/esphome/components/seeed_mr60fda2/seeed_mr60fda2.h new file mode 100644 index 0000000000..e1ffa4f071 --- /dev/null +++ b/esphome/components/seeed_mr60fda2/seeed_mr60fda2.h @@ -0,0 +1,101 @@ +#pragma once +#include "esphome/core/component.h" +#include "esphome/core/defines.h" +#ifdef USE_BINARY_SENSOR +#include "esphome/components/binary_sensor/binary_sensor.h" +#endif +#ifdef USE_BUTTON +#include "esphome/components/button/button.h" +#endif +#ifdef USE_SELECT +#include "esphome/components/select/select.h" +#endif +#ifdef USE_TEXT_SENSOR +#include "esphome/components/text_sensor/text_sensor.h" +#endif +#include "esphome/components/uart/uart.h" +#include "esphome/core/automation.h" +#include "esphome/core/helpers.h" + +#include + +namespace esphome { +namespace seeed_mr60fda2 { + +static const uint8_t DATA_BUF_MAX_SIZE = 28; +static const uint8_t FRAME_BUF_MAX_SIZE = 37; +static const uint8_t LEN_TO_HEAD_CKSUM = 8; +static const uint8_t LEN_TO_DATA_FRAME = 9; + +static const uint8_t FRAME_HEADER_BUFFER = 0x01; +static const uint16_t IS_FALL_TYPE_BUFFER = 0x0E02; +static const uint16_t PEOPLE_EXIST_TYPE_BUFFER = 0x0F09; +static const uint16_t RESULT_INSTALL_HEIGHT = 0x0E04; +static const uint16_t RESULT_PARAMETERS = 0x0E06; +static const uint16_t RESULT_HEIGHT_THRESHOLD = 0x0E08; +static const uint16_t RESULT_SENSITIVITY = 0x0E0A; + +enum FrameLocation { + LOCATE_FRAME_HEADER, + LOCATE_ID_FRAME1, + LOCATE_ID_FRAME2, + LOCATE_LENGTH_FRAME_H, + LOCATE_LENGTH_FRAME_L, + LOCATE_TYPE_FRAME1, + LOCATE_TYPE_FRAME2, + LOCATE_HEAD_CKSUM_FRAME, // Header checksum: [from the first byte to the previous byte of the HEAD_CKSUM bit] + LOCATE_DATA_FRAME, + LOCATE_DATA_CKSUM_FRAME, // Data checksum: [from the first to the previous byte of the DATA_CKSUM bit] + LOCATE_PROCESS_FRAME, +}; + +static const float INSTALL_HEIGHT[7] = {2.4f, 2.5f, 2.6f, 2.7f, 2.8f, 2.9f, 3.0f}; +static const float HEIGHT_THRESHOLD[7] = {0.0f, 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f}; +static const float SENSITIVITY[3] = {3, 15, 30}; + +static const char *const INSTALL_HEIGHT_STR[7] = {"2.4m", "2.5m", "2.6", "2.7m", "2.8", "2.9m", "3.0m"}; +static const char *const HEIGHT_THRESHOLD_STR[7] = {"0.0m", "0.1m", "0.2m", "0.3m", "0.4m", "0.5m", "0.6m"}; +static const char *const SENSITIVITY_STR[3] = {"1", "2", "3"}; + +class MR60FDA2Component : public Component, + public uart::UARTDevice { // The class name must be the name defined by text_sensor.py +#ifdef USE_BINARY_SENSOR + SUB_BINARY_SENSOR(people_exist) + SUB_BINARY_SENSOR(fall_detected) +#endif +#ifdef USE_BUTTON + SUB_BUTTON(get_radar_parameters) + SUB_BUTTON(factory_reset) +#endif +#ifdef USE_SELECT + SUB_SELECT(install_height) + SUB_SELECT(height_threshold) + SUB_SELECT(sensitivity) +#endif + + protected: + uint8_t current_frame_locate_; + uint8_t current_frame_buf_[FRAME_BUF_MAX_SIZE]; + uint8_t current_data_buf_[DATA_BUF_MAX_SIZE]; + uint16_t current_frame_id_; + size_t current_frame_len_; + size_t current_data_frame_len_; + uint16_t current_frame_type_; + + void split_frame_(uint8_t buffer); + void process_frame_(); + + public: + float get_setup_priority() const override { return esphome::setup_priority::LATE; } + void setup() override; + void dump_config() override; + void loop() override; + void set_install_height(uint8_t index); + void set_height_threshold(uint8_t index); + void set_sensitivity(uint8_t index); + void get_radar_parameters(); + void factory_reset(); +}; + +} // namespace seeed_mr60fda2 +} // namespace esphome diff --git a/esphome/components/seeed_mr60fda2/select/__init__.py b/esphome/components/seeed_mr60fda2/select/__init__.py new file mode 100644 index 0000000000..a6f9eeb920 --- /dev/null +++ b/esphome/components/seeed_mr60fda2/select/__init__.py @@ -0,0 +1,59 @@ +import esphome.codegen as cg +from esphome.components import select +import esphome.config_validation as cv +from esphome.const import CONF_SENSITIVITY, ENTITY_CATEGORY_CONFIG, ICON_ACCELERATION_Z + +from .. import CONF_MR60FDA2_ID, MR60FDA2Component, mr60fda2_ns + + +DEPENDENCIES = ["seeed_mr60fda2"] + +InstallHeightSelect = mr60fda2_ns.class_("InstallHeightSelect", select.Select) +HeightThresholdSelect = mr60fda2_ns.class_("HeightThresholdSelect", select.Select) +SensitivitySelect = mr60fda2_ns.class_("SensitivitySelect", select.Select) + +CONF_INSTALL_HEIGHT = "install_height" +CONF_HEIGHT_THRESHOLD = "height_threshold" + +CONFIG_SCHEMA = { + cv.GenerateID(CONF_MR60FDA2_ID): cv.use_id(MR60FDA2Component), + cv.Optional(CONF_INSTALL_HEIGHT): select.select_schema( + InstallHeightSelect, + entity_category=ENTITY_CATEGORY_CONFIG, + icon=ICON_ACCELERATION_Z, + ), + cv.Optional(CONF_HEIGHT_THRESHOLD): select.select_schema( + HeightThresholdSelect, + entity_category=ENTITY_CATEGORY_CONFIG, + icon=ICON_ACCELERATION_Z, + ), + cv.Optional(CONF_SENSITIVITY): select.select_schema( + SensitivitySelect, + entity_category=ENTITY_CATEGORY_CONFIG, + ), +} + + +async def to_code(config): + mr60fda2_component = await cg.get_variable(config[CONF_MR60FDA2_ID]) + if install_height_config := config.get(CONF_INSTALL_HEIGHT): + s = await select.new_select( + install_height_config, + options=["2.4m", "2.5m", "2.6m", "2.7m", "2.8m", "2.9m", "3.0m"], + ) + await cg.register_parented(s, config[CONF_MR60FDA2_ID]) + cg.add(mr60fda2_component.set_install_height_select(s)) + if height_threshold_config := config.get(CONF_HEIGHT_THRESHOLD): + s = await select.new_select( + height_threshold_config, + options=["0.0m", "0.1m", "0.2m", "0.3m", "0.4m", "0.5m", "0.6m"], + ) + await cg.register_parented(s, config[CONF_MR60FDA2_ID]) + cg.add(mr60fda2_component.set_height_threshold_select(s)) + if sensitivity_config := config.get(CONF_SENSITIVITY): + s = await select.new_select( + sensitivity_config, + options=["1", "2", "3"], + ) + await cg.register_parented(s, config[CONF_MR60FDA2_ID]) + cg.add(mr60fda2_component.set_sensitivity_select(s)) diff --git a/esphome/components/seeed_mr60fda2/select/height_threshold_select.cpp b/esphome/components/seeed_mr60fda2/select/height_threshold_select.cpp new file mode 100644 index 0000000000..037f8c6036 --- /dev/null +++ b/esphome/components/seeed_mr60fda2/select/height_threshold_select.cpp @@ -0,0 +1,15 @@ +#include "height_threshold_select.h" + +namespace esphome { +namespace seeed_mr60fda2 { + +void HeightThresholdSelect::control(const std::string &value) { + this->publish_state(value); + auto index = this->index_of(value); + if (index.has_value()) { + this->parent_->set_height_threshold(index.value()); + } +} + +} // namespace seeed_mr60fda2 +} // namespace esphome diff --git a/esphome/components/seeed_mr60fda2/select/height_threshold_select.h b/esphome/components/seeed_mr60fda2/select/height_threshold_select.h new file mode 100644 index 0000000000..b856dbc89a --- /dev/null +++ b/esphome/components/seeed_mr60fda2/select/height_threshold_select.h @@ -0,0 +1,18 @@ +#pragma once + +#include "esphome/components/select/select.h" +#include "../seeed_mr60fda2.h" + +namespace esphome { +namespace seeed_mr60fda2 { + +class HeightThresholdSelect : public select::Select, public Parented { + public: + HeightThresholdSelect() = default; + + protected: + void control(const std::string &value) override; +}; + +} // namespace seeed_mr60fda2 +} // namespace esphome diff --git a/esphome/components/seeed_mr60fda2/select/install_height_select.cpp b/esphome/components/seeed_mr60fda2/select/install_height_select.cpp new file mode 100644 index 0000000000..e791911613 --- /dev/null +++ b/esphome/components/seeed_mr60fda2/select/install_height_select.cpp @@ -0,0 +1,15 @@ +#include "install_height_select.h" + +namespace esphome { +namespace seeed_mr60fda2 { + +void InstallHeightSelect::control(const std::string &value) { + this->publish_state(value); + auto index = this->index_of(value); + if (index.has_value()) { + this->parent_->set_install_height(index.value()); + } +} + +} // namespace seeed_mr60fda2 +} // namespace esphome diff --git a/esphome/components/seeed_mr60fda2/select/install_height_select.h b/esphome/components/seeed_mr60fda2/select/install_height_select.h new file mode 100644 index 0000000000..7430da3493 --- /dev/null +++ b/esphome/components/seeed_mr60fda2/select/install_height_select.h @@ -0,0 +1,18 @@ +#pragma once + +#include "esphome/components/select/select.h" +#include "../seeed_mr60fda2.h" + +namespace esphome { +namespace seeed_mr60fda2 { + +class InstallHeightSelect : public select::Select, public Parented { + public: + InstallHeightSelect() = default; + + protected: + void control(const std::string &value) override; +}; + +} // namespace seeed_mr60fda2 +} // namespace esphome diff --git a/esphome/components/seeed_mr60fda2/select/sensitivity_select.cpp b/esphome/components/seeed_mr60fda2/select/sensitivity_select.cpp new file mode 100644 index 0000000000..e2507fb7cc --- /dev/null +++ b/esphome/components/seeed_mr60fda2/select/sensitivity_select.cpp @@ -0,0 +1,15 @@ +#include "sensitivity_select.h" + +namespace esphome { +namespace seeed_mr60fda2 { + +void SensitivitySelect::control(const std::string &value) { + this->publish_state(value); + auto index = this->index_of(value); + if (index.has_value()) { + this->parent_->set_sensitivity(index.value()); + } +} + +} // namespace seeed_mr60fda2 +} // namespace esphome diff --git a/esphome/components/seeed_mr60fda2/select/sensitivity_select.h b/esphome/components/seeed_mr60fda2/select/sensitivity_select.h new file mode 100644 index 0000000000..d1accc1b5b --- /dev/null +++ b/esphome/components/seeed_mr60fda2/select/sensitivity_select.h @@ -0,0 +1,18 @@ +#pragma once + +#include "esphome/components/select/select.h" +#include "../seeed_mr60fda2.h" + +namespace esphome { +namespace seeed_mr60fda2 { + +class SensitivitySelect : public select::Select, public Parented { + public: + SensitivitySelect() = default; + + protected: + void control(const std::string &value) override; +}; + +} // namespace seeed_mr60fda2 +} // namespace esphome diff --git a/esphome/components/sen5x/sensor.py b/esphome/components/sen5x/sensor.py index 67bd627f7f..a8a796853e 100644 --- a/esphome/components/sen5x/sensor.py +++ b/esphome/components/sen5x/sensor.py @@ -1,19 +1,19 @@ -import esphome.codegen as cg -import esphome.config_validation as cv -from esphome.components import i2c, sensor, sensirion_common from esphome import automation from esphome.automation import maybe_simple_id - +import esphome.codegen as cg +from esphome.components import i2c, sensirion_common, sensor +import esphome.config_validation as cv from esphome.const import ( CONF_HUMIDITY, CONF_ID, CONF_OFFSET, CONF_PM_1_0, - CONF_PM_10_0, CONF_PM_2_5, CONF_PM_4_0, + CONF_PM_10_0, CONF_STORE_BASELINE, CONF_TEMPERATURE, + CONF_TEMPERATURE_COMPENSATION, DEVICE_CLASS_AQI, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_PM1, @@ -51,7 +51,6 @@ CONF_LEARNING_TIME_OFFSET_HOURS = "learning_time_offset_hours" CONF_NORMALIZED_OFFSET_SLOPE = "normalized_offset_slope" CONF_NOX = "nox" CONF_STD_INITIAL = "std_initial" -CONF_TEMPERATURE_COMPENSATION = "temperature_compensation" CONF_TIME_CONSTANT = "time_constant" CONF_VOC = "voc" CONF_VOC_BASELINE = "voc_baseline" diff --git a/esphome/components/sgp30/sensor.py b/esphome/components/sgp30/sensor.py index 13e859cc09..8c92f55ef7 100644 --- a/esphome/components/sgp30/sensor.py +++ b/esphome/components/sgp30/sensor.py @@ -1,23 +1,22 @@ import esphome.codegen as cg +from esphome.components import i2c, sensirion_common, sensor import esphome.config_validation as cv -from esphome.components import i2c, sensor, sensirion_common - from esphome.const import ( - CONF_COMPENSATION, - CONF_ID, CONF_BASELINE, + CONF_COMPENSATION, CONF_ECO2, + CONF_ID, CONF_STORE_BASELINE, CONF_TEMPERATURE_SOURCE, CONF_TVOC, - ICON_RADIATOR, DEVICE_CLASS_CARBON_DIOXIDE, DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS_PARTS, - STATE_CLASS_MEASUREMENT, - UNIT_PARTS_PER_MILLION, - UNIT_PARTS_PER_BILLION, - ICON_MOLECULE_CO2, ENTITY_CATEGORY_DIAGNOSTIC, + ICON_MOLECULE_CO2, + ICON_RADIATOR, + STATE_CLASS_MEASUREMENT, + UNIT_PARTS_PER_BILLION, + UNIT_PARTS_PER_MILLION, ) DEPENDENCIES = ["i2c"] @@ -77,7 +76,7 @@ CONFIG_SCHEMA = ( ), } ) - .extend(cv.polling_component_schema("1s")) + .extend(cv.polling_component_schema("60s")) .extend(i2c.i2c_device_schema(0x58)) ) diff --git a/esphome/components/sgp30/sgp30.cpp b/esphome/components/sgp30/sgp30.cpp index 261604b992..77e9ef9820 100644 --- a/esphome/components/sgp30/sgp30.cpp +++ b/esphome/components/sgp30/sgp30.cpp @@ -1,8 +1,8 @@ #include "sgp30.h" +#include +#include "esphome/core/application.h" #include "esphome/core/hal.h" #include "esphome/core/log.h" -#include "esphome/core/application.h" -#include namespace esphome { namespace sgp30 { @@ -295,10 +295,6 @@ void SGP30Component::update() { if (this->tvoc_sensor_ != nullptr) this->tvoc_sensor_->publish_state(tvoc); - if (this->get_update_interval() != 1000) { - ESP_LOGW(TAG, "Update interval for SGP30 sensor must be set to 1s for optimized readout"); - } - this->status_clear_warning(); this->send_env_data_(); this->read_iaq_baseline_(); diff --git a/esphome/components/shelly_dimmer/shelly_dimmer.cpp b/esphome/components/shelly_dimmer/shelly_dimmer.cpp index b415840bdc..d4229b2384 100644 --- a/esphome/components/shelly_dimmer/shelly_dimmer.cpp +++ b/esphome/components/shelly_dimmer/shelly_dimmer.cpp @@ -466,7 +466,7 @@ bool ShellyDimmer::handle_frame_() { } case SHELLY_DIMMER_PROTO_CMD_SWITCH: case SHELLY_DIMMER_PROTO_CMD_SETTINGS: { - return !(payload_len < 1 || payload[0] != 0x01); + return payload_len >= 1 && payload[0] == 0x01; } default: { return false; diff --git a/esphome/components/sim800l/sim800l.cpp b/esphome/components/sim800l/sim800l.cpp index 4f7aa228e9..38775b0062 100644 --- a/esphome/components/sim800l/sim800l.cpp +++ b/esphome/components/sim800l/sim800l.cpp @@ -324,7 +324,7 @@ void Sim800LComponent::parse_cmd_(std::string message) { this->sms_received_callback_.call(this->message_, this->sender_); this->state_ = STATE_RECEIVED_SMS; } else { - if (this->message_.length() > 0) + if (!this->message_.empty()) this->message_ += "\n"; this->message_ += message; } diff --git a/esphome/components/sntp/sntp_component.cpp b/esphome/components/sntp/sntp_component.cpp index 4ded98d483..21add1451d 100644 --- a/esphome/components/sntp/sntp_component.cpp +++ b/esphome/components/sntp/sntp_component.cpp @@ -9,11 +9,6 @@ #include "lwip/apps/sntp.h" #endif -// Yes, the server names are leaked, but that's fine. -#ifdef CLANG_TIDY -#define strdup(x) (const_cast(x)) -#endif - namespace esphome { namespace sntp { @@ -26,30 +21,29 @@ void SNTPComponent::setup() { esp_sntp_stop(); } esp_sntp_setoperatingmode(ESP_SNTP_OPMODE_POLL); + size_t i = 0; + for (auto &server : this->servers_) { + esp_sntp_setservername(i++, server.c_str()); + } + esp_sntp_set_sync_interval(this->get_update_interval()); + esp_sntp_init(); #else sntp_stop(); sntp_setoperatingmode(SNTP_OPMODE_POLL); -#endif - sntp_setservername(0, strdup(this->server_1_.c_str())); - if (!this->server_2_.empty()) { - sntp_setservername(1, strdup(this->server_2_.c_str())); + size_t i = 0; + for (auto &server : this->servers_) { + sntp_setservername(i++, server.c_str()); } - if (!this->server_3_.empty()) { - sntp_setservername(2, strdup(this->server_3_.c_str())); - } -#ifdef USE_ESP_IDF - esp_sntp_set_sync_interval(this->get_update_interval()); -#endif - sntp_init(); +#endif } void SNTPComponent::dump_config() { ESP_LOGCONFIG(TAG, "SNTP Time:"); - ESP_LOGCONFIG(TAG, " Server 1: '%s'", this->server_1_.c_str()); - ESP_LOGCONFIG(TAG, " Server 2: '%s'", this->server_2_.c_str()); - ESP_LOGCONFIG(TAG, " Server 3: '%s'", this->server_3_.c_str()); - ESP_LOGCONFIG(TAG, " Timezone: '%s'", this->timezone_.c_str()); + size_t i = 0; + for (auto &server : this->servers_) { + ESP_LOGCONFIG(TAG, " Server %zu: '%s'", i++, server.c_str()); + } } void SNTPComponent::update() { #if !defined(USE_ESP_IDF) diff --git a/esphome/components/sntp/sntp_component.h b/esphome/components/sntp/sntp_component.h index 987dd23a19..a4e8267383 100644 --- a/esphome/components/sntp/sntp_component.h +++ b/esphome/components/sntp/sntp_component.h @@ -14,23 +14,20 @@ namespace sntp { /// \see https://www.gnu.org/software/libc/manual/html_node/TZ-Variable.html class SNTPComponent : public time::RealTimeClock { public: + SNTPComponent(const std::vector &servers) : servers_(servers) {} + + // Note: set_servers() has been removed and replaced by a constructor - calling set_servers after setup would + // have had no effect anyway, and making the strings immutable avoids the need to strdup their contents. + void setup() override; void dump_config() override; - /// Change the servers used by SNTP for timekeeping - void set_servers(const std::string &server_1, const std::string &server_2, const std::string &server_3) { - this->server_1_ = server_1; - this->server_2_ = server_2; - this->server_3_ = server_3; - } float get_setup_priority() const override { return setup_priority::BEFORE_CONNECTION; } void update() override; void loop() override; protected: - std::string server_1_; - std::string server_2_; - std::string server_3_; + std::vector servers_; bool has_time_{false}; }; diff --git a/esphome/components/sntp/time.py b/esphome/components/sntp/time.py index 7cc82e3dff..6f883d5bed 100644 --- a/esphome/components/sntp/time.py +++ b/esphome/components/sntp/time.py @@ -1,16 +1,16 @@ +import esphome.codegen as cg from esphome.components import time as time_ import esphome.config_validation as cv -import esphome.codegen as cg -from esphome.core import CORE from esphome.const import ( CONF_ID, CONF_SERVERS, + PLATFORM_BK72XX, PLATFORM_ESP32, PLATFORM_ESP8266, PLATFORM_RP2040, PLATFORM_RTL87XX, - PLATFORM_BK72XX, ) +from esphome.core import CORE DEPENDENCIES = ["network"] sntp_ns = cg.esphome_ns.namespace("sntp") @@ -40,11 +40,8 @@ CONFIG_SCHEMA = cv.All( async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) - servers = config[CONF_SERVERS] - servers += [""] * (3 - len(servers)) - cg.add(var.set_servers(*servers)) + var = cg.new_Pvariable(config[CONF_ID], servers) await cg.register_component(var, config) await time_.register_time(var, config) diff --git a/esphome/components/sprinkler/sprinkler.cpp b/esphome/components/sprinkler/sprinkler.cpp index 59565251c3..5384d29871 100644 --- a/esphome/components/sprinkler/sprinkler.cpp +++ b/esphome/components/sprinkler/sprinkler.cpp @@ -419,7 +419,7 @@ void Sprinkler::add_valve(SprinklerControllerSwitch *valve_sw, SprinklerControll SprinklerValve *new_valve = &this->valve_[new_valve_number]; new_valve->controller_switch = valve_sw; - new_valve->controller_switch->set_state_lambda([=]() -> optional { + new_valve->controller_switch->set_state_lambda([this, new_valve_number]() -> optional { if (this->valve_pump_switch(new_valve_number) != nullptr) { return this->valve_switch(new_valve_number)->state() && this->valve_pump_switch(new_valve_number)->state(); } @@ -445,7 +445,7 @@ void Sprinkler::add_controller(Sprinkler *other_controller) { this->other_contro void Sprinkler::set_controller_main_switch(SprinklerControllerSwitch *controller_switch) { this->controller_sw_ = controller_switch; - controller_switch->set_state_lambda([=]() -> optional { + controller_switch->set_state_lambda([this]() -> optional { for (size_t valve_number = 0; valve_number < this->number_of_valves(); valve_number++) { if (this->valve_[valve_number].controller_switch->state) { return true; diff --git a/esphome/components/st7735/st7735.cpp b/esphome/components/st7735/st7735.cpp index a0c2d80d16..5985d8bfb3 100644 --- a/esphome/components/st7735/st7735.cpp +++ b/esphome/components/st7735/st7735.cpp @@ -483,7 +483,7 @@ void ST7735::spi_master_write_color_(uint16_t color, uint16_t size) { } this->dc_pin_->digital_write(true); - return write_array(byte, size * 2); + write_array(byte, size * 2); } } // namespace st7735 diff --git a/esphome/components/st7789v/st7789v.cpp b/esphome/components/st7789v/st7789v.cpp index 74c7a4e9e3..0d2f35aef5 100644 --- a/esphome/components/st7789v/st7789v.cpp +++ b/esphome/components/st7789v/st7789v.cpp @@ -252,7 +252,7 @@ void ST7789V::write_color_(uint16_t color, uint16_t size) { } this->dc_pin_->digital_write(true); - return write_array(byte, size * 2); + write_array(byte, size * 2); } size_t ST7789V::get_buffer_length_() { diff --git a/esphome/components/stepper/stepper.h b/esphome/components/stepper/stepper.h index 560362e4d0..ba2b3182d7 100644 --- a/esphome/components/stepper/stepper.h +++ b/esphome/components/stepper/stepper.h @@ -2,7 +2,6 @@ #include "esphome/core/component.h" #include "esphome/core/automation.h" -#include "esphome/components/stepper/stepper.h" namespace esphome { namespace stepper { diff --git a/esphome/components/sun_gtil2/sun_gtil2.cpp b/esphome/components/sun_gtil2/sun_gtil2.cpp index 1653f937dd..46b4902654 100644 --- a/esphome/components/sun_gtil2/sun_gtil2.cpp +++ b/esphome/components/sun_gtil2/sun_gtil2.cpp @@ -83,7 +83,7 @@ void SunGTIL2::handle_char_(uint8_t c) { memcpy(&msg, this->rx_message_.data(), MESSAGE_SIZE); this->rx_message_.clear(); - if (!((msg.end[0] == 0) && (msg.end[38] == 0x08))) + if ((msg.end[0] != 0) || (msg.end[38] != 0x08)) return; ESP_LOGVV(TAG, "Frequency raw value: %02x", msg.frequency); diff --git a/esphome/components/switch/binary_sensor/__init__.py b/esphome/components/switch/binary_sensor/__init__.py new file mode 100644 index 0000000000..61ca1a14a2 --- /dev/null +++ b/esphome/components/switch/binary_sensor/__init__.py @@ -0,0 +1,31 @@ +import esphome.codegen as cg +from esphome.components import binary_sensor +import esphome.config_validation as cv +from esphome.const import CONF_SOURCE_ID + +from .. import Switch, switch_ns + +CODEOWNERS = ["@ssieb"] + +SwitchBinarySensor = switch_ns.class_( + "SwitchBinarySensor", binary_sensor.BinarySensor, cg.Component +) + + +CONFIG_SCHEMA = ( + binary_sensor.binary_sensor_schema(SwitchBinarySensor) + .extend( + { + cv.Required(CONF_SOURCE_ID): cv.use_id(Switch), + } + ) + .extend(cv.COMPONENT_SCHEMA) +) + + +async def to_code(config): + var = await binary_sensor.new_binary_sensor(config) + await cg.register_component(var, config) + + source = await cg.get_variable(config[CONF_SOURCE_ID]) + cg.add(var.set_source(source)) diff --git a/esphome/components/switch/binary_sensor/switch_binary_sensor.cpp b/esphome/components/switch/binary_sensor/switch_binary_sensor.cpp new file mode 100644 index 0000000000..ba57154446 --- /dev/null +++ b/esphome/components/switch/binary_sensor/switch_binary_sensor.cpp @@ -0,0 +1,17 @@ +#include "switch_binary_sensor.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace switch_ { + +static const char *const TAG = "switch.binary_sensor"; + +void SwitchBinarySensor::setup() { + source_->add_on_state_callback([this](bool value) { this->publish_state(value); }); + this->publish_state(source_->state); +} + +void SwitchBinarySensor::dump_config() { LOG_BINARY_SENSOR("", "Switch Binary Sensor", this); } + +} // namespace switch_ +} // namespace esphome diff --git a/esphome/components/switch/binary_sensor/switch_binary_sensor.h b/esphome/components/switch/binary_sensor/switch_binary_sensor.h new file mode 100644 index 0000000000..5a947c2fb4 --- /dev/null +++ b/esphome/components/switch/binary_sensor/switch_binary_sensor.h @@ -0,0 +1,22 @@ +#pragma once + +#include "../switch.h" +#include "esphome/core/component.h" +#include "esphome/components/binary_sensor/binary_sensor.h" + +namespace esphome { +namespace switch_ { + +class SwitchBinarySensor : public binary_sensor::BinarySensor, public Component { + public: + void set_source(Switch *source) { source_ = source; } + void setup() override; + void dump_config() override; + float get_setup_priority() const override { return setup_priority::DATA; } + + protected: + Switch *source_; +}; + +} // namespace switch_ +} // namespace esphome diff --git a/esphome/components/sx1509/sx1509_gpio_pin.cpp b/esphome/components/sx1509/sx1509_gpio_pin.cpp index 56b51ae311..a74c8b60b8 100644 --- a/esphome/components/sx1509/sx1509_gpio_pin.cpp +++ b/esphome/components/sx1509/sx1509_gpio_pin.cpp @@ -1,5 +1,6 @@ #include "esphome/core/helpers.h" #include "esphome/core/log.h" +#include "sx1509.h" #include "sx1509_gpio_pin.h" namespace esphome { @@ -13,7 +14,7 @@ bool SX1509GPIOPin::digital_read() { return this->parent_->digital_read(this->pi void SX1509GPIOPin::digital_write(bool value) { this->parent_->digital_write(this->pin_, value != this->inverted_); } std::string SX1509GPIOPin::dump_summary() const { char buffer[32]; - snprintf(buffer, sizeof(buffer), "%u via sx1509", pin_); + snprintf(buffer, sizeof(buffer), "%u via sx1509", this->pin_); return buffer; } diff --git a/esphome/components/sx1509/sx1509_gpio_pin.h b/esphome/components/sx1509/sx1509_gpio_pin.h index 4d8aa5ec83..1cfa341ee7 100644 --- a/esphome/components/sx1509/sx1509_gpio_pin.h +++ b/esphome/components/sx1509/sx1509_gpio_pin.h @@ -1,6 +1,6 @@ #pragma once -#include "sx1509.h" +#include "esphome/core/gpio.h" namespace esphome { namespace sx1509 { @@ -15,10 +15,10 @@ class SX1509GPIOPin : public GPIOPin { void digital_write(bool value) override; std::string dump_summary() const override; - void set_parent(SX1509Component *parent) { parent_ = parent; } - void set_pin(uint8_t pin) { pin_ = pin; } - void set_inverted(bool inverted) { inverted_ = inverted; } - void set_flags(gpio::Flags flags) { flags_ = flags; } + void set_parent(SX1509Component *parent) { this->parent_ = parent; } + void set_pin(uint8_t pin) { this->pin_ = pin; } + void set_inverted(bool inverted) { this->inverted_ = inverted; } + void set_flags(gpio::Flags flags) { this->flags_ = flags; } protected: SX1509Component *parent_; diff --git a/esphome/components/uart/uart.h b/esphome/components/uart/uart.h index d41dbe26e6..dc6962fbae 100644 --- a/esphome/components/uart/uart.h +++ b/esphome/components/uart/uart.h @@ -40,7 +40,7 @@ class UARTDevice { int available() { return this->parent_->available(); } - void flush() { return this->parent_->flush(); } + void flush() { this->parent_->flush(); } // Compat APIs int read() { diff --git a/esphome/components/udp/udp_component.cpp b/esphome/components/udp/udp_component.cpp index a1c8889997..b8727ec423 100644 --- a/esphome/components/udp/udp_component.cpp +++ b/esphome/components/udp/udp_component.cpp @@ -434,7 +434,8 @@ static bool process_rolling_code(Provider &provider, uint8_t *&buf, const uint8_ void UDPComponent::process_(uint8_t *buf, const size_t len) { auto ping_key_seen = !this->ping_pong_enable_; if (len < 8) { - return ESP_LOGV(TAG, "Bad length %zu", len); + ESP_LOGV(TAG, "Bad length %zu", len); + return; } char namebuf[256]{}; uint8_t byte; @@ -442,31 +443,40 @@ void UDPComponent::process_(uint8_t *buf, const size_t len) { const uint8_t *end = buf + len; FuData rdata{}; auto magic = get_uint16(buf); - if (magic != MAGIC_NUMBER && magic != MAGIC_PING) - return ESP_LOGV(TAG, "Bad magic %X", magic); + if (magic != MAGIC_NUMBER && magic != MAGIC_PING) { + ESP_LOGV(TAG, "Bad magic %X", magic); + return; + } auto hlen = *buf++; if (hlen > len - 3) { - return ESP_LOGV(TAG, "Bad hostname length %u > %zu", hlen, len - 3); + ESP_LOGV(TAG, "Bad hostname length %u > %zu", hlen, len - 3); + return; } memcpy(namebuf, buf, hlen); if (strcmp(this->name_, namebuf) == 0) { - return ESP_LOGV(TAG, "Ignoring our own data"); + ESP_LOGV(TAG, "Ignoring our own data"); + return; } buf += hlen; - if (magic == MAGIC_PING) - return this->process_ping_request_(namebuf, buf, end - buf); + if (magic == MAGIC_PING) { + this->process_ping_request_(namebuf, buf, end - buf); + return; + } if (round4(len) != len) { - return ESP_LOGW(TAG, "Bad length %zu", len); + ESP_LOGW(TAG, "Bad length %zu", len); + return; } hlen = round4(hlen + 3); buf = start_ptr + hlen; if (buf == end) { - return ESP_LOGV(TAG, "No data after header"); + ESP_LOGV(TAG, "No data after header"); + return; } if (this->providers_.count(namebuf) == 0) { - return ESP_LOGVV(TAG, "Unknown hostname %s", namebuf); + ESP_LOGVV(TAG, "Unknown hostname %s", namebuf); + return; } auto &provider = this->providers_[namebuf]; // if encryption not used with this host, ping check is pointless since it would be easily spoofed. @@ -489,7 +499,8 @@ void UDPComponent::process_(uint8_t *buf, const size_t len) { if (!process_rolling_code(provider, buf, end)) return; } else if (byte != DATA_KEY) { - return ESP_LOGV(TAG, "Expected rolling_key or data_key, got %X", byte); + ESP_LOGV(TAG, "Expected rolling_key or data_key, got %X", byte); + return; } while (buf < end) { byte = *buf++; @@ -497,7 +508,8 @@ void UDPComponent::process_(uint8_t *buf, const size_t len) { continue; if (byte == PING_KEY) { if (end - buf < 4) { - return ESP_LOGV(TAG, "PING_KEY requires 4 more bytes"); + ESP_LOGV(TAG, "PING_KEY requires 4 more bytes"); + return; } auto key = get_uint32(buf); if (key == this->ping_key_) { @@ -515,21 +527,25 @@ void UDPComponent::process_(uint8_t *buf, const size_t len) { } if (byte == BINARY_SENSOR_KEY) { if (end - buf < 3) { - return ESP_LOGV(TAG, "Binary sensor key requires at least 3 more bytes"); + ESP_LOGV(TAG, "Binary sensor key requires at least 3 more bytes"); + return; } rdata.u32 = *buf++; } else if (byte == SENSOR_KEY) { if (end - buf < 6) { - return ESP_LOGV(TAG, "Sensor key requires at least 6 more bytes"); + ESP_LOGV(TAG, "Sensor key requires at least 6 more bytes"); + return; } rdata.u32 = get_uint32(buf); } else { - return ESP_LOGW(TAG, "Unknown key byte %X", byte); + ESP_LOGW(TAG, "Unknown key byte %X", byte); + return; } hlen = *buf++; if (end - buf < hlen) { - return ESP_LOGV(TAG, "Name length of %u not available", hlen); + ESP_LOGV(TAG, "Name length of %u not available", hlen); + return; } memset(namebuf, 0, sizeof namebuf); memcpy(namebuf, buf, hlen); diff --git a/esphome/components/ufire_ec/sensor.py b/esphome/components/ufire_ec/sensor.py index 9602d0c2d0..944fdfdee9 100644 --- a/esphome/components/ufire_ec/sensor.py +++ b/esphome/components/ufire_ec/sensor.py @@ -1,11 +1,12 @@ -import esphome.codegen as cg from esphome import automation -import esphome.config_validation as cv +import esphome.codegen as cg from esphome.components import i2c, sensor +import esphome.config_validation as cv from esphome.const import ( - CONF_ID, CONF_EC, + CONF_ID, CONF_TEMPERATURE, + CONF_TEMPERATURE_COMPENSATION, DEVICE_CLASS_EMPTY, DEVICE_CLASS_TEMPERATURE, ICON_EMPTY, @@ -18,7 +19,6 @@ DEPENDENCIES = ["i2c"] CONF_SOLUTION = "solution" CONF_TEMPERATURE_SENSOR = "temperature_sensor" -CONF_TEMPERATURE_COMPENSATION = "temperature_compensation" CONF_TEMPERATURE_COEFFICIENT = "temperature_coefficient" ufire_ec_ns = cg.esphome_ns.namespace("ufire_ec") diff --git a/esphome/components/uln2003/uln2003.cpp b/esphome/components/uln2003/uln2003.cpp index 1af9806906..991fe53487 100644 --- a/esphome/components/uln2003/uln2003.cpp +++ b/esphome/components/uln2003/uln2003.cpp @@ -40,7 +40,7 @@ void ULN2003::dump_config() { LOG_PIN(" Pin C: ", this->pin_c_); LOG_PIN(" Pin D: ", this->pin_d_); ESP_LOGCONFIG(TAG, " Sleep when done: %s", YESNO(this->sleep_when_done_)); - const char *step_mode_s = ""; + const char *step_mode_s; switch (this->step_mode_) { case ULN2003_STEP_MODE_FULL_STEP: step_mode_s = "FULL STEP"; @@ -51,6 +51,9 @@ void ULN2003::dump_config() { case ULN2003_STEP_MODE_WAVE_DRIVE: step_mode_s = "WAVE DRIVE"; break; + default: + step_mode_s = "UNKNOWN"; + break; } ESP_LOGCONFIG(TAG, " Step Mode: %s", step_mode_s); } diff --git a/esphome/components/waveshare_epaper/display.py b/esphome/components/waveshare_epaper/display.py index 8287788de5..d5240b2674 100644 --- a/esphome/components/waveshare_epaper/display.py +++ b/esphome/components/waveshare_epaper/display.py @@ -27,6 +27,9 @@ WaveshareEPaperBWR = waveshare_epaper_ns.class_( WaveshareEPaperTypeA = waveshare_epaper_ns.class_( "WaveshareEPaperTypeA", WaveshareEPaper ) +WaveshareEpaper1P54INBV2 = waveshare_epaper_ns.class_( + "WaveshareEPaper1P54InBV2", WaveshareEPaperBWR +) WaveshareEPaper2P7In = waveshare_epaper_ns.class_( "WaveshareEPaper2P7In", WaveshareEPaper ) @@ -76,6 +79,9 @@ WaveshareEPaper7P5InBV2 = waveshare_epaper_ns.class_( WaveshareEPaper7P5InBV3 = waveshare_epaper_ns.class_( "WaveshareEPaper7P5InBV3", WaveshareEPaper ) +WaveshareEPaper7P5InBV3BWR = waveshare_epaper_ns.class_( + "WaveshareEPaper7P5InBV3BWR", WaveshareEPaperBWR +) WaveshareEPaper7P5InV2 = waveshare_epaper_ns.class_( "WaveshareEPaper7P5InV2", WaveshareEPaper ) @@ -105,6 +111,7 @@ WaveshareEPaperTypeBModel = waveshare_epaper_ns.enum("WaveshareEPaperTypeBModel" MODELS = { "1.54in": ("a", WaveshareEPaperTypeAModel.WAVESHARE_EPAPER_1_54_IN), "1.54inv2": ("a", WaveshareEPaperTypeAModel.WAVESHARE_EPAPER_1_54_IN_V2), + "1.54inv2-b": ("b", WaveshareEpaper1P54INBV2), "2.13in": ("a", WaveshareEPaperTypeAModel.WAVESHARE_EPAPER_2_13_IN), "2.13inv2": ("a", WaveshareEPaperTypeAModel.WAVESHARE_EPAPER_2_13_IN_V2), "2.13in-ttgo": ("a", WaveshareEPaperTypeAModel.TTGO_EPAPER_2_13_IN), @@ -129,6 +136,7 @@ MODELS = { "7.50in": ("b", WaveshareEPaper7P5In), "7.50in-bv2": ("b", WaveshareEPaper7P5InBV2), "7.50in-bv3": ("b", WaveshareEPaper7P5InBV3), + "7.50in-bv3-bwr": ("b", WaveshareEPaper7P5InBV3BWR), "7.50in-bc": ("b", WaveshareEPaper7P5InBC), "7.50inv2": ("b", WaveshareEPaper7P5InV2), "7.50inv2alt": ("b", WaveshareEPaper7P5InV2alt), diff --git a/esphome/components/waveshare_epaper/waveshare_epaper.cpp b/esphome/components/waveshare_epaper/waveshare_epaper.cpp index 7c1d436673..cb3b19aa1a 100644 --- a/esphome/components/waveshare_epaper/waveshare_epaper.cpp +++ b/esphome/components/waveshare_epaper/waveshare_epaper.cpp @@ -808,6 +808,90 @@ void WaveshareEPaper2P7InV2::dump_config() { LOG_UPDATE_INTERVAL(this); } +// ======================================================== +// 1.54inch_v2_e-paper_b +// ======================================================== +// Datasheet: +// - https://files.waveshare.com/upload/9/9e/1.54inch-e-paper-b-v2-specification.pdf +// - https://www.waveshare.com/wiki/1.54inch_e-Paper_Module_(B)_Manual + +void WaveshareEPaper1P54InBV2::initialize() { + this->reset_(); + + this->wait_until_idle_(); + + this->command(0x12); + this->wait_until_idle_(); + + this->command(0x01); + this->data(0xC7); + this->data(0x00); + this->data(0x01); + + this->command(0x11); // data entry mode + this->data(0x01); + + this->command(0x44); // set Ram-X address start/end position + this->data(0x00); + this->data(0x18); // 0x18-->(24+1)*8=200 + + this->command(0x45); // set Ram-Y address start/end position + this->data(0xC7); // 0xC7-->(199+1)=200 + this->data(0x00); + this->data(0x00); + this->data(0x00); + + this->command(0x3C); // BorderWavefrom + this->data(0x05); + + this->command(0x18); // Read built-in temperature sensor + this->data(0x80); + + this->command(0x4E); // set RAM x address count to 0; + this->data(0x00); + this->command(0x4F); // set RAM y address count to 0X199; + this->data(0xC7); + this->data(0x00); + + this->wait_until_idle_(); +} + +void HOT WaveshareEPaper1P54InBV2::display() { + uint32_t buf_len_half = this->get_buffer_length_() >> 1; + this->initialize(); + + // COMMAND DATA START TRANSMISSION 1 (BLACK) + this->command(0x24); + delay(2); + for (uint32_t i = 0; i < buf_len_half; i++) { + this->data(~this->buffer_[i]); + } + delay(2); + + // COMMAND DATA START TRANSMISSION 2 (RED) + this->command(0x26); + delay(2); + for (uint32_t i = buf_len_half; i < buf_len_half * 2u; i++) { + this->data(this->buffer_[i]); + } + this->command(0x22); + this->data(0xf7); + this->command(0x20); + this->wait_until_idle_(); + + this->deep_sleep(); +} +int WaveshareEPaper1P54InBV2::get_height_internal() { return 200; } +int WaveshareEPaper1P54InBV2::get_width_internal() { return 200; } +void WaveshareEPaper1P54InBV2::dump_config() { + LOG_DISPLAY("", "Waveshare E-Paper", this); + ESP_LOGCONFIG(TAG, " Model: 1.54in V2 B"); + LOG_PIN(" Reset Pin: ", this->reset_pin_); + LOG_PIN(" DC Pin: ", this->dc_pin_); + LOG_PIN(" Busy Pin: ", this->busy_pin_); + LOG_UPDATE_INTERVAL(this); +} + // ======================================================== // 2.7inch_e-paper_b // ======================================================== @@ -2315,6 +2399,113 @@ void WaveshareEPaper7P5InBV3::dump_config() { LOG_UPDATE_INTERVAL(this); } +void WaveshareEPaper7P5InBV3BWR::initialize() { this->init_display_(); } +bool WaveshareEPaper7P5InBV3BWR::wait_until_idle_() { + if (this->busy_pin_ == nullptr) { + return true; + } + + const uint32_t start = millis(); + while (this->busy_pin_->digital_read()) { + this->command(0x71); + if (millis() - start > this->idle_timeout_()) { + ESP_LOGI(TAG, "Timeout while displaying image!"); + return false; + } + App.feed_wdt(); + delay(10); + } + delay(200); // NOLINT + return true; +}; +void WaveshareEPaper7P5InBV3BWR::init_display_() { + this->reset_(); + + // COMMAND POWER SETTING + this->command(0x01); + + // 1-0=11: internal power + this->data(0x07); + this->data(0x17); // VGH&VGL + this->data(0x3F); // VSH + this->data(0x26); // VSL + this->data(0x11); // VSHR + + // VCOM DC Setting + this->command(0x82); + this->data(0x24); // VCOM + + // Booster Setting + this->command(0x06); + this->data(0x27); + this->data(0x27); + this->data(0x2F); + this->data(0x17); + + // POWER ON + this->command(0x04); + + delay(100); // NOLINT + this->wait_until_idle_(); + // COMMAND PANEL SETTING + this->command(0x00); + this->data(0x0F); // KW-3f KWR-2F BWROTP 0f BWOTP 1f + + // COMMAND RESOLUTION SETTING + this->command(0x61); + this->data(0x03); // source 800 + this->data(0x20); + this->data(0x01); // gate 480 + this->data(0xE0); + // COMMAND ...? + this->command(0x15); + this->data(0x00); + // COMMAND VCOM AND DATA INTERVAL SETTING + this->command(0x50); + this->data(0x20); + this->data(0x00); + // COMMAND TCON SETTING + this->command(0x60); + this->data(0x22); + // Resolution setting + this->command(0x65); + this->data(0x00); + this->data(0x00); // 800*480 + this->data(0x00); + this->data(0x00); +}; +void HOT WaveshareEPaper7P5InBV3BWR::display() { + this->init_display_(); + const uint32_t buf_len = this->get_buffer_length_() / 2u; + + this->command(0x10); // Send BW data Transmission + delay(2); + for (uint32_t i = 0; i < buf_len; i++) { + this->data(this->buffer_[i]); + } + + this->command(0x13); // Send red data Transmission + delay(2); + for (uint32_t i = 0; i < buf_len; i++) { + this->data(this->buffer_[i + buf_len]); + } + + this->command(0x12); // Display Refresh + delay(100); // NOLINT + this->wait_until_idle_(); + this->deep_sleep(); +} +int WaveshareEPaper7P5InBV3BWR::get_width_internal() { return 800; } +int WaveshareEPaper7P5InBV3BWR::get_height_internal() { return 480; } +void WaveshareEPaper7P5InBV3BWR::dump_config() { + LOG_DISPLAY("", "Waveshare E-Paper", this); + ESP_LOGCONFIG(TAG, " Model: 7.5in-bv3 BWR-Mode"); + LOG_PIN(" Reset Pin: ", this->reset_pin_); + LOG_PIN(" DC Pin: ", this->dc_pin_); + LOG_PIN(" Busy Pin: ", this->busy_pin_); + LOG_UPDATE_INTERVAL(this); +} + void WaveshareEPaper7P5In::initialize() { // COMMAND POWER SETTING this->command(0x01); diff --git a/esphome/components/waveshare_epaper/waveshare_epaper.h b/esphome/components/waveshare_epaper/waveshare_epaper.h index 7572982a20..4544f7df59 100644 --- a/esphome/components/waveshare_epaper/waveshare_epaper.h +++ b/esphome/components/waveshare_epaper/waveshare_epaper.h @@ -166,6 +166,24 @@ enum WaveshareEPaperTypeBModel { WAVESHARE_EPAPER_13_3_IN_K, }; +class WaveshareEPaper1P54InBV2 : public WaveshareEPaperBWR { + public: + void initialize() override; + + void display() override; + + void dump_config() override; + + void deep_sleep() override { + this->command(0x10); + this->data(0x01); + } + + protected: + int get_width_internal() override; + int get_height_internal() override; +}; + class WaveshareEPaper2P7In : public WaveshareEPaper { public: void initialize() override; @@ -619,6 +637,44 @@ class WaveshareEPaper7P5InBV3 : public WaveshareEPaper { void init_display_(); }; +class WaveshareEPaper7P5InBV3BWR : public WaveshareEPaperBWR { + public: + bool wait_until_idle_(); + + void initialize() override; + + void display() override; + + void dump_config() override; + + void deep_sleep() override { + this->command(0x02); // Power off + this->wait_until_idle_(); + this->command(0x07); // Deep sleep + this->data(0xA5); + } + + void clear_screen(); + + protected: + int get_width_internal() override; + + int get_height_internal() override; + + void reset_() { + if (this->reset_pin_ != nullptr) { + this->reset_pin_->digital_write(true); + delay(200); // NOLINT + this->reset_pin_->digital_write(false); + delay(5); + this->reset_pin_->digital_write(true); + delay(200); // NOLINT + } + }; + + void init_display_(); +}; + class WaveshareEPaper7P5InBC : public WaveshareEPaper { public: void initialize() override; diff --git a/esphome/components/wiegand/__init__.py b/esphome/components/wiegand/__init__.py index 7b05c43198..962ac4c373 100644 --- a/esphome/components/wiegand/__init__.py +++ b/esphome/components/wiegand/__init__.py @@ -1,8 +1,8 @@ +from esphome import automation, pins import esphome.codegen as cg -import esphome.config_validation as cv -from esphome import pins, automation from esphome.components import key_provider -from esphome.const import CONF_ID, CONF_ON_TAG, CONF_TRIGGER_ID +import esphome.config_validation as cv +from esphome.const import CONF_ID, CONF_ON_KEY, CONF_ON_TAG, CONF_TRIGGER_ID CODEOWNERS = ["@ssieb"] @@ -25,7 +25,6 @@ WiegandKeyTrigger = wiegand_ns.class_( CONF_D0 = "d0" CONF_D1 = "d1" -CONF_ON_KEY = "on_key" CONF_ON_RAW = "on_raw" CONFIG_SCHEMA = cv.Schema( diff --git a/esphome/components/wifi/__init__.py b/esphome/components/wifi/__init__.py index ea03cc16d1..ad1a4f5262 100644 --- a/esphome/components/wifi/__init__.py +++ b/esphome/components/wifi/__init__.py @@ -27,6 +27,7 @@ from esphome.const import ( CONF_NETWORKS, CONF_ON_CONNECT, CONF_ON_DISCONNECT, + CONF_ON_ERROR, CONF_PASSWORD, CONF_POWER_SAVE_MODE, CONF_PRIORITY, @@ -34,6 +35,7 @@ from esphome.const import ( CONF_SSID, CONF_STATIC_IP, CONF_SUBNET, + CONF_TIMEOUT, CONF_TTLS_PHASE_2, CONF_USE_ADDRESS, CONF_USERNAME, @@ -46,6 +48,7 @@ from . import wpa2_eap AUTO_LOAD = ["network"] NO_WIFI_VARIANTS = [const.VARIANT_ESP32H2] +CONF_SAVE = "save" wifi_ns = cg.esphome_ns.namespace("wifi") EAPAuth = wifi_ns.struct("EAPAuth") @@ -63,6 +66,9 @@ WiFiConnectedCondition = wifi_ns.class_("WiFiConnectedCondition", Condition) WiFiEnabledCondition = wifi_ns.class_("WiFiEnabledCondition", Condition) WiFiEnableAction = wifi_ns.class_("WiFiEnableAction", automation.Action) WiFiDisableAction = wifi_ns.class_("WiFiDisableAction", automation.Action) +WiFiConfigureAction = wifi_ns.class_( + "WiFiConfigureAction", automation.Action, cg.Component +) def validate_password(value): @@ -483,3 +489,39 @@ async def wifi_enable_to_code(config, action_id, template_arg, args): @automation.register_action("wifi.disable", WiFiDisableAction, cv.Schema({})) async def wifi_disable_to_code(config, action_id, template_arg, args): return cg.new_Pvariable(action_id, template_arg) + + +@automation.register_action( + "wifi.configure", + WiFiConfigureAction, + cv.Schema( + { + cv.Required(CONF_SSID): cv.templatable(cv.ssid), + cv.Required(CONF_PASSWORD): cv.templatable(validate_password), + cv.Optional(CONF_SAVE, default=True): cv.templatable(cv.boolean), + cv.Optional(CONF_TIMEOUT, default="30000ms"): cv.templatable( + cv.positive_time_period_milliseconds + ), + cv.Optional(CONF_ON_CONNECT): automation.validate_automation(single=True), + cv.Optional(CONF_ON_ERROR): automation.validate_automation(single=True), + } + ), +) +async def wifi_set_sta_to_code(config, action_id, template_arg, args): + var = cg.new_Pvariable(action_id, template_arg) + ssid = await cg.templatable(config[CONF_SSID], args, cg.std_string) + password = await cg.templatable(config[CONF_PASSWORD], args, cg.std_string) + save = await cg.templatable(config[CONF_SAVE], args, cg.bool_) + timeout = await cg.templatable(config.get(CONF_TIMEOUT), args, cg.uint32) + cg.add(var.set_ssid(ssid)) + cg.add(var.set_password(password)) + cg.add(var.set_save(save)) + cg.add(var.set_connection_timeout(timeout)) + if on_connect_config := config.get(CONF_ON_CONNECT): + await automation.build_automation( + var.get_connect_trigger(), [], on_connect_config + ) + if on_error_config := config.get(CONF_ON_ERROR): + await automation.build_automation(var.get_error_trigger(), [], on_error_config) + await cg.register_component(var, config) + return var diff --git a/esphome/components/wifi/wifi_component.cpp b/esphome/components/wifi/wifi_component.cpp index 8788711d5a..eef962b8c4 100644 --- a/esphome/components/wifi/wifi_component.cpp +++ b/esphome/components/wifi/wifi_component.cpp @@ -444,7 +444,7 @@ void WiFiComponent::print_connect_params_() { if (this->selected_ap_.get_bssid().has_value()) { ESP_LOGV(TAG, " Priority: %.1f", this->get_sta_priority(*this->selected_ap_.get_bssid())); } - ESP_LOGCONFIG(TAG, " Channel: %" PRId32, wifi_channel_()); + ESP_LOGCONFIG(TAG, " Channel: %" PRId32, get_wifi_channel()); ESP_LOGCONFIG(TAG, " Subnet: %s", wifi_subnet_mask_().str().c_str()); ESP_LOGCONFIG(TAG, " Gateway: %s", wifi_gateway_ip_().str().c_str()); ESP_LOGCONFIG(TAG, " DNS1: %s", wifi_dns_ip_(0).str().c_str()); @@ -763,7 +763,7 @@ void WiFiComponent::load_fast_connect_settings_() { void WiFiComponent::save_fast_connect_settings_() { bssid_t bssid = wifi_bssid(); - uint8_t channel = wifi_channel_(); + uint8_t channel = get_wifi_channel(); if (bssid != this->selected_ap_.get_bssid() || channel != this->selected_ap_.get_channel()) { SavedWifiFastConnectSettings fast_connect_save{}; diff --git a/esphome/components/wifi/wifi_component.h b/esphome/components/wifi/wifi_component.h index dde0d1d5a5..abedfab3a6 100644 --- a/esphome/components/wifi/wifi_component.h +++ b/esphome/components/wifi/wifi_component.h @@ -209,6 +209,7 @@ class WiFiComponent : public Component { WiFiComponent(); void set_sta(const WiFiAP &ap); + WiFiAP get_sta() { return this->selected_ap_; } void add_sta(const WiFiAP &ap); void clear_sta(); @@ -317,6 +318,8 @@ class WiFiComponent : public Component { Trigger<> *get_connect_trigger() const { return this->connect_trigger_; }; Trigger<> *get_disconnect_trigger() const { return this->disconnect_trigger_; }; + int32_t get_wifi_channel(); + protected: static std::string format_mac_addr(const uint8_t mac[6]); @@ -344,7 +347,7 @@ class WiFiComponent : public Component { #endif // USE_WIFI_AP bool wifi_disconnect_(); - int32_t wifi_channel_(); + network::IPAddress wifi_subnet_mask_(); network::IPAddress wifi_gateway_ip_(); network::IPAddress wifi_dns_ip_(int num); @@ -441,6 +444,84 @@ template class WiFiDisableAction : public Action { void play(Ts... x) override { global_wifi_component->disable(); } }; +template class WiFiConfigureAction : public Action, public Component { + public: + TEMPLATABLE_VALUE(std::string, ssid) + TEMPLATABLE_VALUE(std::string, password) + TEMPLATABLE_VALUE(bool, save) + TEMPLATABLE_VALUE(uint32_t, connection_timeout) + + void play(Ts... x) override { + auto ssid = this->ssid_.value(x...); + auto password = this->password_.value(x...); + // Avoid multiple calls + if (this->connecting_) + return; + // If already connected to the same AP, do nothing + if (global_wifi_component->wifi_ssid() == ssid) { + // Callback to notify the user that the connection was successful + this->connect_trigger_->trigger(); + return; + } + // Create a new WiFiAP object with the new SSID and password + this->new_sta_.set_ssid(ssid); + this->new_sta_.set_password(password); + // Save the current STA + this->old_sta_ = global_wifi_component->get_sta(); + // Disable WiFi + global_wifi_component->disable(); + // Set the state to connecting + this->connecting_ = true; + // Store the new STA so once the WiFi is enabled, it will connect to it + // This is necessary because the WiFiComponent will raise an error and fallback to the saved STA + // if trying to connect to a new STA while already connected to another one + if (this->save_.value(x...)) { + global_wifi_component->save_wifi_sta(new_sta_.get_ssid(), new_sta_.get_password()); + } else { + global_wifi_component->set_sta(new_sta_); + } + // Enable WiFi + global_wifi_component->enable(); + // Set timeout for the connection + this->set_timeout("wifi-connect-timeout", this->connection_timeout_.value(x...), [this]() { + this->connecting_ = false; + // If the timeout is reached, stop connecting and revert to the old AP + global_wifi_component->disable(); + global_wifi_component->save_wifi_sta(old_sta_.get_ssid(), old_sta_.get_password()); + global_wifi_component->enable(); + // Callback to notify the user that the connection failed + this->error_trigger_->trigger(); + }); + } + + Trigger<> *get_connect_trigger() const { return this->connect_trigger_; } + Trigger<> *get_error_trigger() const { return this->error_trigger_; } + + void loop() override { + if (!this->connecting_) + return; + if (global_wifi_component->is_connected()) { + // The WiFi is connected, stop the timeout and reset the connecting flag + this->cancel_timeout("wifi-connect-timeout"); + this->connecting_ = false; + if (global_wifi_component->wifi_ssid() == this->new_sta_.get_ssid()) { + // Callback to notify the user that the connection was successful + this->connect_trigger_->trigger(); + } else { + // Callback to notify the user that the connection failed + this->error_trigger_->trigger(); + } + } + } + + protected: + bool connecting_{false}; + WiFiAP new_sta_; + WiFiAP old_sta_; + Trigger<> *connect_trigger_{new Trigger<>()}; + Trigger<> *error_trigger_{new Trigger<>()}; +}; + } // namespace wifi } // namespace esphome #endif diff --git a/esphome/components/wifi/wifi_component_esp32_arduino.cpp b/esphome/components/wifi/wifi_component_esp32_arduino.cpp index ee00e2ac6c..bc10bbd1e5 100644 --- a/esphome/components/wifi/wifi_component_esp32_arduino.cpp +++ b/esphome/components/wifi/wifi_component_esp32_arduino.cpp @@ -815,7 +815,7 @@ bssid_t WiFiComponent::wifi_bssid() { } std::string WiFiComponent::wifi_ssid() { return WiFi.SSID().c_str(); } int8_t WiFiComponent::wifi_rssi() { return WiFi.RSSI(); } -int32_t WiFiComponent::wifi_channel_() { return WiFi.channel(); } +int32_t WiFiComponent::get_wifi_channel() { return WiFi.channel(); } network::IPAddress WiFiComponent::wifi_subnet_mask_() { return network::IPAddress(WiFi.subnetMask()); } network::IPAddress WiFiComponent::wifi_gateway_ip_() { return network::IPAddress(WiFi.gatewayIP()); } network::IPAddress WiFiComponent::wifi_dns_ip_(int num) { return network::IPAddress(WiFi.dnsIP(num)); } diff --git a/esphome/components/wifi/wifi_component_esp8266.cpp b/esphome/components/wifi/wifi_component_esp8266.cpp index 14506f569c..8e1c2e70d8 100644 --- a/esphome/components/wifi/wifi_component_esp8266.cpp +++ b/esphome/components/wifi/wifi_component_esp8266.cpp @@ -841,7 +841,7 @@ bssid_t WiFiComponent::wifi_bssid() { } std::string WiFiComponent::wifi_ssid() { return WiFi.SSID().c_str(); } int8_t WiFiComponent::wifi_rssi() { return WiFi.RSSI(); } -int32_t WiFiComponent::wifi_channel_() { return WiFi.channel(); } +int32_t WiFiComponent::get_wifi_channel() { return WiFi.channel(); } network::IPAddress WiFiComponent::wifi_subnet_mask_() { return {(const ip_addr_t *) WiFi.subnetMask()}; } network::IPAddress WiFiComponent::wifi_gateway_ip_() { return {(const ip_addr_t *) WiFi.gatewayIP()}; } network::IPAddress WiFiComponent::wifi_dns_ip_(int num) { return {(const ip_addr_t *) WiFi.dnsIP(num)}; } diff --git a/esphome/components/wifi/wifi_component_esp_idf.cpp b/esphome/components/wifi/wifi_component_esp_idf.cpp index 3074ffbe1b..1af271345f 100644 --- a/esphome/components/wifi/wifi_component_esp_idf.cpp +++ b/esphome/components/wifi/wifi_component_esp_idf.cpp @@ -989,7 +989,7 @@ int8_t WiFiComponent::wifi_rssi() { } return info.rssi; } -int32_t WiFiComponent::wifi_channel_() { +int32_t WiFiComponent::get_wifi_channel() { uint8_t primary; wifi_second_chan_t second; esp_err_t err = esp_wifi_get_channel(&primary, &second); diff --git a/esphome/components/wifi/wifi_component_libretiny.cpp b/esphome/components/wifi/wifi_component_libretiny.cpp index afb30c3bcf..b02f8ef0ce 100644 --- a/esphome/components/wifi/wifi_component_libretiny.cpp +++ b/esphome/components/wifi/wifi_component_libretiny.cpp @@ -473,7 +473,7 @@ bssid_t WiFiComponent::wifi_bssid() { } std::string WiFiComponent::wifi_ssid() { return WiFi.SSID().c_str(); } int8_t WiFiComponent::wifi_rssi() { return WiFi.RSSI(); } -int32_t WiFiComponent::wifi_channel_() { return WiFi.channel(); } +int32_t WiFiComponent::get_wifi_channel() { return WiFi.channel(); } network::IPAddress WiFiComponent::wifi_subnet_mask_() { return {WiFi.subnetMask()}; } network::IPAddress WiFiComponent::wifi_gateway_ip_() { return {WiFi.gatewayIP()}; } network::IPAddress WiFiComponent::wifi_dns_ip_(int num) { return {WiFi.dnsIP(num)}; } diff --git a/esphome/components/wifi/wifi_component_pico_w.cpp b/esphome/components/wifi/wifi_component_pico_w.cpp index bac986d899..23fd766abe 100644 --- a/esphome/components/wifi/wifi_component_pico_w.cpp +++ b/esphome/components/wifi/wifi_component_pico_w.cpp @@ -189,7 +189,7 @@ bssid_t WiFiComponent::wifi_bssid() { } std::string WiFiComponent::wifi_ssid() { return WiFi.SSID().c_str(); } int8_t WiFiComponent::wifi_rssi() { return WiFi.RSSI(); } -int32_t WiFiComponent::wifi_channel_() { return WiFi.channel(); } +int32_t WiFiComponent::get_wifi_channel() { return WiFi.channel(); } network::IPAddresses WiFiComponent::wifi_sta_ip_addresses() { network::IPAddresses addresses; diff --git a/esphome/components/wireguard/wireguard.cpp b/esphome/components/wireguard/wireguard.cpp index 7b4011cb79..431bf52227 100644 --- a/esphome/components/wireguard/wireguard.cpp +++ b/esphome/components/wireguard/wireguard.cpp @@ -37,7 +37,7 @@ void Wireguard::setup() { this->wg_config_.netmask = this->netmask_.c_str(); this->wg_config_.persistent_keepalive = this->keepalive_; - if (this->preshared_key_.length() > 0) + if (!this->preshared_key_.empty()) this->wg_config_.preshared_key = this->preshared_key_.c_str(); this->publish_enabled_state(); @@ -137,7 +137,7 @@ void Wireguard::dump_config() { ESP_LOGCONFIG(TAG, " Peer Port: " LOG_SECRET("%d"), this->peer_port_); ESP_LOGCONFIG(TAG, " Peer Public Key: " LOG_SECRET("%s"), this->peer_public_key_.c_str()); ESP_LOGCONFIG(TAG, " Peer Pre-shared Key: " LOG_SECRET("%s"), - (this->preshared_key_.length() > 0 ? mask_key(this->preshared_key_).c_str() : "NOT IN USE")); + (!this->preshared_key_.empty() ? mask_key(this->preshared_key_).c_str() : "NOT IN USE")); ESP_LOGCONFIG(TAG, " Peer Allowed IPs:"); for (auto &allowed_ip : this->allowed_ips_) { ESP_LOGCONFIG(TAG, " - %s/%s", std::get<0>(allowed_ip).c_str(), std::get<1>(allowed_ip).c_str()); diff --git a/esphome/components/xiaomi_ble/xiaomi_ble.cpp b/esphome/components/xiaomi_ble/xiaomi_ble.cpp index 85434341cc..04e0724ba7 100644 --- a/esphome/components/xiaomi_ble/xiaomi_ble.cpp +++ b/esphome/components/xiaomi_ble/xiaomi_ble.cpp @@ -249,7 +249,7 @@ optional parse_xiaomi_header(const esp32_ble_tracker::Service } bool decrypt_xiaomi_payload(std::vector &raw, const uint8_t *bindkey, const uint64_t &address) { - if (!((raw.size() == 19) || ((raw.size() >= 22) && (raw.size() <= 24)))) { + if ((raw.size() != 19) && ((raw.size() < 22) || (raw.size() > 24))) { ESP_LOGVV(TAG, "decrypt_xiaomi_payload(): data packet has wrong size (%d)!", raw.size()); ESP_LOGVV(TAG, " Packet : %s", format_hex_pretty(raw.data(), raw.size()).c_str()); return false; diff --git a/esphome/config_validation.py b/esphome/config_validation.py index 98b81ec328..ebfb2631c3 100644 --- a/esphome/config_validation.py +++ b/esphome/config_validation.py @@ -1839,8 +1839,6 @@ def validate_registry_entry(name, registry): def none(value): if value in ("none", "None"): return None - if boolean(value) is False: - return None raise Invalid("Must be none") @@ -1912,17 +1910,23 @@ MQTT_COMMAND_COMPONENT_SCHEMA = MQTT_COMPONENT_SCHEMA.extend( } ) + +def _validate_entity_name(value): + value = string(value) + try: + value = none(value) # pylint: disable=assignment-from-none + except Invalid: + pass + else: + requires_friendly_name( + "Name cannot be None when esphome->friendly_name is not set!" + )(value) + return value + + ENTITY_BASE_SCHEMA = Schema( { - Optional(CONF_NAME): Any( - All( - none, - requires_friendly_name( - "Name cannot be None when esphome->friendly_name is not set!" - ), - ), - string, - ), + Optional(CONF_NAME): _validate_entity_name, Optional(CONF_INTERNAL): boolean, Optional(CONF_DISABLED_BY_DEFAULT, default=False): boolean, Optional(CONF_ICON): icon, diff --git a/esphome/const.py b/esphome/const.py index ae7feda6d8..e9d7612e4a 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2024.11.3" +__version__ = "2024.12.0" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" VALID_SUBSTITUTIONS_CHARACTERS = ( @@ -131,7 +131,9 @@ CONF_CLIENT_CERTIFICATE = "client_certificate" CONF_CLIENT_CERTIFICATE_KEY = "client_certificate_key" CONF_CLIENT_ID = "client_id" CONF_CLK_PIN = "clk_pin" +CONF_CLOCK_DIVIDER = "clock_divider" CONF_CLOCK_PIN = "clock_pin" +CONF_CLOCK_RESOLUTION = "clock_resolution" CONF_CLOSE_ACTION = "close_action" CONF_CLOSE_DURATION = "close_duration" CONF_CLOSE_ENDSTOP = "close_endstop" @@ -528,6 +530,7 @@ CONF_MULTIPLE = "multiple" CONF_MULTIPLEXER = "multiplexer" CONF_MULTIPLY = "multiply" CONF_NAME = "name" +CONF_NAME_ADD_MAC_SUFFIX = "name_add_mac_suffix" CONF_NAME_FONT = "name_font" CONF_NBITS = "nbits" CONF_NEC = "nec" @@ -574,6 +577,7 @@ CONF_ON_FINGER_SCAN_UNMATCHED = "on_finger_scan_unmatched" CONF_ON_FINISHED_WRITE = "on_finished_write" CONF_ON_IDLE = "on_idle" CONF_ON_JSON_MESSAGE = "on_json_message" +CONF_ON_KEY = "on_key" CONF_ON_LOCK = "on_lock" CONF_ON_LOOP = "on_loop" CONF_ON_MESSAGE = "on_message" @@ -690,6 +694,7 @@ CONF_PRIORITY = "priority" CONF_PROJECT = "project" CONF_PROTOCOL = "protocol" CONF_PUBLISH_INITIAL_STATE = "publish_initial_state" +CONF_PUBLISH_NAN_AS_NONE = "publish_nan_as_none" CONF_PULL_MODE = "pull_mode" CONF_PULLDOWN = "pulldown" CONF_PULLUP = "pullup" @@ -736,6 +741,7 @@ CONF_RGB_ORDER = "rgb_order" CONF_RGBW = "rgbw" CONF_RISING_EDGE = "rising_edge" CONF_RMT_CHANNEL = "rmt_channel" +CONF_RMT_SYMBOLS = "rmt_symbols" CONF_ROTATION = "rotation" CONF_ROW = "row" CONF_RS_PIN = "rs_pin" @@ -861,6 +867,7 @@ CONF_TARGET_TEMPERATURE_LOW_COMMAND_TOPIC = "target_temperature_low_command_topi CONF_TARGET_TEMPERATURE_LOW_STATE_TOPIC = "target_temperature_low_state_topic" CONF_TARGET_TEMPERATURE_STATE_TOPIC = "target_temperature_state_topic" CONF_TEMPERATURE = "temperature" +CONF_TEMPERATURE_COMPENSATION = "temperature_compensation" CONF_TEMPERATURE_OFFSET = "temperature_offset" CONF_TEMPERATURE_SOURCE = "temperature_source" CONF_TEMPERATURE_STEP = "temperature_step" @@ -914,6 +921,7 @@ CONF_UPDATE_ON_BOOT = "update_on_boot" CONF_URL = "url" CONF_USE_ABBREVIATIONS = "use_abbreviations" CONF_USE_ADDRESS = "use_address" +CONF_USE_DMA = "use_dma" CONF_USE_FAHRENHEIT = "use_fahrenheit" CONF_USERNAME = "username" CONF_UUID = "uuid" @@ -998,6 +1006,7 @@ ICON_GRAIN = "mdi:grain" ICON_GYROSCOPE_X = "mdi:axis-x-rotate-clockwise" ICON_GYROSCOPE_Y = "mdi:axis-y-rotate-clockwise" ICON_GYROSCOPE_Z = "mdi:axis-z-rotate-clockwise" +ICON_HEART_PULSE = "mdi:heart-pulse" ICON_HEATING_COIL = "mdi:heating-coil" ICON_KEY_PLUS = "mdi:key-plus" ICON_LIGHTBULB = "mdi:lightbulb" @@ -1037,6 +1046,7 @@ ICON_WEATHER_WINDY = "mdi:weather-windy" ICON_WIFI = "mdi:wifi" UNIT_AMPERE = "A" +UNIT_BEATS_PER_MINUTE = "bpm" UNIT_BECQUEREL_PER_CUBIC_METER = "Bq/m³" UNIT_BYTES = "B" UNIT_CELSIUS = "°C" diff --git a/esphome/core/component.cpp b/esphome/core/component.cpp index ae73a451d9..a6224a17c0 100644 --- a/esphome/core/component.cpp +++ b/esphome/core/component.cpp @@ -67,7 +67,7 @@ bool Component::cancel_retry(const std::string &name) { // NOLINT } void Component::set_timeout(const std::string &name, uint32_t timeout, std::function &&f) { // NOLINT - return App.scheduler.set_timeout(this, name, timeout, std::move(f)); + App.scheduler.set_timeout(this, name, timeout, std::move(f)); } bool Component::cancel_timeout(const std::string &name) { // NOLINT diff --git a/esphome/core/config.py b/esphome/core/config.py index 367e61c413..eee8b73934 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -21,6 +21,7 @@ from esphome.const import ( CONF_LIBRARIES, CONF_MIN_VERSION, CONF_NAME, + CONF_NAME_ADD_MAC_SUFFIX, CONF_ON_BOOT, CONF_ON_LOOP, CONF_ON_SHUTDOWN, @@ -59,8 +60,6 @@ ProjectUpdateTrigger = cg.esphome_ns.class_( VERSION_REGEX = re.compile(r"^[0-9]+\.[0-9]+\.[0-9]+(?:[ab]\d+)?$") -CONF_NAME_ADD_MAC_SUFFIX = "name_add_mac_suffix" - VALID_INCLUDE_EXTS = {".h", ".hpp", ".tcc", ".ino", ".cpp", ".c"} diff --git a/esphome/core/defines.h b/esphome/core/defines.h index 3798ddba6a..eb3b20d007 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -84,7 +84,6 @@ // Arduino-specific feature flags #ifdef USE_ARDUINO -#define USE_CAPTIVE_PORTAL #define USE_PROMETHEUS #define USE_WIFI_WPA2_EAP #endif @@ -97,6 +96,7 @@ // ESP32-specific feature flags #ifdef USE_ESP32 #define USE_BLUETOOTH_PROXY +#define USE_CAPTIVE_PORTAL #define USE_ESP32_BLE #define USE_ESP32_BLE_CLIENT #define USE_ESP32_BLE_SERVER @@ -135,6 +135,7 @@ #ifdef USE_ESP8266 #define USE_ADC_SENSOR_VCC #define USE_ARDUINO_VERSION_CODE VERSION_CODE(3, 1, 2) +#define USE_CAPTIVE_PORTAL #define USE_ESP8266_PREFERENCES_FLASH #define USE_HTTP_REQUEST_ESP8266_HTTPS #define USE_SOCKET_IMPL_LWIP_TCP @@ -159,6 +160,7 @@ #endif #ifdef USE_LIBRETINY +#define USE_CAPTIVE_PORTAL #define USE_SOCKET_IMPL_LWIP_SOCKETS #define USE_WEBSERVER #define USE_WEBSERVER_PORT 80 // NOLINT diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index befc84516c..b11615204e 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -259,10 +259,15 @@ bool random_bytes(uint8_t *data, size_t len) { bool str_equals_case_insensitive(const std::string &a, const std::string &b) { return strcasecmp(a.c_str(), b.c_str()) == 0; } +#if ESP_IDF_VERSION_MAJOR >= 5 +bool str_startswith(const std::string &str, const std::string &start) { return str.starts_with(start); } +bool str_endswith(const std::string &str, const std::string &end) { return str.ends_with(end); } +#else bool str_startswith(const std::string &str, const std::string &start) { return str.rfind(start, 0) == 0; } bool str_endswith(const std::string &str, const std::string &end) { return str.rfind(end) == (str.size() - end.size()); } +#endif std::string str_truncate(const std::string &str, size_t length) { return str.length() > length ? str.substr(0, length) : str; } @@ -293,7 +298,7 @@ std::string str_sanitize(const std::string &str) { std::replace_if( out.begin(), out.end(), [](const char &c) { - return !(c == '-' || c == '_' || (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')); + return c != '-' && c != '_' && (c < '0' || c > '9') && (c < 'a' || c > 'z') && (c < 'A' || c > 'Z'); }, '_'); return out; @@ -762,7 +767,8 @@ bool mac_address_is_valid(const uint8_t *mac) { return !(is_all_zeros || is_all_ones); } -void delay_microseconds_safe(uint32_t us) { // avoids CPU locks that could trigger WDT or affect WiFi/BT stability +void IRAM_ATTR HOT delay_microseconds_safe(uint32_t us) { + // avoids CPU locks that could trigger WDT or affect WiFi/BT stability uint32_t start = micros(); const uint32_t lag = 5000; // microseconds, specifies the maximum time for a CPU busy-loop. diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index 305ec47f76..fcbd8d8683 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -565,7 +565,8 @@ class Mutex { #if defined(USE_ESP32) || defined(USE_LIBRETINY) SemaphoreHandle_t handle_; #else - void *handle_; // d-pointer to store private data on new platforms + // d-pointer to store private data on new platforms + void *handle_; // NOLINT(clang-diagnostic-unused-private-field) #endif }; diff --git a/esphome/core/optional.h b/esphome/core/optional.h index 1e28ef1354..591bc7aa68 100644 --- a/esphome/core/optional.h +++ b/esphome/core/optional.h @@ -59,7 +59,7 @@ template class optional { // NOLINT return *this; } - void swap(optional &rhs) { + void swap(optional &rhs) noexcept { using std::swap; if (has_value() && rhs.has_value()) { swap(**this, *rhs); @@ -206,7 +206,7 @@ template inline bool operator>=(U const &v, optional // Specialized algorithms -template void swap(optional &x, optional &y) { x.swap(y); } +template void swap(optional &x, optional &y) noexcept { x.swap(y); } // Convenience function to create an optional. diff --git a/esphome/dashboard/web_server.py b/esphome/dashboard/web_server.py index 0fed8e9c53..67712da9b6 100644 --- a/esphome/dashboard/web_server.py +++ b/esphome/dashboard/web_server.py @@ -108,6 +108,12 @@ def is_authenticated(handler: BaseHandler) -> bool: return True if settings.using_auth: + if auth_header := handler.request.headers.get("Authorization"): + assert isinstance(auth_header, str) + if auth_header.startswith("Basic "): + auth_decoded = base64.b64decode(auth_header[6:]).decode() + username, password = auth_decoded.split(":", 1) + return settings.check_password(username, password) return handler.get_secure_cookie(AUTH_COOKIE_NAME) == COOKIE_AUTHENTICATED_YES return True diff --git a/platformio.ini b/platformio.ini index 04afc059af..b9b80e933f 100644 --- a/platformio.ini +++ b/platformio.ini @@ -137,9 +137,9 @@ extra_scripts = post:esphome/components/esp32/post_build.py.script ; This are common settings for the ESP32 (all variants) using IDF. [common:esp32-idf] extends = common:idf -platform = platformio/espressif32@5.4.0 +platform = https://github.com/pioarduino/platform-espressif32/releases/download/51.03.06/platform-espressif32.zip platform_packages = - platformio/framework-espidf@~3.40408.0 + pioarduino/framework-espidf@https://github.com/pioarduino/esp-idf/releases/download/v5.1.5/esp-idf-v5.1.5.zip framework = espidf lib_deps = diff --git a/pyproject.toml b/pyproject.toml index cfc279845f..7789f6d645 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta" [project] name = "esphome" license = {text = "MIT"} -description = "Make creating custom firmwares for ESP32/ESP8266 super easy." +description = "ESPHome is a system to configure your microcontrollers by simple yet powerful configuration files and control them remotely through Home Automation systems." readme = "README.md" authors = [ {name = "The ESPHome Authors", email = "esphome@nabucasa.com"} diff --git a/requirements.txt b/requirements.txt index 7bc1c895df..d96004c8ef 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,7 +12,7 @@ pyserial==3.5 platformio==6.1.16 # When updating platformio, also update Dockerfile esptool==4.7.0 click==8.1.7 -esphome-dashboard==20241120.0 +esphome-dashboard==20241217.1 aioesphomeapi==24.6.2 zeroconf==0.132.2 puremagic==1.27 diff --git a/requirements_dev.txt b/requirements_dev.txt index eb749a861d..1a98a15ab2 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -1,4 +1,4 @@ # Useful stuff when working in a development environment clang-format==13.0.1 # also change in .pre-commit-config.yaml and Dockerfile when updating -clang-tidy==14.0.6 # When updating clang-tidy, also update Dockerfile +clang-tidy==18.1.8 # When updating clang-tidy, also update Dockerfile yamllint==1.35.1 # also change in .pre-commit-config.yaml when updating diff --git a/script/build_language_schema.py b/script/build_language_schema.py index 8b2c28b06b..2023dc0402 100644 --- a/script/build_language_schema.py +++ b/script/build_language_schema.py @@ -394,9 +394,8 @@ def add_referenced_recursive(referenced_schemas, config_var, path, eat_schema=Fa for k in schema.get(S_EXTENDS, []): if k not in referenced_schemas: referenced_schemas[k] = [path] - else: - if path not in referenced_schemas[k]: - referenced_schemas[k].append(path) + elif path not in referenced_schemas[k]: + referenced_schemas[k].append(path) s1 = get_str_path_schema(k) p = k.split(".") @@ -868,13 +867,12 @@ def convert(schema, config_var, path): config_var[S_TYPE] = "use_id" else: print("TODO deferred?") + elif isinstance(data, str): + # TODO: Figure out why pipsolar does this + config_var["use_id_type"] = data else: - if isinstance(data, str): - # TODO: Figure out why pipsolar does this - config_var["use_id_type"] = data - else: - config_var["use_id_type"] = str(data.base) - config_var[S_TYPE] = "use_id" + config_var["use_id_type"] = str(data.base) + config_var[S_TYPE] = "use_id" else: raise TypeError("Unknown extracted schema type") elif config_var.get("key") == "GeneratedID": diff --git a/script/clang-tidy b/script/clang-tidy index 61199edce3..5c19f81043 100755 --- a/script/clang-tidy +++ b/script/clang-tidy @@ -63,8 +63,6 @@ def clang_options(idedata): "-Ddeprecated(x)=", # allow to condition code on the presence of clang-tidy "-DCLANG_TIDY", - # (esp-idf) Disable this header because they use asm with registers clang-tidy doesn't know - "-D__XTENSA_API_H__", # (esp-idf) Fix __once_callable in some libstdc++ headers "-D_GLIBCXX_HAVE_TLS", ] @@ -100,10 +98,13 @@ def clang_options(idedata): # add library include directories using -isystem to suppress their errors for directory in list(idedata["includes"]["build"]): # skip our own directories, we add those later - if ( - not directory.startswith(f"{root_path}/") - or directory.startswith(f"{root_path}/.pio/") - or directory.startswith(f"{root_path}/managed_components/") + if not directory.startswith(f"{root_path}") or directory.startswith( + ( + f"{root_path}/.pio", + f"{root_path}/.platformio", + f"{root_path}/.temp", + f"{root_path}/managed_components", + ) ): cmd.extend(["-isystem", directory]) @@ -235,7 +236,7 @@ def main(): failed_files = [] try: - executable = get_binary("clang-tidy", 14) + executable = get_binary("clang-tidy", 18) task_queue = queue.Queue(args.jobs) lock = threading.Lock() for _ in range(args.jobs): @@ -280,12 +281,12 @@ def main(): print("Applying fixes ...") try: try: - subprocess.call(["clang-apply-replacements-14", tmpdir]) + subprocess.call(["clang-apply-replacements-18", tmpdir]) except FileNotFoundError: subprocess.call(["clang-apply-replacements", tmpdir]) except FileNotFoundError: print( - "Error please install clang-apply-replacements-14 or clang-apply-replacements.\n", + "Error please install clang-apply-replacements-18 or clang-apply-replacements.\n", file=sys.stderr, ) except: diff --git a/tests/components/adc/test.bk72xx-ard.yaml b/tests/components/adc/test.bk72xx-ard.yaml new file mode 100644 index 0000000000..491d0af3b9 --- /dev/null +++ b/tests/components/adc/test.bk72xx-ard.yaml @@ -0,0 +1,4 @@ +sensor: + - platform: adc + pin: P23 + name: Basic ADC Test diff --git a/tests/components/hbridge/common.yaml b/tests/components/hbridge/common.yaml new file mode 100644 index 0000000000..0504cdea03 --- /dev/null +++ b/tests/components/hbridge/common.yaml @@ -0,0 +1,39 @@ +output: + - platform: ${pwm_platform} + pin: ${output1_pin} + id: gpio_output1 + - platform: ${pwm_platform} + pin: ${output2_pin} + id: gpio_output2 + - platform: ${pwm_platform} + pin: ${output3_pin} + id: gpio_output3 + - platform: ${pwm_platform} + pin: ${output4_pin} + id: gpio_output4 + +light: + - platform: hbridge + name: Icicle Lights + pin_a: gpio_output3 + pin_b: gpio_output4 + +fan: + - platform: hbridge + id: fan_hbridge + speed_count: 4 + name: H-bridge Fan with Presets + pin_a: gpio_output1 + pin_b: gpio_output2 + preset_modes: + - Preset 1 + - Preset 2 + on_preset_set: + then: + - logger.log: Preset mode was changed! + +switch: + - platform: hbridge + id: switch_hbridge + on_pin: ${hbridge_on_pin} + off_pin: ${hbridge_off_pin} diff --git a/tests/components/hbridge/test.esp32-ard.yaml b/tests/components/hbridge/test.esp32-ard.yaml index 6a80aaaf3b..e50d537749 100644 --- a/tests/components/hbridge/test.esp32-ard.yaml +++ b/tests/components/hbridge/test.esp32-ard.yaml @@ -1,33 +1,17 @@ -output: - - platform: ledc - pin: 14 - id: gpio_output1 - - platform: ledc - pin: 15 - id: gpio_output2 - - platform: ledc - pin: 12 - id: gpio_output3 - - platform: ledc - pin: 13 - id: gpio_output4 +substitutions: + pwm_platform: ledc + output1_pin: "14" + output2_pin: "15" + output3_pin: "12" + output4_pin: "13" + hbridge_on_pin: "4" + hbridge_off_pin: "5" -light: - - platform: hbridge - name: Icicle Lights - pin_a: gpio_output3 - pin_b: gpio_output4 +packages: + common: !include common.yaml -fan: - - platform: hbridge - id: fan_hbridge - speed_count: 4 - name: H-bridge Fan with Presets - pin_a: gpio_output1 - pin_b: gpio_output2 - preset_modes: - - Preset 1 - - Preset 2 - on_preset_set: - then: - - logger.log: Preset mode was changed! +switch: + - id: !extend switch_hbridge + pulse_length: 60ms + wait_time: 10ms + optimistic: false diff --git a/tests/components/hbridge/test.esp32-c3-ard.yaml b/tests/components/hbridge/test.esp32-c3-ard.yaml index 70cfd6ab6f..b9e8738442 100644 --- a/tests/components/hbridge/test.esp32-c3-ard.yaml +++ b/tests/components/hbridge/test.esp32-c3-ard.yaml @@ -1,33 +1,16 @@ -output: - - platform: ledc - pin: 4 - id: gpio_output1 - - platform: ledc - pin: 5 - id: gpio_output2 - - platform: ledc - pin: 6 - id: gpio_output3 - - platform: ledc - pin: 7 - id: gpio_output4 +substitutions: + pwm_platform: "ledc" + output1_pin: "4" + output2_pin: "5" + output3_pin: "6" + output4_pin: "7" + hbridge_on_pin: "2" + hbridge_off_pin: "3" -light: - - platform: hbridge - name: Icicle Lights - pin_a: gpio_output3 - pin_b: gpio_output4 +packages: + common: !include common.yaml -fan: - - platform: hbridge - id: fan_hbridge - speed_count: 4 - name: H-bridge Fan with Presets - pin_a: gpio_output1 - pin_b: gpio_output2 - preset_modes: - - Preset 1 - - Preset 2 - on_preset_set: - then: - - logger.log: Preset mode was changed! +switch: + - id: !extend switch_hbridge + wait_time: 10ms + optimistic: true diff --git a/tests/components/hbridge/test.esp32-c3-idf.yaml b/tests/components/hbridge/test.esp32-c3-idf.yaml index 70cfd6ab6f..c73f08b6de 100644 --- a/tests/components/hbridge/test.esp32-c3-idf.yaml +++ b/tests/components/hbridge/test.esp32-c3-idf.yaml @@ -1,33 +1,15 @@ -output: - - platform: ledc - pin: 4 - id: gpio_output1 - - platform: ledc - pin: 5 - id: gpio_output2 - - platform: ledc - pin: 6 - id: gpio_output3 - - platform: ledc - pin: 7 - id: gpio_output4 +substitutions: + pwm_platform: "ledc" + output1_pin: "4" + output2_pin: "5" + output3_pin: "6" + output4_pin: "7" + hbridge_on_pin: "2" + hbridge_off_pin: "3" -light: - - platform: hbridge - name: Icicle Lights - pin_a: gpio_output3 - pin_b: gpio_output4 +packages: + common: !include common.yaml -fan: - - platform: hbridge - id: fan_hbridge - speed_count: 4 - name: H-bridge Fan with Presets - pin_a: gpio_output1 - pin_b: gpio_output2 - preset_modes: - - Preset 1 - - Preset 2 - on_preset_set: - then: - - logger.log: Preset mode was changed! +switch: + - id: !extend switch_hbridge + pulse_length: 60ms diff --git a/tests/components/hbridge/test.esp32-idf.yaml b/tests/components/hbridge/test.esp32-idf.yaml index 6a80aaaf3b..dbbfa738c7 100644 --- a/tests/components/hbridge/test.esp32-idf.yaml +++ b/tests/components/hbridge/test.esp32-idf.yaml @@ -1,33 +1,16 @@ -output: - - platform: ledc - pin: 14 - id: gpio_output1 - - platform: ledc - pin: 15 - id: gpio_output2 - - platform: ledc - pin: 12 - id: gpio_output3 - - platform: ledc - pin: 13 - id: gpio_output4 +substitutions: + pwm_platform: "ledc" + output1_pin: "14" + output2_pin: "15" + output3_pin: "12" + output4_pin: "13" + hbridge_on_pin: "4" + hbridge_off_pin: "5" -light: - - platform: hbridge - name: Icicle Lights - pin_a: gpio_output3 - pin_b: gpio_output4 +packages: + common: !include common.yaml -fan: - - platform: hbridge - id: fan_hbridge - speed_count: 4 - name: H-bridge Fan with Presets - pin_a: gpio_output1 - pin_b: gpio_output2 - preset_modes: - - Preset 1 - - Preset 2 - on_preset_set: - then: - - logger.log: Preset mode was changed! +switch: + - id: !extend switch_hbridge + pulse_length: 60ms + wait_time: 10ms diff --git a/tests/components/hbridge/test.esp8266-ard.yaml b/tests/components/hbridge/test.esp8266-ard.yaml index 4f8915879d..f560da5d38 100644 --- a/tests/components/hbridge/test.esp8266-ard.yaml +++ b/tests/components/hbridge/test.esp8266-ard.yaml @@ -1,33 +1,16 @@ -output: - - platform: esp8266_pwm - pin: 4 - id: gpio_output1 - - platform: esp8266_pwm - pin: 5 - id: gpio_output2 - - platform: esp8266_pwm - pin: 12 - id: gpio_output3 - - platform: esp8266_pwm - pin: 13 - id: gpio_output4 +substitutions: + pwm_platform: "esp8266_pwm" + output1_pin: "4" + output2_pin: "5" + output3_pin: "12" + output4_pin: "13" + hbridge_on_pin: "14" + hbridge_off_pin: "15" -light: - - platform: hbridge - name: Icicle Lights - pin_a: gpio_output3 - pin_b: gpio_output4 +packages: + common: !include common.yaml -fan: - - platform: hbridge - id: fan_hbridge - speed_count: 4 - name: H-bridge Fan with Presets - pin_a: gpio_output1 - pin_b: gpio_output2 - preset_modes: - - Preset 1 - - Preset 2 - on_preset_set: - then: - - logger.log: Preset mode was changed! +switch: + - id: !extend switch_hbridge + pulse_length: 60ms + wait_time: 10ms diff --git a/tests/components/hbridge/test.rp2040-ard.yaml b/tests/components/hbridge/test.rp2040-ard.yaml index e21b55091d..aa6e290cab 100644 --- a/tests/components/hbridge/test.rp2040-ard.yaml +++ b/tests/components/hbridge/test.rp2040-ard.yaml @@ -1,33 +1,16 @@ -output: - - platform: rp2040_pwm - pin: 4 - id: gpio_output1 - - platform: rp2040_pwm - pin: 5 - id: gpio_output2 - - platform: rp2040_pwm - pin: 6 - id: gpio_output3 - - platform: rp2040_pwm - pin: 7 - id: gpio_output4 +substitutions: + pwm_platform: "rp2040_pwm" + output1_pin: "4" + output2_pin: "5" + output3_pin: "6" + output4_pin: "7" + hbridge_on_pin: "2" + hbridge_off_pin: "3" -light: - - platform: hbridge - name: Icicle Lights - pin_a: gpio_output3 - pin_b: gpio_output4 +packages: + common: !include common.yaml -fan: - - platform: hbridge - id: fan_hbridge - speed_count: 4 - name: H-bridge Fan with Presets - pin_a: gpio_output1 - pin_b: gpio_output2 - preset_modes: - - Preset 1 - - Preset 2 - on_preset_set: - then: - - logger.log: Preset mode was changed! +switch: + - id: !extend switch_hbridge + wait_time: 10ms + optimistic: true diff --git a/tests/components/lvgl/lvgl-package.yaml b/tests/components/lvgl/lvgl-package.yaml index e5df30f136..b1b89adfe0 100644 --- a/tests/components/lvgl/lvgl-package.yaml +++ b/tests/components/lvgl/lvgl-package.yaml @@ -165,6 +165,11 @@ lvgl: - Nov - Dec selected_index: 1 + on_change: + then: + - logger.log: + format: "Roller changed = %d: %s" + args: [x, text.c_str()] on_value: then: - logger.log: @@ -178,9 +183,13 @@ lvgl: duration: 1s auto_start: true on_all_events: - logger.log: - format: "Event %s" - args: ['lv_event_code_name_for(event->code).c_str()'] + - logger.log: + format: "Event %s" + args: ['lv_event_code_name_for(event->code).c_str()'] + - lvgl.animimg.update: + id: anim_img + src: !lambda "return {dog_image, cat_image};" + duration: 2s - label: id: hello_label text: Hello world @@ -447,6 +456,7 @@ lvgl: src: cat_image align: top_left y: "50" + mode: real - tileview: id: tileview_id scrollbar_mode: active @@ -642,7 +652,8 @@ lvgl: - image: grid_cell_row_pos: 0 grid_cell_column_pos: 0 - src: dog_image + src: !lambda return dog_image; + mode: virtual on_click: then: - lvgl.tabview.select: diff --git a/tests/components/lvgl/test.esp32-ard.yaml b/tests/components/lvgl/test.esp32-ard.yaml index 80d5ce503f..5b09147de7 100644 --- a/tests/components/lvgl/test.esp32-ard.yaml +++ b/tests/components/lvgl/test.esp32-ard.yaml @@ -55,5 +55,5 @@ lvgl: packages: lvgl: !include lvgl-package.yaml + xvgl: !include common.yaml -<<: !include common.yaml diff --git a/tests/components/matrix_keypad/common.yaml b/tests/components/matrix_keypad/common.yaml new file mode 100644 index 0000000000..32e334d890 --- /dev/null +++ b/tests/components/matrix_keypad/common.yaml @@ -0,0 +1,8 @@ +binary_sensor: + - platform: matrix_keypad + id: key4 + row: 1 + col: 1 + - platform: matrix_keypad + id: key1 + key: 1 diff --git a/tests/components/matrix_keypad/test.esp32-ard.yaml b/tests/components/matrix_keypad/test.esp32-ard.yaml index c8e9b54534..70bb70638d 100644 --- a/tests/components/matrix_keypad/test.esp32-ard.yaml +++ b/tests/components/matrix_keypad/test.esp32-ard.yaml @@ -1,11 +1,5 @@ -binary_sensor: - - platform: matrix_keypad - id: key4 - row: 1 - col: 1 - - platform: matrix_keypad - id: key1 - key: 1 +packages: + common: !include common.yaml matrix_keypad: id: keypad @@ -17,3 +11,5 @@ matrix_keypad: - pin: 15 keys: "1234" has_pulldowns: true + on_key: + - lambda: ESP_LOGI("KEY", "key %d pressed", x); diff --git a/tests/components/matrix_keypad/test.esp32-c3-ard.yaml b/tests/components/matrix_keypad/test.esp32-c3-ard.yaml index d15e6af21a..75d9c0b263 100644 --- a/tests/components/matrix_keypad/test.esp32-c3-ard.yaml +++ b/tests/components/matrix_keypad/test.esp32-c3-ard.yaml @@ -1,11 +1,5 @@ -binary_sensor: - - platform: matrix_keypad - id: key4 - row: 1 - col: 1 - - platform: matrix_keypad - id: key1 - key: 1 +packages: + common: !include common.yaml matrix_keypad: id: keypad @@ -17,3 +11,5 @@ matrix_keypad: - pin: 4 keys: "1234" has_pulldowns: true + on_key: + - lambda: ESP_LOGI("KEY", "key %d pressed", x); diff --git a/tests/components/matrix_keypad/test.esp32-c3-idf.yaml b/tests/components/matrix_keypad/test.esp32-c3-idf.yaml index d15e6af21a..75d9c0b263 100644 --- a/tests/components/matrix_keypad/test.esp32-c3-idf.yaml +++ b/tests/components/matrix_keypad/test.esp32-c3-idf.yaml @@ -1,11 +1,5 @@ -binary_sensor: - - platform: matrix_keypad - id: key4 - row: 1 - col: 1 - - platform: matrix_keypad - id: key1 - key: 1 +packages: + common: !include common.yaml matrix_keypad: id: keypad @@ -17,3 +11,5 @@ matrix_keypad: - pin: 4 keys: "1234" has_pulldowns: true + on_key: + - lambda: ESP_LOGI("KEY", "key %d pressed", x); diff --git a/tests/components/matrix_keypad/test.esp32-idf.yaml b/tests/components/matrix_keypad/test.esp32-idf.yaml index c8e9b54534..70bb70638d 100644 --- a/tests/components/matrix_keypad/test.esp32-idf.yaml +++ b/tests/components/matrix_keypad/test.esp32-idf.yaml @@ -1,11 +1,5 @@ -binary_sensor: - - platform: matrix_keypad - id: key4 - row: 1 - col: 1 - - platform: matrix_keypad - id: key1 - key: 1 +packages: + common: !include common.yaml matrix_keypad: id: keypad @@ -17,3 +11,5 @@ matrix_keypad: - pin: 15 keys: "1234" has_pulldowns: true + on_key: + - lambda: ESP_LOGI("KEY", "key %d pressed", x); diff --git a/tests/components/matrix_keypad/test.esp32-s3-idf.yaml b/tests/components/matrix_keypad/test.esp32-s3-idf.yaml new file mode 100644 index 0000000000..a491f2ed59 --- /dev/null +++ b/tests/components/matrix_keypad/test.esp32-s3-idf.yaml @@ -0,0 +1,15 @@ +packages: + common: !include common.yaml + +matrix_keypad: + id: keypad + rows: + - pin: 10 + - pin: 11 + columns: + - pin: 12 + - pin: 13 + keys: "1234" + has_pulldowns: true + on_key: + - lambda: ESP_LOGI("KEY", "key %d pressed", x); diff --git a/tests/components/matrix_keypad/test.esp8266-ard.yaml b/tests/components/matrix_keypad/test.esp8266-ard.yaml index c8e9b54534..70bb70638d 100644 --- a/tests/components/matrix_keypad/test.esp8266-ard.yaml +++ b/tests/components/matrix_keypad/test.esp8266-ard.yaml @@ -1,11 +1,5 @@ -binary_sensor: - - platform: matrix_keypad - id: key4 - row: 1 - col: 1 - - platform: matrix_keypad - id: key1 - key: 1 +packages: + common: !include common.yaml matrix_keypad: id: keypad @@ -17,3 +11,5 @@ matrix_keypad: - pin: 15 keys: "1234" has_pulldowns: true + on_key: + - lambda: ESP_LOGI("KEY", "key %d pressed", x); diff --git a/tests/components/matrix_keypad/test.rp2040-ard.yaml b/tests/components/matrix_keypad/test.rp2040-ard.yaml index d15e6af21a..75d9c0b263 100644 --- a/tests/components/matrix_keypad/test.rp2040-ard.yaml +++ b/tests/components/matrix_keypad/test.rp2040-ard.yaml @@ -1,11 +1,5 @@ -binary_sensor: - - platform: matrix_keypad - id: key4 - row: 1 - col: 1 - - platform: matrix_keypad - id: key1 - key: 1 +packages: + common: !include common.yaml matrix_keypad: id: keypad @@ -17,3 +11,5 @@ matrix_keypad: - pin: 4 keys: "1234" has_pulldowns: true + on_key: + - lambda: ESP_LOGI("KEY", "key %d pressed", x); diff --git a/tests/components/mqtt/common.yaml b/tests/components/mqtt/common.yaml index d22fe9579f..a4bdf58809 100644 --- a/tests/components/mqtt/common.yaml +++ b/tests/components/mqtt/common.yaml @@ -60,6 +60,7 @@ mqtt: - mqtt.publish: topic: some/topic payload: Good-bye + publish_nan_as_none: false binary_sensor: - platform: template diff --git a/tests/components/nextion/common.yaml b/tests/components/nextion/common.yaml new file mode 100644 index 0000000000..e84cd08422 --- /dev/null +++ b/tests/components/nextion/common.yaml @@ -0,0 +1,293 @@ +esphome: + on_boot: + # Binary sensor publish action tests + - binary_sensor.nextion.publish: + id: r0_sensor + state: True + + - binary_sensor.nextion.publish: + id: r0_sensor + state: True + publish_state: True + send_to_nextion: True + + - binary_sensor.nextion.publish: + id: r0_sensor + state: True + publish_state: False + send_to_nextion: True + + - binary_sensor.nextion.publish: + id: r0_sensor + state: True + publish_state: True + send_to_nextion: False + + - binary_sensor.nextion.publish: + id: r0_sensor + state: True + publish_state: False + send_to_nextion: False + + # Templated + - binary_sensor.nextion.publish: + id: r0_sensor + state: !lambda 'return true;' + + - binary_sensor.nextion.publish: + id: r0_sensor + state: !lambda 'return true;' + publish_state: !lambda 'return true;' + send_to_nextion: !lambda 'return true;' + + - binary_sensor.nextion.publish: + id: r0_sensor + state: !lambda 'return true;' + publish_state: !lambda 'return false;' + send_to_nextion: !lambda 'return true;' + + - binary_sensor.nextion.publish: + id: r0_sensor + state: !lambda 'return true;' + publish_state: !lambda 'return true;' + send_to_nextion: !lambda 'return false;' + + - binary_sensor.nextion.publish: + id: r0_sensor + state: !lambda 'return true;' + publish_state: !lambda 'return false;' + send_to_nextion: !lambda 'return false;' + + # Sensor publish action tests + - sensor.nextion.publish: + id: testnumber + state: 42.0 + + - sensor.nextion.publish: + id: testnumber + state: 42.0 + publish_state: True + send_to_nextion: True + + - sensor.nextion.publish: + id: testnumber + state: 42.0 + publish_state: False + send_to_nextion: True + + - sensor.nextion.publish: + id: testnumber + state: 42.0 + publish_state: True + send_to_nextion: False + + - sensor.nextion.publish: + id: testnumber + state: 42.0 + publish_state: False + send_to_nextion: False + + # Templated + - sensor.nextion.publish: + id: testnumber + state: !lambda 'return 42.0;' + + - sensor.nextion.publish: + id: testnumber + state: !lambda 'return 42.0;' + publish_state: !lambda 'return true;' + send_to_nextion: !lambda 'return true;' + + - sensor.nextion.publish: + id: testnumber + state: !lambda 'return 42.0;' + publish_state: !lambda 'return false;' + send_to_nextion: !lambda 'return true;' + + - sensor.nextion.publish: + id: testnumber + state: !lambda 'return 42.0;' + publish_state: !lambda 'return true;' + send_to_nextion: !lambda 'return false;' + + - sensor.nextion.publish: + id: testnumber + state: !lambda 'return 42.0;' + publish_state: !lambda 'return false;' + send_to_nextion: !lambda 'return false;' + + # Switch publish action tests + - switch.nextion.publish: + id: r0 + state: True + + - switch.nextion.publish: + id: r0 + state: True + publish_state: true + send_to_nextion: true + + - switch.nextion.publish: + id: r0 + state: True + publish_state: false + send_to_nextion: true + + - switch.nextion.publish: + id: r0 + state: True + publish_state: true + send_to_nextion: false + + - switch.nextion.publish: + id: r0 + state: True + publish_state: false + send_to_nextion: false + + # Templated + - switch.nextion.publish: + id: r0 + state: !lambda 'return true;' + + - switch.nextion.publish: + id: r0 + state: !lambda 'return true;' + publish_state: !lambda 'return true;' + send_to_nextion: !lambda 'return true;' + + - switch.nextion.publish: + id: r0 + state: !lambda 'return true;' + publish_state: !lambda 'return false;' + send_to_nextion: !lambda 'return true;' + + - switch.nextion.publish: + id: r0 + state: !lambda 'return true;' + publish_state: !lambda 'return true;' + send_to_nextion: !lambda 'return false;' + + - switch.nextion.publish: + id: r0 + state: !lambda 'return true;' + publish_state: !lambda 'return false;' + send_to_nextion: !lambda 'return false;' + + # Test sensor publish action tests + - text_sensor.nextion.publish: + id: text0 + state: 'Test' + publish_state: true + send_to_nextion: true + + - text_sensor.nextion.publish: + id: text0 + state: 'Test' + publish_state: false + send_to_nextion: true + + - text_sensor.nextion.publish: + id: text0 + state: 'Test' + publish_state: true + send_to_nextion: false + + - text_sensor.nextion.publish: + id: text0 + state: 'Test' + publish_state: false + send_to_nextion: false + + # Templated + - text_sensor.nextion.publish: + id: text0 + state: !lambda 'return "Test";' + + - text_sensor.nextion.publish: + id: text0 + state: !lambda 'return "Test";' + publish_state: !lambda 'return true;' + send_to_nextion: !lambda 'return true;' + + - text_sensor.nextion.publish: + id: text0 + state: !lambda 'return "Test";' + publish_state: !lambda 'return false;' + send_to_nextion: !lambda 'return true;' + + - text_sensor.nextion.publish: + id: text0 + state: !lambda 'return "Test";' + publish_state: !lambda 'return true;' + send_to_nextion: !lambda 'return false;' + + - text_sensor.nextion.publish: + id: text0 + state: !lambda 'return "Test";' + publish_state: !lambda 'return false;' + send_to_nextion: !lambda 'return false;' + +wifi: + ssid: MySSID + password: password1 + +uart: + - id: uart_nextion + tx_pin: ${tx_pin} + rx_pin: ${rx_pin} + baud_rate: 115200 + +binary_sensor: + - platform: nextion + page_id: 0 + component_id: 2 + name: Nextion Touch Component + - platform: nextion + id: r0_sensor + name: R0 Sensor + component_name: page0.r0 + +sensor: + - platform: nextion + id: testnumber + name: testnumber + variable_name: testnumber + - platform: nextion + id: testwave + name: testwave + component_id: 2 + wave_channel_id: 1 + +switch: + - platform: nextion + id: r0 + name: R0 Switch + component_name: page0.r0 + +text_sensor: + - platform: nextion + name: text0 + id: text0 + update_interval: 4s + component_name: text0 + +display: + - platform: nextion + id: main_lcd + update_interval: 5s + on_sleep: + then: + lambda: 'ESP_LOGD("display","Display went to sleep");' + on_wake: + then: + lambda: 'ESP_LOGD("display","Display woke up");' + on_setup: + then: + lambda: 'ESP_LOGD("display","Display setup completed");' + on_page: + then: + lambda: 'ESP_LOGD("display","Display shows new page %u", x);' + on_buffer_overflow: + then: + logger.log: "Nextion reported a buffer overflow!" diff --git a/tests/components/nextion/test.esp32-ard.yaml b/tests/components/nextion/test.esp32-ard.yaml index 27568ebc2a..d5e02b8b85 100644 --- a/tests/components/nextion/test.esp32-ard.yaml +++ b/tests/components/nextion/test.esp32-ard.yaml @@ -1,60 +1,10 @@ -wifi: - ssid: MySSID - password: password1 +substitutions: + tx_pin: GPIO17 + rx_pin: GPIO16 -uart: - - id: uart_nextion - tx_pin: 17 - rx_pin: 16 - baud_rate: 115200 - -binary_sensor: - - platform: nextion - page_id: 0 - component_id: 2 - name: Nextion Touch Component - - platform: nextion - id: r0_sensor - name: R0 Sensor - component_name: page0.r0 - -sensor: - - platform: nextion - id: testnumber - name: testnumber - variable_name: testnumber - - platform: nextion - id: testwave - name: testwave - component_id: 2 - wave_channel_id: 1 - -switch: - - platform: nextion - id: r0 - name: R0 Switch - component_name: page0.r0 - -text_sensor: - - platform: nextion - name: text0 - id: text0 - update_interval: 4s - component_name: text0 +packages: + base: !include common.yaml display: - - platform: nextion + - id: !extend main_lcd tft_url: http://esphome.io/default35.tft - update_interval: 5s - on_sleep: - then: - lambda: 'ESP_LOGD("display","Display went to sleep");' - on_wake: - then: - lambda: 'ESP_LOGD("display","Display woke up");' - on_setup: - then: - lambda: 'ESP_LOGD("display","Display setup completed");' - on_page: - then: - lambda: 'ESP_LOGD("display","Display shows new page %u", x);' diff --git a/tests/components/nextion/test.esp32-c3-ard.yaml b/tests/components/nextion/test.esp32-c3-ard.yaml index 5881d6e165..5135c7e4f4 100644 --- a/tests/components/nextion/test.esp32-c3-ard.yaml +++ b/tests/components/nextion/test.esp32-c3-ard.yaml @@ -1,60 +1,10 @@ -wifi: - ssid: MySSID - password: password1 +substitutions: + tx_pin: GPIO4 + rx_pin: GPIO5 -uart: - - id: uart_nextion - tx_pin: 4 - rx_pin: 5 - baud_rate: 115200 - -binary_sensor: - - platform: nextion - page_id: 0 - component_id: 2 - name: Nextion Touch Component - - platform: nextion - id: r0_sensor - name: R0 Sensor - component_name: page0.r0 - -sensor: - - platform: nextion - id: testnumber - name: testnumber - variable_name: testnumber - - platform: nextion - id: testwave - name: testwave - component_id: 2 - wave_channel_id: 1 - -switch: - - platform: nextion - id: r0 - name: R0 Switch - component_name: page0.r0 - -text_sensor: - - platform: nextion - name: text0 - id: text0 - update_interval: 4s - component_name: text0 +packages: + base: !include common.yaml display: - - platform: nextion + - id: !extend main_lcd tft_url: http://esphome.io/default35.tft - update_interval: 5s - on_sleep: - then: - lambda: 'ESP_LOGD("display","Display went to sleep");' - on_wake: - then: - lambda: 'ESP_LOGD("display","Display woke up");' - on_setup: - then: - lambda: 'ESP_LOGD("display","Display setup completed");' - on_page: - then: - lambda: 'ESP_LOGD("display","Display shows new page %u", x);' diff --git a/tests/components/nextion/test.esp32-c3-idf.yaml b/tests/components/nextion/test.esp32-c3-idf.yaml index 5881d6e165..5135c7e4f4 100644 --- a/tests/components/nextion/test.esp32-c3-idf.yaml +++ b/tests/components/nextion/test.esp32-c3-idf.yaml @@ -1,60 +1,10 @@ -wifi: - ssid: MySSID - password: password1 +substitutions: + tx_pin: GPIO4 + rx_pin: GPIO5 -uart: - - id: uart_nextion - tx_pin: 4 - rx_pin: 5 - baud_rate: 115200 - -binary_sensor: - - platform: nextion - page_id: 0 - component_id: 2 - name: Nextion Touch Component - - platform: nextion - id: r0_sensor - name: R0 Sensor - component_name: page0.r0 - -sensor: - - platform: nextion - id: testnumber - name: testnumber - variable_name: testnumber - - platform: nextion - id: testwave - name: testwave - component_id: 2 - wave_channel_id: 1 - -switch: - - platform: nextion - id: r0 - name: R0 Switch - component_name: page0.r0 - -text_sensor: - - platform: nextion - name: text0 - id: text0 - update_interval: 4s - component_name: text0 +packages: + base: !include common.yaml display: - - platform: nextion + - id: !extend main_lcd tft_url: http://esphome.io/default35.tft - update_interval: 5s - on_sleep: - then: - lambda: 'ESP_LOGD("display","Display went to sleep");' - on_wake: - then: - lambda: 'ESP_LOGD("display","Display woke up");' - on_setup: - then: - lambda: 'ESP_LOGD("display","Display setup completed");' - on_page: - then: - lambda: 'ESP_LOGD("display","Display shows new page %u", x);' diff --git a/tests/components/nextion/test.esp32-idf.yaml b/tests/components/nextion/test.esp32-idf.yaml index 27568ebc2a..d5e02b8b85 100644 --- a/tests/components/nextion/test.esp32-idf.yaml +++ b/tests/components/nextion/test.esp32-idf.yaml @@ -1,60 +1,10 @@ -wifi: - ssid: MySSID - password: password1 +substitutions: + tx_pin: GPIO17 + rx_pin: GPIO16 -uart: - - id: uart_nextion - tx_pin: 17 - rx_pin: 16 - baud_rate: 115200 - -binary_sensor: - - platform: nextion - page_id: 0 - component_id: 2 - name: Nextion Touch Component - - platform: nextion - id: r0_sensor - name: R0 Sensor - component_name: page0.r0 - -sensor: - - platform: nextion - id: testnumber - name: testnumber - variable_name: testnumber - - platform: nextion - id: testwave - name: testwave - component_id: 2 - wave_channel_id: 1 - -switch: - - platform: nextion - id: r0 - name: R0 Switch - component_name: page0.r0 - -text_sensor: - - platform: nextion - name: text0 - id: text0 - update_interval: 4s - component_name: text0 +packages: + base: !include common.yaml display: - - platform: nextion + - id: !extend main_lcd tft_url: http://esphome.io/default35.tft - update_interval: 5s - on_sleep: - then: - lambda: 'ESP_LOGD("display","Display went to sleep");' - on_wake: - then: - lambda: 'ESP_LOGD("display","Display woke up");' - on_setup: - then: - lambda: 'ESP_LOGD("display","Display setup completed");' - on_page: - then: - lambda: 'ESP_LOGD("display","Display shows new page %u", x);' diff --git a/tests/components/nextion/test.esp8266-ard.yaml b/tests/components/nextion/test.esp8266-ard.yaml index 5881d6e165..5135c7e4f4 100644 --- a/tests/components/nextion/test.esp8266-ard.yaml +++ b/tests/components/nextion/test.esp8266-ard.yaml @@ -1,60 +1,10 @@ -wifi: - ssid: MySSID - password: password1 +substitutions: + tx_pin: GPIO4 + rx_pin: GPIO5 -uart: - - id: uart_nextion - tx_pin: 4 - rx_pin: 5 - baud_rate: 115200 - -binary_sensor: - - platform: nextion - page_id: 0 - component_id: 2 - name: Nextion Touch Component - - platform: nextion - id: r0_sensor - name: R0 Sensor - component_name: page0.r0 - -sensor: - - platform: nextion - id: testnumber - name: testnumber - variable_name: testnumber - - platform: nextion - id: testwave - name: testwave - component_id: 2 - wave_channel_id: 1 - -switch: - - platform: nextion - id: r0 - name: R0 Switch - component_name: page0.r0 - -text_sensor: - - platform: nextion - name: text0 - id: text0 - update_interval: 4s - component_name: text0 +packages: + base: !include common.yaml display: - - platform: nextion + - id: !extend main_lcd tft_url: http://esphome.io/default35.tft - update_interval: 5s - on_sleep: - then: - lambda: 'ESP_LOGD("display","Display went to sleep");' - on_wake: - then: - lambda: 'ESP_LOGD("display","Display woke up");' - on_setup: - then: - lambda: 'ESP_LOGD("display","Display setup completed");' - on_page: - then: - lambda: 'ESP_LOGD("display","Display shows new page %u", x);' diff --git a/tests/components/nextion/test.rp2040-ard.yaml b/tests/components/nextion/test.rp2040-ard.yaml index a1c5848ce6..20347c6eff 100644 --- a/tests/components/nextion/test.rp2040-ard.yaml +++ b/tests/components/nextion/test.rp2040-ard.yaml @@ -1,55 +1,7 @@ -uart: - - id: uart_nextion - tx_pin: 4 - rx_pin: 5 - baud_rate: 115200 +substitutions: + tx_pin: GPIO4 + rx_pin: GPIO5 -binary_sensor: - - platform: nextion - page_id: 0 - component_id: 2 - name: Nextion Touch Component - - platform: nextion - id: r0_sensor - name: R0 Sensor - component_name: page0.r0 +packages: + base: !include common.yaml -sensor: - - platform: nextion - id: testnumber - name: testnumber - variable_name: testnumber - - platform: nextion - id: testwave - name: testwave - component_id: 2 - wave_channel_id: 1 - -switch: - - platform: nextion - id: r0 - name: R0 Switch - component_name: page0.r0 - -text_sensor: - - platform: nextion - name: text0 - id: text0 - update_interval: 4s - component_name: text0 - -display: - - platform: nextion - update_interval: 5s - on_sleep: - then: - lambda: 'ESP_LOGD("display","Display went to sleep");' - on_wake: - then: - lambda: 'ESP_LOGD("display","Display woke up");' - on_setup: - then: - lambda: 'ESP_LOGD("display","Display setup completed");' - on_page: - then: - lambda: 'ESP_LOGD("display","Display shows new page %u", x);' diff --git a/tests/components/online_image/common-rp2040.yaml b/tests/components/online_image/common-rp2040.yaml new file mode 100644 index 0000000000..16bb2b2c44 --- /dev/null +++ b/tests/components/online_image/common-rp2040.yaml @@ -0,0 +1,19 @@ +<<: !include common.yaml + +spi: + - id: spi_main_lcd + clk_pin: 18 + mosi_pin: 19 + miso_pin: 16 + +display: + - platform: ili9xxx + id: main_lcd + model: ili9342 + cs_pin: 20 + dc_pin: 17 + reset_pin: 21 + invert_colors: true + lambda: |- + it.fill(Color(0, 0, 0)); + it.image(0, 0, id(online_rgba_image)); diff --git a/tests/components/online_image/test.rp2040-ard.yaml b/tests/components/online_image/test.rp2040-ard.yaml new file mode 100644 index 0000000000..d10f36b4e9 --- /dev/null +++ b/tests/components/online_image/test.rp2040-ard.yaml @@ -0,0 +1,4 @@ +<<: !include common-rp2040.yaml + +http_request: + verify_ssl: false diff --git a/tests/components/seeed_mr60bha2/common.yaml b/tests/components/seeed_mr60bha2/common.yaml new file mode 100644 index 0000000000..e9d0c735af --- /dev/null +++ b/tests/components/seeed_mr60bha2/common.yaml @@ -0,0 +1,19 @@ +uart: + - id: seeed_mr60fda2_uart + tx_pin: ${uart_tx_pin} + rx_pin: ${uart_rx_pin} + baud_rate: 115200 + parity: NONE + stop_bits: 1 + +seeed_mr60bha2: + id: my_seeed_mr60bha2 + +sensor: + - platform: seeed_mr60bha2 + breath_rate: + name: "Real-time respiratory rate" + heart_rate: + name: "Real-time heart rate" + distance: + name: "Distance to detection object" diff --git a/tests/components/seeed_mr60bha2/test.esp32-c3-ard.yaml b/tests/components/seeed_mr60bha2/test.esp32-c3-ard.yaml new file mode 100644 index 0000000000..4fb884abf4 --- /dev/null +++ b/tests/components/seeed_mr60bha2/test.esp32-c3-ard.yaml @@ -0,0 +1,5 @@ +substitutions: + uart_tx_pin: GPIO5 + uart_rx_pin: GPIO4 + +<<: !include common.yaml diff --git a/tests/components/seeed_mr60bha2/test.esp32-c3-idf.yaml b/tests/components/seeed_mr60bha2/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..4fb884abf4 --- /dev/null +++ b/tests/components/seeed_mr60bha2/test.esp32-c3-idf.yaml @@ -0,0 +1,5 @@ +substitutions: + uart_tx_pin: GPIO5 + uart_rx_pin: GPIO4 + +<<: !include common.yaml diff --git a/tests/components/seeed_mr60fda2/common.yaml b/tests/components/seeed_mr60fda2/common.yaml new file mode 100644 index 0000000000..55a7cc1ab3 --- /dev/null +++ b/tests/components/seeed_mr60fda2/common.yaml @@ -0,0 +1,34 @@ +uart: + - id: seeed_mr60fda2_uart + tx_pin: ${uart_tx_pin} + rx_pin: ${uart_rx_pin} + baud_rate: 115200 + parity: NONE + stop_bits: 1 + +seeed_mr60fda2: + id: my_seeed_mr60fda2 + uart_id: seeed_mr60fda2_uart + +binary_sensor: + - platform: seeed_mr60fda2 + people_exist: + name: "Person Information" + fall_detected: + name: "Falling Information" + +button: + - platform: seeed_mr60fda2 + get_radar_parameters: + name: "Get Radar Parameters" + factory_reset: + name: "Reset" + +select: + - platform: seeed_mr60fda2 + install_height: + name: "Set Install Height" + height_threshold: + name: "Set Height Threshold" + sensitivity: + name: "Set Sensitivity" diff --git a/tests/components/seeed_mr60fda2/test.esp32-c3-ard.yaml b/tests/components/seeed_mr60fda2/test.esp32-c3-ard.yaml new file mode 100644 index 0000000000..4fb884abf4 --- /dev/null +++ b/tests/components/seeed_mr60fda2/test.esp32-c3-ard.yaml @@ -0,0 +1,5 @@ +substitutions: + uart_tx_pin: GPIO5 + uart_rx_pin: GPIO4 + +<<: !include common.yaml diff --git a/tests/components/seeed_mr60fda2/test.esp32-c3-idf.yaml b/tests/components/seeed_mr60fda2/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..4fb884abf4 --- /dev/null +++ b/tests/components/seeed_mr60fda2/test.esp32-c3-idf.yaml @@ -0,0 +1,5 @@ +substitutions: + uart_tx_pin: GPIO5 + uart_rx_pin: GPIO4 + +<<: !include common.yaml diff --git a/tests/components/switch/common.yaml b/tests/components/switch/common.yaml new file mode 100644 index 0000000000..8d6972f91b --- /dev/null +++ b/tests/components/switch/common.yaml @@ -0,0 +1,11 @@ +binary_sensor: + - platform: switch + id: some_binary_sensor + name: "Template Switch State" + source_id: the_switch + +switch: + - platform: template + name: "Template Switch" + id: the_switch + optimistic: true diff --git a/tests/components/switch/test.bk72xx-ard.yaml b/tests/components/switch/test.bk72xx-ard.yaml new file mode 100644 index 0000000000..25cb37a0b4 --- /dev/null +++ b/tests/components/switch/test.bk72xx-ard.yaml @@ -0,0 +1,2 @@ +packages: + common: !include common.yaml diff --git a/tests/components/switch/test.esp32-ard.yaml b/tests/components/switch/test.esp32-ard.yaml new file mode 100644 index 0000000000..25cb37a0b4 --- /dev/null +++ b/tests/components/switch/test.esp32-ard.yaml @@ -0,0 +1,2 @@ +packages: + common: !include common.yaml diff --git a/tests/components/switch/test.esp32-c3-ard.yaml b/tests/components/switch/test.esp32-c3-ard.yaml new file mode 100644 index 0000000000..25cb37a0b4 --- /dev/null +++ b/tests/components/switch/test.esp32-c3-ard.yaml @@ -0,0 +1,2 @@ +packages: + common: !include common.yaml diff --git a/tests/components/switch/test.esp32-c3-idf.yaml b/tests/components/switch/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..25cb37a0b4 --- /dev/null +++ b/tests/components/switch/test.esp32-c3-idf.yaml @@ -0,0 +1,2 @@ +packages: + common: !include common.yaml diff --git a/tests/components/switch/test.esp32-idf.yaml b/tests/components/switch/test.esp32-idf.yaml new file mode 100644 index 0000000000..25cb37a0b4 --- /dev/null +++ b/tests/components/switch/test.esp32-idf.yaml @@ -0,0 +1,2 @@ +packages: + common: !include common.yaml diff --git a/tests/components/switch/test.esp32-s3-idf.yaml b/tests/components/switch/test.esp32-s3-idf.yaml new file mode 100644 index 0000000000..25cb37a0b4 --- /dev/null +++ b/tests/components/switch/test.esp32-s3-idf.yaml @@ -0,0 +1,2 @@ +packages: + common: !include common.yaml diff --git a/tests/components/switch/test.esp8266-ard.yaml b/tests/components/switch/test.esp8266-ard.yaml new file mode 100644 index 0000000000..25cb37a0b4 --- /dev/null +++ b/tests/components/switch/test.esp8266-ard.yaml @@ -0,0 +1,2 @@ +packages: + common: !include common.yaml diff --git a/tests/components/switch/test.rp2040-ard.yaml b/tests/components/switch/test.rp2040-ard.yaml new file mode 100644 index 0000000000..25cb37a0b4 --- /dev/null +++ b/tests/components/switch/test.rp2040-ard.yaml @@ -0,0 +1,2 @@ +packages: + common: !include common.yaml diff --git a/tests/components/waveshare_epaper/test.esp32-ard.yaml b/tests/components/waveshare_epaper/test.esp32-ard.yaml index 2f06c5c51b..944f98a1e9 100644 --- a/tests/components/waveshare_epaper/test.esp32-ard.yaml +++ b/tests/components/waveshare_epaper/test.esp32-ard.yaml @@ -188,3 +188,20 @@ display: full_update_every: 30 lambda: |- it.rectangle(0, 0, it.get_width(), it.get_height()); + - platform: waveshare_epaper + model: 7.50in-bv3-bwr + spi_id: spi_id_1 + cs_pin: + allow_other_uses: true + number: GPIO25 + dc_pin: + allow_other_uses: true + number: GPIO26 + busy_pin: + allow_other_uses: true + number: GPIO27 + reset_pin: + allow_other_uses: true + number: GPIO32 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); diff --git a/tests/components/waveshare_epaper/test.esp32-c3-ard.yaml b/tests/components/waveshare_epaper/test.esp32-c3-ard.yaml index 1c4547b7b4..5d651bd180 100644 --- a/tests/components/waveshare_epaper/test.esp32-c3-ard.yaml +++ b/tests/components/waveshare_epaper/test.esp32-c3-ard.yaml @@ -105,3 +105,19 @@ display: model: 1.54in-m5coreink-m09 lambda: |- it.rectangle(0, 0, it.get_width(), it.get_height()); + - platform: waveshare_epaper + cs_pin: + allow_other_uses: true + number: 4 + dc_pin: + allow_other_uses: true + number: 4 + busy_pin: + allow_other_uses: true + number: 4 + reset_pin: + allow_other_uses: true + number: 4 + model: 7.50in-bv3-bwr + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); diff --git a/tests/components/waveshare_epaper/test.esp32-c3-idf.yaml b/tests/components/waveshare_epaper/test.esp32-c3-idf.yaml index 1c4547b7b4..5d651bd180 100644 --- a/tests/components/waveshare_epaper/test.esp32-c3-idf.yaml +++ b/tests/components/waveshare_epaper/test.esp32-c3-idf.yaml @@ -105,3 +105,19 @@ display: model: 1.54in-m5coreink-m09 lambda: |- it.rectangle(0, 0, it.get_width(), it.get_height()); + - platform: waveshare_epaper + cs_pin: + allow_other_uses: true + number: 4 + dc_pin: + allow_other_uses: true + number: 4 + busy_pin: + allow_other_uses: true + number: 4 + reset_pin: + allow_other_uses: true + number: 4 + model: 7.50in-bv3-bwr + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); diff --git a/tests/components/waveshare_epaper/test.esp32-idf.yaml b/tests/components/waveshare_epaper/test.esp32-idf.yaml index b6082fcfbf..47f894d967 100644 --- a/tests/components/waveshare_epaper/test.esp32-idf.yaml +++ b/tests/components/waveshare_epaper/test.esp32-idf.yaml @@ -105,3 +105,19 @@ display: model: 1.54in-m5coreink-m09 lambda: |- it.rectangle(0, 0, it.get_width(), it.get_height()); + - platform: waveshare_epaper + cs_pin: + allow_other_uses: true + number: 4 + dc_pin: + allow_other_uses: true + number: 4 + busy_pin: + allow_other_uses: true + number: 4 + reset_pin: + allow_other_uses: true + number: 4 + model: 7.50in-bv3-bwr + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); diff --git a/tests/components/waveshare_epaper/test.esp8266-ard.yaml b/tests/components/waveshare_epaper/test.esp8266-ard.yaml index 1f076a67be..ceda328598 100644 --- a/tests/components/waveshare_epaper/test.esp8266-ard.yaml +++ b/tests/components/waveshare_epaper/test.esp8266-ard.yaml @@ -105,3 +105,19 @@ display: model: 1.54in-m5coreink-m09 lambda: |- it.rectangle(0, 0, it.get_width(), it.get_height()); + - platform: waveshare_epaper + cs_pin: + allow_other_uses: true + number: 4 + dc_pin: + allow_other_uses: true + number: 4 + busy_pin: + allow_other_uses: true + number: 4 + reset_pin: + allow_other_uses: true + number: 4 + model: 7.50in-bv3-bwr + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); diff --git a/tests/components/waveshare_epaper/test.rp2040-ard.yaml b/tests/components/waveshare_epaper/test.rp2040-ard.yaml index 6050062d7e..be7e780033 100644 --- a/tests/components/waveshare_epaper/test.rp2040-ard.yaml +++ b/tests/components/waveshare_epaper/test.rp2040-ard.yaml @@ -105,3 +105,19 @@ display: model: 1.54in-m5coreink-m09 lambda: |- it.rectangle(0, 0, it.get_width(), it.get_height()); + - platform: waveshare_epaper + cs_pin: + allow_other_uses: true + number: 5 + dc_pin: + allow_other_uses: true + number: 5 + busy_pin: + allow_other_uses: true + number: 5 + reset_pin: + allow_other_uses: true + number: 5 + model: 7.50in-bv3-bwr + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); diff --git a/tests/components/wifi/common.yaml b/tests/components/wifi/common.yaml index 003f6347be..343d44b177 100644 --- a/tests/components/wifi/common.yaml +++ b/tests/components/wifi/common.yaml @@ -3,6 +3,13 @@ esphome: then: - wifi.disable - wifi.enable + - wifi.configure: + ssid: MySSID + password: password1 + on_connect: + - logger.log: "Connected to WiFi!" + on_error: + - logger.log: "Failed to connect to WiFi!" wifi: ssid: MySSID diff --git a/tests/test_build_components/build_components_base.esp32-c3-idf-51.yaml b/tests/test_build_components/build_components_base.esp32-c3-idf-51.yaml deleted file mode 100644 index eb5b23a4ec..0000000000 --- a/tests/test_build_components/build_components_base.esp32-c3-idf-51.yaml +++ /dev/null @@ -1,19 +0,0 @@ -esphome: - name: componenttestesp32c3idf51 - friendly_name: $component_name - -esp32: - board: lolin_c3_mini - framework: - type: esp-idf - version: 5.1.2 - platform_version: 6.5.0 - -logger: - level: VERY_VERBOSE - -packages: - component_under_test: !include - file: $component_test_file - vars: - component_test_file: $component_test_file diff --git a/tests/test_build_components/build_components_base.esp32-idf-51.yaml b/tests/test_build_components/build_components_base.esp32-idf-51.yaml deleted file mode 100644 index b5e3dd6d83..0000000000 --- a/tests/test_build_components/build_components_base.esp32-idf-51.yaml +++ /dev/null @@ -1,19 +0,0 @@ -esphome: - name: componenttestesp32idf51 - friendly_name: $component_name - -esp32: - board: nodemcu-32s - framework: - type: esp-idf - version: 5.1.2 - platform_version: 6.5.0 - -logger: - level: VERY_VERBOSE - -packages: - component_under_test: !include - file: $component_test_file - vars: - component_test_file: $component_test_file diff --git a/tests/test_build_components/build_components_base.esp32-s2-idf-51.yaml b/tests/test_build_components/build_components_base.esp32-s2-idf-51.yaml deleted file mode 100644 index 11b077509e..0000000000 --- a/tests/test_build_components/build_components_base.esp32-s2-idf-51.yaml +++ /dev/null @@ -1,20 +0,0 @@ -esphome: - name: componenttestesp32s2idf51 - friendly_name: $component_name - -esp32: - board: esp32-s2-saola-1 - variant: ESP32S2 - framework: - type: esp-idf - version: 5.1.2 - platform_version: 6.5.0 - -logger: - level: VERY_VERBOSE - -packages: - component_under_test: !include - file: $component_test_file - vars: - component_test_file: $component_test_file diff --git a/tests/test_build_components/build_components_base.esp32-s3-idf-51.yaml b/tests/test_build_components/build_components_base.esp32-s3-idf-51.yaml deleted file mode 100644 index 4357b3581b..0000000000 --- a/tests/test_build_components/build_components_base.esp32-s3-idf-51.yaml +++ /dev/null @@ -1,20 +0,0 @@ -esphome: - name: componenttestesp32s3idf51 - friendly_name: $component_name - -esp32: - board: esp32s3box - variant: ESP32S3 - framework: - type: esp-idf - version: 5.1.2 - platform_version: 6.5.0 - -logger: - level: VERY_VERBOSE - -packages: - component_under_test: !include - file: $component_test_file - vars: - component_test_file: $component_test_file