1
0
mirror of https://github.com/esphome/esphome.git synced 2025-03-15 15:18:16 +00:00

Merge branch 'dev' into optolink

This commit is contained in:
j0ta29 2024-12-27 21:02:19 +01:00 committed by GitHub
commit 62b8a77c49
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
174 changed files with 4038 additions and 1757 deletions

View File

@ -7,28 +7,39 @@ Checks: >-
-boost-*, -boost-*,
-bugprone-easily-swappable-parameters, -bugprone-easily-swappable-parameters,
-bugprone-implicit-widening-of-multiplication-result, -bugprone-implicit-widening-of-multiplication-result,
-bugprone-multi-level-implicit-pointer-conversion,
-bugprone-narrowing-conversions, -bugprone-narrowing-conversions,
-bugprone-signed-char-misuse, -bugprone-signed-char-misuse,
-bugprone-switch-missing-default-case,
-cert-dcl50-cpp, -cert-dcl50-cpp,
-cert-err33-c, -cert-err33-c,
-cert-err58-cpp, -cert-err58-cpp,
-cert-oop57-cpp, -cert-oop57-cpp,
-cert-str34-c, -cert-str34-c,
-clang-analyzer-optin.core.EnumCastOutOfRange,
-clang-analyzer-optin.cplusplus.UninitializedObject, -clang-analyzer-optin.cplusplus.UninitializedObject,
-clang-analyzer-osx.*, -clang-analyzer-osx.*,
-clang-diagnostic-delete-abstract-non-virtual-dtor, -clang-diagnostic-delete-abstract-non-virtual-dtor,
-clang-diagnostic-delete-non-abstract-non-virtual-dtor, -clang-diagnostic-delete-non-abstract-non-virtual-dtor,
-clang-diagnostic-deprecated-declarations,
-clang-diagnostic-ignored-optimization-argument, -clang-diagnostic-ignored-optimization-argument,
-clang-diagnostic-missing-field-initializers,
-clang-diagnostic-shadow-field, -clang-diagnostic-shadow-field,
-clang-diagnostic-unused-const-variable, -clang-diagnostic-unused-const-variable,
-clang-diagnostic-unused-parameter, -clang-diagnostic-unused-parameter,
-clang-diagnostic-vla-cxx-extension,
-concurrency-*, -concurrency-*,
-cppcoreguidelines-avoid-c-arrays, -cppcoreguidelines-avoid-c-arrays,
-cppcoreguidelines-avoid-const-or-ref-data-members,
-cppcoreguidelines-avoid-do-while,
-cppcoreguidelines-avoid-magic-numbers, -cppcoreguidelines-avoid-magic-numbers,
-cppcoreguidelines-init-variables, -cppcoreguidelines-init-variables,
-cppcoreguidelines-macro-to-enum,
-cppcoreguidelines-macro-usage, -cppcoreguidelines-macro-usage,
-cppcoreguidelines-missing-std-forward,
-cppcoreguidelines-narrowing-conversions, -cppcoreguidelines-narrowing-conversions,
-cppcoreguidelines-non-private-member-variables-in-classes, -cppcoreguidelines-non-private-member-variables-in-classes,
-cppcoreguidelines-owning-memory,
-cppcoreguidelines-prefer-member-initializer, -cppcoreguidelines-prefer-member-initializer,
-cppcoreguidelines-pro-bounds-array-to-pointer-decay, -cppcoreguidelines-pro-bounds-array-to-pointer-decay,
-cppcoreguidelines-pro-bounds-constant-array-index, -cppcoreguidelines-pro-bounds-constant-array-index,
@ -40,7 +51,9 @@ Checks: >-
-cppcoreguidelines-pro-type-static-cast-downcast, -cppcoreguidelines-pro-type-static-cast-downcast,
-cppcoreguidelines-pro-type-union-access, -cppcoreguidelines-pro-type-union-access,
-cppcoreguidelines-pro-type-vararg, -cppcoreguidelines-pro-type-vararg,
-cppcoreguidelines-rvalue-reference-param-not-moved,
-cppcoreguidelines-special-member-functions, -cppcoreguidelines-special-member-functions,
-cppcoreguidelines-use-default-member-init,
-cppcoreguidelines-virtual-class-destructor, -cppcoreguidelines-virtual-class-destructor,
-fuchsia-multiple-inheritance, -fuchsia-multiple-inheritance,
-fuchsia-overloaded-operator, -fuchsia-overloaded-operator,
@ -60,21 +73,32 @@ Checks: >-
-llvm-include-order, -llvm-include-order,
-llvm-qualified-auto, -llvm-qualified-auto,
-llvmlibc-*, -llvmlibc-*,
-misc-non-private-member-variables-in-classes, -misc-const-correctness,
-misc-include-cleaner,
-misc-no-recursion, -misc-no-recursion,
-misc-non-private-member-variables-in-classes,
-misc-unused-parameters, -misc-unused-parameters,
-misc-use-anonymous-namespace,
-modernize-avoid-bind, -modernize-avoid-bind,
-modernize-avoid-c-arrays, -modernize-avoid-c-arrays,
-modernize-concat-nested-namespaces, -modernize-concat-nested-namespaces,
-modernize-macro-to-enum,
-modernize-return-braced-init-list, -modernize-return-braced-init-list,
-modernize-type-traits,
-modernize-use-auto, -modernize-use-auto,
-modernize-use-constraints,
-modernize-use-default-member-init, -modernize-use-default-member-init,
-modernize-use-equals-default, -modernize-use-equals-default,
-modernize-use-nodiscard, -modernize-use-nodiscard,
-modernize-use-nullptr, -modernize-use-nullptr,
-modernize-use-nodiscard,
-modernize-use-nullptr,
-modernize-use-trailing-return-type, -modernize-use-trailing-return-type,
-mpi-*, -mpi-*,
-objc-*, -objc-*,
-performance-enum-size,
-readability-avoid-nested-conditional-operator,
-readability-container-contains,
-readability-container-data-pointer, -readability-container-data-pointer,
-readability-convert-member-functions-to-static, -readability-convert-member-functions-to-static,
-readability-else-after-return, -readability-else-after-return,
@ -84,11 +108,13 @@ Checks: >-
-readability-magic-numbers, -readability-magic-numbers,
-readability-make-member-function-const, -readability-make-member-function-const,
-readability-named-parameter, -readability-named-parameter,
-readability-redundant-casting,
-readability-redundant-inline-specifier,
-readability-redundant-member-init,
-readability-redundant-string-init, -readability-redundant-string-init,
-readability-uppercase-literal-suffix, -readability-uppercase-literal-suffix,
-readability-use-anyofallof, -readability-use-anyofallof,
WarningsAsErrors: '*' WarningsAsErrors: '*'
AnalyzeTemporaryDtors: false
FormatStyle: google FormatStyle: google
CheckOptions: CheckOptions:
- key: google-readability-function-size.StatementThreshold - key: google-readability-function-size.StatementThreshold

View File

@ -22,7 +22,7 @@ runs:
python-version: ${{ inputs.python-version }} python-version: ${{ inputs.python-version }}
- name: Restore Python virtual environment - name: Restore Python virtual environment
id: cache-venv id: cache-venv
uses: actions/cache/restore@v4.1.2 uses: actions/cache/restore@v4.2.0
with: with:
path: venv path: venv
# yamllint disable-line rule:line-length # yamllint disable-line rule:line-length

View File

@ -46,7 +46,7 @@ jobs:
with: with:
python-version: "3.9" python-version: "3.9"
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3.7.1 uses: docker/setup-buildx-action@v3.8.0
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@v3.2.0 uses: docker/setup-qemu-action@v3.2.0

View File

@ -13,6 +13,7 @@ on:
- ".github/workflows/ci.yml" - ".github/workflows/ci.yml"
- "!.yamllint" - "!.yamllint"
- "!.github/dependabot.yml" - "!.github/dependabot.yml"
- "!docker/**"
merge_group: merge_group:
permissions: permissions:
@ -30,7 +31,7 @@ concurrency:
jobs: jobs:
common: common:
name: Create common environment name: Create common environment
runs-on: ubuntu-latest runs-on: ubuntu-24.04
outputs: outputs:
cache-key: ${{ steps.cache-key.outputs.key }} cache-key: ${{ steps.cache-key.outputs.key }}
steps: steps:
@ -46,7 +47,7 @@ jobs:
python-version: ${{ env.DEFAULT_PYTHON }} python-version: ${{ env.DEFAULT_PYTHON }}
- name: Restore Python virtual environment - name: Restore Python virtual environment
id: cache-venv id: cache-venv
uses: actions/cache@v4.1.2 uses: actions/cache@v4.2.0
with: with:
path: venv path: venv
# yamllint disable-line rule:line-length # yamllint disable-line rule:line-length
@ -62,7 +63,7 @@ jobs:
black: black:
name: Check black name: Check black
runs-on: ubuntu-latest runs-on: ubuntu-24.04
needs: needs:
- common - common
steps: steps:
@ -83,7 +84,7 @@ jobs:
flake8: flake8:
name: Check flake8 name: Check flake8
runs-on: ubuntu-latest runs-on: ubuntu-24.04
needs: needs:
- common - common
steps: steps:
@ -104,7 +105,7 @@ jobs:
pylint: pylint:
name: Check pylint name: Check pylint
runs-on: ubuntu-latest runs-on: ubuntu-24.04
needs: needs:
- common - common
steps: steps:
@ -125,7 +126,7 @@ jobs:
pyupgrade: pyupgrade:
name: Check pyupgrade name: Check pyupgrade
runs-on: ubuntu-latest runs-on: ubuntu-24.04
needs: needs:
- common - common
steps: steps:
@ -146,7 +147,7 @@ jobs:
ci-custom: ci-custom:
name: Run script/ci-custom name: Run script/ci-custom
runs-on: ubuntu-latest runs-on: ubuntu-24.04
needs: needs:
- common - common
steps: steps:
@ -225,7 +226,7 @@ jobs:
clang-format: clang-format:
name: Check clang-format name: Check clang-format
runs-on: ubuntu-latest runs-on: ubuntu-24.04
needs: needs:
- common - common
steps: steps:
@ -251,7 +252,7 @@ jobs:
clang-tidy: clang-tidy:
name: ${{ matrix.name }} name: ${{ matrix.name }}
runs-on: ubuntu-latest runs-on: ubuntu-24.04
needs: needs:
- common - common
- black - black
@ -302,23 +303,18 @@ jobs:
- name: Cache platformio - name: Cache platformio
if: github.ref == 'refs/heads/dev' if: github.ref == 'refs/heads/dev'
uses: actions/cache@v4.1.2 uses: actions/cache@v4.2.0
with: with:
path: ~/.platformio path: ~/.platformio
key: platformio-${{ matrix.pio_cache_key }} key: platformio-${{ matrix.pio_cache_key }}
- name: Cache platformio - name: Cache platformio
if: github.ref != 'refs/heads/dev' if: github.ref != 'refs/heads/dev'
uses: actions/cache/restore@v4.1.2 uses: actions/cache/restore@v4.2.0
with: with:
path: ~/.platformio path: ~/.platformio
key: platformio-${{ matrix.pio_cache_key }} 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 - name: Register problem matchers
run: | run: |
echo "::add-matcher::.github/workflows/matchers/gcc.json" echo "::add-matcher::.github/workflows/matchers/gcc.json"
@ -345,7 +341,7 @@ jobs:
if: always() if: always()
list-components: list-components:
runs-on: ubuntu-latest runs-on: ubuntu-24.04
needs: needs:
- common - common
if: github.event_name == 'pull_request' if: github.event_name == 'pull_request'
@ -387,7 +383,7 @@ jobs:
test-build-components: test-build-components:
name: Component test ${{ matrix.file }} name: Component test ${{ matrix.file }}
runs-on: ubuntu-latest runs-on: ubuntu-24.04
needs: needs:
- common - common
- list-components - list-components
@ -421,7 +417,7 @@ jobs:
test-build-components-splitter: test-build-components-splitter:
name: Split components for testing into 20 groups maximum name: Split components for testing into 20 groups maximum
runs-on: ubuntu-latest runs-on: ubuntu-24.04
needs: needs:
- common - common
- list-components - list-components
@ -439,7 +435,7 @@ jobs:
test-build-components-split: test-build-components-split:
name: Test split components name: Test split components
runs-on: ubuntu-latest runs-on: ubuntu-24.04
needs: needs:
- common - common
- list-components - list-components
@ -483,7 +479,7 @@ jobs:
ci-status: ci-status:
name: CI Status name: CI Status
runs-on: ubuntu-latest runs-on: ubuntu-24.04
needs: needs:
- common - common
- black - black

View File

@ -65,7 +65,7 @@ jobs:
pip3 install build pip3 install build
python3 -m build python3 -m build
- name: Publish - name: Publish
uses: pypa/gh-action-pypi-publish@v1.12.2 uses: pypa/gh-action-pypi-publish@v1.12.3
deploy-docker: deploy-docker:
name: Build ESPHome ${{ matrix.platform }} name: Build ESPHome ${{ matrix.platform }}
@ -90,7 +90,7 @@ jobs:
python-version: "3.9" python-version: "3.9"
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3.7.1 uses: docker/setup-buildx-action@v3.8.0
- name: Set up QEMU - name: Set up QEMU
if: matrix.platform != 'linux/amd64' if: matrix.platform != 'linux/amd64'
uses: docker/setup-qemu-action@v3.2.0 uses: docker/setup-qemu-action@v3.2.0
@ -141,7 +141,7 @@ jobs:
echo name=$(cat /tmp/platform) >> $GITHUB_OUTPUT echo name=$(cat /tmp/platform) >> $GITHUB_OUTPUT
- name: Upload digests - name: Upload digests
uses: actions/upload-artifact@v4.4.3 uses: actions/upload-artifact@v4.5.0
with: with:
name: digests-${{ steps.sanitize.outputs.name }} name: digests-${{ steps.sanitize.outputs.name }}
path: /tmp/digests path: /tmp/digests
@ -184,7 +184,7 @@ jobs:
merge-multiple: true merge-multiple: true
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3.7.1 uses: docker/setup-buildx-action@v3.8.0
- name: Log in to docker hub - name: Log in to docker hub
if: matrix.registry == 'dockerhub' if: matrix.registry == 'dockerhub'

View File

@ -179,6 +179,7 @@ esphome/components/haier/text_sensor/* @paveldn
esphome/components/havells_solar/* @sourabhjaiswal esphome/components/havells_solar/* @sourabhjaiswal
esphome/components/hbridge/fan/* @WeekendWarrior esphome/components/hbridge/fan/* @WeekendWarrior
esphome/components/hbridge/light/* @DotNetDann esphome/components/hbridge/light/* @DotNetDann
esphome/components/hbridge/switch/* @dwmw2
esphome/components/he60r/* @clydebarrow esphome/components/he60r/* @clydebarrow
esphome/components/heatpumpir/* @rob-deutsch esphome/components/heatpumpir/* @rob-deutsch
esphome/components/hitachi_ac424/* @sourabhjaiswal esphome/components/hitachi_ac424/* @sourabhjaiswal
@ -362,6 +363,7 @@ esphome/components/sdl/* @clydebarrow
esphome/components/sdm_meter/* @jesserockz @polyfaces esphome/components/sdm_meter/* @jesserockz @polyfaces
esphome/components/sdp3x/* @Azimath esphome/components/sdp3x/* @Azimath
esphome/components/seeed_mr24hpc1/* @limengdu esphome/components/seeed_mr24hpc1/* @limengdu
esphome/components/seeed_mr60bha2/* @limengdu
esphome/components/seeed_mr60fda2/* @limengdu esphome/components/seeed_mr60fda2/* @limengdu
esphome/components/selec_meter/* @sourabhjaiswal esphome/components/selec_meter/* @sourabhjaiswal
esphome/components/select/* @esphome/core esphome/components/select/* @esphome/core

View File

@ -163,6 +163,18 @@ ENTRYPOINT ["/entrypoint.sh"]
CMD ["dashboard", "/config"] 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 ======================= # ======================= hassio-type image =======================
@ -194,7 +206,7 @@ RUN if [ "$TARGETARCH$TARGETVARIANT" = "armv7" ]; then \
# Labels # Labels
LABEL \ LABEL \
io.hass.name="ESPHome" \ 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.type="addon" \
io.hass.version="${BUILD_VERSION}" io.hass.version="${BUILD_VERSION}"
# io.hass.arch is inherited from addon-debian-base # io.hass.arch is inherited from addon-debian-base
@ -209,17 +221,22 @@ ENV \
PLATFORMIO_CORE_DIR=/esphome/.temp/platformio PLATFORMIO_CORE_DIR=/esphome/.temp/platformio
RUN \ 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 # Use pinned versions so that we get updates with build caching
&& apt-get install -y --no-install-recommends \ && apt-get install -y --no-install-recommends \
clang-format-13=1:13.0.1-11+b2 \ clang-format-13=1:13.0.1-11+b2 \
clang-tidy-14=1:14.0.6-12 \
patch=2.7.6-7 \ patch=2.7.6-7 \
software-properties-common=0.99.30-4.1~deb12u1 \ software-properties-common=0.99.30-4.1~deb12u1 \
nano=7.2-1+deb12u1 \ nano=7.2-1+deb12u1 \
build-essential=12.9 \ build-essential=12.9 \
python3-dev=3.11.2-1+b1 \ 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/* \ /tmp/* \
/var/{cache,log}/* \ /var/{cache,log}/* \
/var/lib/apt/lists/* /var/lib/apt/lists/*

View File

@ -363,7 +363,7 @@ def upload_program(config, args, host):
from esphome import espota2 from esphome import espota2
remote_port = ota_conf[CONF_PORT] remote_port = int(ota_conf[CONF_PORT])
password = ota_conf.get(CONF_PASSWORD, "") password = ota_conf.get(CONF_PASSWORD, "")
if ( if (

View File

@ -1,11 +1,6 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import pins from esphome import pins
from esphome.const import CONF_ANALOG, CONF_INPUT, CONF_NUMBER import esphome.codegen as cg
from esphome.core import CORE
from esphome.components.esp32 import get_esp32_variant from esphome.components.esp32 import get_esp32_variant
from esphome.const import PLATFORM_ESP8266
from esphome.components.esp32.const import ( from esphome.components.esp32.const import (
VARIANT_ESP32, VARIANT_ESP32,
VARIANT_ESP32C2, VARIANT_ESP32C2,
@ -15,6 +10,9 @@ from esphome.components.esp32.const import (
VARIANT_ESP32S2, VARIANT_ESP32S2,
VARIANT_ESP32S3, VARIANT_ESP32S3,
) )
import esphome.config_validation as cv
from esphome.const import CONF_ANALOG, CONF_INPUT, CONF_NUMBER, PLATFORM_ESP8266
from esphome.core import CORE
CODEOWNERS = ["@esphome/core"] CODEOWNERS = ["@esphome/core"]
@ -102,11 +100,11 @@ ESP32_VARIANT_ADC1_PIN_TO_CHANNEL = {
6: adc1_channel_t.ADC1_CHANNEL_6, 6: adc1_channel_t.ADC1_CHANNEL_6,
}, },
VARIANT_ESP32H2: { VARIANT_ESP32H2: {
0: adc1_channel_t.ADC1_CHANNEL_0, 1: adc1_channel_t.ADC1_CHANNEL_0,
1: adc1_channel_t.ADC1_CHANNEL_1, 2: adc1_channel_t.ADC1_CHANNEL_1,
2: adc1_channel_t.ADC1_CHANNEL_2, 3: adc1_channel_t.ADC1_CHANNEL_2,
3: adc1_channel_t.ADC1_CHANNEL_3, 4: adc1_channel_t.ADC1_CHANNEL_3,
4: adc1_channel_t.ADC1_CHANNEL_4, 5: adc1_channel_t.ADC1_CHANNEL_4,
}, },
} }

View File

@ -3,13 +3,12 @@
#include "esphome/components/sensor/sensor.h" #include "esphome/components/sensor/sensor.h"
#include "esphome/components/voltage_sampler/voltage_sampler.h" #include "esphome/components/voltage_sampler/voltage_sampler.h"
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/core/defines.h"
#include "esphome/core/hal.h" #include "esphome/core/hal.h"
#ifdef USE_ESP32 #ifdef USE_ESP32
#include <esp_adc_cal.h> #include <esp_adc_cal.h>
#include "driver/adc.h" #include "driver/adc.h"
#endif #endif // USE_ESP32
namespace esphome { namespace esphome {
namespace adc { namespace adc {
@ -43,7 +42,7 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage
this->channel1_ = ADC1_CHANNEL_MAX; this->channel1_ = ADC1_CHANNEL_MAX;
} }
void set_autorange(bool autorange) { this->autorange_ = autorange; } void set_autorange(bool autorange) { this->autorange_ = autorange; }
#endif #endif // USE_ESP32
/// Update ADC values /// Update ADC values
void update() override; void update() override;
@ -59,11 +58,11 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage
#ifdef USE_ESP8266 #ifdef USE_ESP8266
std::string unique_id() override; std::string unique_id() override;
#endif #endif // USE_ESP8266
#ifdef USE_RP2040 #ifdef USE_RP2040
void set_is_temperature() { this->is_temperature_ = true; } void set_is_temperature() { this->is_temperature_ = true; }
#endif #endif // USE_RP2040
protected: protected:
InternalGPIOPin *pin_; InternalGPIOPin *pin_;
@ -72,7 +71,7 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage
#ifdef USE_RP2040 #ifdef USE_RP2040
bool is_temperature_{false}; bool is_temperature_{false};
#endif #endif // USE_RP2040
#ifdef USE_ESP32 #ifdef USE_ESP32
adc_atten_t attenuation_{ADC_ATTEN_DB_0}; 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] = {}; esp_adc_cal_characteristics_t cal_characteristics_[SOC_ADC_ATTEN_NUM] = {};
#else #else
esp_adc_cal_characteristics_t cal_characteristics_[ADC_ATTEN_MAX] = {}; esp_adc_cal_characteristics_t cal_characteristics_[ADC_ATTEN_MAX] = {};
#endif #endif // ESP_IDF_VERSION_MAJOR
#endif #endif // USE_ESP32
}; };
} // namespace adc } // namespace adc

View File

@ -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

View File

@ -1,30 +1,13 @@
#ifdef USE_ESP32
#include "adc_sensor.h" #include "adc_sensor.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
#ifdef USE_ESP8266
#ifdef USE_ADC_SENSOR_VCC
#include <Esp.h>
ADC_MODE(ADC_VCC)
#else
#include <Arduino.h>
#endif
#endif
#ifdef USE_RP2040
#ifdef CYW43_USES_VSYS_PIN
#include "pico/cyw43_arch.h"
#endif
#include <hardware/adc.h>
#endif
namespace esphome { namespace esphome {
namespace adc { 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_bits_width_t>(ADC_WIDTH_MAX - 1); static const adc_bits_width_t ADC_WIDTH_MAX_SOC_BITS = static_cast<adc_bits_width_t>(ADC_WIDTH_MAX - 1);
#ifndef SOC_ADC_RTC_MAX_BITWIDTH #ifndef SOC_ADC_RTC_MAX_BITWIDTH
@ -32,24 +15,15 @@ static const adc_bits_width_t ADC_WIDTH_MAX_SOC_BITS = static_cast<adc_bits_widt
static const int32_t SOC_ADC_RTC_MAX_BITWIDTH = 13; static const int32_t SOC_ADC_RTC_MAX_BITWIDTH = 13;
#else #else
static const int32_t SOC_ADC_RTC_MAX_BITWIDTH = 12; static const int32_t SOC_ADC_RTC_MAX_BITWIDTH = 12;
#endif #endif // USE_ESP32_VARIANT_ESP32S2
#endif #endif // SOC_ADC_RTC_MAX_BITWIDTH
static const int ADC_MAX = (1 << SOC_ADC_RTC_MAX_BITWIDTH) - 1; // 4095 (12 bit) or 8191 (13 bit) static const int ADC_MAX = (1 << SOC_ADC_RTC_MAX_BITWIDTH) - 1;
static const int ADC_HALF = (1 << SOC_ADC_RTC_MAX_BITWIDTH) >> 1; // 2048 (12 bit) or 4096 (13 bit) static const int ADC_HALF = (1 << SOC_ADC_RTC_MAX_BITWIDTH) >> 1;
#endif
#ifdef USE_RP2040 void ADCSensor::setup() {
extern "C"
#endif
void
ADCSensor::setup() {
ESP_LOGCONFIG(TAG, "Setting up ADC '%s'...", this->get_name().c_str()); 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) { if (this->channel1_ != ADC1_CHANNEL_MAX) {
adc1_config_width(ADC_WIDTH_MAX_SOC_BITS); adc1_config_width(ADC_WIDTH_MAX_SOC_BITS);
if (!this->autorange_) { 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++) { 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 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, 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; 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() { void ADCSensor::dump_config() {
LOG_SENSOR("", "ADC Sensor", this); 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_); LOG_PIN(" Pin: ", this->pin_);
if (this->autorange_) { if (this->autorange_) {
ESP_LOGCONFIG(TAG, " Attenuation: auto"); ESP_LOGCONFIG(TAG, " Attenuation: auto");
@ -125,55 +77,10 @@ void ADCSensor::dump_config() {
break; 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_); ESP_LOGCONFIG(TAG, " Samples: %i", this->sample_count_);
LOG_UPDATE_INTERVAL(this); 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() { float ADCSensor::sample() {
if (!this->autorange_) { if (!this->autorange_) {
uint32_t sum = 0; 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 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]); 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 c12 = std::min(raw12, ADC_HALF);
uint32_t c6 = ADC_HALF - std::abs(raw6 - ADC_HALF); uint32_t c6 = ADC_HALF - std::abs(raw6 - ADC_HALF);
uint32_t c2 = ADC_HALF - std::abs(raw2 - ADC_HALF); uint32_t c2 = ADC_HALF - std::abs(raw2 - ADC_HALF);
uint32_t c0 = std::min(ADC_MAX - raw0, 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; 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); uint32_t mv_scaled = (mv12 * c12) + (mv6 * c6) + (mv2 * c2) + (mv0 * c0);
return mv_scaled / (float) (csum * 1000U); 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 adc
} // namespace esphome } // namespace esphome
#endif // USE_ESP32

View File

@ -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 <Esp.h>
ADC_MODE(ADC_VCC)
#else
#include <Arduino.h>
#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

View File

@ -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

View File

@ -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 <hardware/adc.h>
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

View File

@ -2,7 +2,6 @@ import logging
from esphome import automation, core from esphome import automation, core
import esphome.codegen as cg import esphome.codegen as cg
from esphome.components import font
import esphome.components.image as espImage import esphome.components.image as espImage
from esphome.components.image import ( from esphome.components.image import (
CONF_USE_TRANSPARENCY, 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( NEXT_FRAME_SCHEMA = automation.maybe_simple_id(
{ {

View File

@ -25,8 +25,7 @@ void BLEClient::loop() {
void BLEClient::dump_config() { void BLEClient::dump_config() {
ESP_LOGCONFIG(TAG, "BLE Client:"); ESP_LOGCONFIG(TAG, "BLE Client:");
ESP_LOGCONFIG(TAG, " Address: %s", this->address_str().c_str()); BLEClientBase::dump_config();
ESP_LOGCONFIG(TAG, " Auto-Connect: %s", TRUEFALSE(this->auto_connect_));
} }
bool BLEClient::parse_device(const espbt::ESPBTDevice &device) { bool BLEClient::parse_device(const espbt::ESPBTDevice &device) {

View File

@ -13,6 +13,11 @@ namespace bluetooth_proxy {
static const char *const TAG = "bluetooth_proxy.connection"; static const char *const TAG = "bluetooth_proxy.connection";
void BluetoothConnection::dump_config() {
ESP_LOGCONFIG(TAG, "BLE Connection:");
BLEClientBase::dump_config();
}
bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
esp_ble_gattc_cb_param_t *param) { esp_ble_gattc_cb_param_t *param) {
if (!BLEClientBase::gattc_event_handler(event, gattc_if, param)) if (!BLEClientBase::gattc_event_handler(event, gattc_if, param))

View File

@ -11,6 +11,7 @@ class BluetoothProxy;
class BluetoothConnection : public esp32_ble_client::BLEClientBase { class BluetoothConnection : public esp32_ble_client::BLEClientBase {
public: public:
void dump_config() override;
bool gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, bool gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
esp_ble_gattc_cb_param_t *param) override; esp_ble_gattc_cb_param_t *param) override;
void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override; void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override;

View File

@ -30,6 +30,57 @@ static const char *const TAG = "debug";
std::string DebugComponent::get_reset_reason_() { std::string DebugComponent::get_reset_reason_() {
std::string reset_reason; std::string reset_reason;
switch (esp_reset_reason()) {
case ESP_RST_POWERON:
reset_reason = "Reset due to power-on event";
break;
case ESP_RST_EXT:
reset_reason = "Reset by external pin";
break;
case ESP_RST_SW:
reset_reason = "Software reset via esp_restart";
break;
case ESP_RST_PANIC:
reset_reason = "Software reset due to exception/panic";
break;
case ESP_RST_INT_WDT:
reset_reason = "Reset (software or hardware) due to interrupt watchdog";
break;
case ESP_RST_TASK_WDT:
reset_reason = "Reset due to task watchdog";
break;
case ESP_RST_WDT:
reset_reason = "Reset due to other watchdogs";
break;
case ESP_RST_DEEPSLEEP:
reset_reason = "Reset after exiting deep sleep mode";
break;
case ESP_RST_BROWNOUT:
reset_reason = "Brownout reset (software or hardware)";
break;
case ESP_RST_SDIO:
reset_reason = "Reset over SDIO";
break;
#ifdef USE_ESP32_VARIANT_ESP32
#if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 1, 4))
case ESP_RST_USB:
reset_reason = "Reset by USB peripheral";
break;
case ESP_RST_JTAG:
reset_reason = "Reset by JTAG";
break;
case ESP_RST_EFUSE:
reset_reason = "Reset due to efuse error";
break;
case ESP_RST_PWR_GLITCH:
reset_reason = "Reset due to power glitch detected";
break;
case ESP_RST_CPU_LOCKUP:
reset_reason = "Reset due to CPU lock up (double exception)";
break;
#endif // ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 1, 4)
#endif // USE_ESP32_VARIANT_ESP32
default: // Includes ESP_RST_UNKNOWN
switch (rtc_get_reset_reason(0)) { switch (rtc_get_reset_reason(0)) {
case POWERON_RESET: case POWERON_RESET:
reset_reason = "Power On Reset"; reset_reason = "Power On Reset";
@ -134,6 +185,8 @@ std::string DebugComponent::get_reset_reason_() {
default: default:
reset_reason = "Unknown Reset Reason"; reset_reason = "Unknown Reset Reason";
} }
break;
}
ESP_LOGD(TAG, "Reset Reason: %s", reset_reason.c_str()); ESP_LOGD(TAG, "Reset Reason: %s", reset_reason.c_str());
return reset_reason; return reset_reason;
} }
@ -294,4 +347,4 @@ void DebugComponent::update_platform_() {
} // namespace debug } // namespace debug
} // namespace esphome } // namespace esphome
#endif #endif // USE_ESP32

View File

@ -1,6 +1,6 @@
#include "display.h" #include "display.h"
#include "display_color_utils.h"
#include <utility> #include <utility>
#include "display_color_utils.h"
#include "esphome/core/hal.h" #include "esphome/core/hal.h"
#include "esphome/core/log.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)) if ((this->from_ == nullptr || this->from_ == from) && (this->to_ == nullptr || this->to_ == to))
this->trigger(from, 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]; char buffer[64];
size_t ret = time.strftime(buffer, sizeof(buffer), format); size_t ret = time.strftime(buffer, sizeof(buffer), format);
if (ret > 0) 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) { 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) { 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) { 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) { void Display::start_clipping(Rect rect) {

View File

@ -437,6 +437,20 @@ class Display : public PollingComponent {
*/ */
void printf(int x, int y, BaseFont *font, const char *format, ...) __attribute__((format(printf, 5, 6))); 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`. /** 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 x The x coordinate of the text alignment anchor point.

View File

@ -65,6 +65,8 @@ _LOGGER = logging.getLogger(__name__)
CODEOWNERS = ["@esphome/core"] CODEOWNERS = ["@esphome/core"]
AUTO_LOAD = ["preferences"] AUTO_LOAD = ["preferences"]
CONF_RELEASE = "release"
def set_core_data(config): def set_core_data(config):
CORE.data[KEY_ESP32] = {} 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" 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 # format the given arduino (https://github.com/espressif/esp-idf/releases) version to
# a PIO platformio/framework-espidf value # a PIO platformio/framework-espidf value
# List of package versions: https://api.registry.platformio.org/v3/packages/platformio/tool/framework-espidf # 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: # 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 # The default/recommended esp-idf framework version
# - https://github.com/espressif/esp-idf/releases # - https://github.com/espressif/esp-idf/releases
# - https://api.registry.platformio.org/v3/packages/platformio/tool/framework-espidf # - 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 # The platformio/espressif32 version to use for esp-idf frameworks
# - https://github.com/platformio/platform-espressif32/releases # - https://github.com/platformio/platform-espressif32/releases
# - https://api.registry.platformio.org/v3/packages/platformio/platform/espressif32 # - 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): def _arduino_check_versions(value):
@ -286,8 +316,8 @@ def _arduino_check_versions(value):
def _esp_idf_check_versions(value): def _esp_idf_check_versions(value):
value = value.copy() value = value.copy()
lookups = { lookups = {
"dev": (cv.Version(5, 1, 2), "https://github.com/espressif/esp-idf.git"), "dev": (cv.Version(5, 1, 5), "https://github.com/espressif/esp-idf.git"),
"latest": (cv.Version(5, 1, 2), None), "latest": (cv.Version(5, 1, 5), None),
"recommended": (RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION, 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): if version < cv.Version(4, 0, 0):
raise cv.Invalid("Only ESP-IDF 4.0+ is supported.") raise cv.Invalid("Only ESP-IDF 4.0+ is supported.")
value[CONF_VERSION] = str(version) # flag this for later *before* we set value[CONF_PLATFORM_VERSION] below
value[CONF_SOURCE] = source or _format_framework_espidf_version(version) has_platform_ver = CONF_PLATFORM_VERSION in value
value[CONF_PLATFORM_VERSION] = value.get( value[CONF_PLATFORM_VERSION] = value.get(
CONF_PLATFORM_VERSION, _parse_platform_version(str(ESP_IDF_PLATFORM_VERSION)) 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: if version != RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION:
_LOGGER.warning( _LOGGER.warning(
"The selected ESP-IDF framework version is not the recommended one. " "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): def _parse_platform_version(value):
try: 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 # if platform version is a valid version constraint, prefix the default package
cv.platformio_version_constraint(value) cv.platformio_version_constraint(value)
return f"platformio/espressif32@{value}" return f"platformio/espressif32@{value}"
@ -330,6 +404,14 @@ def _parse_platform_version(value):
return 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): def _detect_variant(value):
board = value[CONF_BOARD] board = value[CONF_BOARD]
if board in BOARDS: if board in BOARDS:
@ -355,24 +437,20 @@ def _detect_variant(value):
def final_validate(config): def final_validate(config):
if CONF_PLATFORMIO_OPTIONS not in fv.full_config.get()[CONF_ESPHOME]: if not (
pio_options := fv.full_config.get()[CONF_ESPHOME].get(CONF_PLATFORMIO_OPTIONS)
):
# Not specified or empty
return config return config
pio_flash_size_key = "board_upload.flash_size" pio_flash_size_key = "board_upload.flash_size"
pio_partitions_key = "board_build.partitions" pio_partitions_key = "board_build.partitions"
if ( if CONF_PARTITIONS in config and pio_partitions_key in pio_options:
CONF_PARTITIONS in config
and pio_partitions_key
in fv.full_config.get()[CONF_ESPHOME][CONF_PLATFORMIO_OPTIONS]
):
raise cv.Invalid( raise cv.Invalid(
f"Do not specify '{pio_partitions_key}' in '{CONF_PLATFORMIO_OPTIONS}' with '{CONF_PARTITIONS}' in esp32" f"Do not specify '{pio_partitions_key}' in '{CONF_PLATFORMIO_OPTIONS}' with '{CONF_PARTITIONS}' in esp32"
) )
if ( if pio_flash_size_key in pio_options:
pio_flash_size_key
in fv.full_config.get()[CONF_ESPHOME][CONF_PLATFORMIO_OPTIONS]
):
raise cv.Invalid( raise cv.Invalid(
f"Please specify {CONF_FLASH_SIZE} within esp32 configuration only" f"Please specify {CONF_FLASH_SIZE} within esp32 configuration only"
) )
@ -412,6 +490,7 @@ ESP_IDF_FRAMEWORK_SCHEMA = cv.All(
cv.Schema( cv.Schema(
{ {
cv.Optional(CONF_VERSION, default="recommended"): cv.string_strict, 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_SOURCE): cv.string_strict,
cv.Optional(CONF_PLATFORM_VERSION): _parse_platform_version, cv.Optional(CONF_PLATFORM_VERSION): _parse_platform_version,
cv.Optional(CONF_SDKCONFIG_OPTIONS, default={}): { cv.Optional(CONF_SDKCONFIG_OPTIONS, default={}): {
@ -515,15 +594,17 @@ async def to_code(config):
cg.add_build_flag("-DUSE_ESP_IDF") cg.add_build_flag("-DUSE_ESP_IDF")
cg.add_build_flag("-DUSE_ESP32_FRAMEWORK_ESP_IDF") cg.add_build_flag("-DUSE_ESP32_FRAMEWORK_ESP_IDF")
cg.add_build_flag("-Wno-nonnull-compare") cg.add_build_flag("-Wno-nonnull-compare")
cg.add_platformio_option(
"platform_packages", cg.add_platformio_option("platform_packages", [conf[CONF_SOURCE]])
[f"platformio/framework-espidf@{conf[CONF_SOURCE]}"],
)
# platformio/toolchain-esp32ulp does not support linux_aarch64 yet and has not been updated for over 2 years # 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. # This is espressif's own published version which is more up to date.
cg.add_platformio_option( cg.add_platformio_option(
"platform_packages", ["espressif/toolchain-esp32ulp@2.35.0-20220830"] "platform_packages", ["espressif/toolchain-esp32ulp@2.35.0-20220830"]
) )
add_idf_sdkconfig_option(
f"CONFIG_ESPTOOLPY_FLASHSIZE_{config[CONF_FLASH_SIZE]}", True
)
add_idf_sdkconfig_option("CONFIG_PARTITION_TABLE_SINGLE_APP", False) add_idf_sdkconfig_option("CONFIG_PARTITION_TABLE_SINGLE_APP", False)
add_idf_sdkconfig_option("CONFIG_PARTITION_TABLE_CUSTOM", True) add_idf_sdkconfig_option("CONFIG_PARTITION_TABLE_CUSTOM", True)
add_idf_sdkconfig_option( add_idf_sdkconfig_option(

View File

@ -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 = { ESP32_BASE_PINS = {
"TX": 1, "TX": 1,
@ -1344,6 +1352,26 @@ done | sort
""" """
BOARDS = { 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": { "adafruit_feather_esp32s2_tft": {
"name": "Adafruit Feather ESP32-S2 TFT", "name": "Adafruit Feather ESP32-S2 TFT",
"variant": VARIANT_ESP32S2, "variant": VARIANT_ESP32S2,
@ -1356,6 +1384,10 @@ BOARDS = {
"name": "Adafruit Feather ESP32-S3 No PSRAM", "name": "Adafruit Feather ESP32-S3 No PSRAM",
"variant": VARIANT_ESP32S3, "variant": VARIANT_ESP32S3,
}, },
"adafruit_feather_esp32s3_reversetft": {
"name": "Adafruit Feather ESP32-S3 Reverse TFT",
"variant": VARIANT_ESP32S3,
},
"adafruit_feather_esp32s3_tft": { "adafruit_feather_esp32s3_tft": {
"name": "Adafruit Feather ESP32-S3 TFT", "name": "Adafruit Feather ESP32-S3 TFT",
"variant": VARIANT_ESP32S3, "variant": VARIANT_ESP32S3,
@ -1376,10 +1408,18 @@ BOARDS = {
"name": "Adafruit MagTag 2.9", "name": "Adafruit MagTag 2.9",
"variant": VARIANT_ESP32S2, "variant": VARIANT_ESP32S2,
}, },
"adafruit_matrixportal_esp32s3": {
"name": "Adafruit MatrixPortal ESP32-S3",
"variant": VARIANT_ESP32S3,
},
"adafruit_metro_esp32s2": { "adafruit_metro_esp32s2": {
"name": "Adafruit Metro ESP32-S2", "name": "Adafruit Metro ESP32-S2",
"variant": VARIANT_ESP32S2, "variant": VARIANT_ESP32S2,
}, },
"adafruit_metro_esp32s3": {
"name": "Adafruit Metro ESP32-S3",
"variant": VARIANT_ESP32S3,
},
"adafruit_qtpy_esp32c3": { "adafruit_qtpy_esp32c3": {
"name": "Adafruit QT Py ESP32-C3", "name": "Adafruit QT Py ESP32-C3",
"variant": VARIANT_ESP32C3, "variant": VARIANT_ESP32C3,
@ -1392,10 +1432,18 @@ BOARDS = {
"name": "Adafruit QT Py ESP32-S2", "name": "Adafruit QT Py ESP32-S2",
"variant": VARIANT_ESP32S2, "variant": VARIANT_ESP32S2,
}, },
"adafruit_qtpy_esp32s3_n4r2": {
"name": "Adafruit QT Py ESP32-S3 (4M Flash 2M PSRAM)",
"variant": VARIANT_ESP32S3,
},
"adafruit_qtpy_esp32s3_nopsram": { "adafruit_qtpy_esp32s3_nopsram": {
"name": "Adafruit QT Py ESP32-S3 No PSRAM", "name": "Adafruit QT Py ESP32-S3 No PSRAM",
"variant": VARIANT_ESP32S3, "variant": VARIANT_ESP32S3,
}, },
"adafruit_qualia_s3_rgb666": {
"name": "Adafruit Qualia ESP32-S3 RGB666",
"variant": VARIANT_ESP32S3,
},
"airm2m_core_esp32c3": { "airm2m_core_esp32c3": {
"name": "AirM2M CORE ESP32C3", "name": "AirM2M CORE ESP32C3",
"variant": VARIANT_ESP32C3, "variant": VARIANT_ESP32C3,
@ -1404,14 +1452,30 @@ BOARDS = {
"name": "ALKS ESP32", "name": "ALKS ESP32",
"variant": VARIANT_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": { "atmegazero_esp32s2": {
"name": "EspinalLab ATMegaZero ESP32-S2", "name": "EspinalLab ATMegaZero ESP32-S2",
"variant": VARIANT_ESP32S2, "variant": VARIANT_ESP32S2,
}, },
"aventen_s3_sync": {
"name": "Aventen S3 Sync",
"variant": VARIANT_ESP32S3,
},
"az-delivery-devkit-v4": { "az-delivery-devkit-v4": {
"name": "AZ-Delivery ESP-32 Dev Kit C V4", "name": "AZ-Delivery ESP-32 Dev Kit C V4",
"variant": VARIANT_ESP32, "variant": VARIANT_ESP32,
}, },
"bee_data_logger": {
"name": "Smart Bee Data Logger",
"variant": VARIANT_ESP32S3,
},
"bee_motion_mini": { "bee_motion_mini": {
"name": "Smart Bee Motion Mini", "name": "Smart Bee Motion Mini",
"variant": VARIANT_ESP32C3, "variant": VARIANT_ESP32C3,
@ -1436,14 +1500,6 @@ BOARDS = {
"name": "BPI-Leaf-S3", "name": "BPI-Leaf-S3",
"variant": VARIANT_ESP32S3, "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": { "cnrs_aw2eth": {
"name": "CNRS AW2ETH", "name": "CNRS AW2ETH",
"variant": VARIANT_ESP32, "variant": VARIANT_ESP32,
@ -1496,18 +1552,38 @@ BOARDS = {
"name": "DFRobot Beetle ESP32-C3", "name": "DFRobot Beetle ESP32-C3",
"variant": VARIANT_ESP32C3, "variant": VARIANT_ESP32C3,
}, },
"dfrobot_firebeetle2_esp32e": {
"name": "DFRobot Firebeetle 2 ESP32-E",
"variant": VARIANT_ESP32,
},
"dfrobot_firebeetle2_esp32s3": { "dfrobot_firebeetle2_esp32s3": {
"name": "DFRobot Firebeetle 2 ESP32-S3", "name": "DFRobot Firebeetle 2 ESP32-S3",
"variant": VARIANT_ESP32S3, "variant": VARIANT_ESP32S3,
}, },
"dfrobot_romeo_esp32s3": {
"name": "DFRobot Romeo ESP32-S3",
"variant": VARIANT_ESP32S3,
},
"dpu_esp32": { "dpu_esp32": {
"name": "TAMC DPU ESP32", "name": "TAMC DPU ESP32",
"variant": VARIANT_ESP32, "variant": VARIANT_ESP32,
}, },
"edgebox-esp-100": {
"name": "Seeed Studio Edgebox-ESP-100",
"variant": VARIANT_ESP32S3,
},
"esp320": { "esp320": {
"name": "Electronic SweetPeas ESP320", "name": "Electronic SweetPeas ESP320",
"variant": VARIANT_ESP32, "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": { "esp32-c3-devkitm-1": {
"name": "Espressif ESP32-C3-DevKitM-1", "name": "Espressif ESP32-C3-DevKitM-1",
"variant": VARIANT_ESP32C3, "variant": VARIANT_ESP32C3,
@ -1516,6 +1592,14 @@ BOARDS = {
"name": "Ai-Thinker ESP-C3-M1-I-Kit", "name": "Ai-Thinker ESP-C3-M1-I-Kit",
"variant": VARIANT_ESP32C3, "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": { "esp32cam": {
"name": "AI Thinker ESP32-CAM", "name": "AI Thinker ESP32-CAM",
"variant": VARIANT_ESP32, "variant": VARIANT_ESP32,
@ -1544,6 +1628,14 @@ BOARDS = {
"name": "OLIMEX ESP32-GATEWAY", "name": "OLIMEX ESP32-GATEWAY",
"variant": VARIANT_ESP32, "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": { "esp32-poe-iso": {
"name": "OLIMEX ESP32-PoE-ISO", "name": "OLIMEX ESP32-PoE-ISO",
"variant": VARIANT_ESP32, "variant": VARIANT_ESP32,
@ -1580,10 +1672,22 @@ BOARDS = {
"name": "Espressif ESP32-S3-DevKitC-1-N8 (8 MB QD, No PSRAM)", "name": "Espressif ESP32-S3-DevKitC-1-N8 (8 MB QD, No PSRAM)",
"variant": VARIANT_ESP32S3, "variant": VARIANT_ESP32S3,
}, },
"esp32-s3-korvo-2": { "esp32-s3-devkitm-1": {
"name": "Espressif ESP32-S3-Korvo-2", "name": "Espressif ESP32-S3-DevKitM-1",
"variant": VARIANT_ESP32S3, "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": { "esp32thing": {
"name": "SparkFun ESP32 Thing", "name": "SparkFun ESP32 Thing",
"variant": VARIANT_ESP32, "variant": VARIANT_ESP32,
@ -1652,9 +1756,9 @@ BOARDS = {
"name": "Heltec WiFi Kit 32", "name": "Heltec WiFi Kit 32",
"variant": VARIANT_ESP32, "variant": VARIANT_ESP32,
}, },
"heltec_wifi_kit_32_v2": { "heltec_wifi_kit_32_V3": {
"name": "Heltec WiFi Kit 32 (V2)", "name": "Heltec WiFi Kit 32 (V3)",
"variant": VARIANT_ESP32, "variant": VARIANT_ESP32S3,
}, },
"heltec_wifi_lora_32": { "heltec_wifi_lora_32": {
"name": "Heltec WiFi LoRa 32", "name": "Heltec WiFi LoRa 32",
@ -1664,6 +1768,10 @@ BOARDS = {
"name": "Heltec WiFi LoRa 32 (V2)", "name": "Heltec WiFi LoRa 32 (V2)",
"variant": VARIANT_ESP32, "variant": VARIANT_ESP32,
}, },
"heltec_wifi_lora_32_V3": {
"name": "Heltec WiFi LoRa 32 (V3)",
"variant": VARIANT_ESP32S3,
},
"heltec_wireless_stick_lite": { "heltec_wireless_stick_lite": {
"name": "Heltec Wireless Stick Lite", "name": "Heltec Wireless Stick Lite",
"variant": VARIANT_ESP32, "variant": VARIANT_ESP32,
@ -1708,6 +1816,14 @@ BOARDS = {
"name": "oddWires IoT-Bus Proteus", "name": "oddWires IoT-Bus Proteus",
"variant": VARIANT_ESP32, "variant": VARIANT_ESP32,
}, },
"ioxesp32": {
"name": "ArtronShop IOXESP32",
"variant": VARIANT_ESP32,
},
"ioxesp32ps": {
"name": "ArtronShop IOXESP32PS",
"variant": VARIANT_ESP32,
},
"kb32-ft": { "kb32-ft": {
"name": "MakerAsia KB32-FT", "name": "MakerAsia KB32-FT",
"variant": VARIANT_ESP32, "variant": VARIANT_ESP32,
@ -1720,10 +1836,26 @@ BOARDS = {
"name": "Labplus mPython", "name": "Labplus mPython",
"variant": VARIANT_ESP32, "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": { "lionbit": {
"name": "Lion:Bit Dev Board", "name": "Lion:Bit Dev Board",
"variant": VARIANT_ESP32, "variant": VARIANT_ESP32,
}, },
"lionbits3": {
"name": "Lion:Bit S3 STEM Dev Board",
"variant": VARIANT_ESP32S3,
},
"lolin32_lite": { "lolin32_lite": {
"name": "WEMOS LOLIN32 Lite", "name": "WEMOS LOLIN32 Lite",
"variant": VARIANT_ESP32, "variant": VARIANT_ESP32,
@ -1752,10 +1884,18 @@ BOARDS = {
"name": "WEMOS LOLIN S2 PICO", "name": "WEMOS LOLIN S2 PICO",
"variant": VARIANT_ESP32S2, "variant": VARIANT_ESP32S2,
}, },
"lolin_s3_mini": {
"name": "WEMOS LOLIN S3 Mini",
"variant": VARIANT_ESP32S3,
},
"lolin_s3": { "lolin_s3": {
"name": "WEMOS LOLIN S3", "name": "WEMOS LOLIN S3",
"variant": VARIANT_ESP32S3, "variant": VARIANT_ESP32S3,
}, },
"lolin_s3_pro": {
"name": "WEMOS LOLIN S3 PRO",
"variant": VARIANT_ESP32S3,
},
"lopy4": { "lopy4": {
"name": "Pycom LoPy4", "name": "Pycom LoPy4",
"variant": VARIANT_ESP32, "variant": VARIANT_ESP32,
@ -1768,10 +1908,18 @@ BOARDS = {
"name": "M5Stack-ATOM", "name": "M5Stack-ATOM",
"variant": VARIANT_ESP32, "variant": VARIANT_ESP32,
}, },
"m5stack-atoms3": {
"name": "M5Stack AtomS3",
"variant": VARIANT_ESP32S3,
},
"m5stack-core2": { "m5stack-core2": {
"name": "M5Stack Core2", "name": "M5Stack Core2",
"variant": VARIANT_ESP32, "variant": VARIANT_ESP32,
}, },
"m5stack-core-esp32-16M": {
"name": "M5Stack Core ESP32 16M",
"variant": VARIANT_ESP32,
},
"m5stack-core-esp32": { "m5stack-core-esp32": {
"name": "M5Stack Core ESP32", "name": "M5Stack Core ESP32",
"variant": VARIANT_ESP32, "variant": VARIANT_ESP32,
@ -1780,6 +1928,10 @@ BOARDS = {
"name": "M5Stack-Core Ink", "name": "M5Stack-Core Ink",
"variant": VARIANT_ESP32, "variant": VARIANT_ESP32,
}, },
"m5stack-cores3": {
"name": "M5Stack CoreS3",
"variant": VARIANT_ESP32S3,
},
"m5stack-fire": { "m5stack-fire": {
"name": "M5Stack FIRE", "name": "M5Stack FIRE",
"variant": VARIANT_ESP32, "variant": VARIANT_ESP32,
@ -1788,6 +1940,14 @@ BOARDS = {
"name": "M5Stack GREY ESP32", "name": "M5Stack GREY ESP32",
"variant": VARIANT_ESP32, "variant": VARIANT_ESP32,
}, },
"m5stack_paper": {
"name": "M5Stack Paper",
"variant": VARIANT_ESP32,
},
"m5stack-stamps3": {
"name": "M5Stack StampS3",
"variant": VARIANT_ESP32S3,
},
"m5stack-station": { "m5stack-station": {
"name": "M5Stack Station", "name": "M5Stack Station",
"variant": VARIANT_ESP32, "variant": VARIANT_ESP32,
@ -1796,6 +1956,10 @@ BOARDS = {
"name": "M5Stack Timer CAM", "name": "M5Stack Timer CAM",
"variant": VARIANT_ESP32, "variant": VARIANT_ESP32,
}, },
"m5stamp-pico": {
"name": "M5Stamp-Pico",
"variant": VARIANT_ESP32,
},
"m5stick-c": { "m5stick-c": {
"name": "M5Stick-C", "name": "M5Stick-C",
"variant": VARIANT_ESP32, "variant": VARIANT_ESP32,
@ -1832,10 +1996,26 @@ BOARDS = {
"name": "Deparment of Alchemy MiniMain ESP32-S2", "name": "Deparment of Alchemy MiniMain ESP32-S2",
"variant": VARIANT_ESP32S2, "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": { "nano32": {
"name": "MakerAsia Nano32", "name": "MakerAsia Nano32",
"variant": VARIANT_ESP32, "variant": VARIANT_ESP32,
}, },
"nebulas3": {
"name": "Kinetic Dynamics Nebula S3",
"variant": VARIANT_ESP32S3,
},
"nina_w10": { "nina_w10": {
"name": "u-blox NINA-W10 series", "name": "u-blox NINA-W10 series",
"variant": VARIANT_ESP32, "variant": VARIANT_ESP32,
@ -1896,10 +2076,22 @@ BOARDS = {
"name": "Munich Labs RedPill ESP32-S3", "name": "Munich Labs RedPill ESP32-S3",
"variant": VARIANT_ESP32S3, "variant": VARIANT_ESP32S3,
}, },
"roboheart_hercules": {
"name": "RoboHeart Hercules",
"variant": VARIANT_ESP32,
},
"seeed_xiao_esp32c3": { "seeed_xiao_esp32c3": {
"name": "Seeed Studio XIAO ESP32C3", "name": "Seeed Studio XIAO ESP32C3",
"variant": VARIANT_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": { "sensesiot_weizen": {
"name": "LOGISENSES Senses Weizen", "name": "LOGISENSES Senses Weizen",
"variant": VARIANT_ESP32, "variant": VARIANT_ESP32,
@ -1912,6 +2104,10 @@ BOARDS = {
"name": "S.ODI Ultra v1", "name": "S.ODI Ultra v1",
"variant": VARIANT_ESP32, "variant": VARIANT_ESP32,
}, },
"sparkfun_esp32c6_thing_plus": {
"name": "Sparkfun ESP32-C6 Thing Plus",
"variant": VARIANT_ESP32C6,
},
"sparkfun_esp32_iot_redboard": { "sparkfun_esp32_iot_redboard": {
"name": "SparkFun ESP32 IoT RedBoard", "name": "SparkFun ESP32 IoT RedBoard",
"variant": VARIANT_ESP32, "variant": VARIANT_ESP32,
@ -2004,6 +2200,10 @@ BOARDS = {
"name": "Unexpected Maker FeatherS3", "name": "Unexpected Maker FeatherS3",
"variant": VARIANT_ESP32S3, "variant": VARIANT_ESP32S3,
}, },
"um_nanos3": {
"name": "Unexpected Maker NanoS3",
"variant": VARIANT_ESP32S3,
},
"um_pros3": { "um_pros3": {
"name": "Unexpected Maker PROS3", "name": "Unexpected Maker PROS3",
"variant": VARIANT_ESP32S3, "variant": VARIANT_ESP32S3,
@ -2040,6 +2240,14 @@ BOARDS = {
"name": "uPesy ESP32 Wrover DevKit", "name": "uPesy ESP32 Wrover DevKit",
"variant": VARIANT_ESP32, "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": { "vintlabs-devkit-v1": {
"name": "VintLabs ESP32 Devkit", "name": "VintLabs ESP32 Devkit",
"variant": VARIANT_ESP32, "variant": VARIANT_ESP32,

View File

@ -2,8 +2,10 @@ from esphome import automation
import esphome.codegen as cg import esphome.codegen as cg
from esphome.components.esp32 import add_idf_sdkconfig_option, const, get_esp32_variant from esphome.components.esp32 import add_idf_sdkconfig_option, const, get_esp32_variant
import esphome.config_validation as cv 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 import CORE
from esphome.core.config import CONF_NAME_ADD_MAC_SUFFIX
import esphome.final_validate as fv
DEPENDENCIES = ["esp32"] DEPENDENCIES = ["esp32"]
CODEOWNERS = ["@jesserockz", "@Rapsssito"] CODEOWNERS = ["@jesserockz", "@Rapsssito"]
@ -50,6 +52,7 @@ TX_POWER_LEVELS = {
CONFIG_SCHEMA = cv.Schema( CONFIG_SCHEMA = cv.Schema(
{ {
cv.GenerateID(): cv.declare_id(ESP32BLE), 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( cv.Optional(CONF_IO_CAPABILITY, default="none"): cv.enum(
IO_CAPABILITY, lower=True IO_CAPABILITY, lower=True
), ),
@ -67,7 +70,22 @@ def validate_variant(_):
raise cv.Invalid(f"{variant} does not support Bluetooth") 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): 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_enable_on_boot(config[CONF_ENABLE_ON_BOOT]))
cg.add(var.set_io_capability(config[CONF_IO_CAPABILITY])) cg.add(var.set_io_capability(config[CONF_IO_CAPABILITY]))
cg.add(var.set_advertising_cycle_time(config[CONF_ADVERTISING_CYCLE_TIME])) 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) await cg.register_component(var, config)
if CORE.using_esp_idf: if CORE.using_esp_idf:

View File

@ -27,6 +27,9 @@ namespace esp32_ble {
static const char *const TAG = "esp32_ble"; static const char *const TAG = "esp32_ble";
static RAMAllocator<BLEEvent> EVENT_ALLOCATOR( // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
RAMAllocator<BLEEvent>::ALLOW_FAILURE | RAMAllocator<BLEEvent>::ALLOC_INTERNAL);
void ESP32BLE::setup() { void ESP32BLE::setup() {
global_ble = this; global_ble = this;
ESP_LOGCONFIG(TAG, "Setting up BLE..."); ESP_LOGCONFIG(TAG, "Setting up BLE...");
@ -188,7 +191,14 @@ bool ESP32BLE::ble_setup_() {
} }
} }
std::string name = App.get_name(); std::string name;
if (this->name_.has_value()) {
name = this->name_.value();
if (App.is_name_add_mac_suffix_enabled()) {
name += "-" + get_mac_address().substr(6);
}
} else {
name = App.get_name();
if (name.length() > 20) { if (name.length() > 20) {
if (App.is_name_add_mac_suffix_enabled()) { if (App.is_name_add_mac_suffix_enabled()) {
name.erase(name.begin() + 13, name.end() - 7); // Remove characters between 13 and the mac address name.erase(name.begin() + 13, name.end() - 7); // Remove characters between 13 and the mac address
@ -196,6 +206,7 @@ bool ESP32BLE::ble_setup_() {
name = name.substr(0, 20); name = name.substr(0, 20);
} }
} }
}
err = esp_ble_gap_set_device_name(name.c_str()); err = esp_ble_gap_set_device_name(name.c_str());
if (err != ESP_OK) { if (err != ESP_OK) {
@ -314,7 +325,8 @@ void ESP32BLE::loop() {
default: default:
break; break;
} }
delete ble_event; // NOLINT(cppcoreguidelines-owning-memory) ble_event->~BLEEvent();
EVENT_ALLOCATOR.deallocate(ble_event, 1);
ble_event = this->ble_events_.pop(); ble_event = this->ble_events_.pop();
} }
if (this->advertising_ != nullptr) { 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) { 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); 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) { 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); 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, void ESP32BLE::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if,
esp_ble_gatts_cb_param_t *param) { 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); 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, 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) { 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, void ESP32BLE::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
esp_ble_gattc_cb_param_t *param) { 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); 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, 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) { esp_ble_gattc_cb_param_t *param) {

View File

@ -90,6 +90,7 @@ class ESP32BLE : public Component {
void loop() override; void loop() override;
void dump_config() override; void dump_config() override;
float get_setup_priority() const override; float get_setup_priority() const override;
void set_name(const std::string &name) { this->name_ = name; }
void advertising_start(); void advertising_start();
void advertising_set_service_data(const std::vector<uint8_t> &data); void advertising_set_service_data(const std::vector<uint8_t> &data);
@ -131,6 +132,7 @@ class ESP32BLE : public Component {
esp_ble_io_cap_t io_cap_{ESP_IO_CAP_NONE}; esp_ble_io_cap_t io_cap_{ESP_IO_CAP_NONE};
uint32_t advertising_cycle_time_; uint32_t advertising_cycle_time_;
bool enable_on_boot_; bool enable_on_boot_;
optional<std::string> name_;
}; };
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)

View File

@ -26,11 +26,11 @@ template<class T> class Queue {
void push(T *element) { void push(T *element) {
if (element == nullptr) if (element == nullptr)
return; return;
if (xSemaphoreTake(m_, 5L / portTICK_PERIOD_MS)) { // It is not called from main loop. Thus it won't block main thread.
xSemaphoreTake(m_, portMAX_DELAY);
q_.push(element); q_.push(element);
xSemaphoreGive(m_); xSemaphoreGive(m_);
} }
}
T *pop() { T *pop() {
T *element = nullptr; T *element = nullptr;

View File

@ -44,6 +44,50 @@ void BLEClientBase::loop() {
float BLEClientBase::get_setup_priority() const { return setup_priority::AFTER_BLUETOOTH; } float BLEClientBase::get_setup_priority() const { return setup_priority::AFTER_BLUETOOTH; }
void BLEClientBase::dump_config() {
ESP_LOGCONFIG(TAG, " Address: %s", this->address_str().c_str());
ESP_LOGCONFIG(TAG, " Auto-Connect: %s", TRUEFALSE(this->auto_connect_));
std::string state_name;
switch (this->state()) {
case espbt::ClientState::INIT:
state_name = "INIT";
break;
case espbt::ClientState::DISCONNECTING:
state_name = "DISCONNECTING";
break;
case espbt::ClientState::IDLE:
state_name = "IDLE";
break;
case espbt::ClientState::SEARCHING:
state_name = "SEARCHING";
break;
case espbt::ClientState::DISCOVERED:
state_name = "DISCOVERED";
break;
case espbt::ClientState::READY_TO_CONNECT:
state_name = "READY_TO_CONNECT";
break;
case espbt::ClientState::CONNECTING:
state_name = "CONNECTING";
break;
case espbt::ClientState::CONNECTED:
state_name = "CONNECTED";
break;
case espbt::ClientState::ESTABLISHED:
state_name = "ESTABLISHED";
break;
default:
state_name = "UNKNOWN_STATE";
break;
}
ESP_LOGCONFIG(TAG, " State: %s", state_name.c_str());
if (this->status_ == ESP_GATT_NO_RESOURCES) {
ESP_LOGE(TAG, " Failed due to no resources. Try to reduce number of BLE clients in config.");
} else if (this->status_ != ESP_GATT_OK) {
ESP_LOGW(TAG, " Failed due to error code %d", this->status_);
}
}
bool BLEClientBase::parse_device(const espbt::ESPBTDevice &device) { bool BLEClientBase::parse_device(const espbt::ESPBTDevice &device) {
if (!this->auto_connect_) if (!this->auto_connect_)
return false; return false;
@ -129,6 +173,8 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
} else { } else {
ESP_LOGE(TAG, "[%d] [%s] gattc app registration failed id=%d code=%d", this->connection_index_, ESP_LOGE(TAG, "[%d] [%s] gattc app registration failed id=%d code=%d", this->connection_index_,
this->address_str_.c_str(), param->reg.app_id, param->reg.status); this->address_str_.c_str(), param->reg.app_id, param->reg.status);
this->status_ = param->reg.status;
this->mark_failed();
} }
break; break;
} }

View File

@ -26,6 +26,7 @@ class BLEClientBase : public espbt::ESPBTClient, public Component {
void setup() override; void setup() override;
void loop() override; void loop() override;
float get_setup_priority() const override; float get_setup_priority() const override;
void dump_config() override;
void run_later(std::function<void()> &&f); // NOLINT void run_later(std::function<void()> &&f); // NOLINT
bool parse_device(const espbt::ESPBTDevice &device) override; bool parse_device(const espbt::ESPBTDevice &device) override;
@ -103,6 +104,7 @@ class BLEClientBase : public espbt::ESPBTClient, public Component {
bool paired_{false}; bool paired_{false};
espbt::ConnectionType connection_type_{espbt::ConnectionType::V1}; espbt::ConnectionType connection_type_{espbt::ConnectionType::V1};
std::vector<BLEService *> services_; std::vector<BLEService *> services_;
esp_gatt_status_t status_{ESP_GATT_OK};
void log_event_(const char *name); void log_event_(const char *name);
}; };

View File

@ -58,7 +58,6 @@ void ESP32BLETracker::setup() {
global_esp32_ble_tracker = this; global_esp32_ble_tracker = this;
this->scan_result_lock_ = xSemaphoreCreateMutex(); this->scan_result_lock_ = xSemaphoreCreateMutex();
this->scan_end_lock_ = xSemaphoreCreateMutex(); this->scan_end_lock_ = xSemaphoreCreateMutex();
this->scanner_idle_ = true;
#ifdef USE_OTA #ifdef USE_OTA
ota::get_global_ota_callback()->add_on_state_callback( ota::get_global_ota_callback()->add_on_state_callback(
@ -107,6 +106,15 @@ void ESP32BLETracker::loop() {
break; break;
} }
} }
if (connecting != connecting_ || discovered != discovered_ || searching != searching_ ||
disconnecting != disconnecting_) {
connecting_ = connecting;
discovered_ = discovered;
searching_ = searching;
disconnecting_ = disconnecting;
ESP_LOGD(TAG, "connecting: %d, discovered: %d, searching: %d, disconnecting: %d", connecting_, discovered_,
searching_, disconnecting_);
}
bool promote_to_connecting = discovered && !searching && !connecting; bool promote_to_connecting = discovered && !searching && !connecting;
if (!this->scanner_idle_) { if (!this->scanner_idle_) {
@ -183,8 +191,9 @@ void ESP32BLETracker::loop() {
} }
if (this->scan_start_failed_ || this->scan_set_param_failed_) { if (this->scan_start_failed_ || this->scan_set_param_failed_) {
if (this->scan_start_fail_count_ == 255) { if (this->scan_start_fail_count_ == std::numeric_limits<uint8_t>::max()) {
ESP_LOGE(TAG, "ESP-IDF BLE scan could not restart after 255 attempts, rebooting to restore BLE stack..."); ESP_LOGE(TAG, "ESP-IDF BLE scan could not restart after %d attempts, rebooting to restore BLE stack...",
std::numeric_limits<uint8_t>::max());
App.reboot(); App.reboot();
} }
if (xSemaphoreTake(this->scan_end_lock_, 0L)) { if (xSemaphoreTake(this->scan_end_lock_, 0L)) {
@ -282,6 +291,12 @@ void ESP32BLETracker::start_scan_(bool first) {
this->scan_params_.scan_interval = this->scan_interval_; this->scan_params_.scan_interval = this->scan_interval_;
this->scan_params_.scan_window = this->scan_window_; this->scan_params_.scan_window = this->scan_window_;
// Start timeout before scan is started. Otherwise scan never starts if any error.
this->set_timeout("scan", this->scan_duration_ * 2000, []() {
ESP_LOGE(TAG, "ESP-IDF BLE scan never terminated, rebooting to restore BLE stack...");
App.reboot();
});
esp_err_t err = esp_ble_gap_set_scan_params(&this->scan_params_); esp_err_t err = esp_ble_gap_set_scan_params(&this->scan_params_);
if (err != ESP_OK) { if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_ble_gap_set_scan_params failed: %d", err); ESP_LOGE(TAG, "esp_ble_gap_set_scan_params failed: %d", err);
@ -293,11 +308,6 @@ void ESP32BLETracker::start_scan_(bool first) {
return; return;
} }
this->scanner_idle_ = false; this->scanner_idle_ = false;
this->set_timeout("scan", this->scan_duration_ * 2000, []() {
ESP_LOGE(TAG, "ESP-IDF BLE scan never terminated, rebooting to restore BLE stack...");
App.reboot();
});
} }
void ESP32BLETracker::end_of_scan_() { void ESP32BLETracker::end_of_scan_() {
@ -371,6 +381,7 @@ void ESP32BLETracker::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_ga
} }
void ESP32BLETracker::gap_scan_set_param_complete_(const esp_ble_gap_cb_param_t::ble_scan_param_cmpl_evt_param &param) { void ESP32BLETracker::gap_scan_set_param_complete_(const esp_ble_gap_cb_param_t::ble_scan_param_cmpl_evt_param &param) {
ESP_LOGV(TAG, "gap_scan_set_param_complete - status %d", param.status);
if (param.status == ESP_BT_STATUS_DONE) { if (param.status == ESP_BT_STATUS_DONE) {
this->scan_set_param_failed_ = ESP_BT_STATUS_SUCCESS; this->scan_set_param_failed_ = ESP_BT_STATUS_SUCCESS;
} else { } else {
@ -379,20 +390,25 @@ void ESP32BLETracker::gap_scan_set_param_complete_(const esp_ble_gap_cb_param_t:
} }
void ESP32BLETracker::gap_scan_start_complete_(const esp_ble_gap_cb_param_t::ble_scan_start_cmpl_evt_param &param) { void ESP32BLETracker::gap_scan_start_complete_(const esp_ble_gap_cb_param_t::ble_scan_start_cmpl_evt_param &param) {
ESP_LOGV(TAG, "gap_scan_start_complete - status %d", param.status);
this->scan_start_failed_ = param.status; this->scan_start_failed_ = param.status;
if (param.status == ESP_BT_STATUS_SUCCESS) { if (param.status == ESP_BT_STATUS_SUCCESS) {
this->scan_start_fail_count_ = 0; this->scan_start_fail_count_ = 0;
} else { } else {
if (this->scan_start_fail_count_ != std::numeric_limits<uint8_t>::max()) {
this->scan_start_fail_count_++; this->scan_start_fail_count_++;
}
xSemaphoreGive(this->scan_end_lock_); xSemaphoreGive(this->scan_end_lock_);
} }
} }
void ESP32BLETracker::gap_scan_stop_complete_(const esp_ble_gap_cb_param_t::ble_scan_stop_cmpl_evt_param &param) { void ESP32BLETracker::gap_scan_stop_complete_(const esp_ble_gap_cb_param_t::ble_scan_stop_cmpl_evt_param &param) {
ESP_LOGV(TAG, "gap_scan_stop_complete - status %d", param.status);
xSemaphoreGive(this->scan_end_lock_); xSemaphoreGive(this->scan_end_lock_);
} }
void ESP32BLETracker::gap_scan_result_(const esp_ble_gap_cb_param_t::ble_scan_result_evt_param &param) { void ESP32BLETracker::gap_scan_result_(const esp_ble_gap_cb_param_t::ble_scan_result_evt_param &param) {
ESP_LOGV(TAG, "gap_scan_result - event %d", param.search_evt);
if (param.search_evt == ESP_GAP_SEARCH_INQ_RES_EVT) { if (param.search_evt == ESP_GAP_SEARCH_INQ_RES_EVT) {
if (xSemaphoreTake(this->scan_result_lock_, 0L)) { if (xSemaphoreTake(this->scan_result_lock_, 0L)) {
if (this->scan_result_index_ < ESP32BLETracker::SCAN_RESULT_BUFFER_SIZE) { if (this->scan_result_index_ < ESP32BLETracker::SCAN_RESULT_BUFFER_SIZE) {
@ -663,7 +679,14 @@ void ESP32BLETracker::dump_config() {
ESP_LOGCONFIG(TAG, " Scan Interval: %.1f ms", this->scan_interval_ * 0.625f); ESP_LOGCONFIG(TAG, " Scan Interval: %.1f ms", this->scan_interval_ * 0.625f);
ESP_LOGCONFIG(TAG, " Scan Window: %.1f ms", this->scan_window_ * 0.625f); ESP_LOGCONFIG(TAG, " Scan Window: %.1f ms", this->scan_window_ * 0.625f);
ESP_LOGCONFIG(TAG, " Scan Type: %s", this->scan_active_ ? "ACTIVE" : "PASSIVE"); ESP_LOGCONFIG(TAG, " Scan Type: %s", this->scan_active_ ? "ACTIVE" : "PASSIVE");
ESP_LOGCONFIG(TAG, " Continuous Scanning: %s", this->scan_continuous_ ? "True" : "False"); ESP_LOGCONFIG(TAG, " Continuous Scanning: %s", YESNO(this->scan_continuous_));
ESP_LOGCONFIG(TAG, " Scanner Idle: %s", YESNO(this->scanner_idle_));
ESP_LOGCONFIG(TAG, " Scan End: %s", YESNO(xSemaphoreGetMutexHolder(this->scan_end_lock_) == nullptr));
ESP_LOGCONFIG(TAG, " Connecting: %d, discovered: %d, searching: %d, disconnecting: %d", connecting_, discovered_,
searching_, disconnecting_);
if (this->scan_start_fail_count_) {
ESP_LOGCONFIG(TAG, " Scan Start Fail Count: %d", this->scan_start_fail_count_);
}
} }
void ESP32BLETracker::print_bt_device_info(const ESPBTDevice &device) { void ESP32BLETracker::print_bt_device_info(const ESPBTDevice &device) {

View File

@ -178,7 +178,7 @@ class ESPBTClient : public ESPBTDeviceListener {
int app_id; int app_id;
protected: protected:
ClientState state_; ClientState state_{ClientState::INIT};
}; };
class ESP32BLETracker : public Component, class ESP32BLETracker : public Component,
@ -229,7 +229,7 @@ class ESP32BLETracker : public Component,
/// Called when a `ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT` event is received. /// Called when a `ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT` event is received.
void gap_scan_stop_complete_(const esp_ble_gap_cb_param_t::ble_scan_stop_cmpl_evt_param &param); void gap_scan_stop_complete_(const esp_ble_gap_cb_param_t::ble_scan_stop_cmpl_evt_param &param);
int app_id_; int app_id_{0};
/// Vector of addresses that have already been printed in print_bt_device_info /// Vector of addresses that have already been printed in print_bt_device_info
std::vector<uint64_t> already_discovered_; std::vector<uint64_t> already_discovered_;
@ -242,10 +242,10 @@ class ESP32BLETracker : public Component,
uint32_t scan_duration_; uint32_t scan_duration_;
uint32_t scan_interval_; uint32_t scan_interval_;
uint32_t scan_window_; uint32_t scan_window_;
uint8_t scan_start_fail_count_; uint8_t scan_start_fail_count_{0};
bool scan_continuous_; bool scan_continuous_;
bool scan_active_; bool scan_active_;
bool scanner_idle_; bool scanner_idle_{true};
bool ble_was_disabled_{true}; bool ble_was_disabled_{true};
bool raw_advertisements_{false}; bool raw_advertisements_{false};
bool parse_advertisements_{false}; bool parse_advertisements_{false};
@ -260,6 +260,10 @@ class ESP32BLETracker : public Component,
esp_ble_gap_cb_param_t::ble_scan_result_evt_param *scan_result_buffer_; esp_ble_gap_cb_param_t::ble_scan_result_evt_param *scan_result_buffer_;
esp_bt_status_t scan_start_failed_{ESP_BT_STATUS_SUCCESS}; esp_bt_status_t scan_start_failed_{ESP_BT_STATUS_SUCCESS};
esp_bt_status_t scan_set_param_failed_{ESP_BT_STATUS_SUCCESS}; esp_bt_status_t scan_set_param_failed_{ESP_BT_STATUS_SUCCESS};
int connecting_{0};
int discovered_{0};
int searching_{0};
int disconnecting_{0};
}; };
// NOLINTNEXTLINE // NOLINTNEXTLINE

View File

@ -1,7 +1,8 @@
import esphome.config_validation as cv
import esphome.codegen as cg import esphome.codegen as cg
from esphome.components import esp32 from esphome.components import esp32
import esphome.config_validation as cv
from esphome.const import KEY_CORE, KEY_FRAMEWORK_VERSION
from esphome.core import CORE
CODEOWNERS = ["@jesserockz"] CODEOWNERS = ["@jesserockz"]
@ -36,8 +37,32 @@ RMT_CHANNEL_ENUMS = {
} }
def validate_rmt_channel(*, tx: bool): def use_new_rmt_driver():
framework_version = CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION]
if CORE.using_esp_idf and framework_version >= cv.Version(5, 0, 0):
return True
return False
def validate_clock_resolution():
def _validator(value):
cv.only_on_esp32(value)
value = cv.int_(value)
variant = esp32.get_esp32_variant()
if variant == esp32.const.VARIANT_ESP32H2 and value > 32000000:
raise cv.Invalid(
f"ESP32 variant {variant} has a max clock_resolution of 32000000."
)
if value > 80000000:
raise cv.Invalid(
f"ESP32 variant {variant} has a max clock_resolution of 80000000."
)
return value
return _validator
def validate_rmt_channel(*, tx: bool):
rmt_channels = RMT_TX_CHANNELS if tx else RMT_RX_CHANNELS rmt_channels = RMT_TX_CHANNELS if tx else RMT_RX_CHANNELS
def _validator(value): def _validator(value):

View File

@ -1,5 +1,5 @@
#include <cinttypes>
#include "led_strip.h" #include "led_strip.h"
#include <cinttypes>
#ifdef USE_ESP32 #ifdef USE_ESP32
@ -13,9 +13,13 @@ namespace esp32_rmt_led_strip {
static const char *const TAG = "esp32_rmt_led_strip"; static const char *const TAG = "esp32_rmt_led_strip";
#ifdef USE_ESP32_VARIANT_ESP32H2
static const uint32_t RMT_CLK_FREQ = 32000000;
static const uint8_t RMT_CLK_DIV = 1;
#else
static const uint32_t RMT_CLK_FREQ = 80000000; static const uint32_t RMT_CLK_FREQ = 80000000;
static const uint8_t RMT_CLK_DIV = 2; static const uint8_t RMT_CLK_DIV = 2;
#endif
void ESP32RMTLEDStripLightOutput::setup() { void ESP32RMTLEDStripLightOutput::setup() {
ESP_LOGCONFIG(TAG, "Setting up ESP32 LED Strip..."); ESP_LOGCONFIG(TAG, "Setting up ESP32 LED Strip...");
@ -37,9 +41,48 @@ void ESP32RMTLEDStripLightOutput::setup() {
return; return;
} }
#if ESP_IDF_VERSION_MAJOR >= 5
RAMAllocator<rmt_symbol_word_t> rmt_allocator(this->use_psram_ ? 0 : RAMAllocator<rmt_symbol_word_t>::ALLOC_INTERNAL);
// 8 bits per byte, 1 rmt_symbol_word_t per bit + 1 rmt_symbol_word_t for reset
this->rmt_buf_ = rmt_allocator.allocate(buffer_size * 8 + 1);
rmt_tx_channel_config_t channel;
memset(&channel, 0, sizeof(channel));
channel.clk_src = RMT_CLK_SRC_DEFAULT;
channel.resolution_hz = RMT_CLK_FREQ / RMT_CLK_DIV;
channel.gpio_num = gpio_num_t(this->pin_);
channel.mem_block_symbols = this->rmt_symbols_;
channel.trans_queue_depth = 1;
channel.flags.io_loop_back = 0;
channel.flags.io_od_mode = 0;
channel.flags.invert_out = 0;
channel.flags.with_dma = 0;
channel.intr_priority = 0;
if (rmt_new_tx_channel(&channel, &this->channel_) != ESP_OK) {
ESP_LOGE(TAG, "Channel creation failed");
this->mark_failed();
return;
}
rmt_copy_encoder_config_t encoder;
memset(&encoder, 0, sizeof(encoder));
if (rmt_new_copy_encoder(&encoder, &this->encoder_) != ESP_OK) {
ESP_LOGE(TAG, "Encoder creation failed");
this->mark_failed();
return;
}
if (rmt_enable(this->channel_) != ESP_OK) {
ESP_LOGE(TAG, "Enabling channel failed");
this->mark_failed();
return;
}
#else
RAMAllocator<rmt_item32_t> rmt_allocator(this->use_psram_ ? 0 : RAMAllocator<rmt_item32_t>::ALLOC_INTERNAL); RAMAllocator<rmt_item32_t> rmt_allocator(this->use_psram_ ? 0 : RAMAllocator<rmt_item32_t>::ALLOC_INTERNAL);
this->rmt_buf_ = rmt_allocator.allocate(buffer_size * 8 +
1); // 8 bits per byte, 1 rmt_item32_t per bit + 1 rmt_item32_t for reset // 8 bits per byte, 1 rmt_item32_t per bit + 1 rmt_item32_t for reset
this->rmt_buf_ = rmt_allocator.allocate(buffer_size * 8 + 1);
rmt_config_t config; rmt_config_t config;
memset(&config, 0, sizeof(config)); memset(&config, 0, sizeof(config));
@ -64,6 +107,7 @@ void ESP32RMTLEDStripLightOutput::setup() {
this->mark_failed(); this->mark_failed();
return; return;
} }
#endif
} }
void ESP32RMTLEDStripLightOutput::set_led_params(uint32_t bit0_high, uint32_t bit0_low, uint32_t bit1_high, void ESP32RMTLEDStripLightOutput::set_led_params(uint32_t bit0_high, uint32_t bit0_low, uint32_t bit1_high,
@ -100,7 +144,12 @@ void ESP32RMTLEDStripLightOutput::write_state(light::LightState *state) {
ESP_LOGVV(TAG, "Writing RGB values to bus..."); ESP_LOGVV(TAG, "Writing RGB values to bus...");
if (rmt_wait_tx_done(this->channel_, pdMS_TO_TICKS(1000)) != ESP_OK) { #if ESP_IDF_VERSION_MAJOR >= 5
esp_err_t error = rmt_tx_wait_all_done(this->channel_, 1000);
#else
esp_err_t error = rmt_wait_tx_done(this->channel_, pdMS_TO_TICKS(1000));
#endif
if (error != ESP_OK) {
ESP_LOGE(TAG, "RMT TX timeout"); ESP_LOGE(TAG, "RMT TX timeout");
this->status_set_warning(); this->status_set_warning();
return; return;
@ -112,7 +161,11 @@ void ESP32RMTLEDStripLightOutput::write_state(light::LightState *state) {
size_t size = 0; size_t size = 0;
size_t len = 0; size_t len = 0;
uint8_t *psrc = this->buf_; uint8_t *psrc = this->buf_;
#if ESP_IDF_VERSION_MAJOR >= 5
rmt_symbol_word_t *pdest = this->rmt_buf_;
#else
rmt_item32_t *pdest = this->rmt_buf_; rmt_item32_t *pdest = this->rmt_buf_;
#endif
while (size < buffer_size) { while (size < buffer_size) {
uint8_t b = *psrc; uint8_t b = *psrc;
for (int i = 0; i < 8; i++) { for (int i = 0; i < 8; i++) {
@ -130,7 +183,16 @@ void ESP32RMTLEDStripLightOutput::write_state(light::LightState *state) {
len++; len++;
} }
if (rmt_write_items(this->channel_, this->rmt_buf_, len, false) != ESP_OK) { #if ESP_IDF_VERSION_MAJOR >= 5
rmt_transmit_config_t config;
memset(&config, 0, sizeof(config));
config.loop_count = 0;
config.flags.eot_level = 0;
error = rmt_transmit(this->channel_, this->encoder_, this->rmt_buf_, len * sizeof(rmt_symbol_word_t), &config);
#else
error = rmt_write_items(this->channel_, this->rmt_buf_, len, false);
#endif
if (error != ESP_OK) {
ESP_LOGE(TAG, "RMT TX error"); ESP_LOGE(TAG, "RMT TX error");
this->status_set_warning(); this->status_set_warning();
return; return;
@ -186,7 +248,11 @@ light::ESPColorView ESP32RMTLEDStripLightOutput::get_view_internal(int32_t index
void ESP32RMTLEDStripLightOutput::dump_config() { void ESP32RMTLEDStripLightOutput::dump_config() {
ESP_LOGCONFIG(TAG, "ESP32 RMT LED Strip:"); ESP_LOGCONFIG(TAG, "ESP32 RMT LED Strip:");
ESP_LOGCONFIG(TAG, " Pin: %u", this->pin_); ESP_LOGCONFIG(TAG, " Pin: %u", this->pin_);
#if ESP_IDF_VERSION_MAJOR >= 5
ESP_LOGCONFIG(TAG, " RMT Symbols: %" PRIu32, this->rmt_symbols_);
#else
ESP_LOGCONFIG(TAG, " Channel: %u", this->channel_); ESP_LOGCONFIG(TAG, " Channel: %u", this->channel_);
#endif
const char *rgb_order; const char *rgb_order;
switch (this->rgb_order_) { switch (this->rgb_order_) {
case ORDER_RGB: case ORDER_RGB:

View File

@ -9,8 +9,14 @@
#include "esphome/core/helpers.h" #include "esphome/core/helpers.h"
#include <driver/gpio.h> #include <driver/gpio.h>
#include <driver/rmt.h>
#include <esp_err.h> #include <esp_err.h>
#include <esp_idf_version.h>
#if ESP_IDF_VERSION_MAJOR >= 5
#include <driver/rmt_tx.h>
#else
#include <driver/rmt.h>
#endif
namespace esphome { namespace esphome {
namespace esp32_rmt_led_strip { namespace esp32_rmt_led_strip {
@ -54,7 +60,11 @@ class ESP32RMTLEDStripLightOutput : public light::AddressableLight {
uint32_t reset_time_high, uint32_t reset_time_low); uint32_t reset_time_high, uint32_t reset_time_low);
void set_rgb_order(RGBOrder rgb_order) { this->rgb_order_ = rgb_order; } void set_rgb_order(RGBOrder rgb_order) { this->rgb_order_ = rgb_order; }
#if ESP_IDF_VERSION_MAJOR >= 5
void set_rmt_symbols(uint32_t rmt_symbols) { this->rmt_symbols_ = rmt_symbols; }
#else
void set_rmt_channel(rmt_channel_t channel) { this->channel_ = channel; } void set_rmt_channel(rmt_channel_t channel) { this->channel_ = channel; }
#endif
void clear_effect_data() override { void clear_effect_data() override {
for (int i = 0; i < this->size(); i++) for (int i = 0; i < this->size(); i++)
@ -70,7 +80,17 @@ class ESP32RMTLEDStripLightOutput : public light::AddressableLight {
uint8_t *buf_{nullptr}; uint8_t *buf_{nullptr};
uint8_t *effect_data_{nullptr}; uint8_t *effect_data_{nullptr};
#if ESP_IDF_VERSION_MAJOR >= 5
rmt_channel_handle_t channel_{nullptr};
rmt_encoder_handle_t encoder_{nullptr};
rmt_symbol_word_t *rmt_buf_{nullptr};
rmt_symbol_word_t bit0_, bit1_, reset_;
uint32_t rmt_symbols_;
#else
rmt_item32_t *rmt_buf_{nullptr}; rmt_item32_t *rmt_buf_{nullptr};
rmt_item32_t bit0_, bit1_, reset_;
rmt_channel_t channel_{RMT_CHANNEL_0};
#endif
uint8_t pin_; uint8_t pin_;
uint16_t num_leds_; uint16_t num_leds_;
@ -78,9 +98,7 @@ class ESP32RMTLEDStripLightOutput : public light::AddressableLight {
bool is_wrgb_; bool is_wrgb_;
bool use_psram_; bool use_psram_;
rmt_item32_t bit0_, bit1_, reset_;
RGBOrder rgb_order_; RGBOrder rgb_order_;
rmt_channel_t channel_;
uint32_t last_refresh_{0}; uint32_t last_refresh_{0};
optional<uint32_t> max_refresh_rate_{}; optional<uint32_t> max_refresh_rate_{};

View File

@ -1,9 +1,9 @@
from dataclasses import dataclass from dataclasses import dataclass
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import pins from esphome import pins
import esphome.codegen as cg
from esphome.components import esp32_rmt, light from esphome.components import esp32_rmt, light
import esphome.config_validation as cv
from esphome.const import ( from esphome.const import (
CONF_CHIPSET, CONF_CHIPSET,
CONF_IS_RGBW, CONF_IS_RGBW,
@ -13,6 +13,7 @@ from esphome.const import (
CONF_PIN, CONF_PIN,
CONF_RGB_ORDER, CONF_RGB_ORDER,
CONF_RMT_CHANNEL, CONF_RMT_CHANNEL,
CONF_RMT_SYMBOLS,
) )
CODEOWNERS = ["@jesserockz"] CODEOWNERS = ["@jesserockz"]
@ -23,8 +24,6 @@ ESP32RMTLEDStripLightOutput = esp32_rmt_led_strip_ns.class_(
"ESP32RMTLEDStripLightOutput", light.AddressableLight "ESP32RMTLEDStripLightOutput", light.AddressableLight
) )
rmt_channel_t = cg.global_ns.enum("rmt_channel_t")
RGBOrder = esp32_rmt_led_strip_ns.enum("RGBOrder") RGBOrder = esp32_rmt_led_strip_ns.enum("RGBOrder")
RGB_ORDERS = { RGB_ORDERS = {
@ -65,6 +64,13 @@ CONF_RESET_HIGH = "reset_high"
CONF_RESET_LOW = "reset_low" CONF_RESET_LOW = "reset_low"
def final_validation(config):
if not esp32_rmt.use_new_rmt_driver() and CONF_RMT_CHANNEL not in config:
raise cv.Invalid("rmt_channel is a required option.")
FINAL_VALIDATE_SCHEMA = final_validation
CONFIG_SCHEMA = cv.All( CONFIG_SCHEMA = cv.All(
light.ADDRESSABLE_LIGHT_SCHEMA.extend( light.ADDRESSABLE_LIGHT_SCHEMA.extend(
{ {
@ -72,7 +78,18 @@ CONFIG_SCHEMA = cv.All(
cv.Required(CONF_PIN): pins.internal_gpio_output_pin_number, cv.Required(CONF_PIN): pins.internal_gpio_output_pin_number,
cv.Required(CONF_NUM_LEDS): cv.positive_not_null_int, cv.Required(CONF_NUM_LEDS): cv.positive_not_null_int,
cv.Required(CONF_RGB_ORDER): cv.enum(RGB_ORDERS, upper=True), cv.Required(CONF_RGB_ORDER): cv.enum(RGB_ORDERS, upper=True),
cv.Required(CONF_RMT_CHANNEL): esp32_rmt.validate_rmt_channel(tx=True), cv.Optional(CONF_RMT_CHANNEL): cv.All(
cv.only_with_arduino, esp32_rmt.validate_rmt_channel(tx=True)
),
cv.SplitDefault(
CONF_RMT_SYMBOLS,
esp32_idf=64,
esp32_s2_idf=64,
esp32_s3_idf=48,
esp32_c3_idf=48,
esp32_c6_idf=48,
esp32_h2_idf=48,
): cv.All(cv.only_with_esp_idf, cv.int_range(min=2)),
cv.Optional(CONF_MAX_REFRESH_RATE): cv.positive_time_period_microseconds, cv.Optional(CONF_MAX_REFRESH_RATE): cv.positive_time_period_microseconds,
cv.Optional(CONF_CHIPSET): cv.one_of(*CHIPSETS, upper=True), cv.Optional(CONF_CHIPSET): cv.one_of(*CHIPSETS, upper=True),
cv.Optional(CONF_IS_RGBW, default=False): cv.boolean, cv.Optional(CONF_IS_RGBW, default=False): cv.boolean,
@ -103,7 +120,7 @@ CONFIG_SCHEMA = cv.All(
default="0 us", default="0 us",
): cv.positive_time_period_nanoseconds, ): cv.positive_time_period_nanoseconds,
} }
), ).extend(cv.COMPONENT_SCHEMA),
cv.has_exactly_one_key(CONF_CHIPSET, CONF_BIT0_HIGH), cv.has_exactly_one_key(CONF_CHIPSET, CONF_BIT0_HIGH),
) )
@ -148,6 +165,10 @@ async def to_code(config):
cg.add(var.set_is_wrgb(config[CONF_IS_WRGB])) cg.add(var.set_is_wrgb(config[CONF_IS_WRGB]))
cg.add(var.set_use_psram(config[CONF_USE_PSRAM])) cg.add(var.set_use_psram(config[CONF_USE_PSRAM]))
if esp32_rmt.use_new_rmt_driver():
cg.add(var.set_rmt_symbols(config[CONF_RMT_SYMBOLS]))
else:
rmt_channel_t = cg.global_ns.enum("rmt_channel_t")
cg.add( cg.add(
var.set_rmt_channel( var.set_rmt_channel(
getattr(rmt_channel_t, f"RMT_CHANNEL_{config[CONF_RMT_CHANNEL]}") getattr(rmt_channel_t, f"RMT_CHANNEL_{config[CONF_RMT_CHANNEL]}")

View File

@ -1,4 +1,3 @@
from collections.abc import Iterable
import functools import functools
import hashlib import hashlib
import logging import logging
@ -8,7 +7,6 @@ import re
import freetype import freetype
import glyphsets import glyphsets
from packaging import version
import requests import requests
from esphome import core, external_files from esphome import core, external_files
@ -53,8 +51,11 @@ CONF_IGNORE_MISSING_GLYPHS = "ignore_missing_glyphs"
# Cache loaded freetype fonts # Cache loaded freetype fonts
class FontCache(dict): class FontCache(dict):
def __missing__(self, key): def __missing__(self, key):
try:
res = self[key] = freetype.Face(key) res = self[key] = freetype.Face(key)
return res return res
except freetype.FT_Exception as e:
raise cv.Invalid(f"Could not load Font file {key}: {e}") from e
FONT_CACHE = FontCache() FONT_CACHE = FontCache()
@ -88,7 +89,7 @@ def flatten(lists) -> list:
return list(chain.from_iterable(lists)) 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 Check that the given font file actually contains the requested glyphs
:param file: A Truetype font file :param file: A Truetype font file
@ -177,24 +178,6 @@ def validate_glyphs(config):
return 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") FONT_EXTENSIONS = (".ttf", ".woff", ".otf")
@ -393,7 +376,9 @@ def font_file_schema(value):
# Default if no glyphs or glyphsets are provided # Default if no glyphs or glyphsets are provided
DEFAULT_GLYPHSET = "GF_Latin_Kernel" DEFAULT_GLYPHSET = "GF_Latin_Kernel"
# default for bitmap fonts # default for bitmap fonts
DEFAULT_GLYPHS = ' !"%()+=,-.:/?0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz<C2><B0>' DEFAULT_GLYPHS = (
' !"%()+=,-.:/?0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz°'
)
CONF_RAW_GLYPH_ID = "raw_glyph_id" 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 # PIL doesn't provide a consistent interface for both TrueType and bitmap

View File

@ -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_r = (float) color.r - (float) background.r;
auto diff_g = (float) color.g - (float) background.g; auto diff_g = (float) color.g - (float) background.g;
auto diff_b = (float) color.b - (float) background.b; 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_r = (float) background.r;
auto b_g = (float) background.g; 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_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++) { for (int glyph_x = x_at + scan_x1; glyph_x != max_x; glyph_x++) {
uint8_t pixel = 0; 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); display->draw_pixel_at(glyph_x, glyph_y, color);
} else if (pixel != 0) { } else if (pixel != 0) {
auto on = (float) pixel / (float) bpp_max; auto on = (float) pixel / (float) bpp_max;
auto blended = auto blended = Color((uint8_t) (diff_r * on + b_r), (uint8_t) (diff_g * on + b_g),
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_b * on + b_b), (uint8_t) (diff_w * on + b_w));
display->draw_pixel_at(glyph_x, glyph_y, blended); display->draw_pixel_at(glyph_x, glyph_y, blended);
} }
} }

View File

@ -35,7 +35,9 @@ void HonClimate::set_beeper_state(bool state) {
if (state != this->settings_.beeper_state) { if (state != this->settings_.beeper_state) {
this->settings_.beeper_state = state; this->settings_.beeper_state = state;
#ifdef USE_SWITCH #ifdef USE_SWITCH
if (this->beeper_switch_ != nullptr) {
this->beeper_switch_->publish_state(state); this->beeper_switch_->publish_state(state);
}
#endif #endif
this->hon_rtc_.save(&this->settings_); 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) { void HonClimate::set_quiet_mode_state(bool state) {
if (state != this->get_quiet_mode_state()) { if (state != this->get_quiet_mode_state()) {
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->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; this->settings_.quiet_mode_state = state;
#ifdef USE_SWITCH #ifdef USE_SWITCH
if (this->quiet_mode_switch_ != nullptr) {
this->quiet_mode_switch_->publish_state(state); this->quiet_mode_switch_->publish_state(state);
}
#endif #endif
this->hon_rtc_.save(&this->settings_); this->hon_rtc_.save(&this->settings_);
} }
@ -509,7 +518,7 @@ void HonClimate::initialization() {
} }
this->current_vertical_swing_ = this->settings_.last_vertiacal_swing; this->current_vertical_swing_ = this->settings_.last_vertiacal_swing;
this->current_horizontal_swing_ = this->settings_.last_horizontal_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() { 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) { if (this->mode == CLIMATE_MODE_OFF) {
// AC just turned on from remote need to turn off display // AC just turned on from remote need to turn off display
this->force_send_control_ = true; 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; 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()) { if (new_quiet_mode != this->get_quiet_mode_state()) {
this->quiet_mode_state_ = new_quiet_mode ? SwitchState::ON : SwitchState::OFF; this->quiet_mode_state_ = new_quiet_mode ? SwitchState::ON : SwitchState::OFF;
this->settings_.quiet_mode_state = new_quiet_mode; 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_); this->hon_rtc_.save(&this->settings_);
} }
} }

View File

@ -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))

View File

@ -0,0 +1,95 @@
#include "hbridge_switch.h"
#include "esphome/core/log.h"
#include <cinttypes>
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<bool> 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

View File

@ -0,0 +1,50 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/hal.h"
#include "esphome/components/switch/switch.h"
#include <vector>
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

View File

@ -53,7 +53,7 @@ bool HX711Sensor::read_sensor_(uint32_t *result) {
} }
// Cycle clock pin for gain setting // Cycle clock pin for gain setting
for (uint8_t i = 0; i < this->gain_; i++) { for (uint8_t i = 0; i < static_cast<uint8_t>(this->gain_); i++) {
this->sck_pin_->digital_write(true); this->sck_pin_->digital_write(true);
delayMicroseconds(1); delayMicroseconds(1);
this->sck_pin_->digital_write(false); this->sck_pin_->digital_write(false);

View File

@ -9,7 +9,7 @@
namespace esphome { namespace esphome {
namespace hx711 { namespace hx711 {
enum HX711Gain { enum HX711Gain : uint8_t {
HX711_GAIN_128 = 1, HX711_GAIN_128 = 1,
HX711_GAIN_32 = 2, HX711_GAIN_32 = 2,
HX711_GAIN_64 = 3, HX711_GAIN_64 = 3,

View File

@ -17,14 +17,14 @@ void IDFI2CBus::setup() {
ESP_LOGCONFIG(TAG, "Setting up I2C bus..."); ESP_LOGCONFIG(TAG, "Setting up I2C bus...");
static i2c_port_t next_port = I2C_NUM_0; static i2c_port_t next_port = I2C_NUM_0;
port_ = next_port; 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; next_port = (next_port == I2C_NUM_0) ? I2C_NUM_1 : I2C_NUM_MAX;
#else #else
next_port = I2C_NUM_MAX; next_port = I2C_NUM_MAX;
#endif #endif
if (port_ == I2C_NUM_MAX) { 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(); this->mark_failed();
return; return;
} }

View File

@ -33,14 +33,15 @@ enum SpeakerEventGroupBits : uint32_t {
STATE_RUNNING = (1 << 11), STATE_RUNNING = (1 << 11),
STATE_STOPPING = (1 << 12), STATE_STOPPING = (1 << 12),
STATE_STOPPED = (1 << 13), STATE_STOPPED = (1 << 13),
ERR_INVALID_FORMAT = (1 << 14), ERR_TASK_FAILED_TO_START = (1 << 14),
ERR_TASK_FAILED_TO_START = (1 << 15), ERR_ESP_INVALID_STATE = (1 << 15),
ERR_ESP_INVALID_STATE = (1 << 16), ERR_ESP_NOT_SUPPORTED = (1 << 16),
ERR_ESP_INVALID_ARG = (1 << 17), ERR_ESP_INVALID_ARG = (1 << 17),
ERR_ESP_INVALID_SIZE = (1 << 18), ERR_ESP_INVALID_SIZE = (1 << 18),
ERR_ESP_NO_MEM = (1 << 19), ERR_ESP_NO_MEM = (1 << 19),
ERR_ESP_FAIL = (1 << 20), 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 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; return ESP_ERR_INVALID_SIZE;
case SpeakerEventGroupBits::ERR_ESP_NO_MEM: case SpeakerEventGroupBits::ERR_ESP_NO_MEM:
return ESP_ERR_NO_MEM; return ESP_ERR_NO_MEM;
case SpeakerEventGroupBits::ERR_ESP_NOT_SUPPORTED:
return ESP_ERR_NOT_SUPPORTED;
default: default:
return ESP_FAIL; return ESP_FAIL;
} }
@ -135,19 +138,19 @@ void I2SAudioSpeaker::loop() {
xEventGroupClearBits(this->event_group_, SpeakerEventGroupBits::ERR_TASK_FAILED_TO_START); 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"); this->status_set_error("Failed to adjust I2S bus to match the incoming audio");
ESP_LOGE(TAG, ESP_LOGE(TAG,
"Incompatible audio format: sample rate = %" PRIu32 ", channels = %" PRIu8 ", bits per sample = %" PRIu8, "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_.sample_rate, this->audio_stream_info_.channels,
this->audio_stream_info_.bits_per_sample); 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) { void I2SAudioSpeaker::set_volume(float volume) {
@ -236,13 +239,15 @@ void I2SAudioSpeaker::speaker_task(void *params) {
xEventGroupSetBits(this_speaker->event_group_, SpeakerEventGroupBits::STATE_STARTING); xEventGroupSetBits(this_speaker->event_group_, SpeakerEventGroupBits::STATE_STARTING);
audio::AudioStreamInfo audio_stream_info = this_speaker->audio_stream_info_; 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 * const uint32_t bytes_per_ms =
bytes_per_sample * number_of_channels; 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 = 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))) { if (this_speaker->send_esp_err_to_event_group_(this_speaker->allocate_buffers_(dma_buffers_size, ring_buffer_size))) {
// Failed to allocate buffers // Failed to allocate buffers
@ -250,14 +255,7 @@ void I2SAudioSpeaker::speaker_task(void *params) {
this_speaker->delete_task_(dma_buffers_size); this_speaker->delete_task_(dma_buffers_size);
} }
if (this_speaker->send_esp_err_to_event_group_(this_speaker->start_i2s_driver_())) { if (!this_speaker->send_esp_err_to_event_group_(this_speaker->start_i2s_driver_(audio_stream_info))) {
// 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
xEventGroupSetBits(this_speaker->event_group_, SpeakerEventGroupBits::STATE_RUNNING); xEventGroupSetBits(this_speaker->event_group_, SpeakerEventGroupBits::STATE_RUNNING);
bool stop_gracefully = false; bool stop_gracefully = false;
@ -275,6 +273,12 @@ void I2SAudioSpeaker::speaker_task(void *params) {
stop_gracefully = true; 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; i2s_event_t i2s_event;
while (xQueueReceive(this_speaker->i2s_event_queue_, &i2s_event, 0)) { while (xQueueReceive(this_speaker->i2s_event_queue_, &i2s_event, 0)) {
if (i2s_event.type == I2S_EVENT_TX_Q_OVF) { 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);
}
i2s_zero_dma_buffer(this_speaker->parent_->get_port());
xEventGroupSetBits(this_speaker->event_group_, SpeakerEventGroupBits::STATE_STOPPING); xEventGroupSetBits(this_speaker->event_group_, SpeakerEventGroupBits::STATE_STOPPING);
i2s_driver_uninstall(this_speaker->parent_->get_port()); i2s_driver_uninstall(this_speaker->parent_->get_port());
this_speaker->parent_->unlock(); this_speaker->parent_->unlock();
}
this_speaker->delete_task_(dma_buffers_size); 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: case ESP_ERR_NO_MEM:
xEventGroupSetBits(this->event_group_, SpeakerEventGroupBits::ERR_ESP_NO_MEM); xEventGroupSetBits(this->event_group_, SpeakerEventGroupBits::ERR_ESP_NO_MEM);
return true; return true;
case ESP_ERR_NOT_SUPPORTED:
xEventGroupSetBits(this->event_group_, SpeakerEventGroupBits::ERR_ESP_NOT_SUPPORTED);
return true;
default: default:
xEventGroupSetBits(this->event_group_, SpeakerEventGroupBits::ERR_ESP_FAIL); xEventGroupSetBits(this->event_group_, SpeakerEventGroupBits::ERR_ESP_FAIL);
return true; return true;
@ -411,18 +415,40 @@ esp_err_t I2SAudioSpeaker::allocate_buffers_(size_t data_buffer_size, size_t rin
return ESP_OK; 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()) { if (!this->parent_->try_lock()) {
return ESP_ERR_INVALID_STATE; 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; int dma_buffer_length = DMA_BUFFER_DURATION_MS * this->sample_rate_ / 1000;
i2s_driver_config_t config = { i2s_driver_config_t config = {
.mode = (i2s_mode_t) (this->i2s_mode_ | I2S_MODE_TX), .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_, .bits_per_sample = this->bits_per_sample_,
.channel_format = this->channel_, .channel_format = channel,
.communication_format = this->i2s_comm_fmt_, .communication_format = this->i2s_comm_fmt_,
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
.dma_buf_count = DMA_BUFFERS_COUNT, .dma_buf_count = DMA_BUFFERS_COUNT,
@ -477,30 +503,6 @@ esp_err_t I2SAudioSpeaker::start_i2s_driver_() {
return err; 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) { void I2SAudioSpeaker::delete_task_(size_t buffer_size) {
this->audio_ring_buffer_.reset(); // Releases onwership of the shared_ptr this->audio_ring_buffer_.reset(); // Releases onwership of the shared_ptr

View File

@ -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); esp_err_t allocate_buffers_(size_t data_buffer_size, size_t ring_buffer_size);
/// @brief Starts the ESP32 I2S driver. /// @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 /// Attempts to lock the I2S port, starts the I2S driver using the passed in stream information, and sets the data out
/// the I2S port and uninstall the driver, if necessary. /// 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. /// @param audio_stream_info Stream information for the I2S driver.
/// ESP_ERR_INVALID_ARG if installing the driver or setting the data out pin fails due to a parameter error. /// @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_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_FAIL if setting the data out pin fails due to an IO error ESP_OK if successful
/// ESP_OK if successful esp_err_t start_i2s_driver_(audio::AudioStreamInfo &audio_stream_info);
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);
/// @brief Deletes the speaker's task. /// @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 /// Deallocates the data_buffer_ and audio_ring_buffer_, if necessary, and deletes the task. Should only be called by

View File

@ -1,6 +1,6 @@
from esphome import core, pins from esphome import core, pins
import esphome.codegen as cg 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 from esphome.components.display import validate_rotation
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.const import ( from esphome.const import (
@ -147,7 +147,6 @@ def _validate(config):
CONFIG_SCHEMA = cv.All( CONFIG_SCHEMA = cv.All(
font.validate_pillow_installed,
display.FULL_DISPLAY_SCHEMA.extend( display.FULL_DISPLAY_SCHEMA.extend(
{ {
cv.GenerateID(): cv.declare_id(ILI9XXXDisplay), cv.GenerateID(): cv.declare_id(ILI9XXXDisplay),

View File

@ -10,7 +10,6 @@ import puremagic
from esphome import core, external_files from esphome import core, external_files
import esphome.codegen as cg import esphome.codegen as cg
from esphome.components import font
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.const import ( from esphome.const import (
CONF_DITHER, 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]): def load_svg_image(file: bytes, resize: tuple[int, int]):

View File

@ -1,38 +1,20 @@
#include "json_util.h" #include "json_util.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
#ifdef USE_ESP8266
#include <Esp.h>
#endif
#ifdef USE_ESP32
#include <esp_heap_caps.h>
#endif
#ifdef USE_RP2040
#include <Arduino.h>
#endif
namespace esphome { namespace esphome {
namespace json { namespace json {
static const char *const TAG = "json"; static const char *const TAG = "json";
static std::vector<char> global_json_build_buffer; // NOLINT static std::vector<char> global_json_build_buffer; // NOLINT
static const auto ALLOCATOR = RAMAllocator<uint8_t>(RAMAllocator<uint8_t>::ALLOC_INTERNAL);
std::string build_json(const json_build_t &f) { std::string build_json(const json_build_t &f) {
// Here we are allocating up to 5kb of memory, // Here we are allocating up to 5kb of memory,
// with the heap size minus 2kb to be safe if less than 5kb // with the heap size minus 2kb to be safe if less than 5kb
// as we can not have a true dynamic sized document. // as we can not have a true dynamic sized document.
// The excess memory is freed below with `shrinkToFit()` // The excess memory is freed below with `shrinkToFit()`
#ifdef USE_ESP8266 auto free_heap = ALLOCATOR.get_max_free_block_size();
const size_t free_heap = ESP.getMaxFreeBlockSize(); // NOLINT(readability-static-accessed-through-instance)
#elif defined(USE_ESP32)
const size_t free_heap = heap_caps_get_largest_free_block(MALLOC_CAP_8BIT);
#elif defined(USE_RP2040)
const size_t free_heap = rp2040.getFreeHeap();
#elif defined(USE_LIBRETINY)
const size_t free_heap = lt_heap_get_free();
#endif
size_t request_size = std::min(free_heap, (size_t) 512); size_t request_size = std::min(free_heap, (size_t) 512);
while (true) { while (true) {
ESP_LOGV(TAG, "Attempting to allocate %u bytes for JSON serialization", request_size); ESP_LOGV(TAG, "Attempting to allocate %u bytes for JSON serialization", request_size);
@ -67,20 +49,12 @@ bool parse_json(const std::string &data, const json_parse_t &f) {
// with the heap size minus 2kb to be safe if less than that // with the heap size minus 2kb to be safe if less than that
// as we can not have a true dynamic sized document. // as we can not have a true dynamic sized document.
// The excess memory is freed below with `shrinkToFit()` // The excess memory is freed below with `shrinkToFit()`
#ifdef USE_ESP8266 auto free_heap = ALLOCATOR.get_max_free_block_size();
const size_t free_heap = ESP.getMaxFreeBlockSize(); // NOLINT(readability-static-accessed-through-instance)
#elif defined(USE_ESP32)
const size_t free_heap = heap_caps_get_largest_free_block(MALLOC_CAP_8BIT);
#elif defined(USE_RP2040)
const size_t free_heap = rp2040.getFreeHeap();
#elif defined(USE_LIBRETINY)
const size_t free_heap = lt_heap_get_free();
#endif
size_t request_size = std::min(free_heap, (size_t) (data.size() * 1.5)); size_t request_size = std::min(free_heap, (size_t) (data.size() * 1.5));
while (true) { while (true) {
DynamicJsonDocument json_document(request_size); DynamicJsonDocument json_document(request_size);
if (json_document.capacity() == 0) { if (json_document.capacity() == 0) {
ESP_LOGE(TAG, "Could not allocate memory for JSON document! Requested %u bytes, free heap: %u", request_size, ESP_LOGE(TAG, "Could not allocate memory for JSON document! Requested %zu bytes, free heap: %zu", request_size,
free_heap); free_heap);
return false; return false;
} }

View File

@ -23,7 +23,7 @@ from esphome.helpers import write_file_if_changed
from . import defines as df, helpers, lv_validation as lvalid from . import defines as df, helpers, lv_validation as lvalid
from .automation import disp_update, focused_widgets, update_to_code from .automation import disp_update, focused_widgets, update_to_code
from .defines import add_define from .defines import CONF_DRAW_ROUNDING, add_define
from .encoders import ( from .encoders import (
ENCODERS_CONFIG, ENCODERS_CONFIG,
encoders_to_code, encoders_to_code,
@ -205,6 +205,10 @@ def final_validation(configs):
raise cv.Invalid( raise cv.Invalid(
"Using auto_clear_enabled: true in display config not compatible with LVGL" "Using auto_clear_enabled: true in display config not compatible with LVGL"
) )
if draw_rounding := display.get(CONF_DRAW_ROUNDING):
config[CONF_DRAW_ROUNDING] = max(
draw_rounding, config[CONF_DRAW_ROUNDING]
)
buffer_frac = config[CONF_BUFFER_SIZE] buffer_frac = config[CONF_BUFFER_SIZE]
if CORE.is_esp32 and buffer_frac > 0.5 and "psram" not in global_config: if CORE.is_esp32 and buffer_frac > 0.5 and "psram" not in global_config:
LOGGER.warning("buffer_size: may need to be reduced without PSRAM") LOGGER.warning("buffer_size: may need to be reduced without PSRAM")

View File

@ -38,7 +38,7 @@ def literal(arg):
def call_lambda(lamb: LambdaExpression): def call_lambda(lamb: LambdaExpression):
expr = lamb.content.strip() expr = lamb.content.strip()
if expr.startswith("return") and expr.endswith(";"): if expr.startswith("return") and expr.endswith(";"):
return expr[7:][:-1] return expr[6:][:-1].strip()
return f"{lamb}()" return f"{lamb}()"
@ -168,6 +168,7 @@ LV_EVENT_MAP = {
"READY": "READY", "READY": "READY",
"CANCEL": "CANCEL", "CANCEL": "CANCEL",
"ALL_EVENTS": "ALL", "ALL_EVENTS": "ALL",
"CHANGE": "VALUE_CHANGED",
} }
LV_EVENT_TRIGGERS = tuple(f"on_{x.lower()}" for x in LV_EVENT_MAP) LV_EVENT_TRIGGERS = tuple(f"on_{x.lower()}" for x in LV_EVENT_MAP)

View File

@ -56,6 +56,9 @@ static const display::ColorBitness LV_BITNESS = display::ColorBitness::COLOR_BIT
inline void lv_img_set_src(lv_obj_t *obj, esphome::image::Image *image) { inline void lv_img_set_src(lv_obj_t *obj, esphome::image::Image *image) {
lv_img_set_src(obj, image->get_lv_img_dsc()); lv_img_set_src(obj, image->get_lv_img_dsc());
} }
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 #endif // USE_LVGL_IMAGE
#ifdef USE_LVGL_ANIMIMG #ifdef USE_LVGL_ANIMIMG
inline void lv_animimg_set_src(lv_obj_t *img, std::vector<image::Image *> images) { inline void lv_animimg_set_src(lv_obj_t *img, std::vector<image::Image *> images) {

View File

@ -79,7 +79,7 @@ class ImgType(WidgetType):
if CONF_ANTIALIAS in config: if CONF_ANTIALIAS in config:
lv.img_set_antialias(w.obj, config[CONF_ANTIALIAS]) lv.img_set_antialias(w.obj, config[CONF_ANTIALIAS])
if mode := config.get(CONF_MODE): if mode := config.get(CONF_MODE):
lv.img_set_mode(w.obj, mode) await w.set_property("size_mode", mode)
img_spec = ImgType() img_spec = ImgType()

View File

@ -35,6 +35,11 @@ LINE_SCHEMA = {
cv.GenerateID(CONF_POINT_LIST_ID): cv.declare_id(lv_point_t), cv.GenerateID(CONF_POINT_LIST_ID): cv.declare_id(lv_point_t),
} }
LINE_MODIFY_SCHEMA = {
cv.Optional(CONF_POINTS): cv_point_list,
cv.GenerateID(CONF_POINT_LIST_ID): cv.declare_id(lv_point_t),
}
class LineType(WidgetType): class LineType(WidgetType):
def __init__(self): def __init__(self):
@ -43,6 +48,7 @@ class LineType(WidgetType):
LvType("lv_line_t"), LvType("lv_line_t"),
(CONF_MAIN,), (CONF_MAIN,),
LINE_SCHEMA, LINE_SCHEMA,
modify_schema=LINE_MODIFY_SCHEMA,
) )
async def to_code(self, w: Widget, config): async def to_code(self, w: Widget, config):

View File

@ -29,7 +29,7 @@ from ..lvcode import (
) )
from ..schemas import STYLE_SCHEMA, STYLED_TEXT_SCHEMA, container_schema, part_schema from ..schemas import STYLE_SCHEMA, STYLED_TEXT_SCHEMA, container_schema, part_schema
from ..types import LV_EVENT, char_ptr, lv_obj_t from ..types import LV_EVENT, char_ptr, lv_obj_t
from . import Widget, set_obj_properties from . import Widget, add_widgets, set_obj_properties
from .button import button_spec from .button import button_spec
from .buttonmatrix import ( from .buttonmatrix import (
BUTTONMATRIX_BUTTON_SCHEMA, BUTTONMATRIX_BUTTON_SCHEMA,
@ -119,6 +119,7 @@ async def msgbox_to_code(top_layer, conf):
button_style = {CONF_ITEMS: button_style} button_style = {CONF_ITEMS: button_style}
await set_obj_properties(buttonmatrix_widget, button_style) await set_obj_properties(buttonmatrix_widget, button_style)
await set_obj_properties(msgbox_widget, conf) await set_obj_properties(msgbox_widget, conf)
await add_widgets(msgbox_widget, conf)
async with LambdaContext(EVENT_ARG, where=messagebox_id) as close_action: async with LambdaContext(EVENT_ARG, where=messagebox_id) as close_action:
outer_widget.add_flag("LV_OBJ_FLAG_HIDDEN") outer_widget.add_flag("LV_OBJ_FLAG_HIDDEN")
if close_button: if close_button:

View File

@ -19,10 +19,12 @@ template<typename... Ts> class MideaActionBase : public Action<Ts...> {
template<typename... Ts> class FollowMeAction : public MideaActionBase<Ts...> { template<typename... Ts> class FollowMeAction : public MideaActionBase<Ts...> {
TEMPLATABLE_VALUE(float, temperature) TEMPLATABLE_VALUE(float, temperature)
TEMPLATABLE_VALUE(bool, use_fahrenheit)
TEMPLATABLE_VALUE(bool, beeper) TEMPLATABLE_VALUE(bool, beeper)
void play(Ts... x) override { void play(Ts... x) override {
this->parent_->do_follow_me(this->temperature_.value(x...), this->beeper_.value(x...)); this->parent_->do_follow_me(this->temperature_.value(x...), this->use_fahrenheit_.value(x...),
this->beeper_.value(x...));
} }
}; };

View File

@ -1,6 +1,7 @@
#ifdef USE_ARDUINO #ifdef USE_ARDUINO
#include "esphome/core/log.h" #include "esphome/core/log.h"
#include "esphome/core/helpers.h"
#include "air_conditioner.h" #include "air_conditioner.h"
#include "ac_adapter.h" #include "ac_adapter.h"
#include <cmath> #include <cmath>
@ -121,7 +122,7 @@ void AirConditioner::dump_config() {
/* ACTIONS */ /* ACTIONS */
void AirConditioner::do_follow_me(float temperature, bool beeper) { void AirConditioner::do_follow_me(float temperature, bool use_fahrenheit, bool beeper) {
#ifdef USE_REMOTE_TRANSMITTER #ifdef USE_REMOTE_TRANSMITTER
// Check if temperature is finite (not NaN or infinite) // Check if temperature is finite (not NaN or infinite)
if (!std::isfinite(temperature)) { if (!std::isfinite(temperature)) {
@ -131,13 +132,14 @@ void AirConditioner::do_follow_me(float temperature, bool beeper) {
// Round and convert temperature to long, then clamp and convert it to uint8_t // Round and convert temperature to long, then clamp and convert it to uint8_t
uint8_t temp_uint8 = uint8_t temp_uint8 =
static_cast<uint8_t>(std::max(0L, std::min(static_cast<long>(UINT8_MAX), std::lroundf(temperature)))); static_cast<uint8_t>(esphome::clamp<long>(std::lroundf(temperature), 0L, static_cast<long>(UINT8_MAX)));
ESP_LOGD(Constants::TAG, "Follow me action called with temperature: %f °C, rounded to: %u °C", temperature, char temp_symbol = use_fahrenheit ? 'F' : 'C';
temp_uint8); ESP_LOGD(Constants::TAG, "Follow me action called with temperature: %.5f °%c, rounded to: %u °%c", temperature,
temp_symbol, temp_uint8, temp_symbol);
// Create and transmit the data // Create and transmit the data
IrFollowMeData data(temp_uint8, beeper); IrFollowMeData data(temp_uint8, use_fahrenheit, beeper);
this->transmitter_.transmit(data); this->transmitter_.transmit(data);
#else #else
ESP_LOGW(Constants::TAG, "Action needs remote_transmitter component"); ESP_LOGW(Constants::TAG, "Action needs remote_transmitter component");

View File

@ -32,7 +32,7 @@ class AirConditioner : public ApplianceBase<dudanov::midea::ac::AirConditioner>,
/* ### ACTIONS ### */ /* ### ACTIONS ### */
/* ############### */ /* ############### */
void do_follow_me(float temperature, bool beeper = false); void do_follow_me(float temperature, bool use_fahrenheit, bool beeper = false);
void do_display_toggle(); void do_display_toggle();
void do_swing_step(); void do_swing_step();
void do_beeper_on() { this->set_beeper_feedback(true); } void do_beeper_on() { this->set_beeper_feedback(true); }

View File

@ -18,6 +18,7 @@ from esphome.const import (
CONF_SUPPORTED_SWING_MODES, CONF_SUPPORTED_SWING_MODES,
CONF_TIMEOUT, CONF_TIMEOUT,
CONF_TEMPERATURE, CONF_TEMPERATURE,
CONF_USE_FAHRENHEIT,
DEVICE_CLASS_POWER, DEVICE_CLASS_POWER,
DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_TEMPERATURE,
DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_HUMIDITY,
@ -172,11 +173,10 @@ MIDEA_ACTION_BASE_SCHEMA = cv.Schema(
) )
# FollowMe action # FollowMe action
MIDEA_FOLLOW_ME_MIN = 0
MIDEA_FOLLOW_ME_MAX = 37
MIDEA_FOLLOW_ME_SCHEMA = cv.Schema( MIDEA_FOLLOW_ME_SCHEMA = cv.Schema(
{ {
cv.Required(CONF_TEMPERATURE): cv.templatable(cv.temperature), cv.Required(CONF_TEMPERATURE): cv.templatable(cv.temperature),
cv.Optional(CONF_USE_FAHRENHEIT, default=False): cv.templatable(cv.boolean),
cv.Optional(CONF_BEEPER, default=False): cv.templatable(cv.boolean), cv.Optional(CONF_BEEPER, default=False): cv.templatable(cv.boolean),
} }
) )
@ -186,6 +186,8 @@ MIDEA_FOLLOW_ME_SCHEMA = cv.Schema(
async def follow_me_to_code(var, config, args): async def follow_me_to_code(var, config, args):
template_ = await cg.templatable(config[CONF_BEEPER], args, cg.bool_) template_ = await cg.templatable(config[CONF_BEEPER], args, cg.bool_)
cg.add(var.set_beeper(template_)) cg.add(var.set_beeper(template_))
template_ = await cg.templatable(config[CONF_USE_FAHRENHEIT], args, cg.bool_)
cg.add(var.set_use_fahrenheit(template_))
template_ = await cg.templatable(config[CONF_TEMPERATURE], args, cg.float_) template_ = await cg.templatable(config[CONF_TEMPERATURE], args, cg.float_)
cg.add(var.set_temperature(template_)) cg.add(var.set_temperature(template_))

View File

@ -16,22 +16,53 @@ class IrFollowMeData : public IrData {
IrFollowMeData() : IrData({MIDEA_TYPE_FOLLOW_ME, 0x82, 0x48, 0x7F, 0x1F}) {} IrFollowMeData() : IrData({MIDEA_TYPE_FOLLOW_ME, 0x82, 0x48, 0x7F, 0x1F}) {}
// Copy from Base // Copy from Base
IrFollowMeData(const IrData &data) : IrData(data) {} IrFollowMeData(const IrData &data) : IrData(data) {}
// Direct from temperature and beeper values // Direct from temperature in celsius and beeper values
IrFollowMeData(uint8_t temp, bool beeper = false) : IrFollowMeData() { IrFollowMeData(uint8_t temp, bool beeper = false) : IrFollowMeData() {
this->set_temp(temp); this->set_temp(temp, false);
this->set_beeper(beeper);
}
// Direct from temperature, fahrenheit and beeper values
IrFollowMeData(uint8_t temp, bool fahrenheit, bool beeper) : IrFollowMeData() {
this->set_temp(temp, fahrenheit);
this->set_beeper(beeper); this->set_beeper(beeper);
} }
/* TEMPERATURE */ /* TEMPERATURE */
uint8_t temp() const { return this->get_value_(4) - 1; } uint8_t temp() const {
void set_temp(uint8_t val) { this->set_value_(4, std::min(MAX_TEMP, val) + 1); } if (this->fahrenheit()) {
return this->get_value_(4) + 31;
}
return this->get_value_(4) - 1;
}
void set_temp(uint8_t val, bool fahrenheit = false) {
this->set_fahrenheit(fahrenheit);
if (this->fahrenheit()) {
// see https://github.com/esphome/feature-requests/issues/1627#issuecomment-1365639966
val = esphome::clamp<uint8_t>(val, MIN_TEMP_F, MAX_TEMP_F) - 31;
} else {
val = esphome::clamp<uint8_t>(val, MIN_TEMP_C, MAX_TEMP_C) + 1;
}
this->set_value_(4, val);
}
/* BEEPER */ /* BEEPER */
bool beeper() const { return this->get_value_(3, 128); } bool beeper() const { return this->get_value_(3, 128); }
void set_beeper(bool val) { this->set_mask_(3, val, 128); } void set_beeper(bool val) { this->set_mask_(3, val, 128); }
/* FAHRENHEIT */
bool fahrenheit() const { return this->get_value_(2, 32); }
void set_fahrenheit(bool val) { this->set_mask_(2, val, 32); }
protected: protected:
static const uint8_t MAX_TEMP = 37; static const uint8_t MIN_TEMP_C = 0;
static const uint8_t MAX_TEMP_C = 37;
// see
// https://github.com/crankyoldgit/IRremoteESP8266/blob/9bdf8abcb465268c5409db99dc83a26df64c7445/src/ir_Midea.h#L116
static const uint8_t MIN_TEMP_F = 32;
// see
// https://github.com/crankyoldgit/IRremoteESP8266/blob/9bdf8abcb465268c5409db99dc83a26df64c7445/src/ir_Midea.h#L117
static const uint8_t MAX_TEMP_F = 99;
}; };
class IrSpecialData : public IrData { class IrSpecialData : public IrData {

View File

@ -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 { 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) { auto reg_it = std::find_if(
return (r.start_address == start_address && r.register_type == register_type); 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); ESP_LOGE(TAG, "No matching range for sensor found - start_address : 0x%X", start_address);
} else { } else {
return reg_it->sensors; return reg_it->sensors;
@ -240,18 +240,18 @@ void ModbusController::update() {
// walk through the sensors and determine the register ranges to read // walk through the sensors and determine the register ranges to read
size_t ModbusController::create_register_ranges_() { size_t ModbusController::create_register_ranges_() {
register_ranges_.clear(); this->register_ranges_.clear();
if (this->parent_->role == modbus::ModbusRole::CLIENT && sensorset_.empty()) { if (this->parent_->role == modbus::ModbusRole::CLIENT && this->sensorset_.empty()) {
ESP_LOGW(TAG, "No sensors registered"); ESP_LOGW(TAG, "No sensors registered");
return 0; return 0;
} }
// iterator is sorted see SensorItemsComparator for details // iterator is sorted see SensorItemsComparator for details
auto ix = sensorset_.begin(); auto ix = this->sensorset_.begin();
RegisterRange r = {}; RegisterRange r = {};
uint8_t buffer_offset = 0; uint8_t buffer_offset = 0;
SensorItem *prev = nullptr; SensorItem *prev = nullptr;
while (ix != sensorset_.end()) { while (ix != this->sensorset_.end()) {
SensorItem *curr = *ix; SensorItem *curr = *ix;
ESP_LOGV(TAG, "Register: 0x%X %d %d %d offset=%u skip=%u addr=%p", curr->start_address, curr->register_count, 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 // this register can re-use the data from the previous register
// remove this sensore because start_address is changed (sort-order) // 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->start_address = r.start_address;
curr->offset += prev->offset; curr->offset += prev->offset;
sensorset_.insert(curr); this->sensorset_.insert(curr);
// move iterator backwards because it will be incremented later // move iterator backwards because it will be incremented later
ix--; ix--;
@ -293,14 +293,14 @@ size_t ModbusController::create_register_ranges_() {
// this register can extend the current range // this register can extend the current range
// remove this sensore because start_address is changed (sort-order) // 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->start_address = r.start_address;
curr->offset += buffer_offset; curr->offset += buffer_offset;
buffer_offset += curr->get_register_size(); buffer_offset += curr->get_register_size();
r.register_count += curr->register_count; r.register_count += curr->register_count;
sensorset_.insert(curr); this->sensorset_.insert(curr);
// move iterator backwards because it will be incremented later // move iterator backwards because it will be incremented later
ix--; ix--;
@ -327,7 +327,7 @@ size_t ModbusController::create_register_ranges_() {
ix++; ix++;
} else { } else {
ESP_LOGV(TAG, "Add range 0x%X %d skip:%d", r.start_address, r.register_count, r.skip_updates); 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 = {}; r = {};
buffer_offset = 0; buffer_offset = 0;
// do not increment the iterator here because the current sensor has to be re-evaluated // 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) { if (r.register_count > 0) {
// Add the last range // Add the last range
ESP_LOGV(TAG, "Add last range 0x%X %d skip:%d", r.start_address, r.register_count, r.skip_updates); 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() { void ModbusController::dump_config() {
@ -352,18 +352,18 @@ void ModbusController::dump_config() {
ESP_LOGCONFIG(TAG, " Offline Skip Updates: %d", this->offline_skip_updates_); ESP_LOGCONFIG(TAG, " Offline Skip Updates: %d", this->offline_skip_updates_);
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE #if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
ESP_LOGCONFIG(TAG, "sensormap"); 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", ESP_LOGCONFIG(TAG, " Sensor type=%zu start=0x%X offset=0x%X count=%d size=%d",
static_cast<uint8_t>(it->register_type), it->start_address, it->offset, it->register_count, static_cast<uint8_t>(it->register_type), it->start_address, it->offset, it->register_count,
it->get_register_size()); it->get_register_size());
} }
ESP_LOGCONFIG(TAG, "ranges"); 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<uint8_t>(it.register_type), ESP_LOGCONFIG(TAG, " Range type=%zu start=0x%X count=%d skip_updates=%d", static_cast<uint8_t>(it.register_type),
it.start_address, it.register_count, it.skip_updates); it.start_address, it.register_count, it.skip_updates);
} }
ESP_LOGCONFIG(TAG, "server registers"); 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, ESP_LOGCONFIG(TAG, " Address=0x%02X value_type=%zu register_count=%u", r->address,
static_cast<uint8_t>(r->value_type), r->register_count); static_cast<uint8_t>(r->value_type), r->register_count);
} }
@ -372,15 +372,15 @@ void ModbusController::dump_config() {
void ModbusController::loop() { void ModbusController::loop() {
// Incoming data to process? // Incoming data to process?
if (!incoming_queue_.empty()) { if (!this->incoming_queue_.empty()) {
auto &message = incoming_queue_.front(); auto &message = this->incoming_queue_.front();
if (message != nullptr) if (message != nullptr)
process_modbus_data_(message.get()); this->process_modbus_data_(message.get());
incoming_queue_.pop(); this->incoming_queue_.pop();
} else { } else {
// all messages processed send pending commands // 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_() { void ModbusController::dump_sensors_() {
ESP_LOGV(TAG, "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, 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); it->get_register_size(), it->offset);
} }

View File

@ -240,14 +240,14 @@ class SensorItem {
} }
// Override register size for modbus devices not using 1 register for one dword // 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; } void set_register_size(uint8_t register_size) { response_bytes = register_size; }
ModbusRegisterType register_type; ModbusRegisterType register_type{ModbusRegisterType::CUSTOM};
SensorValueType sensor_value_type; SensorValueType sensor_value_type{SensorValueType::RAW};
uint16_t start_address; uint16_t start_address{0};
uint32_t bitmask; uint32_t bitmask{0};
uint8_t offset; uint8_t offset{0};
uint8_t register_count; uint8_t register_count{0};
uint8_t response_bytes{0}; uint8_t response_bytes{0};
uint16_t skip_updates; uint16_t skip_updates{0};
std::vector<uint8_t> custom_data{}; std::vector<uint8_t> custom_data{};
bool force_new_range{false}; bool force_new_range{false};
}; };
@ -261,9 +261,9 @@ class ServerRegister {
this->register_count = register_count; this->register_count = register_count;
this->read_lambda = std::move(read_lambda); this->read_lambda = std::move(read_lambda);
} }
uint16_t address; uint16_t address{0};
SensorValueType value_type; SensorValueType value_type{SensorValueType::RAW};
uint8_t register_count; uint8_t register_count{0};
std::function<float()> read_lambda; std::function<float()> read_lambda;
}; };
@ -312,11 +312,11 @@ struct RegisterRange {
class ModbusCommandItem { class ModbusCommandItem {
public: public:
static const size_t MAX_PAYLOAD_BYTES = 240; static const size_t MAX_PAYLOAD_BYTES = 240;
ModbusController *modbusdevice; ModbusController *modbusdevice{nullptr};
uint16_t register_address; uint16_t register_address{0};
uint16_t register_count; uint16_t register_count{0};
ModbusFunctionCode function_code; ModbusFunctionCode function_code{ModbusFunctionCode::CUSTOM};
ModbusRegisterType register_type; ModbusRegisterType register_type{ModbusRegisterType::CUSTOM};
std::function<void(ModbusRegisterType register_type, uint16_t start_address, const std::vector<uint8_t> &data)> std::function<void(ModbusRegisterType register_type, uint16_t start_address, const std::vector<uint8_t> &data)>
on_data_func; on_data_func;
std::vector<uint8_t> payload = {}; std::vector<uint8_t> payload = {};
@ -493,23 +493,23 @@ class ModbusController : public PollingComponent, public modbus::ModbusDevice {
/// Collection of all sensors for this component /// Collection of all sensors for this component
SensorSet sensorset_; SensorSet sensorset_;
/// Collection of all server registers for this component /// Collection of all server registers for this component
std::vector<ServerRegister *> server_registers_; std::vector<ServerRegister *> server_registers_{};
/// Continuous range of modbus registers /// Continuous range of modbus registers
std::vector<RegisterRange> register_ranges_; std::vector<RegisterRange> register_ranges_{};
/// Hold the pending requests to be sent /// Hold the pending requests to be sent
std::list<std::unique_ptr<ModbusCommandItem>> command_queue_; std::list<std::unique_ptr<ModbusCommandItem>> command_queue_;
/// modbus response data waiting to get processed /// modbus response data waiting to get processed
std::queue<std::unique_ptr<ModbusCommandItem>> incoming_queue_; std::queue<std::unique_ptr<ModbusCommandItem>> incoming_queue_;
/// if duplicate commands can be sent /// if duplicate commands can be sent
bool allow_duplicate_commands_; bool allow_duplicate_commands_{false};
/// when was the last send operation /// 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 /// 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 /// if module didn't respond the last command
bool module_offline_; bool module_offline_{false};
/// how many updates to skip if module is offline /// 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 /// How many times we will retry a command if we get no response
uint8_t max_cmd_retries_{4}; uint8_t max_cmd_retries_{4};
/// Command sent callback /// Command sent callback

View File

@ -8,7 +8,7 @@ namespace modbus_controller {
static const char *const TAG = "modbus.number"; static const char *const TAG = "modbus.number";
void ModbusNumber::parse_and_publish(const std::vector<uint8_t> &data) { void ModbusNumber::parse_and_publish(const std::vector<uint8_t> &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 // Is there a lambda registered
// call it with the pre converted value and the raw data array // call it with the pre converted value and the raw data array
@ -43,7 +43,7 @@ void ModbusNumber::control(float value) {
return; return;
} }
} else { } else {
write_value = multiply_by_ * write_value; write_value = this->multiply_by_ * write_value;
} }
if (!data.empty()) { if (!data.empty()) {
@ -63,21 +63,21 @@ void ModbusNumber::control(float value) {
// Create and send the write command // Create and send the write command
if (this->register_count == 1 && !this->use_write_multiple_) { 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 // since offset is in bytes and a register is 16 bits we get the start by adding offset/2
write_cmd = write_cmd = ModbusCommandItem::create_write_single_command(this->parent_, this->start_address + this->offset / 2,
ModbusCommandItem::create_write_single_command(parent_, this->start_address + this->offset / 2, data[0]); data[0]);
} else { } else {
write_cmd = ModbusCommandItem::create_write_multiple_command(parent_, this->start_address + this->offset / 2, write_cmd = ModbusCommandItem::create_write_multiple_command(
this->register_count, data); this->parent_, this->start_address + this->offset / 2, this->register_count, data);
} }
// publish new value // publish new value
write_cmd.on_data_func = [this, write_cmd, value](ModbusRegisterType register_type, uint16_t start_address, write_cmd.on_data_func = [this, write_cmd, value](ModbusRegisterType register_type, uint16_t start_address,
const std::vector<uint8_t> &data) { const std::vector<uint8_t> &data) {
// gets called when the write command is ack'd from the device // 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); this->publish_state(value);
}; };
} }
parent_->queue_command(write_cmd); this->parent_->queue_command(write_cmd);
this->publish_state(value); this->publish_state(value);
} }
void ModbusNumber::dump_config() { LOG_NUMBER(TAG, "Modbus Number", this); } void ModbusNumber::dump_config() { LOG_NUMBER(TAG, "Modbus Number", this); }

View File

@ -29,7 +29,7 @@ class ModbusNumber : public number::Number, public Component, public SensorItem
void parse_and_publish(const std::vector<uint8_t> &data) override; void parse_and_publish(const std::vector<uint8_t> &data) override;
float get_setup_priority() const override { return setup_priority::HARDWARE; } float get_setup_priority() const override { return setup_priority::HARDWARE; }
void set_parent(ModbusController *parent) { this->parent_ = parent; } 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<optional<float>(ModbusNumber *, float, const std::vector<uint8_t> &)>; using transform_func_t = std::function<optional<float>(ModbusNumber *, float, const std::vector<uint8_t> &)>;
using write_transform_func_t = std::function<optional<float>(ModbusNumber *, float, std::vector<uint16_t> &)>; using write_transform_func_t = std::function<optional<float>(ModbusNumber *, float, std::vector<uint16_t> &)>;
@ -39,9 +39,9 @@ class ModbusNumber : public number::Number, public Component, public SensorItem
protected: protected:
void control(float value) override; void control(float value) override;
optional<transform_func_t> transform_func_; optional<transform_func_t> transform_func_{nullopt};
optional<write_transform_func_t> write_transform_func_; optional<write_transform_func_t> write_transform_func_{nullopt};
ModbusController *parent_; ModbusController *parent_{nullptr};
float multiply_by_{1.0}; float multiply_by_{1.0};
bool use_write_multiple_{false}; bool use_write_multiple_{false};
}; };

View File

@ -27,7 +27,7 @@ void ModbusFloatOutput::write_state(float value) {
return; return;
} }
} else { } else {
value = multiply_by_ * value; value = this->multiply_by_ * value;
} }
// lambda didn't set payload // lambda didn't set payload
if (data.empty()) { if (data.empty()) {
@ -40,12 +40,13 @@ void ModbusFloatOutput::write_state(float value) {
// Create and send the write command // Create and send the write command
ModbusCommandItem write_cmd; ModbusCommandItem write_cmd;
if (this->register_count == 1 && !this->use_write_multiple_) { 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 { } 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); this->register_count, data);
} }
parent_->queue_command(write_cmd); this->parent_->queue_command(write_cmd);
} }
void ModbusFloatOutput::dump_config() { 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 // offset for coil and discrete inputs is the coil/register number not bytes
if (this->use_write_multiple_) { if (this->use_write_multiple_) {
std::vector<bool> states{state}; std::vector<bool> 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 { } 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); this->parent_->queue_command(cmd);

View File

@ -25,7 +25,7 @@ class ModbusFloatOutput : public output::FloatOutput, public Component, public S
void dump_config() override; void dump_config() override;
void set_parent(ModbusController *parent) { this->parent_ = parent; } 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 // Do nothing
void parse_and_publish(const std::vector<uint8_t> &data) override{}; void parse_and_publish(const std::vector<uint8_t> &data) override{};
@ -37,9 +37,9 @@ class ModbusFloatOutput : public output::FloatOutput, public Component, public S
void write_state(float value) override; void write_state(float value) override;
optional<write_transform_func_t> write_transform_func_{nullopt}; optional<write_transform_func_t> write_transform_func_{nullopt};
ModbusController *parent_; ModbusController *parent_{nullptr};
float multiply_by_{1.0}; float multiply_by_{1.0};
bool use_write_multiple_; bool use_write_multiple_{false};
}; };
class ModbusBinaryOutput : public output::BinaryOutput, public Component, public SensorItem { 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; void write_state(bool state) override;
optional<write_transform_func_t> write_transform_func_{nullopt}; optional<write_transform_func_t> write_transform_func_{nullopt};
ModbusController *parent_; ModbusController *parent_{nullptr};
bool use_write_multiple_; bool use_write_multiple_{false};
}; };
} // namespace modbus_controller } // namespace modbus_controller

View File

@ -74,12 +74,13 @@ void ModbusSelect::control(const std::string &value) {
const uint16_t write_address = this->start_address + this->offset / 2; const uint16_t write_address = this->start_address + this->offset / 2;
ModbusCommandItem write_cmd; ModbusCommandItem write_cmd;
if ((this->register_count == 1) && (!this->use_write_multiple_)) { 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 { } 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_) if (this->optimistic_)
this->publish_state(value); this->publish_state(value);

View File

@ -42,12 +42,12 @@ class ModbusSelect : public Component, public select::Select, public SensorItem
void control(const std::string &value) override; void control(const std::string &value) override;
protected: protected:
std::vector<int64_t> mapping_; std::vector<int64_t> mapping_{};
ModbusController *parent_; ModbusController *parent_{nullptr};
bool use_write_multiple_{false}; bool use_write_multiple_{false};
bool optimistic_{false}; bool optimistic_{false};
optional<transform_func_t> transform_func_; optional<transform_func_t> transform_func_{nullopt};
optional<write_transform_func_t> write_transform_func_; optional<write_transform_func_t> write_transform_func_{nullopt};
}; };
} // namespace modbus_controller } // namespace modbus_controller

View File

@ -80,24 +80,24 @@ void ModbusSwitch::write_state(bool state) {
// offset for coil and discrete inputs is the coil/register number not bytes // offset for coil and discrete inputs is the coil/register number not bytes
if (this->use_write_multiple_) { if (this->use_write_multiple_) {
std::vector<bool> states{state}; std::vector<bool> 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 { } 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 { } else {
// since offset is in bytes and a register is 16 bits we get the start by adding offset/2 // since offset is in bytes and a register is 16 bits we get the start by adding offset/2
if (this->use_write_multiple_) { if (this->use_write_multiple_) {
std::vector<uint16_t> bool_states(1, state ? (0xFFFF & this->bitmask) : 0); std::vector<uint16_t> 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); bool_states);
} else { } 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); state ? 0xFFFF & this->bitmask : 0u);
} }
} }
} }
this->parent_->queue_command(cmd); this->parent_->queue_command(cmd);
publish_state(state); this->publish_state(state);
} }
// ModbusSwitch end // ModbusSwitch end
} // namespace modbus_controller } // namespace modbus_controller

View File

@ -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; } void set_use_write_mutiple(bool use_write_multiple) { this->use_write_multiple_ = use_write_multiple; }
protected: protected:
ModbusController *parent_; ModbusController *parent_{nullptr};
bool use_write_multiple_; bool use_write_multiple_{false};
optional<transform_func_t> publish_transform_func_{nullopt}; optional<transform_func_t> publish_transform_func_{nullopt};
optional<write_transform_func_t> write_transform_func_{nullopt}; optional<write_transform_func_t> write_transform_func_{nullopt};
}; };

View File

@ -49,6 +49,7 @@ from esphome.const import (
CONF_USE_ABBREVIATIONS, CONF_USE_ABBREVIATIONS,
CONF_USERNAME, CONF_USERNAME,
CONF_WILL_MESSAGE, CONF_WILL_MESSAGE,
CONF_PUBLISH_NAN_AS_NONE,
PLATFORM_BK72XX, PLATFORM_BK72XX,
PLATFORM_ESP32, PLATFORM_ESP32,
PLATFORM_ESP8266, PLATFORM_ESP8266,
@ -296,6 +297,7 @@ CONFIG_SCHEMA = cv.All(
cv.Optional(CONF_QOS, default=0): cv.mqtt_qos, cv.Optional(CONF_QOS, default=0): cv.mqtt_qos,
} }
), ),
cv.Optional(CONF_PUBLISH_NAN_AS_NONE, default=False): cv.boolean,
} }
), ),
validate_config, validate_config,
@ -449,6 +451,8 @@ async def to_code(config):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf) 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( MQTT_PUBLISH_ACTION_SCHEMA = cv.Schema(
{ {

View File

@ -608,6 +608,10 @@ void MQTTClientComponent::set_log_message_template(MQTTMessage &&message) { this
const MQTTDiscoveryInfo &MQTTClientComponent::get_discovery_info() const { return this->discovery_info_; } 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; } 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_; } 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() { void MQTTClientComponent::disable_birth_message() {
this->birth_message_.topic = ""; this->birth_message_.topic = "";
this->recalculate_availability_(); this->recalculate_availability_();

View File

@ -263,6 +263,10 @@ class MQTTClientComponent : public Component {
void set_on_connect(mqtt_on_connect_callback_t &&callback); void set_on_connect(mqtt_on_connect_callback_t &&callback);
void set_on_disconnect(mqtt_on_disconnect_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: protected:
void send_device_info_(); void send_device_info_();
@ -328,6 +332,8 @@ class MQTTClientComponent : public Component {
uint32_t connect_begin_; uint32_t connect_begin_;
uint32_t last_connected_{0}; uint32_t last_connected_{0};
optional<MQTTClientDisconnectReason> disconnect_reason_{}; optional<MQTTClientDisconnectReason> disconnect_reason_{};
bool publish_nan_as_none_{false};
}; };
extern MQTTClientComponent *global_mqtt_client; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) extern MQTTClientComponent *global_mqtt_client; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)

View File

@ -69,6 +69,8 @@ bool MQTTSensorComponent::send_initial_state() {
} }
} }
bool MQTTSensorComponent::publish_state(float value) { 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(); int8_t accuracy = this->sensor_->get_accuracy_decimals();
return this->publish(this->get_state_topic_(), value_accuracy_to_string(value, accuracy)); return this->publish(this->get_state_topic_(), value_accuracy_to_string(value, accuracy));
} }

View File

@ -119,17 +119,17 @@ async def to_code(config):
cg.add_library("ESP8266HTTPClient", None) cg.add_library("ESP8266HTTPClient", None)
if CONF_TOUCH_SLEEP_TIMEOUT in config: if CONF_TOUCH_SLEEP_TIMEOUT in config:
cg.add(var.set_touch_sleep_timeout_internal(config[CONF_TOUCH_SLEEP_TIMEOUT])) cg.add(var.set_touch_sleep_timeout(config[CONF_TOUCH_SLEEP_TIMEOUT]))
if CONF_WAKE_UP_PAGE in config: if CONF_WAKE_UP_PAGE in config:
cg.add(var.set_wake_up_page_internal(config[CONF_WAKE_UP_PAGE])) cg.add(var.set_wake_up_page(config[CONF_WAKE_UP_PAGE]))
if CONF_START_UP_PAGE in config: if CONF_START_UP_PAGE in config:
cg.add(var.set_start_up_page_internal(config[CONF_START_UP_PAGE])) cg.add(var.set_start_up_page(config[CONF_START_UP_PAGE]))
cg.add(var.set_auto_wake_on_touch_internal(config[CONF_AUTO_WAKE_ON_TOUCH])) cg.add(var.set_auto_wake_on_touch(config[CONF_AUTO_WAKE_ON_TOUCH]))
cg.add(var.set_exit_reparse_on_start_internal(config[CONF_EXIT_REPARSE_ON_START])) cg.add(var.set_exit_reparse_on_start(config[CONF_EXIT_REPARSE_ON_START]))
cg.add(var.set_skip_connection_handshake(config[CONF_SKIP_CONNECTION_HANDSHAKE])) cg.add(var.set_skip_connection_handshake(config[CONF_SKIP_CONNECTION_HANDSHAKE]))

View File

@ -40,7 +40,7 @@ bool Nextion::send_command_(const std::string &command) {
} }
bool Nextion::check_connect_() { bool Nextion::check_connect_() {
if (this->get_is_connected_()) if (this->is_connected_)
return true; return true;
// Check if the handshake should be skipped for the Nextion connection // Check if the handshake should be skipped for the Nextion connection
@ -280,14 +280,6 @@ void Nextion::loop() {
this->goto_page(this->start_up_page_); this->goto_page(this->start_up_page_);
} }
// This could probably be removed from the loop area, as those are redundant.
this->set_auto_wake_on_touch(this->auto_wake_on_touch_);
this->set_exit_reparse_on_start(this->exit_reparse_on_start_);
if (this->touch_sleep_timeout_ != 0) {
this->set_touch_sleep_timeout(this->touch_sleep_timeout_);
}
if (this->wake_up_page_ != -1) { if (this->wake_up_page_ != -1) {
this->set_wake_up_page(this->wake_up_page_); this->set_wake_up_page(this->wake_up_page_);
} }

View File

@ -856,76 +856,6 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe
*/ */
void set_backlight_brightness(float brightness); void set_backlight_brightness(float brightness);
/**
* Set the touch sleep timeout of the display.
* @param timeout Timeout in seconds.
*
* Example:
* ```cpp
* it.set_touch_sleep_timeout(30);
* ```
*
* After 30 seconds the display will go to sleep. Note: the display will only wakeup by a restart or by setting up
* `thup`.
*/
void set_touch_sleep_timeout(uint16_t timeout);
/**
* Sets which page Nextion loads when exiting sleep mode. Note this can be set even when Nextion is in sleep mode.
* @param page_id The page id, from 0 to the lage page in Nextion. Set 255 (not set to any existing page) to
* wakes up to current page.
*
* Example:
* ```cpp
* it.set_wake_up_page(2);
* ```
*
* The display will wake up to page 2.
*/
void set_wake_up_page(uint8_t page_id = 255);
/**
* Sets which page Nextion loads when connecting to ESPHome.
* @param page_id The page id, from 0 to the lage page in Nextion. Set 255 (not set to any existing page) to
* wakes up to current page.
*
* Example:
* ```cpp
* it.set_start_up_page(2);
* ```
*
* The display will go to page 2 when it establishes a connection to ESPHome.
*/
void set_start_up_page(uint8_t page_id = 255);
/**
* Sets if Nextion should auto-wake from sleep when touch press occurs.
* @param auto_wake True or false. When auto_wake is true and Nextion is in sleep mode,
* the first touch will only trigger the auto wake mode and not trigger a Touch Event.
*
* Example:
* ```cpp
* it.set_auto_wake_on_touch(true);
* ```
*
* The display will wake up by touch.
*/
void set_auto_wake_on_touch(bool auto_wake);
/**
* Sets if Nextion should exit the active reparse mode before the "connect" command is sent
* @param exit_reparse True or false. When exit_reparse is true, the exit reparse command
* will be sent before requesting the connection from Nextion.
*
* Example:
* ```cpp
* it.set_exit_reparse_on_start(true);
* ```
*
* The display will be requested to leave active reparse mode before setup.
*/
void set_exit_reparse_on_start(bool exit_reparse);
/** /**
* Sets whether the Nextion display should skip the connection handshake process. * Sets whether the Nextion display should skip the connection handshake process.
* @param skip_handshake True or false. When skip_connection_handshake is true, * @param skip_handshake True or false. When skip_connection_handshake is true,
@ -1172,15 +1102,75 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe
void update_components_by_prefix(const std::string &prefix); void update_components_by_prefix(const std::string &prefix);
void set_touch_sleep_timeout_internal(uint32_t touch_sleep_timeout) { /**
this->touch_sleep_timeout_ = touch_sleep_timeout; * Set the touch sleep timeout of the display.
} * @param timeout Timeout in seconds.
void set_wake_up_page_internal(uint8_t wake_up_page) { this->wake_up_page_ = wake_up_page; } *
void set_start_up_page_internal(uint8_t start_up_page) { this->start_up_page_ = start_up_page; } * Example:
void set_auto_wake_on_touch_internal(bool auto_wake_on_touch) { this->auto_wake_on_touch_ = auto_wake_on_touch; } * ```cpp
void set_exit_reparse_on_start_internal(bool exit_reparse_on_start) { * it.set_touch_sleep_timeout(30);
this->exit_reparse_on_start_ = exit_reparse_on_start; * ```
} *
* After 30 seconds the display will go to sleep. Note: the display will only wakeup by a restart or by setting up
* `thup`.
*/
void set_touch_sleep_timeout(uint32_t touch_sleep_timeout);
/**
* Sets which page Nextion loads when exiting sleep mode. Note this can be set even when Nextion is in sleep mode.
* @param wake_up_page The page id, from 0 to the lage page in Nextion. Set 255 (not set to any existing page) to
* wakes up to current page.
*
* Example:
* ```cpp
* it.set_wake_up_page(2);
* ```
*
* The display will wake up to page 2.
*/
void set_wake_up_page(uint8_t wake_up_page = 255);
/**
* Sets which page Nextion loads when connecting to ESPHome.
* @param start_up_page The page id, from 0 to the lage page in Nextion. Set 255 (not set to any existing page) to
* wakes up to current page.
*
* Example:
* ```cpp
* it.set_start_up_page(2);
* ```
*
* The display will go to page 2 when it establishes a connection to ESPHome.
*/
void set_start_up_page(uint8_t start_up_page = 255) { this->start_up_page_ = start_up_page; }
/**
* Sets if Nextion should auto-wake from sleep when touch press occurs.
* @param auto_wake_on_touch True or false. When auto_wake is true and Nextion is in sleep mode,
* the first touch will only trigger the auto wake mode and not trigger a Touch Event.
*
* Example:
* ```cpp
* it.set_auto_wake_on_touch(true);
* ```
*
* The display will wake up by touch.
*/
void set_auto_wake_on_touch(bool auto_wake_on_touch);
/**
* Sets if Nextion should exit the active reparse mode before the "connect" command is sent
* @param exit_reparse_on_start True or false. When exit_reparse_on_start is true, the exit reparse command
* will be sent before requesting the connection from Nextion.
*
* Example:
* ```cpp
* it.set_exit_reparse_on_start(true);
* ```
*
* The display will be requested to leave active reparse mode before setup.
*/
void set_exit_reparse_on_start(bool exit_reparse_on_start) { this->exit_reparse_on_start_ = exit_reparse_on_start; }
/** /**
* @brief Retrieves the number of commands pending in the Nextion command queue. * @brief Retrieves the number of commands pending in the Nextion command queue.
@ -1217,6 +1207,25 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe
*/ */
bool is_updating() override; bool is_updating() override;
/**
* @brief Check if the Nextion display is successfully connected.
*
* This method returns whether a successful connection has been established with
* the Nextion display. A connection is considered established when:
*
* - The initial handshake with the display is completed successfully, or
* - The handshake is skipped via skip_connection_handshake_ flag
*
* The connection status is particularly useful when:
* - Troubleshooting communication issues
* - Ensuring the display is ready before sending commands
* - Implementing connection-dependent behaviors
*
* @return true if the Nextion display is connected and ready to receive commands
* @return false if the display is not yet connected or connection was lost
*/
bool is_connected() { return this->is_connected_; }
protected: protected:
std::deque<NextionQueue *> nextion_queue_; std::deque<NextionQueue *> nextion_queue_;
std::deque<NextionQueue *> waveform_queue_; std::deque<NextionQueue *> waveform_queue_;
@ -1315,8 +1324,6 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe
#endif // USE_NEXTION_TFT_UPLOAD #endif // USE_NEXTION_TFT_UPLOAD
bool get_is_connected_() { return this->is_connected_; }
bool check_connect_(); bool check_connect_();
std::vector<NextionComponentBase *> touch_; std::vector<NextionComponentBase *> touch_;

View File

@ -10,19 +10,19 @@ static const char *const TAG = "nextion";
// Sleep safe commands // Sleep safe commands
void Nextion::soft_reset() { this->send_command_("rest"); } void Nextion::soft_reset() { this->send_command_("rest"); }
void Nextion::set_wake_up_page(uint8_t page_id) { void Nextion::set_wake_up_page(uint8_t wake_up_page) {
this->add_no_result_to_queue_with_set_internal_("wake_up_page", "wup", page_id, true); this->wake_up_page_ = wake_up_page;
this->add_no_result_to_queue_with_set_internal_("wake_up_page", "wup", wake_up_page, true);
} }
void Nextion::set_start_up_page(uint8_t page_id) { this->start_up_page_ = page_id; } void Nextion::set_touch_sleep_timeout(uint32_t touch_sleep_timeout) {
if (touch_sleep_timeout < 3) {
void Nextion::set_touch_sleep_timeout(uint16_t timeout) {
if (timeout < 3) {
ESP_LOGD(TAG, "Sleep timeout out of bounds, range 3-65535"); ESP_LOGD(TAG, "Sleep timeout out of bounds, range 3-65535");
return; return;
} }
this->add_no_result_to_queue_with_set_internal_("touch_sleep_timeout", "thsp", timeout, true); this->touch_sleep_timeout_ = touch_sleep_timeout;
this->add_no_result_to_queue_with_set_internal_("touch_sleep_timeout", "thsp", touch_sleep_timeout, true);
} }
void Nextion::sleep(bool sleep) { void Nextion::sleep(bool sleep) {
@ -54,7 +54,6 @@ bool Nextion::set_protocol_reparse_mode(bool active_mode) {
this->ignore_is_setup_ = false; this->ignore_is_setup_ = false;
return all_commands_sent; return all_commands_sent;
} }
void Nextion::set_exit_reparse_on_start(bool exit_reparse) { this->exit_reparse_on_start_ = exit_reparse; }
// Set Colors - Background // Set Colors - Background
void Nextion::set_component_background_color(const char *component, uint16_t color) { void Nextion::set_component_background_color(const char *component, uint16_t color) {
@ -191,8 +190,9 @@ void Nextion::set_backlight_brightness(float brightness) {
this->add_no_result_to_queue_with_printf_("backlight_brightness", "dim=%d", static_cast<int>(brightness * 100)); this->add_no_result_to_queue_with_printf_("backlight_brightness", "dim=%d", static_cast<int>(brightness * 100));
} }
void Nextion::set_auto_wake_on_touch(bool auto_wake) { void Nextion::set_auto_wake_on_touch(bool auto_wake_on_touch) {
this->add_no_result_to_queue_with_set("auto_wake_on_touch", "thup", auto_wake ? 1 : 0); this->auto_wake_on_touch_ = auto_wake_on_touch;
this->add_no_result_to_queue_with_set("auto_wake_on_touch", "thup", auto_wake_on_touch ? 1 : 0);
} }
// General Component // General Component

View File

@ -80,15 +80,7 @@ bool OnlineImage::resize_(int width_in, int height_in) {
this->width_ = width; this->width_ = width;
ESP_LOGD(TAG, "New size: (%d, %d)", width, height); ESP_LOGD(TAG, "New size: (%d, %d)", width, height);
} else { } else {
#if defined(USE_ESP8266) ESP_LOGE(TAG, "allocation failed. Biggest block in heap: %zu Bytes", this->allocator_.get_max_free_block_size());
// NOLINTNEXTLINE(readability-static-accessed-through-instance)
int max_block = ESP.getMaxFreeBlockSize();
#elif defined(USE_ESP32)
int max_block = heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL);
#else
int max_block = -1;
#endif
ESP_LOGE(TAG, "allocation failed. Biggest block in heap: %d Bytes", max_block);
this->end_connection_(); this->end_connection_();
return false; return false;
} }

View File

@ -1,10 +1,12 @@
from typing import Any from typing import Any
import logging
from esphome import automation
import esphome.codegen as cg import esphome.codegen as cg
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome import pins from esphome import pins
from esphome.components import sensor from esphome.components import sensor
from esphome.const import CONF_ID, PLATFORM_ESP32, PLATFORM_ESP8266 from esphome.const import CONF_ID, PLATFORM_ESP32, PLATFORM_ESP8266, CONF_TRIGGER_ID
from . import const, schema, validate, generate from . import const, schema, validate, generate
CODEOWNERS = ["@olegtarasov"] CODEOWNERS = ["@olegtarasov"]
@ -20,7 +22,21 @@ CONF_CH2_ACTIVE = "ch2_active"
CONF_SUMMER_MODE_ACTIVE = "summer_mode_active" CONF_SUMMER_MODE_ACTIVE = "summer_mode_active"
CONF_DHW_BLOCK = "dhw_block" CONF_DHW_BLOCK = "dhw_block"
CONF_SYNC_MODE = "sync_mode" CONF_SYNC_MODE = "sync_mode"
CONF_OPENTHERM_VERSION = "opentherm_version" CONF_OPENTHERM_VERSION = "opentherm_version" # Deprecated, will be removed
CONF_BEFORE_SEND = "before_send"
CONF_BEFORE_PROCESS_RESPONSE = "before_process_response"
# Triggers
BeforeSendTrigger = generate.opentherm_ns.class_(
"BeforeSendTrigger",
automation.Trigger.template(generate.OpenthermData.operator("ref")),
)
BeforeProcessResponseTrigger = generate.opentherm_ns.class_(
"BeforeProcessResponseTrigger",
automation.Trigger.template(generate.OpenthermData.operator("ref")),
)
_LOGGER = logging.getLogger(__name__)
CONFIG_SCHEMA = cv.All( CONFIG_SCHEMA = cv.All(
cv.Schema( cv.Schema(
@ -36,7 +52,19 @@ CONFIG_SCHEMA = cv.All(
cv.Optional(CONF_SUMMER_MODE_ACTIVE, False): cv.boolean, cv.Optional(CONF_SUMMER_MODE_ACTIVE, False): cv.boolean,
cv.Optional(CONF_DHW_BLOCK, False): cv.boolean, cv.Optional(CONF_DHW_BLOCK, False): cv.boolean,
cv.Optional(CONF_SYNC_MODE, False): cv.boolean, cv.Optional(CONF_SYNC_MODE, False): cv.boolean,
cv.Optional(CONF_OPENTHERM_VERSION): cv.positive_float, cv.Optional(CONF_OPENTHERM_VERSION): cv.positive_float, # Deprecated
cv.Optional(CONF_BEFORE_SEND): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(BeforeSendTrigger),
}
),
cv.Optional(CONF_BEFORE_PROCESS_RESPONSE): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
BeforeProcessResponseTrigger
),
}
),
} }
) )
.extend( .extend(
@ -44,6 +72,11 @@ CONFIG_SCHEMA = cv.All(
schema.INPUTS, (lambda _: cv.use_id(sensor.Sensor)) schema.INPUTS, (lambda _: cv.use_id(sensor.Sensor))
) )
) )
.extend(
validate.create_entities_schema(
schema.SETTINGS, (lambda s: s.validation_schema)
)
)
.extend(cv.COMPONENT_SCHEMA), .extend(cv.COMPONENT_SCHEMA),
cv.only_on([PLATFORM_ESP32, PLATFORM_ESP8266]), cv.only_on([PLATFORM_ESP32, PLATFORM_ESP8266]),
) )
@ -60,18 +93,33 @@ async def to_code(config: dict[str, Any]) -> None:
out_pin = await cg.gpio_pin_expression(config[CONF_OUT_PIN]) out_pin = await cg.gpio_pin_expression(config[CONF_OUT_PIN])
cg.add(var.set_out_pin(out_pin)) cg.add(var.set_out_pin(out_pin))
non_sensors = {CONF_ID, CONF_IN_PIN, CONF_OUT_PIN} non_sensors = {
CONF_ID,
CONF_IN_PIN,
CONF_OUT_PIN,
CONF_BEFORE_SEND,
CONF_BEFORE_PROCESS_RESPONSE,
}
input_sensors = [] input_sensors = []
settings = []
for key, value in config.items(): for key, value in config.items():
if key in non_sensors: if key in non_sensors:
continue continue
if key in schema.INPUTS: if key in schema.INPUTS:
input_sensor = await cg.get_variable(value) input_sensor = await cg.get_variable(value)
cg.add( cg.add(getattr(var, f"set_{key}_{const.INPUT_SENSOR}")(input_sensor))
getattr(var, f"set_{key}_{const.INPUT_SENSOR.lower()}")(input_sensor)
)
input_sensors.append(key) input_sensors.append(key)
elif key in schema.SETTINGS:
if value == schema.SETTINGS[key].default_value:
continue
cg.add(getattr(var, f"set_{key}_{const.SETTING}")(value))
settings.append(key)
else: else:
if key == CONF_OPENTHERM_VERSION:
_LOGGER.warning(
"opentherm_version is deprecated and will be removed in esphome 2025.2.0\n"
"Please change to 'opentherm_version_controller'."
)
cg.add(getattr(var, f"set_{key}")(value)) cg.add(getattr(var, f"set_{key}")(value))
if len(input_sensors) > 0: if len(input_sensors) > 0:
@ -81,3 +129,21 @@ async def to_code(config: dict[str, Any]) -> None:
) )
generate.define_readers(const.INPUT_SENSOR, input_sensors) generate.define_readers(const.INPUT_SENSOR, input_sensors)
generate.add_messages(var, input_sensors, schema.INPUTS) generate.add_messages(var, input_sensors, schema.INPUTS)
if len(settings) > 0:
generate.define_has_settings(settings, schema.SETTINGS)
generate.define_message_handler(const.SETTING, settings, schema.SETTINGS)
generate.define_setting_readers(const.SETTING, settings)
generate.add_messages(var, settings, schema.SETTINGS)
for conf in config.get(CONF_BEFORE_SEND, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(
trigger, [(generate.OpenthermData.operator("ref"), "x")], conf
)
for conf in config.get(CONF_BEFORE_PROCESS_RESPONSE, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(
trigger, [(generate.OpenthermData.operator("ref"), "x")], conf
)

View File

@ -0,0 +1,25 @@
#pragma once
#include "esphome/core/automation.h"
#include "hub.h"
#include "opentherm.h"
namespace esphome {
namespace opentherm {
class BeforeSendTrigger : public Trigger<OpenthermData &> {
public:
BeforeSendTrigger(OpenthermHub *hub) {
hub->add_on_before_send_callback([this](OpenthermData &x) { this->trigger(x); });
}
};
class BeforeProcessResponseTrigger : public Trigger<OpenthermData &> {
public:
BeforeProcessResponseTrigger(OpenthermHub *hub) {
hub->add_on_before_process_response_callback([this](OpenthermData &x) { this->trigger(x); });
}
};
} // namespace opentherm
} // namespace esphome

View File

@ -9,3 +9,4 @@ SWITCH = "switch"
NUMBER = "number" NUMBER = "number"
OUTPUT = "output" OUTPUT = "output"
INPUT_SENSOR = "input_sensor" INPUT_SENSOR = "input_sensor"
SETTING = "setting"

View File

@ -1,13 +1,14 @@
from collections.abc import Awaitable from collections.abc import Awaitable
from typing import Any, Callable from typing import Any, Callable, Optional
import esphome.codegen as cg import esphome.codegen as cg
from esphome.const import CONF_ID from esphome.const import CONF_ID
from . import const from . import const
from .schema import TSchema from .schema import TSchema, SettingSchema
opentherm_ns = cg.esphome_ns.namespace("opentherm") opentherm_ns = cg.esphome_ns.namespace("opentherm")
OpenthermHub = opentherm_ns.class_("OpenthermHub", cg.Component) OpenthermHub = opentherm_ns.class_("OpenthermHub", cg.Component)
OpenthermData = opentherm_ns.class_("OpenthermData")
def define_has_component(component_type: str, keys: list[str]) -> None: def define_has_component(component_type: str, keys: list[str]) -> None:
@ -21,6 +22,24 @@ def define_has_component(component_type: str, keys: list[str]) -> None:
cg.add_define(f"OPENTHERM_HAS_{component_type.upper()}_{key}") cg.add_define(f"OPENTHERM_HAS_{component_type.upper()}_{key}")
# We need a separate set of macros for settings because there are different backing field types we need to take
# into account
def define_has_settings(keys: list[str], schemas: dict[str, SettingSchema]) -> None:
cg.add_define(
"OPENTHERM_SETTING_LIST(F, sep)",
cg.RawExpression(
" sep ".join(
map(
lambda key: f"F({schemas[key].backing_type}, {key}_setting, {schemas[key].default_value})",
keys,
)
)
),
)
for key in keys:
cg.add_define(f"OPENTHERM_HAS_SETTING_{key}")
def define_message_handler( def define_message_handler(
component_type: str, keys: list[str], schemas: dict[str, TSchema] component_type: str, keys: list[str], schemas: dict[str, TSchema]
) -> None: ) -> None:
@ -74,14 +93,28 @@ def define_readers(component_type: str, keys: list[str]) -> None:
) )
def add_messages(hub: cg.MockObj, keys: list[str], schemas: dict[str, TSchema]): def define_setting_readers(component_type: str, keys: list[str]) -> None:
messages: set[tuple[str, bool]] = set()
for key in keys: for key in keys:
messages.add((schemas[key].message, schemas[key].keep_updated)) cg.add_define(
for msg, keep_updated in messages: f"OPENTHERM_READ_{key}",
cg.RawExpression(f"this->{key}_{component_type.lower()}"),
)
def add_messages(hub: cg.MockObj, keys: list[str], schemas: dict[str, TSchema]):
messages: dict[str, tuple[bool, Optional[int]]] = {}
for key in keys:
messages[schemas[key].message] = (
schemas[key].keep_updated,
schemas[key].order if hasattr(schemas[key], "order") else None,
)
for msg, (keep_updated, order) in messages.items():
msg_expr = cg.RawExpression(f"esphome::opentherm::MessageId::{msg}") msg_expr = cg.RawExpression(f"esphome::opentherm::MessageId::{msg}")
if keep_updated: if keep_updated:
cg.add(hub.add_repeating_message(msg_expr)) cg.add(hub.add_repeating_message(msg_expr))
else:
if order is not None:
cg.add(hub.add_initial_message(msg_expr, order))
else: else:
cg.add(hub.add_initial_message(msg_expr)) cg.add(hub.add_initial_message(msg_expr))

View File

@ -63,7 +63,7 @@ void write_f88(const float value, OpenthermData &data) { data.f88(value); }
OpenthermData OpenthermHub::build_request_(MessageId request_id) const { OpenthermData OpenthermHub::build_request_(MessageId request_id) const {
OpenthermData data; OpenthermData data;
data.type = 0; data.type = 0;
data.id = 0; data.id = request_id;
data.valueHB = 0; data.valueHB = 0;
data.valueLB = 0; data.valueLB = 0;
@ -82,28 +82,13 @@ OpenthermData OpenthermHub::build_request_(MessageId request_id) const {
// NOLINTEND // NOLINTEND
data.type = MessageType::READ_DATA; data.type = MessageType::READ_DATA;
data.id = MessageId::STATUS;
data.valueHB = ch_enabled | (dhw_enabled << 1) | (cooling_enabled << 2) | (otc_enabled << 3) | (ch2_enabled << 4) | data.valueHB = ch_enabled | (dhw_enabled << 1) | (cooling_enabled << 2) | (otc_enabled << 3) | (ch2_enabled << 4) |
(summer_mode_is_active << 5) | (dhw_blocked << 6); (summer_mode_is_active << 5) | (dhw_blocked << 6);
return data; return data;
} }
// Another special case is OpenTherm version number which is configured at hub level as a constant // Next, we start with write requests from switches and other inputs,
if (request_id == MessageId::OT_VERSION_CONTROLLER) {
data.type = MessageType::WRITE_DATA;
data.id = MessageId::OT_VERSION_CONTROLLER;
data.f88(this->opentherm_version_);
return data;
}
// Disable incomplete switch statement warnings, because the cases in each
// switch are generated based on the configured sensors and inputs.
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wswitch"
// Next, we start with the write requests from switches and other inputs,
// because we would want to write that data if it is available, rather than // because we would want to write that data if it is available, rather than
// request a read for that type (in the case that both read and write are // request a read for that type (in the case that both read and write are
// supported). // supported).
@ -116,14 +101,23 @@ OpenthermData OpenthermHub::build_request_(MessageId request_id) const {
OPENTHERM_MESSAGE_WRITE_POSTSCRIPT, ) OPENTHERM_MESSAGE_WRITE_POSTSCRIPT, )
OPENTHERM_INPUT_SENSOR_MESSAGE_HANDLERS(OPENTHERM_MESSAGE_WRITE_MESSAGE, OPENTHERM_MESSAGE_WRITE_ENTITY, , OPENTHERM_INPUT_SENSOR_MESSAGE_HANDLERS(OPENTHERM_MESSAGE_WRITE_MESSAGE, OPENTHERM_MESSAGE_WRITE_ENTITY, ,
OPENTHERM_MESSAGE_WRITE_POSTSCRIPT, ) OPENTHERM_MESSAGE_WRITE_POSTSCRIPT, )
OPENTHERM_SETTING_MESSAGE_HANDLERS(OPENTHERM_MESSAGE_WRITE_MESSAGE, OPENTHERM_MESSAGE_WRITE_SETTING, ,
OPENTHERM_MESSAGE_WRITE_POSTSCRIPT, )
default:
break;
} }
// Finally, handle the simple read requests, which only change with the message id. // Finally, handle the simple read requests, which only change with the message id.
switch (request_id) { OPENTHERM_SENSOR_MESSAGE_HANDLERS(OPENTHERM_MESSAGE_READ_MESSAGE, OPENTHERM_IGNORE, , , ) } switch (request_id) {
OPENTHERM_SENSOR_MESSAGE_HANDLERS(OPENTHERM_MESSAGE_READ_MESSAGE, OPENTHERM_IGNORE, , , )
default:
break;
}
switch (request_id) { switch (request_id) {
OPENTHERM_BINARY_SENSOR_MESSAGE_HANDLERS(OPENTHERM_MESSAGE_READ_MESSAGE, OPENTHERM_IGNORE, , , ) OPENTHERM_BINARY_SENSOR_MESSAGE_HANDLERS(OPENTHERM_MESSAGE_READ_MESSAGE, OPENTHERM_IGNORE, , , )
default:
break;
} }
#pragma GCC diagnostic pop
// And if we get here, a message was requested which somehow wasn't handled. // And if we get here, a message was requested which somehow wasn't handled.
// This shouldn't happen due to the way the defines are configured, so we // This shouldn't happen due to the way the defines are configured, so we
@ -163,19 +157,37 @@ void OpenthermHub::setup() {
// communicate at least once every second. Sending the status request is // communicate at least once every second. Sending the status request is
// good practice anyway. // good practice anyway.
this->add_repeating_message(MessageId::STATUS); this->add_repeating_message(MessageId::STATUS);
this->write_initial_messages_(this->messages_);
// Also ensure that we start communication with the STATUS message this->message_iterator_ = this->messages_.begin();
this->initial_messages_.insert(this->initial_messages_.begin(), MessageId::STATUS);
if (this->opentherm_version_ > 0.0f) {
this->initial_messages_.insert(this->initial_messages_.begin(), MessageId::OT_VERSION_CONTROLLER);
}
this->current_message_iterator_ = this->initial_messages_.begin();
} }
void OpenthermHub::on_shutdown() { this->opentherm_->stop(); } void OpenthermHub::on_shutdown() { this->opentherm_->stop(); }
// Disabling clang-tidy for this particular line since it keeps removing the trailing underscore (bug?)
void OpenthermHub::write_initial_messages_(std::vector<MessageId> &target) { // NOLINT
std::vector<std::pair<MessageId, uint8_t>> sorted;
std::copy_if(this->configured_messages_.begin(), this->configured_messages_.end(), std::back_inserter(sorted),
[](const std::pair<MessageId, uint8_t> &pair) { return pair.second < REPEATING_MESSAGE_ORDER; });
std::sort(sorted.begin(), sorted.end(),
[](const std::pair<MessageId, uint8_t> &a, const std::pair<MessageId, uint8_t> &b) {
return a.second < b.second;
});
target.clear();
std::transform(sorted.begin(), sorted.end(), std::back_inserter(target),
[](const std::pair<MessageId, uint8_t> &pair) { return pair.first; });
}
// Disabling clang-tidy for this particular line since it keeps removing the trailing underscore (bug?)
void OpenthermHub::write_repeating_messages_(std::vector<MessageId> &target) { // NOLINT
target.clear();
for (auto const &pair : this->configured_messages_) {
if (pair.second == REPEATING_MESSAGE_ORDER) {
target.push_back(pair.first);
}
}
}
void OpenthermHub::loop() { void OpenthermHub::loop() {
if (this->sync_mode_) { if (this->sync_mode_) {
this->sync_loop_(); this->sync_loop_();
@ -184,29 +196,18 @@ void OpenthermHub::loop() {
auto cur_time = millis(); auto cur_time = millis();
auto const cur_mode = this->opentherm_->get_mode(); auto const cur_mode = this->opentherm_->get_mode();
if (this->handle_error_(cur_mode)) {
return;
}
switch (cur_mode) { switch (cur_mode) {
case OperationMode::WRITE: case OperationMode::WRITE:
case OperationMode::READ: case OperationMode::READ:
case OperationMode::LISTEN: case OperationMode::LISTEN:
if (!this->check_timings_(cur_time)) {
break;
}
this->last_mode_ = cur_mode;
break;
case OperationMode::ERROR_PROTOCOL:
if (this->last_mode_ == OperationMode::WRITE) {
this->handle_protocol_write_error_();
} else if (this->last_mode_ == OperationMode::READ) {
this->handle_protocol_read_error_();
}
this->stop_opentherm_();
break;
case OperationMode::ERROR_TIMEOUT:
this->handle_timeout_error_();
this->stop_opentherm_();
break; break;
case OperationMode::IDLE: case OperationMode::IDLE:
this->check_timings_(cur_time);
if (this->should_skip_loop_(cur_time)) { if (this->should_skip_loop_(cur_time)) {
break; break;
} }
@ -219,6 +220,28 @@ void OpenthermHub::loop() {
case OperationMode::RECEIVED: case OperationMode::RECEIVED:
this->read_response_(); this->read_response_();
break; break;
default:
break;
}
this->last_mode_ = cur_mode;
}
bool OpenthermHub::handle_error_(OperationMode mode) {
switch (mode) {
case OperationMode::ERROR_PROTOCOL:
// Protocol error can happen only while reading boiler response.
this->handle_protocol_error_();
return true;
case OperationMode::ERROR_TIMEOUT:
// Timeout error might happen while we wait for device to respond.
this->handle_timeout_error_();
return true;
case OperationMode::ERROR_TIMER:
// Timer error can happen only on ESP32.
this->handle_timer_error_();
return true;
default:
return false;
} }
} }
@ -237,16 +260,20 @@ void OpenthermHub::sync_loop_() {
} }
this->start_conversation_(); this->start_conversation_();
// There may be a timer error at this point
if (this->handle_error_(this->opentherm_->get_mode())) {
return;
}
// Spin while message is being sent to device
if (!this->spin_wait_(1150, [&] { return this->opentherm_->is_active(); })) { if (!this->spin_wait_(1150, [&] { return this->opentherm_->is_active(); })) {
ESP_LOGE(TAG, "Hub timeout triggered during send"); ESP_LOGE(TAG, "Hub timeout triggered during send");
this->stop_opentherm_(); this->stop_opentherm_();
return; return;
} }
if (this->opentherm_->is_error()) { // Check for errors and ensure we are in the right state (message sent successfully)
this->handle_protocol_write_error_(); if (this->handle_error_(this->opentherm_->get_mode())) {
this->stop_opentherm_();
return; return;
} else if (!this->opentherm_->is_sent()) { } else if (!this->opentherm_->is_sent()) {
ESP_LOGW(TAG, "Unexpected state after sending request: %s", ESP_LOGW(TAG, "Unexpected state after sending request: %s",
@ -257,19 +284,20 @@ void OpenthermHub::sync_loop_() {
// Listen for the response // Listen for the response
this->opentherm_->listen(); this->opentherm_->listen();
// There may be a timer error at this point
if (this->handle_error_(this->opentherm_->get_mode())) {
return;
}
// Spin while response is being received
if (!this->spin_wait_(1150, [&] { return this->opentherm_->is_active(); })) { if (!this->spin_wait_(1150, [&] { return this->opentherm_->is_active(); })) {
ESP_LOGE(TAG, "Hub timeout triggered during receive"); ESP_LOGE(TAG, "Hub timeout triggered during receive");
this->stop_opentherm_(); this->stop_opentherm_();
return; return;
} }
if (this->opentherm_->is_timeout()) { // Check for errors and ensure we are in the right state (message received successfully)
this->handle_timeout_error_(); if (this->handle_error_(this->opentherm_->get_mode())) {
this->stop_opentherm_();
return;
} else if (this->opentherm_->is_protocol_error()) {
this->handle_protocol_read_error_();
this->stop_opentherm_();
return; return;
} else if (!this->opentherm_->has_message()) { } else if (!this->opentherm_->has_message()) {
ESP_LOGW(TAG, "Unexpected state after receiving response: %s", ESP_LOGW(TAG, "Unexpected state after receiving response: %s",
@ -281,17 +309,13 @@ void OpenthermHub::sync_loop_() {
this->read_response_(); this->read_response_();
} }
bool OpenthermHub::check_timings_(uint32_t cur_time) { void OpenthermHub::check_timings_(uint32_t cur_time) {
if (this->last_conversation_start_ > 0 && (cur_time - this->last_conversation_start_) > 1150) { if (this->last_conversation_start_ > 0 && (cur_time - this->last_conversation_start_) > 1150) {
ESP_LOGW(TAG, ESP_LOGW(TAG,
"%d ms elapsed since the start of the last convo, but 1150 ms are allowed at maximum. Look at other " "%d ms elapsed since the start of the last convo, but 1150 ms are allowed at maximum. Look at other "
"components that might slow the loop down.", "components that might slow the loop down.",
(int) (cur_time - this->last_conversation_start_)); (int) (cur_time - this->last_conversation_start_));
this->stop_opentherm_();
return false;
} }
return true;
} }
bool OpenthermHub::should_skip_loop_(uint32_t cur_time) const { bool OpenthermHub::should_skip_loop_(uint32_t cur_time) const {
@ -304,14 +328,17 @@ bool OpenthermHub::should_skip_loop_(uint32_t cur_time) const {
} }
void OpenthermHub::start_conversation_() { void OpenthermHub::start_conversation_() {
if (this->sending_initial_ && this->current_message_iterator_ == this->initial_messages_.end()) { if (this->message_iterator_ == this->messages_.end()) {
if (this->sending_initial_) {
this->sending_initial_ = false; this->sending_initial_ = false;
this->current_message_iterator_ = this->repeating_messages_.begin(); this->write_repeating_messages_(this->messages_);
} else if (this->current_message_iterator_ == this->repeating_messages_.end()) { }
this->current_message_iterator_ = this->repeating_messages_.begin(); this->message_iterator_ = this->messages_.begin();
} }
auto request = this->build_request_(*this->current_message_iterator_); auto request = this->build_request_(*this->message_iterator_);
this->before_send_callback_.call(request);
ESP_LOGD(TAG, "Sending request with id %d (%s)", request.id, ESP_LOGD(TAG, "Sending request with id %d (%s)", request.id,
this->opentherm_->message_id_to_str((MessageId) request.id)); this->opentherm_->message_id_to_str((MessageId) request.id));
@ -331,37 +358,48 @@ void OpenthermHub::read_response_() {
this->stop_opentherm_(); this->stop_opentherm_();
this->before_process_response_callback_.call(response);
this->process_response(response); this->process_response(response);
this->current_message_iterator_++; this->message_iterator_++;
} }
void OpenthermHub::stop_opentherm_() { void OpenthermHub::stop_opentherm_() {
this->opentherm_->stop(); this->opentherm_->stop();
this->last_conversation_end_ = millis(); this->last_conversation_end_ = millis();
} }
void OpenthermHub::handle_protocol_write_error_() {
ESP_LOGW(TAG, "Error while sending request: %s", void OpenthermHub::handle_protocol_error_() {
this->opentherm_->operation_mode_to_str(this->opentherm_->get_mode()));
this->opentherm_->debug_data(this->last_request_);
}
void OpenthermHub::handle_protocol_read_error_() {
OpenThermError error; OpenThermError error;
this->opentherm_->get_protocol_error(error); this->opentherm_->get_protocol_error(error);
ESP_LOGW(TAG, "Protocol error occured while receiving response: %s", ESP_LOGW(TAG, "Protocol error occured while receiving response: %s",
this->opentherm_->protocol_error_to_to_str(error.error_type)); this->opentherm_->protocol_error_to_str(error.error_type));
this->opentherm_->debug_error(error); this->opentherm_->debug_error(error);
}
void OpenthermHub::handle_timeout_error_() {
ESP_LOGW(TAG, "Receive response timed out at a protocol level");
this->stop_opentherm_(); this->stop_opentherm_();
} }
void OpenthermHub::handle_timeout_error_() {
ESP_LOGW(TAG, "Timeout while waiting for response from device");
this->stop_opentherm_();
}
void OpenthermHub::handle_timer_error_() {
this->opentherm_->report_and_reset_timer_error();
this->stop_opentherm_();
// Timer error is critical, there is no point in retrying.
this->mark_failed();
}
void OpenthermHub::dump_config() { void OpenthermHub::dump_config() {
std::vector<MessageId> initial_messages;
std::vector<MessageId> repeating_messages;
this->write_initial_messages_(initial_messages);
this->write_repeating_messages_(repeating_messages);
ESP_LOGCONFIG(TAG, "OpenTherm:"); ESP_LOGCONFIG(TAG, "OpenTherm:");
LOG_PIN(" In: ", this->in_pin_); LOG_PIN(" In: ", this->in_pin_);
LOG_PIN(" Out: ", this->out_pin_); LOG_PIN(" Out: ", this->out_pin_);
ESP_LOGCONFIG(TAG, " Sync mode: %d", this->sync_mode_); ESP_LOGCONFIG(TAG, " Sync mode: %s", YESNO(this->sync_mode_));
ESP_LOGCONFIG(TAG, " Sensors: %s", SHOW(OPENTHERM_SENSOR_LIST(ID, ))); ESP_LOGCONFIG(TAG, " Sensors: %s", SHOW(OPENTHERM_SENSOR_LIST(ID, )));
ESP_LOGCONFIG(TAG, " Binary sensors: %s", SHOW(OPENTHERM_BINARY_SENSOR_LIST(ID, ))); ESP_LOGCONFIG(TAG, " Binary sensors: %s", SHOW(OPENTHERM_BINARY_SENSOR_LIST(ID, )));
ESP_LOGCONFIG(TAG, " Switches: %s", SHOW(OPENTHERM_SWITCH_LIST(ID, ))); ESP_LOGCONFIG(TAG, " Switches: %s", SHOW(OPENTHERM_SWITCH_LIST(ID, )));
@ -369,12 +407,12 @@ void OpenthermHub::dump_config() {
ESP_LOGCONFIG(TAG, " Outputs: %s", SHOW(OPENTHERM_OUTPUT_LIST(ID, ))); ESP_LOGCONFIG(TAG, " Outputs: %s", SHOW(OPENTHERM_OUTPUT_LIST(ID, )));
ESP_LOGCONFIG(TAG, " Numbers: %s", SHOW(OPENTHERM_NUMBER_LIST(ID, ))); ESP_LOGCONFIG(TAG, " Numbers: %s", SHOW(OPENTHERM_NUMBER_LIST(ID, )));
ESP_LOGCONFIG(TAG, " Initial requests:"); ESP_LOGCONFIG(TAG, " Initial requests:");
for (auto type : this->initial_messages_) { for (auto type : initial_messages) {
ESP_LOGCONFIG(TAG, " - %d (%s)", type, this->opentherm_->message_id_to_str((type))); ESP_LOGCONFIG(TAG, " - %d (%s)", type, this->opentherm_->message_id_to_str(type));
} }
ESP_LOGCONFIG(TAG, " Repeating requests:"); ESP_LOGCONFIG(TAG, " Repeating requests:");
for (auto type : this->repeating_messages_) { for (auto type : repeating_messages) {
ESP_LOGCONFIG(TAG, " - %d (%s)", type, this->opentherm_->message_id_to_str((type))); ESP_LOGCONFIG(TAG, " - %d (%s)", type, this->opentherm_->message_id_to_str(type));
} }
} }

View File

@ -38,6 +38,9 @@
namespace esphome { namespace esphome {
namespace opentherm { namespace opentherm {
static const uint8_t REPEATING_MESSAGE_ORDER = 255;
static const uint8_t INITIAL_UNORDERED_MESSAGE_ORDER = 254;
// OpenTherm component for ESPHome // OpenTherm component for ESPHome
class OpenthermHub : public Component { class OpenthermHub : public Component {
protected: protected:
@ -58,15 +61,12 @@ class OpenthermHub : public Component {
OPENTHERM_INPUT_SENSOR_LIST(OPENTHERM_DECLARE_INPUT_SENSOR, ) OPENTHERM_INPUT_SENSOR_LIST(OPENTHERM_DECLARE_INPUT_SENSOR, )
// The set of initial messages to send on starting communication with the boiler OPENTHERM_SETTING_LIST(OPENTHERM_DECLARE_SETTING, )
std::vector<MessageId> initial_messages_;
// and the repeating messages which are sent repeatedly to update various sensors
// and boiler parameters (like the setpoint).
std::vector<MessageId> repeating_messages_;
// Indicates if we are still working on the initial requests or not
bool sending_initial_ = true; bool sending_initial_ = true;
// Index for the current request in one of the _requests sets. std::unordered_map<MessageId, uint8_t> configured_messages_;
std::vector<MessageId>::const_iterator current_message_iterator_; std::vector<MessageId> messages_;
std::vector<MessageId>::const_iterator message_iterator_;
uint32_t last_conversation_start_ = 0; uint32_t last_conversation_start_ = 0;
uint32_t last_conversation_end_ = 0; uint32_t last_conversation_end_ = 0;
@ -78,20 +78,25 @@ class OpenthermHub : public Component {
// Very likely to happen while using Dallas temperature sensors. // Very likely to happen while using Dallas temperature sensors.
bool sync_mode_ = false; bool sync_mode_ = false;
float opentherm_version_ = 0.0f; CallbackManager<void(OpenthermData &)> before_send_callback_;
CallbackManager<void(OpenthermData &)> before_process_response_callback_;
// Create OpenTherm messages based on the message id // Create OpenTherm messages based on the message id
OpenthermData build_request_(MessageId request_id) const; OpenthermData build_request_(MessageId request_id) const;
void handle_protocol_write_error_(); bool handle_error_(OperationMode mode);
void handle_protocol_read_error_(); void handle_protocol_error_();
void handle_timeout_error_(); void handle_timeout_error_();
void handle_timer_error_();
void stop_opentherm_(); void stop_opentherm_();
void start_conversation_(); void start_conversation_();
void read_response_(); void read_response_();
bool check_timings_(uint32_t cur_time); void check_timings_(uint32_t cur_time);
bool should_skip_loop_(uint32_t cur_time) const; bool should_skip_loop_(uint32_t cur_time) const;
void sync_loop_(); void sync_loop_();
void write_initial_messages_(std::vector<MessageId> &target);
void write_repeating_messages_(std::vector<MessageId> &target);
template<typename F> bool spin_wait_(uint32_t timeout, F func) { template<typename F> bool spin_wait_(uint32_t timeout, F func) {
auto start_time = millis(); auto start_time = millis();
while (func()) { while (func()) {
@ -127,13 +132,18 @@ class OpenthermHub : public Component {
OPENTHERM_INPUT_SENSOR_LIST(OPENTHERM_SET_INPUT_SENSOR, ) OPENTHERM_INPUT_SENSOR_LIST(OPENTHERM_SET_INPUT_SENSOR, )
OPENTHERM_SETTING_LIST(OPENTHERM_SET_SETTING, )
// Add a request to the vector of initial requests // Add a request to the vector of initial requests
void add_initial_message(MessageId message_id) { this->initial_messages_.push_back(message_id); } void add_initial_message(MessageId message_id) {
this->configured_messages_[message_id] = INITIAL_UNORDERED_MESSAGE_ORDER;
}
void add_initial_message(MessageId message_id, uint8_t order) { this->configured_messages_[message_id] = order; }
// Add a request to the set of repeating requests. Note that a large number of repeating // Add a request to the set of repeating requests. Note that a large number of repeating
// requests will slow down communication with the boiler. Each request may take up to 1 second, // requests will slow down communication with the boiler. Each request may take up to 1 second,
// so with all sensors enabled, it may take about half a minute before a change in setpoint // so with all sensors enabled, it may take about half a minute before a change in setpoint
// will be processed. // will be processed.
void add_repeating_message(MessageId message_id) { this->repeating_messages_.push_back(message_id); } void add_repeating_message(MessageId message_id) { this->configured_messages_[message_id] = REPEATING_MESSAGE_ORDER; }
// There are seven status variables, which can either be set as a simple variable, // There are seven status variables, which can either be set as a simple variable,
// or using a switch. ch_enable and dhw_enable default to true, the others to false. // or using a switch. ch_enable and dhw_enable default to true, the others to false.
@ -149,7 +159,13 @@ class OpenthermHub : public Component {
void set_summer_mode_active(bool value) { this->summer_mode_active = value; } void set_summer_mode_active(bool value) { this->summer_mode_active = value; }
void set_dhw_block(bool value) { this->dhw_block = value; } void set_dhw_block(bool value) { this->dhw_block = value; }
void set_sync_mode(bool sync_mode) { this->sync_mode_ = sync_mode; } void set_sync_mode(bool sync_mode) { this->sync_mode_ = sync_mode; }
void set_opentherm_version(float value) { this->opentherm_version_ = value; }
void add_on_before_send_callback(std::function<void(OpenthermData &)> &&callback) {
this->before_send_callback_.add(std::move(callback));
}
void add_on_before_process_response_callback(std::function<void(OpenthermData &)> &&callback) {
this->before_process_response_callback_.add(std::move(callback));
}
float get_setup_priority() const override { return setup_priority::HARDWARE; } float get_setup_priority() const override { return setup_priority::HARDWARE; }

View File

@ -52,7 +52,9 @@ bool OpenTherm::initialize() {
OpenTherm::instance = this; OpenTherm::instance = this;
#endif #endif
this->in_pin_->pin_mode(gpio::FLAG_INPUT); this->in_pin_->pin_mode(gpio::FLAG_INPUT);
this->in_pin_->setup();
this->out_pin_->pin_mode(gpio::FLAG_OUTPUT); this->out_pin_->pin_mode(gpio::FLAG_OUTPUT);
this->out_pin_->setup();
this->out_pin_->digital_write(true); this->out_pin_->digital_write(true);
#if defined(ESP32) || defined(USE_ESP_IDF) #if defined(ESP32) || defined(USE_ESP_IDF)
@ -182,7 +184,7 @@ bool IRAM_ATTR OpenTherm::timer_isr(OpenTherm *arg) {
} }
arg->capture_ = 1; // reset counter arg->capture_ = 1; // reset counter
} else if (arg->capture_ > 0xFF) { } else if (arg->capture_ > 0xFF) {
// no change for too long, invalid mancheter encoding // no change for too long, invalid manchester encoding
arg->mode_ = OperationMode::ERROR_PROTOCOL; arg->mode_ = OperationMode::ERROR_PROTOCOL;
arg->error_type_ = ProtocolErrorType::NO_CHANGE_TOO_LONG; arg->error_type_ = ProtocolErrorType::NO_CHANGE_TOO_LONG;
arg->stop_timer_(); arg->stop_timer_();
@ -312,21 +314,31 @@ bool OpenTherm::init_esp32_timer_() {
} }
void IRAM_ATTR OpenTherm::start_esp32_timer_(uint64_t alarm_value) { void IRAM_ATTR OpenTherm::start_esp32_timer_(uint64_t alarm_value) {
esp_err_t result; // We will report timer errors outside of interrupt handler
this->timer_error_ = ESP_OK;
this->timer_error_type_ = TimerErrorType::NO_TIMER_ERROR;
result = timer_set_alarm_value(this->timer_group_, this->timer_idx_, alarm_value); this->timer_error_ = timer_set_alarm_value(this->timer_group_, this->timer_idx_, alarm_value);
if (result != ESP_OK) { if (this->timer_error_ != ESP_OK) {
const auto *error = esp_err_to_name(result); this->timer_error_type_ = TimerErrorType::SET_ALARM_VALUE_ERROR;
ESP_LOGE(TAG, "Failed to set alarm value. Error: %s", error); return;
}
this->timer_error_ = timer_start(this->timer_group_, this->timer_idx_);
if (this->timer_error_ != ESP_OK) {
this->timer_error_type_ = TimerErrorType::TIMER_START_ERROR;
}
}
void OpenTherm::report_and_reset_timer_error() {
if (this->timer_error_ == ESP_OK) {
return; return;
} }
result = timer_start(this->timer_group_, this->timer_idx_); ESP_LOGE(TAG, "Error occured while manipulating timer (%s): %s", this->timer_error_to_str(this->timer_error_type_),
if (result != ESP_OK) { esp_err_to_name(this->timer_error_));
const auto *error = esp_err_to_name(result);
ESP_LOGE(TAG, "Failed to start the timer. Error: %s", error); this->timer_error_ = ESP_OK;
return; this->timer_error_type_ = NO_TIMER_ERROR;
}
} }
// 5 kHz timer_ // 5 kHz timer_
@ -343,21 +355,18 @@ void IRAM_ATTR OpenTherm::start_write_timer_() {
void IRAM_ATTR OpenTherm::stop_timer_() { void IRAM_ATTR OpenTherm::stop_timer_() {
InterruptLock const lock; InterruptLock const lock;
// We will report timer errors outside of interrupt handler
this->timer_error_ = ESP_OK;
this->timer_error_type_ = TimerErrorType::NO_TIMER_ERROR;
esp_err_t result; this->timer_error_ = timer_pause(this->timer_group_, this->timer_idx_);
if (this->timer_error_ != ESP_OK) {
result = timer_pause(this->timer_group_, this->timer_idx_); this->timer_error_type_ = TimerErrorType::TIMER_PAUSE_ERROR;
if (result != ESP_OK) {
const auto *error = esp_err_to_name(result);
ESP_LOGE(TAG, "Failed to pause the timer. Error: %s", error);
return; return;
} }
this->timer_error_ = timer_set_counter_value(this->timer_group_, this->timer_idx_, 0);
result = timer_set_counter_value(this->timer_group_, this->timer_idx_, 0); if (this->timer_error_ != ESP_OK) {
if (result != ESP_OK) { this->timer_error_type_ = TimerErrorType::SET_COUNTER_VALUE_ERROR;
const auto *error = esp_err_to_name(result);
ESP_LOGE(TAG, "Failed to set timer counter to 0 after pausing. Error: %s", error);
return;
} }
} }
@ -386,6 +395,9 @@ void IRAM_ATTR OpenTherm::stop_timer_() {
timer1_detachInterrupt(); timer1_detachInterrupt();
} }
// There is nothing to report on ESP8266
void OpenTherm::report_and_reset_timer_error() {}
#endif // END ESP8266 #endif // END ESP8266
// https://stackoverflow.com/questions/21617970/how-to-check-if-value-has-even-parity-of-bits-or-odd // https://stackoverflow.com/questions/21617970/how-to-check-if-value-has-even-parity-of-bits-or-odd
@ -412,11 +424,12 @@ const char *OpenTherm::operation_mode_to_str(OperationMode mode) {
TO_STRING_MEMBER(SENT) TO_STRING_MEMBER(SENT)
TO_STRING_MEMBER(ERROR_PROTOCOL) TO_STRING_MEMBER(ERROR_PROTOCOL)
TO_STRING_MEMBER(ERROR_TIMEOUT) TO_STRING_MEMBER(ERROR_TIMEOUT)
TO_STRING_MEMBER(ERROR_TIMER)
default: default:
return "<INVALID>"; return "<INVALID>";
} }
} }
const char *OpenTherm::protocol_error_to_to_str(ProtocolErrorType error_type) { const char *OpenTherm::protocol_error_to_str(ProtocolErrorType error_type) {
switch (error_type) { switch (error_type) {
TO_STRING_MEMBER(NO_ERROR) TO_STRING_MEMBER(NO_ERROR)
TO_STRING_MEMBER(NO_TRANSITION) TO_STRING_MEMBER(NO_TRANSITION)
@ -427,6 +440,17 @@ const char *OpenTherm::protocol_error_to_to_str(ProtocolErrorType error_type) {
return "<INVALID>"; return "<INVALID>";
} }
} }
const char *OpenTherm::timer_error_to_str(TimerErrorType error_type) {
switch (error_type) {
TO_STRING_MEMBER(NO_TIMER_ERROR)
TO_STRING_MEMBER(SET_ALARM_VALUE_ERROR)
TO_STRING_MEMBER(TIMER_START_ERROR)
TO_STRING_MEMBER(TIMER_PAUSE_ERROR)
TO_STRING_MEMBER(SET_COUNTER_VALUE_ERROR)
default:
return "<INVALID>";
}
}
const char *OpenTherm::message_type_to_str(MessageType message_type) { const char *OpenTherm::message_type_to_str(MessageType message_type) {
switch (message_type) { switch (message_type) {
TO_STRING_MEMBER(READ_DATA) TO_STRING_MEMBER(READ_DATA)

View File

@ -36,11 +36,12 @@ enum OperationMode {
READ = 2, // reading 32-bit data frame READ = 2, // reading 32-bit data frame
RECEIVED = 3, // data frame received with valid start and stop bit RECEIVED = 3, // data frame received with valid start and stop bit
WRITE = 4, // writing data with timer_ WRITE = 4, // writing data to output
SENT = 5, // all data written to output SENT = 5, // all data written to output
ERROR_PROTOCOL = 8, // manchester protocol data transfer error ERROR_PROTOCOL = 8, // protocol error, can happed only during READ
ERROR_TIMEOUT = 9 // read timeout ERROR_TIMEOUT = 9, // timeout while waiting for response from device, only during LISTEN
ERROR_TIMER = 10 // error operating the ESP32 timer
}; };
enum ProtocolErrorType { enum ProtocolErrorType {
@ -51,6 +52,14 @@ enum ProtocolErrorType {
NO_CHANGE_TOO_LONG = 4, // No level change for too much timer ticks NO_CHANGE_TOO_LONG = 4, // No level change for too much timer ticks
}; };
enum TimerErrorType {
NO_TIMER_ERROR = 0, // No error
SET_ALARM_VALUE_ERROR = 1, // No transition in the middle of the bit
TIMER_START_ERROR = 2, // Stop bit wasn't present when expected
TIMER_PAUSE_ERROR = 3, // Parity check didn't pass
SET_COUNTER_VALUE_ERROR = 4, // No level change for too much timer ticks
};
enum MessageType { enum MessageType {
READ_DATA = 0, READ_DATA = 0,
READ_ACK = 4, READ_ACK = 4,
@ -299,7 +308,9 @@ class OpenTherm {
* *
* @return true if last listen() or send() operation ends up with an error. * @return true if last listen() or send() operation ends up with an error.
*/ */
bool is_error() { return mode_ == OperationMode::ERROR_TIMEOUT || mode_ == OperationMode::ERROR_PROTOCOL; } bool is_error() {
return mode_ == OperationMode::ERROR_TIMEOUT || mode_ == OperationMode::ERROR_PROTOCOL || mode_ == ERROR_TIMER;
}
/** /**
* Indicates whether last listen() or send() operation ends up with a *timeout* error * Indicates whether last listen() or send() operation ends up with a *timeout* error
@ -313,14 +324,22 @@ class OpenTherm {
*/ */
bool is_protocol_error() { return mode_ == OperationMode::ERROR_PROTOCOL; } bool is_protocol_error() { return mode_ == OperationMode::ERROR_PROTOCOL; }
/**
* Indicates whether start_esp32_timer_() or stop_timer_() had an error. Only relevant when used on ESP32.
* @return true if there was an error.
*/
bool is_timer_error() { return mode_ == OperationMode::ERROR_TIMER; }
bool is_active() { return mode_ == LISTEN || mode_ == READ || mode_ == WRITE; } bool is_active() { return mode_ == LISTEN || mode_ == READ || mode_ == WRITE; }
OperationMode get_mode() { return mode_; } OperationMode get_mode() { return mode_; }
void debug_data(OpenthermData &data); void debug_data(OpenthermData &data);
void debug_error(OpenThermError &error) const; void debug_error(OpenThermError &error) const;
void report_and_reset_timer_error();
const char *protocol_error_to_to_str(ProtocolErrorType error_type); const char *protocol_error_to_str(ProtocolErrorType error_type);
const char *timer_error_to_str(TimerErrorType error_type);
const char *message_type_to_str(MessageType message_type); const char *message_type_to_str(MessageType message_type);
const char *operation_mode_to_str(OperationMode mode); const char *operation_mode_to_str(OperationMode mode);
const char *message_id_to_str(MessageId id); const char *message_id_to_str(MessageId id);
@ -349,10 +368,12 @@ class OpenTherm {
uint32_t data_; uint32_t data_;
uint8_t bit_pos_; uint8_t bit_pos_;
int32_t timeout_counter_; // <0 no timeout int32_t timeout_counter_; // <0 no timeout
int32_t device_timeout_; int32_t device_timeout_;
#if defined(ESP32) || defined(USE_ESP_IDF) #if defined(ESP32) || defined(USE_ESP_IDF)
esp_err_t timer_error_ = ESP_OK;
TimerErrorType timer_error_type_ = TimerErrorType::NO_TIMER_ERROR;
bool init_esp32_timer_(); bool init_esp32_timer_();
void start_esp32_timer_(uint64_t alarm_value); void start_esp32_timer_(uint64_t alarm_value);
#endif #endif

View File

@ -28,6 +28,9 @@ namespace opentherm {
#ifndef OPENTHERM_INPUT_SENSOR_LIST #ifndef OPENTHERM_INPUT_SENSOR_LIST
#define OPENTHERM_INPUT_SENSOR_LIST(F, sep) #define OPENTHERM_INPUT_SENSOR_LIST(F, sep)
#endif #endif
#ifndef OPENTHERM_SETTING_LIST
#define OPENTHERM_SETTING_LIST(F, sep)
#endif
// Use macros to create fields for every entity specified in the ESPHome configuration // Use macros to create fields for every entity specified in the ESPHome configuration
#define OPENTHERM_DECLARE_SENSOR(entity) sensor::Sensor *entity; #define OPENTHERM_DECLARE_SENSOR(entity) sensor::Sensor *entity;
@ -36,6 +39,7 @@ namespace opentherm {
#define OPENTHERM_DECLARE_NUMBER(entity) OpenthermNumber *entity; #define OPENTHERM_DECLARE_NUMBER(entity) OpenthermNumber *entity;
#define OPENTHERM_DECLARE_OUTPUT(entity) OpenthermOutput *entity; #define OPENTHERM_DECLARE_OUTPUT(entity) OpenthermOutput *entity;
#define OPENTHERM_DECLARE_INPUT_SENSOR(entity) sensor::Sensor *entity; #define OPENTHERM_DECLARE_INPUT_SENSOR(entity) sensor::Sensor *entity;
#define OPENTHERM_DECLARE_SETTING(type, entity, def) type entity = def;
// Setter macros // Setter macros
#define OPENTHERM_SET_SENSOR(entity) \ #define OPENTHERM_SET_SENSOR(entity) \
@ -56,6 +60,9 @@ namespace opentherm {
#define OPENTHERM_SET_INPUT_SENSOR(entity) \ #define OPENTHERM_SET_INPUT_SENSOR(entity) \
void set_##entity(sensor::Sensor *sensor) { this->entity = sensor; } void set_##entity(sensor::Sensor *sensor) { this->entity = sensor; }
#define OPENTHERM_SET_SETTING(type, entity, def) \
void set_##entity(type value) { this->entity = value; }
// ===== hub.cpp macros ===== // ===== hub.cpp macros =====
// *_MESSAGE_HANDLERS are generated in defines.h and look like this: // *_MESSAGE_HANDLERS are generated in defines.h and look like this:
@ -85,6 +92,9 @@ namespace opentherm {
#ifndef OPENTHERM_INPUT_SENSOR_MESSAGE_HANDLERS #ifndef OPENTHERM_INPUT_SENSOR_MESSAGE_HANDLERS
#define OPENTHERM_INPUT_SENSOR_MESSAGE_HANDLERS(MESSAGE, ENTITY, entity_sep, postscript, msg_sep) #define OPENTHERM_INPUT_SENSOR_MESSAGE_HANDLERS(MESSAGE, ENTITY, entity_sep, postscript, msg_sep)
#endif #endif
#ifndef OPENTHERM_SETTING_MESSAGE_HANDLERS
#define OPENTHERM_SETTING_MESSAGE_HANDLERS(MESSAGE, ENTITY, entity_sep, postscript, msg_sep)
#endif
// Write data request builders // Write data request builders
#define OPENTHERM_MESSAGE_WRITE_MESSAGE(msg) \ #define OPENTHERM_MESSAGE_WRITE_MESSAGE(msg) \
@ -92,6 +102,7 @@ namespace opentherm {
data.type = MessageType::WRITE_DATA; \ data.type = MessageType::WRITE_DATA; \
data.id = request_id; data.id = request_id;
#define OPENTHERM_MESSAGE_WRITE_ENTITY(key, msg_data) message_data::write_##msg_data(this->key->state, data); #define OPENTHERM_MESSAGE_WRITE_ENTITY(key, msg_data) message_data::write_##msg_data(this->key->state, data);
#define OPENTHERM_MESSAGE_WRITE_SETTING(key, msg_data) message_data::write_##msg_data(this->key, data);
#define OPENTHERM_MESSAGE_WRITE_POSTSCRIPT \ #define OPENTHERM_MESSAGE_WRITE_POSTSCRIPT \
return data; \ return data; \
} }

View File

@ -2,8 +2,9 @@
# inputs of the OpenTherm component. # inputs of the OpenTherm component.
from dataclasses import dataclass from dataclasses import dataclass
from typing import Optional, TypeVar from typing import Optional, TypeVar, Any
import esphome.config_validation as cv
from esphome.const import ( from esphome.const import (
UNIT_CELSIUS, UNIT_CELSIUS,
UNIT_EMPTY, UNIT_EMPTY,
@ -64,6 +65,7 @@ class SensorSchema(EntitySchema):
icon: Optional[str] = None icon: Optional[str] = None
device_class: Optional[str] = None device_class: Optional[str] = None
disabled_by_default: bool = False disabled_by_default: bool = False
order: Optional[int] = None
SENSORS: dict[str, SensorSchema] = { SENSORS: dict[str, SensorSchema] = {
@ -399,6 +401,7 @@ SENSORS: dict[str, SensorSchema] = {
message="OT_VERSION_DEVICE", message="OT_VERSION_DEVICE",
keep_updated=False, keep_updated=False,
message_data="f88", message_data="f88",
order=2,
), ),
"device_type": SensorSchema( "device_type": SensorSchema(
description="Device product type", description="Device product type",
@ -409,6 +412,7 @@ SENSORS: dict[str, SensorSchema] = {
message="VERSION_DEVICE", message="VERSION_DEVICE",
keep_updated=False, keep_updated=False,
message_data="u8_hb", message_data="u8_hb",
order=0,
), ),
"device_version": SensorSchema( "device_version": SensorSchema(
description="Device product version", description="Device product version",
@ -419,6 +423,7 @@ SENSORS: dict[str, SensorSchema] = {
message="VERSION_DEVICE", message="VERSION_DEVICE",
keep_updated=False, keep_updated=False,
message_data="u8_lb", message_data="u8_lb",
order=0,
), ),
"device_id": SensorSchema( "device_id": SensorSchema(
description="Device ID code", description="Device ID code",
@ -429,6 +434,7 @@ SENSORS: dict[str, SensorSchema] = {
message="DEVICE_CONFIG", message="DEVICE_CONFIG",
keep_updated=False, keep_updated=False,
message_data="u8_lb", message_data="u8_lb",
order=4,
), ),
"otc_hc_ratio_ub": SensorSchema( "otc_hc_ratio_ub": SensorSchema(
description="OTC heat curve ratio upper bound", description="OTC heat curve ratio upper bound",
@ -457,6 +463,7 @@ SENSORS: dict[str, SensorSchema] = {
class BinarySensorSchema(EntitySchema): class BinarySensorSchema(EntitySchema):
icon: Optional[str] = None icon: Optional[str] = None
device_class: Optional[str] = None device_class: Optional[str] = None
order: Optional[int] = None
BINARY_SENSORS: dict[str, BinarySensorSchema] = { BINARY_SENSORS: dict[str, BinarySensorSchema] = {
@ -525,48 +532,56 @@ BINARY_SENSORS: dict[str, BinarySensorSchema] = {
message="DEVICE_CONFIG", message="DEVICE_CONFIG",
keep_updated=False, keep_updated=False,
message_data="flag8_hb_0", message_data="flag8_hb_0",
order=4,
), ),
"control_type_on_off": BinarySensorSchema( "control_type_on_off": BinarySensorSchema(
description="Configuration: Control type is on/off", description="Configuration: Control type is on/off",
message="DEVICE_CONFIG", message="DEVICE_CONFIG",
keep_updated=False, keep_updated=False,
message_data="flag8_hb_1", message_data="flag8_hb_1",
order=4,
), ),
"cooling_supported": BinarySensorSchema( "cooling_supported": BinarySensorSchema(
description="Configuration: Cooling supported", description="Configuration: Cooling supported",
message="DEVICE_CONFIG", message="DEVICE_CONFIG",
keep_updated=False, keep_updated=False,
message_data="flag8_hb_2", message_data="flag8_hb_2",
order=4,
), ),
"dhw_storage_tank": BinarySensorSchema( "dhw_storage_tank": BinarySensorSchema(
description="Configuration: DHW storage tank", description="Configuration: DHW storage tank",
message="DEVICE_CONFIG", message="DEVICE_CONFIG",
keep_updated=False, keep_updated=False,
message_data="flag8_hb_3", message_data="flag8_hb_3",
order=4,
), ),
"controller_pump_control_allowed": BinarySensorSchema( "controller_pump_control_allowed": BinarySensorSchema(
description="Configuration: Controller pump control allowed", description="Configuration: Controller pump control allowed",
message="DEVICE_CONFIG", message="DEVICE_CONFIG",
keep_updated=False, keep_updated=False,
message_data="flag8_hb_4", message_data="flag8_hb_4",
order=4,
), ),
"ch2_present": BinarySensorSchema( "ch2_present": BinarySensorSchema(
description="Configuration: CH2 present", description="Configuration: CH2 present",
message="DEVICE_CONFIG", message="DEVICE_CONFIG",
keep_updated=False, keep_updated=False,
message_data="flag8_hb_5", message_data="flag8_hb_5",
order=4,
), ),
"water_filling": BinarySensorSchema( "water_filling": BinarySensorSchema(
description="Configuration: Remote water filling", description="Configuration: Remote water filling",
message="DEVICE_CONFIG", message="DEVICE_CONFIG",
keep_updated=False, keep_updated=False,
message_data="flag8_hb_6", message_data="flag8_hb_6",
order=4,
), ),
"heat_mode": BinarySensorSchema( "heat_mode": BinarySensorSchema(
description="Configuration: Heating or cooling", description="Configuration: Heating or cooling",
message="DEVICE_CONFIG", message="DEVICE_CONFIG",
keep_updated=False, keep_updated=False,
message_data="flag8_hb_7", message_data="flag8_hb_7",
order=4,
), ),
"dhw_setpoint_transfer_enabled": BinarySensorSchema( "dhw_setpoint_transfer_enabled": BinarySensorSchema(
description="Remote boiler parameters: DHW setpoint transfer enabled", description="Remote boiler parameters: DHW setpoint transfer enabled",
@ -812,3 +827,65 @@ INPUTS: dict[str, InputSchema] = {
auto_max_value=AutoConfigure(message="OTC_CURVE_BOUNDS", message_data="u8_hb"), auto_max_value=AutoConfigure(message="OTC_CURVE_BOUNDS", message_data="u8_hb"),
), ),
} }
@dataclass
class SettingSchema(EntitySchema):
backing_type: str
validation_schema: cv.Schema
default_value: Any
order: Optional[int] = None
SETTINGS: dict[str, SettingSchema] = {
"controller_product_type": SettingSchema(
description="Controller product type",
message="VERSION_CONTROLLER",
keep_updated=False,
message_data="u8_hb",
backing_type="uint8_t",
validation_schema=cv.int_range(min=0, max=255),
default_value=0,
order=1,
),
"controller_product_version": SettingSchema(
description="Controller product version",
message="VERSION_CONTROLLER",
keep_updated=False,
message_data="u8_lb",
backing_type="uint8_t",
validation_schema=cv.int_range(min=0, max=255),
default_value=0,
order=1,
),
"opentherm_version_controller": SettingSchema(
description="Version of OpenTherm implemented by controller",
message="OT_VERSION_CONTROLLER",
keep_updated=False,
message_data="f88",
backing_type="float",
validation_schema=cv.positive_float,
default_value=0,
order=3,
),
"controller_configuration": SettingSchema(
description="Controller configuration",
message="CONTROLLER_CONFIG",
keep_updated=False,
message_data="u8_hb",
backing_type="uint8_t",
validation_schema=cv.int_range(min=0, max=255),
default_value=0,
order=5,
),
"controller_id": SettingSchema(
description="Controller ID code",
message="CONTROLLER_CONFIG",
keep_updated=False,
message_data="u8_lb",
backing_type="uint8_t",
validation_schema=cv.int_range(min=0, max=255),
default_value=0,
order=5,
),
}

View File

@ -9,12 +9,17 @@ from .schema import TSchema
def create_entities_schema( def create_entities_schema(
entities: dict[str, schema.EntitySchema], entities: dict[str, TSchema],
get_entity_validation_schema: Callable[[TSchema], cv.Schema], get_entity_validation_schema: Callable[[TSchema], cv.Schema],
) -> Schema: ) -> Schema:
entity_schema = {} entity_schema = {}
for key, entity in entities.items(): for key, entity in entities.items():
entity_schema[cv.Optional(key)] = get_entity_validation_schema(entity) schema_key = (
cv.Optional(key, entity.default_value)
if hasattr(entity, "default_value")
else cv.Optional(key)
)
entity_schema[schema_key] = get_entity_validation_schema(entity)
return cv.Schema(entity_schema) return cv.Schema(entity_schema)

View File

@ -13,9 +13,9 @@ PulseCounterStorageBase *get_storage(bool hw_pcnt) {
return (hw_pcnt ? (PulseCounterStorageBase *) (new HwPulseCounterStorage) return (hw_pcnt ? (PulseCounterStorageBase *) (new HwPulseCounterStorage)
: (PulseCounterStorageBase *) (new BasicPulseCounterStorage)); : (PulseCounterStorageBase *) (new BasicPulseCounterStorage));
} }
#else #else // HAS_PCNT
PulseCounterStorageBase *get_storage(bool) { return new BasicPulseCounterStorage; } PulseCounterStorageBase *get_storage(bool) { return new BasicPulseCounterStorage; }
#endif #endif // HAS_PCNT
void IRAM_ATTR BasicPulseCounterStorage::gpio_intr(BasicPulseCounterStorage *arg) { void IRAM_ATTR BasicPulseCounterStorage::gpio_intr(BasicPulseCounterStorage *arg) {
const uint32_t now = micros(); const uint32_t now = micros();
@ -28,14 +28,17 @@ void IRAM_ATTR BasicPulseCounterStorage::gpio_intr(BasicPulseCounterStorage *arg
switch (mode) { switch (mode) {
case PULSE_COUNTER_DISABLE: case PULSE_COUNTER_DISABLE:
break; break;
case PULSE_COUNTER_INCREMENT: case PULSE_COUNTER_INCREMENT: {
arg->counter++; auto x = arg->counter + 1;
break; arg->counter = x;
case PULSE_COUNTER_DECREMENT: } break;
arg->counter--; case PULSE_COUNTER_DECREMENT: {
break; auto x = arg->counter - 1;
arg->counter = x;
} break;
} }
} }
bool BasicPulseCounterStorage::pulse_counter_setup(InternalGPIOPin *pin) { bool BasicPulseCounterStorage::pulse_counter_setup(InternalGPIOPin *pin) {
this->pin = pin; this->pin = pin;
this->pin->setup(); this->pin->setup();
@ -43,6 +46,7 @@ bool BasicPulseCounterStorage::pulse_counter_setup(InternalGPIOPin *pin) {
this->pin->attach_interrupt(BasicPulseCounterStorage::gpio_intr, this, gpio::INTERRUPT_ANY_EDGE); this->pin->attach_interrupt(BasicPulseCounterStorage::gpio_intr, this, gpio::INTERRUPT_ANY_EDGE);
return true; return true;
} }
pulse_counter_t BasicPulseCounterStorage::read_raw_value() { pulse_counter_t BasicPulseCounterStorage::read_raw_value() {
pulse_counter_t counter = this->counter; pulse_counter_t counter = this->counter;
pulse_counter_t ret = counter - this->last_value; pulse_counter_t ret = counter - this->last_value;
@ -141,6 +145,7 @@ bool HwPulseCounterStorage::pulse_counter_setup(InternalGPIOPin *pin) {
} }
return true; return true;
} }
pulse_counter_t HwPulseCounterStorage::read_raw_value() { pulse_counter_t HwPulseCounterStorage::read_raw_value() {
pulse_counter_t counter; pulse_counter_t counter;
pcnt_get_counter_value(this->pcnt_unit, &counter); pcnt_get_counter_value(this->pcnt_unit, &counter);
@ -148,7 +153,7 @@ pulse_counter_t HwPulseCounterStorage::read_raw_value() {
this->last_value = counter; this->last_value = counter;
return ret; return ret;
} }
#endif #endif // HAS_PCNT
void PulseCounterSensor::setup() { void PulseCounterSensor::setup() {
ESP_LOGCONFIG(TAG, "Setting up pulse counter '%s'...", this->name_.c_str()); ESP_LOGCONFIG(TAG, "Setting up pulse counter '%s'...", this->name_.c_str());

View File

@ -9,7 +9,7 @@
#if defined(USE_ESP32) && !defined(USE_ESP32_VARIANT_ESP32C3) #if defined(USE_ESP32) && !defined(USE_ESP32_VARIANT_ESP32C3)
#include <driver/pcnt.h> #include <driver/pcnt.h>
#define HAS_PCNT #define HAS_PCNT
#endif #endif // defined(USE_ESP32) && !defined(USE_ESP32_VARIANT_ESP32C3)
namespace esphome { namespace esphome {
namespace pulse_counter { namespace pulse_counter {
@ -22,9 +22,9 @@ enum PulseCounterCountMode {
#ifdef HAS_PCNT #ifdef HAS_PCNT
using pulse_counter_t = int16_t; using pulse_counter_t = int16_t;
#else #else // HAS_PCNT
using pulse_counter_t = int32_t; using pulse_counter_t = int32_t;
#endif #endif // HAS_PCNT
struct PulseCounterStorageBase { struct PulseCounterStorageBase {
virtual bool pulse_counter_setup(InternalGPIOPin *pin) = 0; virtual bool pulse_counter_setup(InternalGPIOPin *pin) = 0;
@ -57,7 +57,7 @@ struct HwPulseCounterStorage : public PulseCounterStorageBase {
pcnt_unit_t pcnt_unit; pcnt_unit_t pcnt_unit;
pcnt_channel_t pcnt_channel; pcnt_channel_t pcnt_channel;
}; };
#endif #endif // HAS_PCNT
PulseCounterStorageBase *get_storage(bool hw_pcnt = false); PulseCounterStorageBase *get_storage(bool hw_pcnt = false);

View File

@ -81,16 +81,39 @@ void QMC5883LComponent::dump_config() {
} }
float QMC5883LComponent::get_setup_priority() const { return setup_priority::DATA; } float QMC5883LComponent::get_setup_priority() const { return setup_priority::DATA; }
void QMC5883LComponent::update() { void QMC5883LComponent::update() {
i2c::ErrorCode err;
uint8_t status = false; 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 uint16_t raw[3] = {0};
// to avoid https://github.com/esphome/issues/issues/5731 // Z must always be requested, otherwise the data registers will remain locked against updates.
uint16_t raw_x, raw_y, raw_z; // Skipping the Y axis if X and Z are needed actually requires an additional byte of comms.
if (!this->read_byte_16_(QMC5883L_REGISTER_DATA_X_LSB, &raw_x) || // Starting partway through the axes does save you traffic.
!this->read_byte_16_(QMC5883L_REGISTER_DATA_Y_LSB, &raw_y) || uint8_t start, dest;
!this->read_byte_16_(QMC5883L_REGISTER_DATA_Z_LSB, &raw_z)) { if (this->heading_sensor_ != nullptr || this->x_sensor_ != nullptr) {
this->status_set_warning(); 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; return;
} }
@ -107,17 +130,18 @@ void QMC5883LComponent::update() {
} }
// in µT // in µT
const float x = int16_t(raw_x) * mg_per_bit * 0.1f; const float x = int16_t(raw[0]) * mg_per_bit * 0.1f;
const float y = int16_t(raw_y) * mg_per_bit * 0.1f; const float y = int16_t(raw[1]) * mg_per_bit * 0.1f;
const float z = int16_t(raw_z) * 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 heading = atan2f(0.0f - x, y) * 180.0f / M_PI;
float temp = NAN; float temp = NAN;
if (this->temperature_sensor_ != nullptr) { if (this->temperature_sensor_ != nullptr) {
uint16_t raw_temp; uint16_t raw_temp;
if (!this->read_byte_16_(QMC5883L_REGISTER_TEMPERATURE_LSB, &raw_temp)) { err = this->read_bytes_16_le_(QMC5883L_REGISTER_TEMPERATURE_LSB, &raw_temp);
this->status_set_warning(); if (err != i2c::ERROR_OK) {
this->status_set_warning(str_sprintf("temp read failed (%d)", err).c_str());
return; return;
} }
temp = int16_t(raw_temp) * 0.01f; temp = int16_t(raw_temp) * 0.01f;
@ -138,11 +162,13 @@ void QMC5883LComponent::update() {
this->temperature_sensor_->publish_state(temp); this->temperature_sensor_->publish_state(temp);
} }
bool QMC5883LComponent::read_byte_16_(uint8_t a_register, uint16_t *data) { i2c::ErrorCode QMC5883LComponent::read_bytes_16_le_(uint8_t a_register, uint16_t *data, uint8_t len) {
if (!this->read_byte_16(a_register, data)) i2c::ErrorCode err = this->read_register(a_register, reinterpret_cast<uint8_t *>(data), len * 2);
return false; if (err != i2c::ERROR_OK)
*data = (*data & 0x00FF) << 8 | (*data & 0xFF00) >> 8; // Flip Byte order, LSB first; return err;
return true; for (size_t i = 0; i < len; i++)
data[i] = convert_little_endian(data[i]);
return err;
} }
} // namespace qmc5883l } // namespace qmc5883l

View File

@ -55,7 +55,7 @@ class QMC5883LComponent : public PollingComponent, public i2c::I2CDevice {
NONE = 0, NONE = 0,
COMMUNICATION_FAILED, COMMUNICATION_FAILED,
} error_code_; } 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_; HighFrequencyLoopRequester high_freq_;
}; };

View File

@ -1 +1,4 @@
CODEOWNERS = ["@clydebarrow"] CODEOWNERS = ["@clydebarrow"]
CONF_DRAW_FROM_ORIGIN = "draw_from_origin"
CONF_DRAW_ROUNDING = "draw_rounding"

View File

@ -24,6 +24,7 @@ from esphome.const import (
) )
from esphome.core import TimePeriod from esphome.core import TimePeriod
from . import CONF_DRAW_FROM_ORIGIN, CONF_DRAW_ROUNDING
from .models import DriverChip from .models import DriverChip
DEPENDENCIES = ["spi"] DEPENDENCIES = ["spi"]
@ -41,7 +42,6 @@ COLOR_ORDERS = {
} }
DATA_PIN_SCHEMA = pins.internal_gpio_output_pin_schema DATA_PIN_SCHEMA = pins.internal_gpio_output_pin_schema
CONF_DRAW_FROM_ORIGIN = "draw_from_origin"
DELAY_FLAG = 0xFF DELAY_FLAG = 0xFF
@ -78,14 +78,17 @@ def _validate(config):
return config return config
CONFIG_SCHEMA = cv.All( def power_of_two(value):
display.FULL_DISPLAY_SCHEMA.extend( value = cv.int_range(1, 128)(value)
if value & (value - 1) != 0:
raise cv.Invalid("value must be a power of two")
return value
BASE_SCHEMA = display.FULL_DISPLAY_SCHEMA.extend(
cv.Schema( cv.Schema(
{ {
cv.GenerateID(): cv.declare_id(QSPI_DBI), cv.GenerateID(): cv.declare_id(QSPI_DBI),
cv.Required(CONF_MODEL): cv.one_of(
*DriverChip.chips.keys(), upper=True
),
cv.Optional(CONF_INIT_SEQUENCE): cv.ensure_list(map_sequence), cv.Optional(CONF_INIT_SEQUENCE): cv.ensure_list(map_sequence),
cv.Required(CONF_DIMENSIONS): cv.Any( cv.Required(CONF_DIMENSIONS): cv.Any(
cv.dimensions, cv.dimensions,
@ -93,32 +96,17 @@ CONFIG_SCHEMA = cv.All(
{ {
cv.Required(CONF_WIDTH): validate_dimension, cv.Required(CONF_WIDTH): validate_dimension,
cv.Required(CONF_HEIGHT): validate_dimension, cv.Required(CONF_HEIGHT): validate_dimension,
cv.Optional( cv.Optional(CONF_OFFSET_HEIGHT, default=0): validate_dimension,
CONF_OFFSET_HEIGHT, default=0 cv.Optional(CONF_OFFSET_WIDTH, default=0): validate_dimension,
): validate_dimension,
cv.Optional(
CONF_OFFSET_WIDTH, default=0
): validate_dimension,
} }
), ),
), ),
cv.Optional(CONF_TRANSFORM): cv.Schema( cv.Optional(CONF_DRAW_FROM_ORIGIN, default=False): cv.boolean,
{
cv.Optional(CONF_MIRROR_X, default=False): cv.boolean,
cv.Optional(CONF_MIRROR_Y, default=False): cv.boolean,
cv.Optional(CONF_SWAP_XY, default=False): cv.boolean,
}
),
cv.Optional(CONF_COLOR_ORDER, default="RGB"): cv.enum(
COLOR_ORDERS, upper=True
),
cv.Optional(CONF_INVERT_COLORS, default=False): cv.boolean,
cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema, cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema,
cv.Optional(CONF_ENABLE_PIN): pins.gpio_output_pin_schema, cv.Optional(CONF_ENABLE_PIN): pins.gpio_output_pin_schema,
cv.Optional(CONF_BRIGHTNESS, default=0xD0): cv.int_range( cv.Optional(CONF_BRIGHTNESS, default=0xD0): cv.int_range(
0, 0xFF, min_included=True, max_included=True 0, 0xFF, min_included=True, max_included=True
), ),
cv.Optional(CONF_DRAW_FROM_ORIGIN, default=False): cv.boolean,
} }
).extend( ).extend(
spi.spi_device_schema( spi.spi_device_schema(
@ -128,6 +116,43 @@ CONFIG_SCHEMA = cv.All(
quad=True, quad=True,
) )
) )
)
def model_property(name, defaults, fallback):
return cv.Optional(name, default=defaults.get(name, fallback))
def model_schema(defaults):
transform = cv.Schema(
{
cv.Optional(CONF_MIRROR_X, default=False): cv.boolean,
cv.Optional(CONF_MIRROR_Y, default=False): cv.boolean,
}
)
if defaults.get(CONF_SWAP_XY, True):
transform = transform.extend(
{
cv.Optional(CONF_SWAP_XY, default=False): cv.boolean,
}
)
return BASE_SCHEMA.extend(
{
model_property(CONF_INVERT_COLORS, defaults, False): cv.boolean,
model_property(CONF_COLOR_ORDER, defaults, "RGB"): cv.enum(
COLOR_ORDERS, upper=True
),
model_property(CONF_DRAW_ROUNDING, defaults, 2): power_of_two,
cv.Optional(CONF_TRANSFORM): transform,
}
)
CONFIG_SCHEMA = cv.All(
cv.typed_schema(
{k.upper(): model_schema(v.defaults) for k, v in DriverChip.chips.items()},
upper=True,
key=CONF_MODEL,
), ),
cv.only_with_esp_idf, cv.only_with_esp_idf,
) )
@ -152,6 +177,7 @@ async def to_code(config):
cg.add(var.set_brightness(config[CONF_BRIGHTNESS])) cg.add(var.set_brightness(config[CONF_BRIGHTNESS]))
cg.add(var.set_model(config[CONF_MODEL])) cg.add(var.set_model(config[CONF_MODEL]))
cg.add(var.set_draw_from_origin(config[CONF_DRAW_FROM_ORIGIN])) cg.add(var.set_draw_from_origin(config[CONF_DRAW_FROM_ORIGIN]))
cg.add(var.set_draw_rounding(config[CONF_DRAW_ROUNDING]))
if enable_pin := config.get(CONF_ENABLE_PIN): if enable_pin := config.get(CONF_ENABLE_PIN):
enable = await cg.gpio_pin_expression(enable_pin) enable = await cg.gpio_pin_expression(enable_pin)
cg.add(var.set_enable_pin(enable)) cg.add(var.set_enable_pin(enable))
@ -163,7 +189,8 @@ async def to_code(config):
if transform := config.get(CONF_TRANSFORM): if transform := config.get(CONF_TRANSFORM):
cg.add(var.set_mirror_x(transform[CONF_MIRROR_X])) cg.add(var.set_mirror_x(transform[CONF_MIRROR_X]))
cg.add(var.set_mirror_y(transform[CONF_MIRROR_Y])) cg.add(var.set_mirror_y(transform[CONF_MIRROR_Y]))
cg.add(var.set_swap_xy(transform[CONF_SWAP_XY])) # swap_xy is not implemented for some chips
cg.add(var.set_swap_xy(transform.get(CONF_SWAP_XY, False)))
if CONF_DIMENSIONS in config: if CONF_DIMENSIONS in config:
dimensions = config[CONF_DIMENSIONS] dimensions = config[CONF_DIMENSIONS]

View File

@ -1,5 +1,10 @@
# Commands # Commands
from esphome.const import CONF_INVERT_COLORS, CONF_SWAP_XY
from . import CONF_DRAW_ROUNDING
SW_RESET_CMD = 0x01 SW_RESET_CMD = 0x01
SLEEP_IN = 0x10
SLEEP_OUT = 0x11 SLEEP_OUT = 0x11
NORON = 0x13 NORON = 0x13
INVERT_OFF = 0x20 INVERT_OFF = 0x20
@ -24,11 +29,12 @@ PAGESEL = 0xFE
class DriverChip: class DriverChip:
chips = {} chips = {}
def __init__(self, name: str): def __init__(self, name: str, defaults=None):
name = name.upper() name = name.upper()
self.name = name self.name = name
self.chips[name] = self self.chips[name] = self
self.initsequence = [] self.initsequence = []
self.defaults = defaults or {}
def cmd(self, c, *args): def cmd(self, c, *args):
""" """
@ -59,9 +65,246 @@ chip.cmd(TEON, 0x00)
chip.cmd(PIXFMT, 0x55) chip.cmd(PIXFMT, 0x55)
chip.cmd(NORON) chip.cmd(NORON)
chip = DriverChip("AXS15231") chip = DriverChip("AXS15231", {CONF_DRAW_ROUNDING: 8, CONF_SWAP_XY: False})
chip.cmd(0xBB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5A, 0xA5) chip.cmd(0xBB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5A, 0xA5)
chip.cmd(0xC1, 0x33) chip.cmd(0xC1, 0x33)
chip.cmd(0xBB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00) chip.cmd(0xBB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00)
chip = DriverChip(
"JC4832W535",
{
CONF_DRAW_ROUNDING: 8,
CONF_SWAP_XY: False,
},
)
chip.cmd(DISPLAY_OFF)
chip.delay(20)
chip.cmd(SLEEP_IN)
chip.delay(80)
chip.cmd(SLEEP_OUT)
chip.cmd(INVERT_OFF)
# A magic sequence to enable the windowed drawing mode
chip.cmd(0xBB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5A, 0xA5)
chip.cmd(0xC1, 0x33)
chip.cmd(0xBB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00)
chip = DriverChip("JC3636W518", {CONF_INVERT_COLORS: True})
chip.cmd(0xF0, 0x08)
chip.cmd(0xF2, 0x08)
chip.cmd(0x9B, 0x51)
chip.cmd(0x86, 0x53)
chip.cmd(0xF2, 0x80)
chip.cmd(0xF0, 0x00)
chip.cmd(0xF0, 0x01)
chip.cmd(0xF1, 0x01)
chip.cmd(0xB0, 0x54)
chip.cmd(0xB1, 0x3F)
chip.cmd(0xB2, 0x2A)
chip.cmd(0xB4, 0x46)
chip.cmd(0xB5, 0x34)
chip.cmd(0xB6, 0xD5)
chip.cmd(0xB7, 0x30)
chip.cmd(0xBA, 0x00)
chip.cmd(0xBB, 0x08)
chip.cmd(0xBC, 0x08)
chip.cmd(0xBD, 0x00)
chip.cmd(0xC0, 0x80)
chip.cmd(0xC1, 0x10)
chip.cmd(0xC2, 0x37)
chip.cmd(0xC3, 0x80)
chip.cmd(0xC4, 0x10)
chip.cmd(0xC5, 0x37)
chip.cmd(0xC6, 0xA9)
chip.cmd(0xC7, 0x41)
chip.cmd(0xC8, 0x51)
chip.cmd(0xC9, 0xA9)
chip.cmd(0xCA, 0x41)
chip.cmd(0xCB, 0x51)
chip.cmd(0xD0, 0x91)
chip.cmd(0xD1, 0x68)
chip.cmd(0xD2, 0x69)
chip.cmd(0xF5, 0x00, 0xA5)
chip.cmd(0xDD, 0x3F)
chip.cmd(0xDE, 0x3F)
chip.cmd(0xF1, 0x10)
chip.cmd(0xF0, 0x00)
chip.cmd(0xF0, 0x02)
chip.cmd(
0xE0,
0x70,
0x09,
0x12,
0x0C,
0x0B,
0x27,
0x38,
0x54,
0x4E,
0x19,
0x15,
0x15,
0x2C,
0x2F,
)
chip.cmd(
0xE1,
0x70,
0x08,
0x11,
0x0C,
0x0B,
0x27,
0x38,
0x43,
0x4C,
0x18,
0x14,
0x14,
0x2B,
0x2D,
)
chip.cmd(0xF0, 0x10)
chip.cmd(0xF3, 0x10)
chip.cmd(0xE0, 0x08)
chip.cmd(0xE1, 0x00)
chip.cmd(0xE2, 0x00)
chip.cmd(0xE3, 0x00)
chip.cmd(0xE4, 0xE0)
chip.cmd(0xE5, 0x06)
chip.cmd(0xE6, 0x21)
chip.cmd(0xE7, 0x00)
chip.cmd(0xE8, 0x05)
chip.cmd(0xE9, 0x82)
chip.cmd(0xEA, 0xDF)
chip.cmd(0xEB, 0x89)
chip.cmd(0xEC, 0x20)
chip.cmd(0xED, 0x14)
chip.cmd(0xEE, 0xFF)
chip.cmd(0xEF, 0x00)
chip.cmd(0xF8, 0xFF)
chip.cmd(0xF9, 0x00)
chip.cmd(0xFA, 0x00)
chip.cmd(0xFB, 0x30)
chip.cmd(0xFC, 0x00)
chip.cmd(0xFD, 0x00)
chip.cmd(0xFE, 0x00)
chip.cmd(0xFF, 0x00)
chip.cmd(0x60, 0x42)
chip.cmd(0x61, 0xE0)
chip.cmd(0x62, 0x40)
chip.cmd(0x63, 0x40)
chip.cmd(0x64, 0x02)
chip.cmd(0x65, 0x00)
chip.cmd(0x66, 0x40)
chip.cmd(0x67, 0x03)
chip.cmd(0x68, 0x00)
chip.cmd(0x69, 0x00)
chip.cmd(0x6A, 0x00)
chip.cmd(0x6B, 0x00)
chip.cmd(0x70, 0x42)
chip.cmd(0x71, 0xE0)
chip.cmd(0x72, 0x40)
chip.cmd(0x73, 0x40)
chip.cmd(0x74, 0x02)
chip.cmd(0x75, 0x00)
chip.cmd(0x76, 0x40)
chip.cmd(0x77, 0x03)
chip.cmd(0x78, 0x00)
chip.cmd(0x79, 0x00)
chip.cmd(0x7A, 0x00)
chip.cmd(0x7B, 0x00)
chip.cmd(0x80, 0x48)
chip.cmd(0x81, 0x00)
chip.cmd(0x82, 0x05)
chip.cmd(0x83, 0x02)
chip.cmd(0x84, 0xDD)
chip.cmd(0x85, 0x00)
chip.cmd(0x86, 0x00)
chip.cmd(0x87, 0x00)
chip.cmd(0x88, 0x48)
chip.cmd(0x89, 0x00)
chip.cmd(0x8A, 0x07)
chip.cmd(0x8B, 0x02)
chip.cmd(0x8C, 0xDF)
chip.cmd(0x8D, 0x00)
chip.cmd(0x8E, 0x00)
chip.cmd(0x8F, 0x00)
chip.cmd(0x90, 0x48)
chip.cmd(0x91, 0x00)
chip.cmd(0x92, 0x09)
chip.cmd(0x93, 0x02)
chip.cmd(0x94, 0xE1)
chip.cmd(0x95, 0x00)
chip.cmd(0x96, 0x00)
chip.cmd(0x97, 0x00)
chip.cmd(0x98, 0x48)
chip.cmd(0x99, 0x00)
chip.cmd(0x9A, 0x0B)
chip.cmd(0x9B, 0x02)
chip.cmd(0x9C, 0xE3)
chip.cmd(0x9D, 0x00)
chip.cmd(0x9E, 0x00)
chip.cmd(0x9F, 0x00)
chip.cmd(0xA0, 0x48)
chip.cmd(0xA1, 0x00)
chip.cmd(0xA2, 0x04)
chip.cmd(0xA3, 0x02)
chip.cmd(0xA4, 0xDC)
chip.cmd(0xA5, 0x00)
chip.cmd(0xA6, 0x00)
chip.cmd(0xA7, 0x00)
chip.cmd(0xA8, 0x48)
chip.cmd(0xA9, 0x00)
chip.cmd(0xAA, 0x06)
chip.cmd(0xAB, 0x02)
chip.cmd(0xAC, 0xDE)
chip.cmd(0xAD, 0x00)
chip.cmd(0xAE, 0x00)
chip.cmd(0xAF, 0x00)
chip.cmd(0xB0, 0x48)
chip.cmd(0xB1, 0x00)
chip.cmd(0xB2, 0x08)
chip.cmd(0xB3, 0x02)
chip.cmd(0xB4, 0xE0)
chip.cmd(0xB5, 0x00)
chip.cmd(0xB6, 0x00)
chip.cmd(0xB7, 0x00)
chip.cmd(0xB8, 0x48)
chip.cmd(0xB9, 0x00)
chip.cmd(0xBA, 0x0A)
chip.cmd(0xBB, 0x02)
chip.cmd(0xBC, 0xE2)
chip.cmd(0xBD, 0x00)
chip.cmd(0xBE, 0x00)
chip.cmd(0xBF, 0x00)
chip.cmd(0xC0, 0x12)
chip.cmd(0xC1, 0xAA)
chip.cmd(0xC2, 0x65)
chip.cmd(0xC3, 0x74)
chip.cmd(0xC4, 0x47)
chip.cmd(0xC5, 0x56)
chip.cmd(0xC6, 0x00)
chip.cmd(0xC7, 0x88)
chip.cmd(0xC8, 0x99)
chip.cmd(0xC9, 0x33)
chip.cmd(0xD0, 0x21)
chip.cmd(0xD1, 0xAA)
chip.cmd(0xD2, 0x65)
chip.cmd(0xD3, 0x74)
chip.cmd(0xD4, 0x47)
chip.cmd(0xD5, 0x56)
chip.cmd(0xD6, 0x00)
chip.cmd(0xD7, 0x88)
chip.cmd(0xD8, 0x99)
chip.cmd(0xD9, 0x33)
chip.cmd(0xF3, 0x01)
chip.cmd(0xF0, 0x00)
chip.cmd(0xF0, 0x01)
chip.cmd(0xF1, 0x01)
chip.cmd(0xA0, 0x0B)
chip.cmd(0xA3, 0x2A)
chip.cmd(0xA5, 0xC3)
chip.cmd(PIXFMT, 0x55)
DriverChip("Custom") DriverChip("Custom")

View File

@ -33,19 +33,12 @@ void QspiDbi::update() {
this->do_update_(); this->do_update_();
if (this->buffer_ == nullptr || this->x_low_ > this->x_high_ || this->y_low_ > this->y_high_) if (this->buffer_ == nullptr || this->x_low_ > this->x_high_ || this->y_low_ > this->y_high_)
return; return;
// Start addresses and widths/heights must be divisible by 2 (CASET/RASET restriction in datasheet) // Some chips require that the drawing window be aligned on certain boundaries
if (this->x_low_ % 2 == 1) { auto dr = this->draw_rounding_;
this->x_low_--; this->x_low_ = this->x_low_ / dr * dr;
} this->y_low_ = this->y_low_ / dr * dr;
if (this->x_high_ % 2 == 0) { this->x_high_ = (this->x_high_ + dr) / dr * dr - 1;
this->x_high_++; this->y_high_ = (this->y_high_ + dr) / dr * dr - 1;
}
if (this->y_low_ % 2 == 1) {
this->y_low_--;
}
if (this->y_high_ % 2 == 0) {
this->y_high_++;
}
if (this->draw_from_origin_) { if (this->draw_from_origin_) {
this->x_low_ = 0; this->x_low_ = 0;
this->y_low_ = 0; this->y_low_ = 0;
@ -175,10 +168,9 @@ void QspiDbi::write_to_display_(int x_start, int y_start, int w, int h, const ui
this->write_cmd_addr_data(8, 0x32, 24, 0x2C00, ptr, w * h * 2, 4); this->write_cmd_addr_data(8, 0x32, 24, 0x2C00, ptr, w * h * 2, 4);
} else { } else {
auto stride = x_offset + w + x_pad; auto stride = x_offset + w + x_pad;
uint16_t cmd = 0x2C00; this->write_cmd_addr_data(8, 0x32, 24, 0x2C00, nullptr, 0, 4);
for (int y = 0; y != h; y++) { for (int y = 0; y != h; y++) {
this->write_cmd_addr_data(8, 0x32, 24, cmd, ptr + ((y + y_offset) * stride + x_offset) * 2, w * 2, 4); this->write_cmd_addr_data(0, 0, 0, 0, ptr + ((y + y_offset) * stride + x_offset) * 2, w * 2, 4);
cmd = 0x3C00;
} }
} }
this->disable(); this->disable();
@ -220,6 +212,7 @@ void QspiDbi::dump_config() {
ESP_LOGCONFIG("", "Model: %s", this->model_); ESP_LOGCONFIG("", "Model: %s", this->model_);
ESP_LOGCONFIG(TAG, " Height: %u", this->height_); ESP_LOGCONFIG(TAG, " Height: %u", this->height_);
ESP_LOGCONFIG(TAG, " Width: %u", this->width_); ESP_LOGCONFIG(TAG, " Width: %u", this->width_);
ESP_LOGCONFIG(TAG, " Draw rounding: %u", this->draw_rounding_);
LOG_PIN(" CS Pin: ", this->cs_); LOG_PIN(" CS Pin: ", this->cs_);
LOG_PIN(" Reset Pin: ", this->reset_pin_); LOG_PIN(" Reset Pin: ", this->reset_pin_);
ESP_LOGCONFIG(TAG, " SPI Data rate: %dMHz", (unsigned) (this->data_rate_ / 1000000)); ESP_LOGCONFIG(TAG, " SPI Data rate: %dMHz", (unsigned) (this->data_rate_ / 1000000));

View File

@ -4,12 +4,10 @@
#pragma once #pragma once
#ifdef USE_ESP_IDF #ifdef USE_ESP_IDF
#include "esphome/core/component.h"
#include "esphome/components/spi/spi.h" #include "esphome/components/spi/spi.h"
#include "esphome/components/display/display.h" #include "esphome/components/display/display.h"
#include "esphome/components/display/display_buffer.h" #include "esphome/components/display/display_buffer.h"
#include "esphome/components/display/display_color_utils.h" #include "esphome/components/display/display_color_utils.h"
#include "esp_lcd_panel_ops.h"
#include "esp_lcd_panel_rgb.h" #include "esp_lcd_panel_rgb.h"
@ -105,6 +103,7 @@ class QspiDbi : public display::DisplayBuffer,
int get_height_internal() override { return this->height_; } int get_height_internal() override { return this->height_; }
bool can_proceed() override { return this->setup_complete_; } bool can_proceed() override { return this->setup_complete_; }
void add_init_sequence(const std::vector<uint8_t> &sequence) { this->init_sequences_.push_back(sequence); } void add_init_sequence(const std::vector<uint8_t> &sequence) { this->init_sequences_.push_back(sequence); }
void set_draw_rounding(unsigned rounding) { this->draw_rounding_ = rounding; }
protected: protected:
void check_buffer_() { void check_buffer_() {
@ -161,6 +160,7 @@ class QspiDbi : public display::DisplayBuffer,
bool mirror_x_{}; bool mirror_x_{};
bool mirror_y_{}; bool mirror_y_{};
bool draw_from_origin_{false}; bool draw_from_origin_{false};
unsigned draw_rounding_{2};
uint8_t brightness_{0xD0}; uint8_t brightness_{0xD0};
const char *model_{"Unknown"}; const char *model_{"Unknown"};
std::vector<std::vector<uint8_t>> init_sequences_{}; std::vector<std::vector<uint8_t>> init_sequences_{};

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