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:
commit
62b8a77c49
30
.clang-tidy
30
.clang-tidy
@ -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
|
||||||
|
2
.github/actions/restore-python/action.yml
vendored
2
.github/actions/restore-python/action.yml
vendored
@ -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
|
||||||
|
2
.github/workflows/ci-docker.yml
vendored
2
.github/workflows/ci-docker.yml
vendored
@ -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
|
||||||
|
|
||||||
|
38
.github/workflows/ci.yml
vendored
38
.github/workflows/ci.yml
vendored
@ -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
|
||||||
|
8
.github/workflows/release.yml
vendored
8
.github/workflows/release.yml
vendored
@ -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'
|
||||||
|
@ -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
|
||||||
|
@ -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/*
|
||||||
|
@ -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 (
|
||||||
|
@ -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,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
24
esphome/components/adc/adc_sensor_common.cpp
Normal file
24
esphome/components/adc/adc_sensor_common.cpp
Normal 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
|
@ -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
|
58
esphome/components/adc/adc_sensor_esp8266.cpp
Normal file
58
esphome/components/adc/adc_sensor_esp8266.cpp
Normal 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
|
48
esphome/components/adc/adc_sensor_libretiny.cpp
Normal file
48
esphome/components/adc/adc_sensor_libretiny.cpp
Normal 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
|
93
esphome/components/adc/adc_sensor_rp2040.cpp
Normal file
93
esphome/components/adc/adc_sensor_rp2040.cpp
Normal 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
|
@ -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(
|
||||||
{
|
{
|
||||||
|
@ -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) {
|
||||||
|
@ -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))
|
||||||
|
@ -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;
|
||||||
|
@ -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
|
||||||
|
@ -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) {
|
||||||
|
@ -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.
|
||||||
|
@ -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(
|
||||||
|
@ -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,
|
||||||
|
@ -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:
|
||||||
|
@ -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) {
|
||||||
|
@ -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)
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
};
|
};
|
||||||
|
@ -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 ¶m) {
|
void ESP32BLETracker::gap_scan_set_param_complete_(const esp_ble_gap_cb_param_t::ble_scan_param_cmpl_evt_param ¶m) {
|
||||||
|
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 ¶m) {
|
void ESP32BLETracker::gap_scan_start_complete_(const esp_ble_gap_cb_param_t::ble_scan_start_cmpl_evt_param ¶m) {
|
||||||
|
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 ¶m) {
|
void ESP32BLETracker::gap_scan_stop_complete_(const esp_ble_gap_cb_param_t::ble_scan_stop_cmpl_evt_param ¶m) {
|
||||||
|
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 ¶m) {
|
void ESP32BLETracker::gap_scan_result_(const esp_ble_gap_cb_param_t::ble_scan_result_evt_param ¶m) {
|
||||||
|
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) {
|
||||||
|
@ -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 ¶m);
|
void gap_scan_stop_complete_(const esp_ble_gap_cb_param_t::ble_scan_stop_cmpl_evt_param ¶m);
|
||||||
|
|
||||||
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
|
||||||
|
@ -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):
|
||||||
|
@ -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:
|
||||||
|
@ -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_{};
|
||||||
|
@ -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]}")
|
||||||
|
@ -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
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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_);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
44
esphome/components/hbridge/switch/__init__.py
Normal file
44
esphome/components/hbridge/switch/__init__.py
Normal 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))
|
95
esphome/components/hbridge/switch/hbridge_switch.cpp
Normal file
95
esphome/components/hbridge/switch/hbridge_switch.cpp
Normal 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
|
50
esphome/components/hbridge/switch/hbridge_switch.h
Normal file
50
esphome/components/hbridge/switch/hbridge_switch.h
Normal 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
|
@ -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);
|
||||||
|
@ -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,
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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),
|
||||||
|
@ -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]):
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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")
|
||||||
|
@ -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)
|
||||||
|
@ -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) {
|
||||||
|
@ -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()
|
||||||
|
@ -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):
|
||||||
|
@ -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:
|
||||||
|
@ -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...));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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");
|
||||||
|
@ -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); }
|
||||||
|
@ -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_))
|
||||||
|
|
||||||
|
@ -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 {
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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); }
|
||||||
|
@ -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};
|
||||||
};
|
};
|
||||||
|
@ -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);
|
||||||
|
@ -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
|
||||||
|
@ -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);
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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};
|
||||||
};
|
};
|
||||||
|
@ -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(
|
||||||
{
|
{
|
||||||
|
@ -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_();
|
||||||
|
@ -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)
|
||||||
|
@ -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));
|
||||||
}
|
}
|
||||||
|
@ -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]))
|
||||||
|
|
||||||
|
@ -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_);
|
||||||
}
|
}
|
||||||
|
@ -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_;
|
||||||
|
@ -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
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
)
|
||||||
|
25
esphome/components/opentherm/automation.h
Normal file
25
esphome/components/opentherm/automation.h
Normal 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
|
@ -9,3 +9,4 @@ SWITCH = "switch"
|
|||||||
NUMBER = "number"
|
NUMBER = "number"
|
||||||
OUTPUT = "output"
|
OUTPUT = "output"
|
||||||
INPUT_SENSOR = "input_sensor"
|
INPUT_SENSOR = "input_sensor"
|
||||||
|
SETTING = "setting"
|
||||||
|
@ -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))
|
||||||
|
|
||||||
|
@ -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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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; }
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
@ -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; \
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
|
||||||
|
@ -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());
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1 +1,4 @@
|
|||||||
CODEOWNERS = ["@clydebarrow"]
|
CODEOWNERS = ["@clydebarrow"]
|
||||||
|
|
||||||
|
CONF_DRAW_FROM_ORIGIN = "draw_from_origin"
|
||||||
|
CONF_DRAW_ROUNDING = "draw_rounding"
|
||||||
|
@ -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]
|
||||||
|
@ -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")
|
||||||
|
@ -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));
|
||||||
|
@ -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
Loading…
x
Reference in New Issue
Block a user