mirror of
https://github.com/esphome/esphome.git
synced 2024-11-28 01:34:18 +01:00
Merge branch 'esphome:dev' into dev
This commit is contained in:
commit
f9672f0ea2
1983 changed files with 6435 additions and 14250 deletions
4
.github/actions/build-image/action.yaml
vendored
4
.github/actions/build-image/action.yaml
vendored
|
@ -46,7 +46,7 @@ runs:
|
||||||
|
|
||||||
- name: Build and push to ghcr by digest
|
- name: Build and push to ghcr by digest
|
||||||
id: build-ghcr
|
id: build-ghcr
|
||||||
uses: docker/build-push-action@v5.3.0
|
uses: docker/build-push-action@v6.2.0
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
file: ./docker/Dockerfile
|
file: ./docker/Dockerfile
|
||||||
|
@ -69,7 +69,7 @@ runs:
|
||||||
|
|
||||||
- name: Build and push to dockerhub by digest
|
- name: Build and push to dockerhub by digest
|
||||||
id: build-dockerhub
|
id: build-dockerhub
|
||||||
uses: docker/build-push-action@v5.3.0
|
uses: docker/build-push-action@v6.2.0
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
file: ./docker/Dockerfile
|
file: ./docker/Dockerfile
|
||||||
|
|
2
.github/workflows/ci-api-proto.yml
vendored
2
.github/workflows/ci-api-proto.yml
vendored
|
@ -21,7 +21,7 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4.1.6
|
uses: actions/checkout@v4.1.7
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v5.1.0
|
uses: actions/setup-python@v5.1.0
|
||||||
with:
|
with:
|
||||||
|
|
2
.github/workflows/ci-docker.yml
vendored
2
.github/workflows/ci-docker.yml
vendored
|
@ -40,7 +40,7 @@ jobs:
|
||||||
arch: [amd64, armv7, aarch64]
|
arch: [amd64, armv7, aarch64]
|
||||||
build_type: ["ha-addon", "docker", "lint"]
|
build_type: ["ha-addon", "docker", "lint"]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4.1.6
|
- uses: actions/checkout@v4.1.7
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v5.1.0
|
uses: actions/setup-python@v5.1.0
|
||||||
with:
|
with:
|
||||||
|
|
108
.github/workflows/ci.yml
vendored
108
.github/workflows/ci.yml
vendored
|
@ -34,7 +34,7 @@ jobs:
|
||||||
cache-key: ${{ steps.cache-key.outputs.key }}
|
cache-key: ${{ steps.cache-key.outputs.key }}
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v4.1.6
|
uses: actions/checkout@v4.1.7
|
||||||
- name: Generate cache-key
|
- name: Generate cache-key
|
||||||
id: cache-key
|
id: cache-key
|
||||||
run: echo key="${{ hashFiles('requirements.txt', 'requirements_optional.txt', 'requirements_test.txt') }}" >> $GITHUB_OUTPUT
|
run: echo key="${{ hashFiles('requirements.txt', 'requirements_optional.txt', 'requirements_test.txt') }}" >> $GITHUB_OUTPUT
|
||||||
|
@ -66,7 +66,7 @@ jobs:
|
||||||
- common
|
- common
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v4.1.6
|
uses: actions/checkout@v4.1.7
|
||||||
- name: Restore Python
|
- name: Restore Python
|
||||||
uses: ./.github/actions/restore-python
|
uses: ./.github/actions/restore-python
|
||||||
with:
|
with:
|
||||||
|
@ -87,7 +87,7 @@ jobs:
|
||||||
- common
|
- common
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v4.1.6
|
uses: actions/checkout@v4.1.7
|
||||||
- name: Restore Python
|
- name: Restore Python
|
||||||
uses: ./.github/actions/restore-python
|
uses: ./.github/actions/restore-python
|
||||||
with:
|
with:
|
||||||
|
@ -108,7 +108,7 @@ jobs:
|
||||||
- common
|
- common
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v4.1.6
|
uses: actions/checkout@v4.1.7
|
||||||
- name: Restore Python
|
- name: Restore Python
|
||||||
uses: ./.github/actions/restore-python
|
uses: ./.github/actions/restore-python
|
||||||
with:
|
with:
|
||||||
|
@ -129,7 +129,7 @@ jobs:
|
||||||
- common
|
- common
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v4.1.6
|
uses: actions/checkout@v4.1.7
|
||||||
- name: Restore Python
|
- name: Restore Python
|
||||||
uses: ./.github/actions/restore-python
|
uses: ./.github/actions/restore-python
|
||||||
with:
|
with:
|
||||||
|
@ -150,7 +150,7 @@ jobs:
|
||||||
- common
|
- common
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v4.1.6
|
uses: actions/checkout@v4.1.7
|
||||||
- name: Restore Python
|
- name: Restore Python
|
||||||
uses: ./.github/actions/restore-python
|
uses: ./.github/actions/restore-python
|
||||||
with:
|
with:
|
||||||
|
@ -199,7 +199,7 @@ jobs:
|
||||||
- common
|
- common
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v4.1.6
|
uses: actions/checkout@v4.1.7
|
||||||
- name: Restore Python
|
- name: Restore Python
|
||||||
uses: ./.github/actions/restore-python
|
uses: ./.github/actions/restore-python
|
||||||
with:
|
with:
|
||||||
|
@ -229,7 +229,7 @@ jobs:
|
||||||
- common
|
- common
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v4.1.6
|
uses: actions/checkout@v4.1.7
|
||||||
- name: Restore Python
|
- name: Restore Python
|
||||||
uses: ./.github/actions/restore-python
|
uses: ./.github/actions/restore-python
|
||||||
with:
|
with:
|
||||||
|
@ -248,72 +248,6 @@ jobs:
|
||||||
run: script/ci-suggest-changes
|
run: script/ci-suggest-changes
|
||||||
if: always()
|
if: always()
|
||||||
|
|
||||||
compile-tests-list:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
outputs:
|
|
||||||
matrix: ${{ steps.set-matrix.outputs.matrix }}
|
|
||||||
steps:
|
|
||||||
- name: Check out code from GitHub
|
|
||||||
uses: actions/checkout@v4.1.6
|
|
||||||
- name: Find all YAML test files
|
|
||||||
id: set-matrix
|
|
||||||
run: echo "matrix=$(ls tests/test*.yaml | jq -R -s -c 'split("\n")[:-1]')" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
validate-tests:
|
|
||||||
name: Validate YAML test ${{ matrix.file }}
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
needs:
|
|
||||||
- common
|
|
||||||
- compile-tests-list
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
file: ${{ fromJson(needs.compile-tests-list.outputs.matrix) }}
|
|
||||||
steps:
|
|
||||||
- name: Check out code from GitHub
|
|
||||||
uses: actions/checkout@v4.1.6
|
|
||||||
- name: Restore Python
|
|
||||||
uses: ./.github/actions/restore-python
|
|
||||||
with:
|
|
||||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
|
||||||
cache-key: ${{ needs.common.outputs.cache-key }}
|
|
||||||
- name: Run esphome config ${{ matrix.file }}
|
|
||||||
run: |
|
|
||||||
. venv/bin/activate
|
|
||||||
esphome config ${{ matrix.file }}
|
|
||||||
|
|
||||||
compile-tests:
|
|
||||||
name: Run YAML test ${{ matrix.file }}
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
needs:
|
|
||||||
- common
|
|
||||||
- black
|
|
||||||
- ci-custom
|
|
||||||
- clang-format
|
|
||||||
- flake8
|
|
||||||
- pylint
|
|
||||||
- pytest
|
|
||||||
- pyupgrade
|
|
||||||
- compile-tests-list
|
|
||||||
- validate-tests
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
max-parallel: 2
|
|
||||||
matrix:
|
|
||||||
file: ${{ fromJson(needs.compile-tests-list.outputs.matrix) }}
|
|
||||||
steps:
|
|
||||||
- name: Check out code from GitHub
|
|
||||||
uses: actions/checkout@v4.1.6
|
|
||||||
- name: Restore Python
|
|
||||||
uses: ./.github/actions/restore-python
|
|
||||||
with:
|
|
||||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
|
||||||
cache-key: ${{ needs.common.outputs.cache-key }}
|
|
||||||
- name: Run esphome compile ${{ matrix.file }}
|
|
||||||
run: |
|
|
||||||
. venv/bin/activate
|
|
||||||
esphome compile ${{ matrix.file }}
|
|
||||||
|
|
||||||
clang-tidy:
|
clang-tidy:
|
||||||
name: ${{ matrix.name }}
|
name: ${{ matrix.name }}
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
@ -358,7 +292,7 @@ jobs:
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v4.1.6
|
uses: actions/checkout@v4.1.7
|
||||||
- name: Restore Python
|
- name: Restore Python
|
||||||
uses: ./.github/actions/restore-python
|
uses: ./.github/actions/restore-python
|
||||||
with:
|
with:
|
||||||
|
@ -387,6 +321,13 @@ jobs:
|
||||||
echo "::add-matcher::.github/workflows/matchers/gcc.json"
|
echo "::add-matcher::.github/workflows/matchers/gcc.json"
|
||||||
echo "::add-matcher::.github/workflows/matchers/clang-tidy.json"
|
echo "::add-matcher::.github/workflows/matchers/clang-tidy.json"
|
||||||
|
|
||||||
|
- name: Run 'pio run --list-targets -e esp32-idf-tidy'
|
||||||
|
if: matrix.name == 'Run script/clang-tidy for ESP32 IDF'
|
||||||
|
run: |
|
||||||
|
. venv/bin/activate
|
||||||
|
mkdir -p .temp
|
||||||
|
pio run --list-targets -e esp32-idf-tidy
|
||||||
|
|
||||||
- name: Run clang-tidy
|
- name: Run clang-tidy
|
||||||
run: |
|
run: |
|
||||||
. venv/bin/activate
|
. venv/bin/activate
|
||||||
|
@ -410,7 +351,7 @@ jobs:
|
||||||
count: ${{ steps.list-components.outputs.count }}
|
count: ${{ steps.list-components.outputs.count }}
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v4.1.6
|
uses: actions/checkout@v4.1.7
|
||||||
with:
|
with:
|
||||||
# Fetch enough history so `git merge-base refs/remotes/origin/dev HEAD` works.
|
# Fetch enough history so `git merge-base refs/remotes/origin/dev HEAD` works.
|
||||||
fetch-depth: 500
|
fetch-depth: 500
|
||||||
|
@ -454,11 +395,11 @@ jobs:
|
||||||
matrix:
|
matrix:
|
||||||
file: ${{ fromJson(needs.list-components.outputs.components) }}
|
file: ${{ fromJson(needs.list-components.outputs.components) }}
|
||||||
steps:
|
steps:
|
||||||
- name: Install libsodium
|
- name: Install dependencies
|
||||||
run: sudo apt-get install libsodium-dev
|
run: sudo apt-get install libsodium-dev libsdl2-dev
|
||||||
|
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v4.1.6
|
uses: actions/checkout@v4.1.7
|
||||||
- name: Restore Python
|
- name: Restore Python
|
||||||
uses: ./.github/actions/restore-python
|
uses: ./.github/actions/restore-python
|
||||||
with:
|
with:
|
||||||
|
@ -484,7 +425,7 @@ jobs:
|
||||||
matrix: ${{ steps.split.outputs.components }}
|
matrix: ${{ steps.split.outputs.components }}
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v4.1.6
|
uses: actions/checkout@v4.1.7
|
||||||
- name: Split components into 20 groups
|
- name: Split components into 20 groups
|
||||||
id: split
|
id: split
|
||||||
run: |
|
run: |
|
||||||
|
@ -508,11 +449,11 @@ jobs:
|
||||||
- name: List components
|
- name: List components
|
||||||
run: echo ${{ matrix.components }}
|
run: echo ${{ matrix.components }}
|
||||||
|
|
||||||
- name: Install libsodium
|
- name: Install dependencies
|
||||||
run: sudo apt-get install libsodium-dev
|
run: sudo apt-get install libsodium-dev libsdl2-dev
|
||||||
|
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v4.1.6
|
uses: actions/checkout@v4.1.7
|
||||||
- name: Restore Python
|
- name: Restore Python
|
||||||
uses: ./.github/actions/restore-python
|
uses: ./.github/actions/restore-python
|
||||||
with:
|
with:
|
||||||
|
@ -543,7 +484,6 @@ jobs:
|
||||||
- pylint
|
- pylint
|
||||||
- pytest
|
- pytest
|
||||||
- pyupgrade
|
- pyupgrade
|
||||||
- compile-tests
|
|
||||||
- clang-tidy
|
- clang-tidy
|
||||||
- list-components
|
- list-components
|
||||||
- test-build-components
|
- test-build-components
|
||||||
|
|
10
.github/workflows/release.yml
vendored
10
.github/workflows/release.yml
vendored
|
@ -19,7 +19,7 @@ jobs:
|
||||||
tag: ${{ steps.tag.outputs.tag }}
|
tag: ${{ steps.tag.outputs.tag }}
|
||||||
branch_build: ${{ steps.tag.outputs.branch_build }}
|
branch_build: ${{ steps.tag.outputs.branch_build }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4.1.6
|
- uses: actions/checkout@v4.1.7
|
||||||
- name: Get tag
|
- name: Get tag
|
||||||
id: tag
|
id: tag
|
||||||
# yamllint disable rule:line-length
|
# yamllint disable rule:line-length
|
||||||
|
@ -51,7 +51,7 @@ jobs:
|
||||||
contents: read
|
contents: read
|
||||||
id-token: write
|
id-token: write
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4.1.6
|
- uses: actions/checkout@v4.1.7
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v5.1.0
|
uses: actions/setup-python@v5.1.0
|
||||||
with:
|
with:
|
||||||
|
@ -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.8.14
|
uses: pypa/gh-action-pypi-publish@v1.9.0
|
||||||
|
|
||||||
deploy-docker:
|
deploy-docker:
|
||||||
name: Build ESPHome ${{ matrix.platform }}
|
name: Build ESPHome ${{ matrix.platform }}
|
||||||
|
@ -83,7 +83,7 @@ jobs:
|
||||||
- linux/arm/v7
|
- linux/arm/v7
|
||||||
- linux/arm64
|
- linux/arm64
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4.1.6
|
- uses: actions/checkout@v4.1.7
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v5.1.0
|
uses: actions/setup-python@v5.1.0
|
||||||
with:
|
with:
|
||||||
|
@ -174,7 +174,7 @@ jobs:
|
||||||
- ghcr
|
- ghcr
|
||||||
- dockerhub
|
- dockerhub
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4.1.6
|
- uses: actions/checkout@v4.1.7
|
||||||
|
|
||||||
- name: Download digests
|
- name: Download digests
|
||||||
uses: actions/download-artifact@v4.1.7
|
uses: actions/download-artifact@v4.1.7
|
||||||
|
|
6
.github/workflows/sync-device-classes.yml
vendored
6
.github/workflows/sync-device-classes.yml
vendored
|
@ -13,10 +13,10 @@ jobs:
|
||||||
if: github.repository == 'esphome/esphome'
|
if: github.repository == 'esphome/esphome'
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4.1.6
|
uses: actions/checkout@v4.1.7
|
||||||
|
|
||||||
- name: Checkout Home Assistant
|
- name: Checkout Home Assistant
|
||||||
uses: actions/checkout@v4.1.6
|
uses: actions/checkout@v4.1.7
|
||||||
with:
|
with:
|
||||||
repository: home-assistant/core
|
repository: home-assistant/core
|
||||||
path: lib/home-assistant
|
path: lib/home-assistant
|
||||||
|
@ -36,7 +36,7 @@ jobs:
|
||||||
python ./script/sync-device_class.py
|
python ./script/sync-device_class.py
|
||||||
|
|
||||||
- name: Commit changes
|
- name: Commit changes
|
||||||
uses: peter-evans/create-pull-request@v6.0.5
|
uses: peter-evans/create-pull-request@v6.1.0
|
||||||
with:
|
with:
|
||||||
commit-message: "Synchronise Device Classes from Home Assistant"
|
commit-message: "Synchronise Device Classes from Home Assistant"
|
||||||
committer: esphomebot <esphome@nabucasa.com>
|
committer: esphomebot <esphome@nabucasa.com>
|
||||||
|
|
2
.github/workflows/yaml-lint.yml
vendored
2
.github/workflows/yaml-lint.yml
vendored
|
@ -18,7 +18,7 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v4.1.6
|
uses: actions/checkout@v4.1.7
|
||||||
- name: Run yamllint
|
- name: Run yamllint
|
||||||
uses: frenck/action-yamllint@v1.5.0
|
uses: frenck/action-yamllint@v1.5.0
|
||||||
with:
|
with:
|
||||||
|
|
|
@ -94,6 +94,7 @@ esphome/components/current_based/* @djwmarcx
|
||||||
esphome/components/dac7678/* @NickB1
|
esphome/components/dac7678/* @NickB1
|
||||||
esphome/components/daikin_arc/* @MagicBear
|
esphome/components/daikin_arc/* @MagicBear
|
||||||
esphome/components/daikin_brc/* @hagak
|
esphome/components/daikin_brc/* @hagak
|
||||||
|
esphome/components/dallas_temp/* @ssieb
|
||||||
esphome/components/daly_bms/* @s1lvi0
|
esphome/components/daly_bms/* @s1lvi0
|
||||||
esphome/components/dashboard_import/* @esphome/core
|
esphome/components/dashboard_import/* @esphome/core
|
||||||
esphome/components/datetime/* @jesserockz @rfdarter
|
esphome/components/datetime/* @jesserockz @rfdarter
|
||||||
|
@ -145,6 +146,7 @@ esphome/components/gdk101/* @Szewcson
|
||||||
esphome/components/globals/* @esphome/core
|
esphome/components/globals/* @esphome/core
|
||||||
esphome/components/gp8403/* @jesserockz
|
esphome/components/gp8403/* @jesserockz
|
||||||
esphome/components/gpio/* @esphome/core
|
esphome/components/gpio/* @esphome/core
|
||||||
|
esphome/components/gpio/one_wire/* @ssieb
|
||||||
esphome/components/gps/* @coogle
|
esphome/components/gps/* @coogle
|
||||||
esphome/components/graph/* @synco
|
esphome/components/graph/* @synco
|
||||||
esphome/components/graphical_display_menu/* @MrMDavidson
|
esphome/components/graphical_display_menu/* @MrMDavidson
|
||||||
|
@ -172,6 +174,8 @@ esphome/components/host/* @clydebarrow @esphome/core
|
||||||
esphome/components/host/time/* @clydebarrow
|
esphome/components/host/time/* @clydebarrow
|
||||||
esphome/components/hrxl_maxsonar_wr/* @netmikey
|
esphome/components/hrxl_maxsonar_wr/* @netmikey
|
||||||
esphome/components/hte501/* @Stock-M
|
esphome/components/hte501/* @Stock-M
|
||||||
|
esphome/components/http_request/ota/* @oarcher
|
||||||
|
esphome/components/http_request/update/* @jesserockz
|
||||||
esphome/components/htu31d/* @betterengineering
|
esphome/components/htu31d/* @betterengineering
|
||||||
esphome/components/hydreon_rgxx/* @functionpointer
|
esphome/components/hydreon_rgxx/* @functionpointer
|
||||||
esphome/components/hyt271/* @Philippe12
|
esphome/components/hyt271/* @Philippe12
|
||||||
|
@ -269,6 +273,7 @@ esphome/components/nextion/text_sensor/* @senexcrenshaw
|
||||||
esphome/components/nfc/* @jesserockz @kbx81
|
esphome/components/nfc/* @jesserockz @kbx81
|
||||||
esphome/components/noblex/* @AGalfra
|
esphome/components/noblex/* @AGalfra
|
||||||
esphome/components/number/* @esphome/core
|
esphome/components/number/* @esphome/core
|
||||||
|
esphome/components/one_wire/* @ssieb
|
||||||
esphome/components/ota/* @esphome/core
|
esphome/components/ota/* @esphome/core
|
||||||
esphome/components/output/* @esphome/core
|
esphome/components/output/* @esphome/core
|
||||||
esphome/components/pca6416a/* @Mat931
|
esphome/components/pca6416a/* @Mat931
|
||||||
|
@ -316,6 +321,7 @@ esphome/components/rtttl/* @glmnet
|
||||||
esphome/components/safe_mode/* @jsuanet @kbx81 @paulmonigatti
|
esphome/components/safe_mode/* @jsuanet @kbx81 @paulmonigatti
|
||||||
esphome/components/scd4x/* @martgras @sjtrny
|
esphome/components/scd4x/* @martgras @sjtrny
|
||||||
esphome/components/script/* @esphome/core
|
esphome/components/script/* @esphome/core
|
||||||
|
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
|
||||||
|
@ -410,6 +416,7 @@ esphome/components/uart/button/* @ssieb
|
||||||
esphome/components/ufire_ec/* @pvizeli
|
esphome/components/ufire_ec/* @pvizeli
|
||||||
esphome/components/ufire_ise/* @pvizeli
|
esphome/components/ufire_ise/* @pvizeli
|
||||||
esphome/components/ultrasonic/* @OttoWinter
|
esphome/components/ultrasonic/* @OttoWinter
|
||||||
|
esphome/components/update/* @jesserockz
|
||||||
esphome/components/uponor_smatrix/* @kroimon
|
esphome/components/uponor_smatrix/* @kroimon
|
||||||
esphome/components/valve/* @esphome/core
|
esphome/components/valve/* @esphome/core
|
||||||
esphome/components/vbus/* @ssieb
|
esphome/components/vbus/* @ssieb
|
||||||
|
|
|
@ -81,7 +81,8 @@ RUN \
|
||||||
fi; \
|
fi; \
|
||||||
pip3 install \
|
pip3 install \
|
||||||
--break-system-packages --no-cache-dir \
|
--break-system-packages --no-cache-dir \
|
||||||
platformio==6.1.13 \
|
# Keep platformio version in sync with requirements.txt
|
||||||
|
platformio==6.1.15 \
|
||||||
# Change some platformio settings
|
# Change some platformio settings
|
||||||
&& platformio settings set enable_telemetry No \
|
&& platformio settings set enable_telemetry No \
|
||||||
&& platformio settings set check_platformio_interval 1000000 \
|
&& platformio settings set check_platformio_interval 1000000 \
|
||||||
|
@ -101,7 +102,7 @@ RUN --mount=type=tmpfs,target=/root/.cargo if [ "$TARGETARCH$TARGETVARIANT" = "a
|
||||||
&& /platformio_install_deps.py /platformio.ini --libraries
|
&& /platformio_install_deps.py /platformio.ini --libraries
|
||||||
|
|
||||||
# Avoid unsafe git error when container user and file config volume permissions don't match
|
# Avoid unsafe git error when container user and file config volume permissions don't match
|
||||||
RUN git config --system --add safe.directory '/config/*'
|
RUN git config --system --add safe.directory '*'
|
||||||
|
|
||||||
|
|
||||||
# ======================= docker-type image =======================
|
# ======================= docker-type image =======================
|
||||||
|
|
|
@ -488,6 +488,15 @@ def command_run(args, config):
|
||||||
if exit_code != 0:
|
if exit_code != 0:
|
||||||
return exit_code
|
return exit_code
|
||||||
_LOGGER.info("Successfully compiled program.")
|
_LOGGER.info("Successfully compiled program.")
|
||||||
|
if CORE.is_host:
|
||||||
|
from esphome.platformio_api import get_idedata
|
||||||
|
|
||||||
|
idedata = get_idedata(config)
|
||||||
|
if idedata is None:
|
||||||
|
return 1
|
||||||
|
program_path = idedata.raw["prog_path"]
|
||||||
|
return run_external_process(program_path)
|
||||||
|
|
||||||
port = choose_upload_log_host(
|
port = choose_upload_log_host(
|
||||||
default=args.device,
|
default=args.device,
|
||||||
check_default=None,
|
check_default=None,
|
||||||
|
|
|
@ -58,7 +58,9 @@ from esphome.cpp_types import ( # noqa
|
||||||
bool_,
|
bool_,
|
||||||
int_,
|
int_,
|
||||||
std_ns,
|
std_ns,
|
||||||
|
std_shared_ptr,
|
||||||
std_string,
|
std_string,
|
||||||
|
std_string_ref,
|
||||||
std_vector,
|
std_vector,
|
||||||
uint8,
|
uint8,
|
||||||
uint16,
|
uint16,
|
||||||
|
|
|
@ -3,7 +3,13 @@ import logging
|
||||||
from esphome import automation, core
|
from esphome import automation, core
|
||||||
from esphome.components import font
|
from esphome.components import font
|
||||||
import esphome.components.image as espImage
|
import esphome.components.image as espImage
|
||||||
from esphome.components.image import CONF_USE_TRANSPARENCY
|
from esphome.components.image import (
|
||||||
|
CONF_USE_TRANSPARENCY,
|
||||||
|
LOCAL_SCHEMA,
|
||||||
|
WEB_SCHEMA,
|
||||||
|
SOURCE_WEB,
|
||||||
|
SOURCE_LOCAL,
|
||||||
|
)
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
from esphome.const import (
|
from esphome.const import (
|
||||||
|
@ -13,6 +19,9 @@ from esphome.const import (
|
||||||
CONF_REPEAT,
|
CONF_REPEAT,
|
||||||
CONF_RESIZE,
|
CONF_RESIZE,
|
||||||
CONF_TYPE,
|
CONF_TYPE,
|
||||||
|
CONF_SOURCE,
|
||||||
|
CONF_PATH,
|
||||||
|
CONF_URL,
|
||||||
)
|
)
|
||||||
from esphome.core import CORE, HexInt
|
from esphome.core import CORE, HexInt
|
||||||
|
|
||||||
|
@ -43,6 +52,40 @@ SetFrameAction = animation_ns.class_(
|
||||||
"AnimationSetFrameAction", automation.Action, cg.Parented.template(Animation_)
|
"AnimationSetFrameAction", automation.Action, cg.Parented.template(Animation_)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
TYPED_FILE_SCHEMA = cv.typed_schema(
|
||||||
|
{
|
||||||
|
SOURCE_LOCAL: LOCAL_SCHEMA,
|
||||||
|
SOURCE_WEB: WEB_SCHEMA,
|
||||||
|
},
|
||||||
|
key=CONF_SOURCE,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _file_schema(value):
|
||||||
|
if isinstance(value, str):
|
||||||
|
return validate_file_shorthand(value)
|
||||||
|
return TYPED_FILE_SCHEMA(value)
|
||||||
|
|
||||||
|
|
||||||
|
FILE_SCHEMA = cv.Schema(_file_schema)
|
||||||
|
|
||||||
|
|
||||||
|
def validate_file_shorthand(value):
|
||||||
|
value = cv.string_strict(value)
|
||||||
|
if value.startswith("http://") or value.startswith("https://"):
|
||||||
|
return FILE_SCHEMA(
|
||||||
|
{
|
||||||
|
CONF_SOURCE: SOURCE_WEB,
|
||||||
|
CONF_URL: value,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return FILE_SCHEMA(
|
||||||
|
{
|
||||||
|
CONF_SOURCE: SOURCE_LOCAL,
|
||||||
|
CONF_PATH: value,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def validate_cross_dependencies(config):
|
def validate_cross_dependencies(config):
|
||||||
"""
|
"""
|
||||||
|
@ -67,7 +110,7 @@ ANIMATION_SCHEMA = cv.Schema(
|
||||||
cv.All(
|
cv.All(
|
||||||
{
|
{
|
||||||
cv.Required(CONF_ID): cv.declare_id(Animation_),
|
cv.Required(CONF_ID): cv.declare_id(Animation_),
|
||||||
cv.Required(CONF_FILE): cv.file_,
|
cv.Required(CONF_FILE): FILE_SCHEMA,
|
||||||
cv.Optional(CONF_RESIZE): cv.dimensions,
|
cv.Optional(CONF_RESIZE): cv.dimensions,
|
||||||
cv.Optional(CONF_TYPE, default="BINARY"): cv.enum(
|
cv.Optional(CONF_TYPE, default="BINARY"): cv.enum(
|
||||||
espImage.IMAGE_TYPE, upper=True
|
espImage.IMAGE_TYPE, upper=True
|
||||||
|
@ -124,7 +167,11 @@ async def animation_action_to_code(config, action_id, template_arg, args):
|
||||||
async def to_code(config):
|
async def to_code(config):
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
path = CORE.relative_config_path(config[CONF_FILE])
|
conf_file = config[CONF_FILE]
|
||||||
|
if conf_file[CONF_SOURCE] == SOURCE_LOCAL:
|
||||||
|
path = CORE.relative_config_path(conf_file[CONF_PATH])
|
||||||
|
elif conf_file[CONF_SOURCE] == SOURCE_WEB:
|
||||||
|
path = espImage.compute_local_image_path(conf_file).as_posix()
|
||||||
try:
|
try:
|
||||||
image = Image.open(path)
|
image = Image.open(path)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
|
@ -48,6 +48,7 @@ service APIConnection {
|
||||||
rpc date_command (DateCommandRequest) returns (void) {}
|
rpc date_command (DateCommandRequest) returns (void) {}
|
||||||
rpc time_command (TimeCommandRequest) returns (void) {}
|
rpc time_command (TimeCommandRequest) returns (void) {}
|
||||||
rpc datetime_command (DateTimeCommandRequest) returns (void) {}
|
rpc datetime_command (DateTimeCommandRequest) returns (void) {}
|
||||||
|
rpc update_command (UpdateCommandRequest) returns (void) {}
|
||||||
|
|
||||||
rpc subscribe_bluetooth_le_advertisements(SubscribeBluetoothLEAdvertisementsRequest) returns (void) {}
|
rpc subscribe_bluetooth_le_advertisements(SubscribeBluetoothLEAdvertisementsRequest) returns (void) {}
|
||||||
rpc bluetooth_device_request(BluetoothDeviceRequest) returns (void) {}
|
rpc bluetooth_device_request(BluetoothDeviceRequest) returns (void) {}
|
||||||
|
@ -1837,3 +1838,46 @@ message DateTimeCommandRequest {
|
||||||
fixed32 key = 1;
|
fixed32 key = 1;
|
||||||
fixed32 epoch_seconds = 2;
|
fixed32 epoch_seconds = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ==================== UPDATE ====================
|
||||||
|
message ListEntitiesUpdateResponse {
|
||||||
|
option (id) = 116;
|
||||||
|
option (source) = SOURCE_SERVER;
|
||||||
|
option (ifdef) = "USE_UPDATE";
|
||||||
|
|
||||||
|
string object_id = 1;
|
||||||
|
fixed32 key = 2;
|
||||||
|
string name = 3;
|
||||||
|
string unique_id = 4;
|
||||||
|
|
||||||
|
string icon = 5;
|
||||||
|
bool disabled_by_default = 6;
|
||||||
|
EntityCategory entity_category = 7;
|
||||||
|
string device_class = 8;
|
||||||
|
}
|
||||||
|
message UpdateStateResponse {
|
||||||
|
option (id) = 117;
|
||||||
|
option (source) = SOURCE_SERVER;
|
||||||
|
option (ifdef) = "USE_UPDATE";
|
||||||
|
option (no_delay) = true;
|
||||||
|
|
||||||
|
fixed32 key = 1;
|
||||||
|
bool missing_state = 2;
|
||||||
|
bool in_progress = 3;
|
||||||
|
bool has_progress = 4;
|
||||||
|
float progress = 5;
|
||||||
|
string current_version = 6;
|
||||||
|
string latest_version = 7;
|
||||||
|
string title = 8;
|
||||||
|
string release_summary = 9;
|
||||||
|
string release_url = 10;
|
||||||
|
}
|
||||||
|
message UpdateCommandRequest {
|
||||||
|
option (id) = 118;
|
||||||
|
option (source) = SOURCE_CLIENT;
|
||||||
|
option (ifdef) = "USE_UPDATE";
|
||||||
|
option (no_delay) = true;
|
||||||
|
|
||||||
|
fixed32 key = 1;
|
||||||
|
bool install = 2;
|
||||||
|
}
|
||||||
|
|
|
@ -1287,6 +1287,51 @@ bool APIConnection::send_event_info(event::Event *event) {
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef USE_UPDATE
|
||||||
|
bool APIConnection::send_update_state(update::UpdateEntity *update) {
|
||||||
|
if (!this->state_subscription_)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
UpdateStateResponse resp{};
|
||||||
|
resp.key = update->get_object_id_hash();
|
||||||
|
resp.missing_state = !update->has_state();
|
||||||
|
if (update->has_state()) {
|
||||||
|
resp.in_progress = update->state == update::UpdateState::UPDATE_STATE_INSTALLING;
|
||||||
|
if (update->update_info.has_progress) {
|
||||||
|
resp.has_progress = true;
|
||||||
|
resp.progress = update->update_info.progress;
|
||||||
|
}
|
||||||
|
resp.current_version = update->update_info.current_version;
|
||||||
|
resp.latest_version = update->update_info.latest_version;
|
||||||
|
resp.title = update->update_info.title;
|
||||||
|
resp.release_summary = update->update_info.summary;
|
||||||
|
resp.release_url = update->update_info.release_url;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this->send_update_state_response(resp);
|
||||||
|
}
|
||||||
|
bool APIConnection::send_update_info(update::UpdateEntity *update) {
|
||||||
|
ListEntitiesUpdateResponse msg;
|
||||||
|
msg.key = update->get_object_id_hash();
|
||||||
|
msg.object_id = update->get_object_id();
|
||||||
|
if (update->has_own_name())
|
||||||
|
msg.name = update->get_name();
|
||||||
|
msg.unique_id = get_default_unique_id("update", update);
|
||||||
|
msg.icon = update->get_icon();
|
||||||
|
msg.disabled_by_default = update->is_disabled_by_default();
|
||||||
|
msg.entity_category = static_cast<enums::EntityCategory>(update->get_entity_category());
|
||||||
|
msg.device_class = update->get_device_class();
|
||||||
|
return this->send_list_entities_update_response(msg);
|
||||||
|
}
|
||||||
|
void APIConnection::update_command(const UpdateCommandRequest &msg) {
|
||||||
|
update::UpdateEntity *update = App.get_update_by_key(msg.key);
|
||||||
|
if (update == nullptr)
|
||||||
|
return;
|
||||||
|
|
||||||
|
update->perform();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
bool APIConnection::send_log_message(int level, const char *tag, const char *line) {
|
bool APIConnection::send_log_message(int level, const char *tag, const char *line) {
|
||||||
if (this->log_subscription_ < level)
|
if (this->log_subscription_ < level)
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -164,6 +164,12 @@ class APIConnection : public APIServerConnection {
|
||||||
bool send_event_info(event::Event *event);
|
bool send_event_info(event::Event *event);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef USE_UPDATE
|
||||||
|
bool send_update_state(update::UpdateEntity *update);
|
||||||
|
bool send_update_info(update::UpdateEntity *update);
|
||||||
|
void update_command(const UpdateCommandRequest &msg) override;
|
||||||
|
#endif
|
||||||
|
|
||||||
void on_disconnect_response(const DisconnectResponse &value) override;
|
void on_disconnect_response(const DisconnectResponse &value) override;
|
||||||
void on_ping_response(const PingResponse &value) override {
|
void on_ping_response(const PingResponse &value) override {
|
||||||
// we initiated ping
|
// we initiated ping
|
||||||
|
|
|
@ -8376,6 +8376,262 @@ void DateTimeCommandRequest::dump_to(std::string &out) const {
|
||||||
out.append("}");
|
out.append("}");
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
bool ListEntitiesUpdateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||||
|
switch (field_id) {
|
||||||
|
case 6: {
|
||||||
|
this->disabled_by_default = value.as_bool();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case 7: {
|
||||||
|
this->entity_category = value.as_enum<enums::EntityCategory>();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bool ListEntitiesUpdateResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
|
||||||
|
switch (field_id) {
|
||||||
|
case 1: {
|
||||||
|
this->object_id = value.as_string();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case 3: {
|
||||||
|
this->name = value.as_string();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case 4: {
|
||||||
|
this->unique_id = value.as_string();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case 5: {
|
||||||
|
this->icon = value.as_string();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case 8: {
|
||||||
|
this->device_class = value.as_string();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bool ListEntitiesUpdateResponse::decode_32bit(uint32_t field_id, Proto32Bit value) {
|
||||||
|
switch (field_id) {
|
||||||
|
case 2: {
|
||||||
|
this->key = value.as_fixed32();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void ListEntitiesUpdateResponse::encode(ProtoWriteBuffer buffer) const {
|
||||||
|
buffer.encode_string(1, this->object_id);
|
||||||
|
buffer.encode_fixed32(2, this->key);
|
||||||
|
buffer.encode_string(3, this->name);
|
||||||
|
buffer.encode_string(4, this->unique_id);
|
||||||
|
buffer.encode_string(5, this->icon);
|
||||||
|
buffer.encode_bool(6, this->disabled_by_default);
|
||||||
|
buffer.encode_enum<enums::EntityCategory>(7, this->entity_category);
|
||||||
|
buffer.encode_string(8, this->device_class);
|
||||||
|
}
|
||||||
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
|
void ListEntitiesUpdateResponse::dump_to(std::string &out) const {
|
||||||
|
__attribute__((unused)) char buffer[64];
|
||||||
|
out.append("ListEntitiesUpdateResponse {\n");
|
||||||
|
out.append(" object_id: ");
|
||||||
|
out.append("'").append(this->object_id).append("'");
|
||||||
|
out.append("\n");
|
||||||
|
|
||||||
|
out.append(" key: ");
|
||||||
|
sprintf(buffer, "%" PRIu32, this->key);
|
||||||
|
out.append(buffer);
|
||||||
|
out.append("\n");
|
||||||
|
|
||||||
|
out.append(" name: ");
|
||||||
|
out.append("'").append(this->name).append("'");
|
||||||
|
out.append("\n");
|
||||||
|
|
||||||
|
out.append(" unique_id: ");
|
||||||
|
out.append("'").append(this->unique_id).append("'");
|
||||||
|
out.append("\n");
|
||||||
|
|
||||||
|
out.append(" icon: ");
|
||||||
|
out.append("'").append(this->icon).append("'");
|
||||||
|
out.append("\n");
|
||||||
|
|
||||||
|
out.append(" disabled_by_default: ");
|
||||||
|
out.append(YESNO(this->disabled_by_default));
|
||||||
|
out.append("\n");
|
||||||
|
|
||||||
|
out.append(" entity_category: ");
|
||||||
|
out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
|
||||||
|
out.append("\n");
|
||||||
|
|
||||||
|
out.append(" device_class: ");
|
||||||
|
out.append("'").append(this->device_class).append("'");
|
||||||
|
out.append("\n");
|
||||||
|
out.append("}");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
bool UpdateStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||||
|
switch (field_id) {
|
||||||
|
case 2: {
|
||||||
|
this->missing_state = value.as_bool();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case 3: {
|
||||||
|
this->in_progress = value.as_bool();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case 4: {
|
||||||
|
this->has_progress = value.as_bool();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bool UpdateStateResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
|
||||||
|
switch (field_id) {
|
||||||
|
case 6: {
|
||||||
|
this->current_version = value.as_string();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case 7: {
|
||||||
|
this->latest_version = value.as_string();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case 8: {
|
||||||
|
this->title = value.as_string();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case 9: {
|
||||||
|
this->release_summary = value.as_string();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case 10: {
|
||||||
|
this->release_url = value.as_string();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bool UpdateStateResponse::decode_32bit(uint32_t field_id, Proto32Bit value) {
|
||||||
|
switch (field_id) {
|
||||||
|
case 1: {
|
||||||
|
this->key = value.as_fixed32();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case 5: {
|
||||||
|
this->progress = value.as_float();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void UpdateStateResponse::encode(ProtoWriteBuffer buffer) const {
|
||||||
|
buffer.encode_fixed32(1, this->key);
|
||||||
|
buffer.encode_bool(2, this->missing_state);
|
||||||
|
buffer.encode_bool(3, this->in_progress);
|
||||||
|
buffer.encode_bool(4, this->has_progress);
|
||||||
|
buffer.encode_float(5, this->progress);
|
||||||
|
buffer.encode_string(6, this->current_version);
|
||||||
|
buffer.encode_string(7, this->latest_version);
|
||||||
|
buffer.encode_string(8, this->title);
|
||||||
|
buffer.encode_string(9, this->release_summary);
|
||||||
|
buffer.encode_string(10, this->release_url);
|
||||||
|
}
|
||||||
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
|
void UpdateStateResponse::dump_to(std::string &out) const {
|
||||||
|
__attribute__((unused)) char buffer[64];
|
||||||
|
out.append("UpdateStateResponse {\n");
|
||||||
|
out.append(" key: ");
|
||||||
|
sprintf(buffer, "%" PRIu32, this->key);
|
||||||
|
out.append(buffer);
|
||||||
|
out.append("\n");
|
||||||
|
|
||||||
|
out.append(" missing_state: ");
|
||||||
|
out.append(YESNO(this->missing_state));
|
||||||
|
out.append("\n");
|
||||||
|
|
||||||
|
out.append(" in_progress: ");
|
||||||
|
out.append(YESNO(this->in_progress));
|
||||||
|
out.append("\n");
|
||||||
|
|
||||||
|
out.append(" has_progress: ");
|
||||||
|
out.append(YESNO(this->has_progress));
|
||||||
|
out.append("\n");
|
||||||
|
|
||||||
|
out.append(" progress: ");
|
||||||
|
sprintf(buffer, "%g", this->progress);
|
||||||
|
out.append(buffer);
|
||||||
|
out.append("\n");
|
||||||
|
|
||||||
|
out.append(" current_version: ");
|
||||||
|
out.append("'").append(this->current_version).append("'");
|
||||||
|
out.append("\n");
|
||||||
|
|
||||||
|
out.append(" latest_version: ");
|
||||||
|
out.append("'").append(this->latest_version).append("'");
|
||||||
|
out.append("\n");
|
||||||
|
|
||||||
|
out.append(" title: ");
|
||||||
|
out.append("'").append(this->title).append("'");
|
||||||
|
out.append("\n");
|
||||||
|
|
||||||
|
out.append(" release_summary: ");
|
||||||
|
out.append("'").append(this->release_summary).append("'");
|
||||||
|
out.append("\n");
|
||||||
|
|
||||||
|
out.append(" release_url: ");
|
||||||
|
out.append("'").append(this->release_url).append("'");
|
||||||
|
out.append("\n");
|
||||||
|
out.append("}");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
bool UpdateCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||||
|
switch (field_id) {
|
||||||
|
case 2: {
|
||||||
|
this->install = value.as_bool();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bool UpdateCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
|
||||||
|
switch (field_id) {
|
||||||
|
case 1: {
|
||||||
|
this->key = value.as_fixed32();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void UpdateCommandRequest::encode(ProtoWriteBuffer buffer) const {
|
||||||
|
buffer.encode_fixed32(1, this->key);
|
||||||
|
buffer.encode_bool(2, this->install);
|
||||||
|
}
|
||||||
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
|
void UpdateCommandRequest::dump_to(std::string &out) const {
|
||||||
|
__attribute__((unused)) char buffer[64];
|
||||||
|
out.append("UpdateCommandRequest {\n");
|
||||||
|
out.append(" key: ");
|
||||||
|
sprintf(buffer, "%" PRIu32, this->key);
|
||||||
|
out.append(buffer);
|
||||||
|
out.append("\n");
|
||||||
|
|
||||||
|
out.append(" install: ");
|
||||||
|
out.append(YESNO(this->install));
|
||||||
|
out.append("\n");
|
||||||
|
out.append("}");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
} // namespace api
|
} // namespace api
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|
|
@ -2130,6 +2130,61 @@ class DateTimeCommandRequest : public ProtoMessage {
|
||||||
protected:
|
protected:
|
||||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||||
};
|
};
|
||||||
|
class ListEntitiesUpdateResponse : public ProtoMessage {
|
||||||
|
public:
|
||||||
|
std::string object_id{};
|
||||||
|
uint32_t key{0};
|
||||||
|
std::string name{};
|
||||||
|
std::string unique_id{};
|
||||||
|
std::string icon{};
|
||||||
|
bool disabled_by_default{false};
|
||||||
|
enums::EntityCategory entity_category{};
|
||||||
|
std::string device_class{};
|
||||||
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
|
void dump_to(std::string &out) const override;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
protected:
|
||||||
|
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||||
|
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||||
|
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||||
|
};
|
||||||
|
class UpdateStateResponse : public ProtoMessage {
|
||||||
|
public:
|
||||||
|
uint32_t key{0};
|
||||||
|
bool missing_state{false};
|
||||||
|
bool in_progress{false};
|
||||||
|
bool has_progress{false};
|
||||||
|
float progress{0.0f};
|
||||||
|
std::string current_version{};
|
||||||
|
std::string latest_version{};
|
||||||
|
std::string title{};
|
||||||
|
std::string release_summary{};
|
||||||
|
std::string release_url{};
|
||||||
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
|
void dump_to(std::string &out) const override;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
protected:
|
||||||
|
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||||
|
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||||
|
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||||
|
};
|
||||||
|
class UpdateCommandRequest : public ProtoMessage {
|
||||||
|
public:
|
||||||
|
uint32_t key{0};
|
||||||
|
bool install{false};
|
||||||
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
|
void dump_to(std::string &out) const override;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
protected:
|
||||||
|
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||||
|
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace api
|
} // namespace api
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|
|
@ -611,6 +611,24 @@ bool APIServerConnectionBase::send_date_time_state_response(const DateTimeStateR
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_DATETIME_DATETIME
|
#ifdef USE_DATETIME_DATETIME
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef USE_UPDATE
|
||||||
|
bool APIServerConnectionBase::send_list_entities_update_response(const ListEntitiesUpdateResponse &msg) {
|
||||||
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
|
ESP_LOGVV(TAG, "send_list_entities_update_response: %s", msg.dump().c_str());
|
||||||
|
#endif
|
||||||
|
return this->send_message_<ListEntitiesUpdateResponse>(msg, 116);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#ifdef USE_UPDATE
|
||||||
|
bool APIServerConnectionBase::send_update_state_response(const UpdateStateResponse &msg) {
|
||||||
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
|
ESP_LOGVV(TAG, "send_update_state_response: %s", msg.dump().c_str());
|
||||||
|
#endif
|
||||||
|
return this->send_message_<UpdateStateResponse>(msg, 117);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#ifdef USE_UPDATE
|
||||||
|
#endif
|
||||||
bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) {
|
bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) {
|
||||||
switch (msg_type) {
|
switch (msg_type) {
|
||||||
case 1: {
|
case 1: {
|
||||||
|
@ -1106,6 +1124,17 @@ bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
||||||
ESP_LOGVV(TAG, "on_voice_assistant_timer_event_response: %s", msg.dump().c_str());
|
ESP_LOGVV(TAG, "on_voice_assistant_timer_event_response: %s", msg.dump().c_str());
|
||||||
#endif
|
#endif
|
||||||
this->on_voice_assistant_timer_event_response(msg);
|
this->on_voice_assistant_timer_event_response(msg);
|
||||||
|
#endif
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 118: {
|
||||||
|
#ifdef USE_UPDATE
|
||||||
|
UpdateCommandRequest msg;
|
||||||
|
msg.decode(msg_data, msg_size);
|
||||||
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
|
ESP_LOGVV(TAG, "on_update_command_request: %s", msg.dump().c_str());
|
||||||
|
#endif
|
||||||
|
this->on_update_command_request(msg);
|
||||||
#endif
|
#endif
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -1434,6 +1463,19 @@ void APIServerConnection::on_date_time_command_request(const DateTimeCommandRequ
|
||||||
this->datetime_command(msg);
|
this->datetime_command(msg);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef USE_UPDATE
|
||||||
|
void APIServerConnection::on_update_command_request(const UpdateCommandRequest &msg) {
|
||||||
|
if (!this->is_connection_setup()) {
|
||||||
|
this->on_no_setup_connection();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!this->is_authenticated()) {
|
||||||
|
this->on_unauthenticated_access();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this->update_command(msg);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
#ifdef USE_BLUETOOTH_PROXY
|
#ifdef USE_BLUETOOTH_PROXY
|
||||||
void APIServerConnection::on_subscribe_bluetooth_le_advertisements_request(
|
void APIServerConnection::on_subscribe_bluetooth_le_advertisements_request(
|
||||||
const SubscribeBluetoothLEAdvertisementsRequest &msg) {
|
const SubscribeBluetoothLEAdvertisementsRequest &msg) {
|
||||||
|
|
|
@ -306,6 +306,15 @@ class APIServerConnectionBase : public ProtoService {
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_DATETIME_DATETIME
|
#ifdef USE_DATETIME_DATETIME
|
||||||
virtual void on_date_time_command_request(const DateTimeCommandRequest &value){};
|
virtual void on_date_time_command_request(const DateTimeCommandRequest &value){};
|
||||||
|
#endif
|
||||||
|
#ifdef USE_UPDATE
|
||||||
|
bool send_list_entities_update_response(const ListEntitiesUpdateResponse &msg);
|
||||||
|
#endif
|
||||||
|
#ifdef USE_UPDATE
|
||||||
|
bool send_update_state_response(const UpdateStateResponse &msg);
|
||||||
|
#endif
|
||||||
|
#ifdef USE_UPDATE
|
||||||
|
virtual void on_update_command_request(const UpdateCommandRequest &value){};
|
||||||
#endif
|
#endif
|
||||||
protected:
|
protected:
|
||||||
bool read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override;
|
bool read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override;
|
||||||
|
@ -373,6 +382,9 @@ class APIServerConnection : public APIServerConnectionBase {
|
||||||
#ifdef USE_DATETIME_DATETIME
|
#ifdef USE_DATETIME_DATETIME
|
||||||
virtual void datetime_command(const DateTimeCommandRequest &msg) = 0;
|
virtual void datetime_command(const DateTimeCommandRequest &msg) = 0;
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef USE_UPDATE
|
||||||
|
virtual void update_command(const UpdateCommandRequest &msg) = 0;
|
||||||
|
#endif
|
||||||
#ifdef USE_BLUETOOTH_PROXY
|
#ifdef USE_BLUETOOTH_PROXY
|
||||||
virtual void subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) = 0;
|
virtual void subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) = 0;
|
||||||
#endif
|
#endif
|
||||||
|
@ -471,6 +483,9 @@ class APIServerConnection : public APIServerConnectionBase {
|
||||||
#ifdef USE_DATETIME_DATETIME
|
#ifdef USE_DATETIME_DATETIME
|
||||||
void on_date_time_command_request(const DateTimeCommandRequest &msg) override;
|
void on_date_time_command_request(const DateTimeCommandRequest &msg) override;
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef USE_UPDATE
|
||||||
|
void on_update_command_request(const UpdateCommandRequest &msg) override;
|
||||||
|
#endif
|
||||||
#ifdef USE_BLUETOOTH_PROXY
|
#ifdef USE_BLUETOOTH_PROXY
|
||||||
void on_subscribe_bluetooth_le_advertisements_request(const SubscribeBluetoothLEAdvertisementsRequest &msg) override;
|
void on_subscribe_bluetooth_le_advertisements_request(const SubscribeBluetoothLEAdvertisementsRequest &msg) override;
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -334,6 +334,13 @@ void APIServer::on_event(event::Event *obj, const std::string &event_type) {
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef USE_UPDATE
|
||||||
|
void APIServer::on_update(update::UpdateEntity *obj) {
|
||||||
|
for (auto &c : this->clients_)
|
||||||
|
c->send_update_state(obj);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
float APIServer::get_setup_priority() const { return setup_priority::AFTER_WIFI; }
|
float APIServer::get_setup_priority() const { return setup_priority::AFTER_WIFI; }
|
||||||
void APIServer::set_port(uint16_t port) { this->port_ = port; }
|
void APIServer::set_port(uint16_t port) { this->port_ = port; }
|
||||||
APIServer *global_api_server = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
APIServer *global_api_server = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||||
|
|
|
@ -102,6 +102,9 @@ class APIServer : public Component, public Controller {
|
||||||
#ifdef USE_EVENT
|
#ifdef USE_EVENT
|
||||||
void on_event(event::Event *obj, const std::string &event_type) override;
|
void on_event(event::Event *obj, const std::string &event_type) override;
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef USE_UPDATE
|
||||||
|
void on_update(update::UpdateEntity *obj) override;
|
||||||
|
#endif
|
||||||
|
|
||||||
bool is_connected() const;
|
bool is_connected() const;
|
||||||
|
|
||||||
|
|
|
@ -98,6 +98,9 @@ bool ListEntitiesIterator::on_alarm_control_panel(alarm_control_panel::AlarmCont
|
||||||
#ifdef USE_EVENT
|
#ifdef USE_EVENT
|
||||||
bool ListEntitiesIterator::on_event(event::Event *event) { return this->client_->send_event_info(event); }
|
bool ListEntitiesIterator::on_event(event::Event *event) { return this->client_->send_event_info(event); }
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef USE_UPDATE
|
||||||
|
bool ListEntitiesIterator::on_update(update::UpdateEntity *update) { return this->client_->send_update_info(update); }
|
||||||
|
#endif
|
||||||
|
|
||||||
} // namespace api
|
} // namespace api
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|
|
@ -75,6 +75,9 @@ class ListEntitiesIterator : public ComponentIterator {
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_EVENT
|
#ifdef USE_EVENT
|
||||||
bool on_event(event::Event *event) override;
|
bool on_event(event::Event *event) override;
|
||||||
|
#endif
|
||||||
|
#ifdef USE_UPDATE
|
||||||
|
bool on_update(update::UpdateEntity *update) override;
|
||||||
#endif
|
#endif
|
||||||
bool on_end() override;
|
bool on_end() override;
|
||||||
|
|
||||||
|
|
|
@ -77,6 +77,9 @@ bool InitialStateIterator::on_alarm_control_panel(alarm_control_panel::AlarmCont
|
||||||
return this->client_->send_alarm_control_panel_state(a_alarm_control_panel);
|
return this->client_->send_alarm_control_panel_state(a_alarm_control_panel);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef USE_UPDATE
|
||||||
|
bool InitialStateIterator::on_update(update::UpdateEntity *update) { return this->client_->send_update_state(update); }
|
||||||
|
#endif
|
||||||
InitialStateIterator::InitialStateIterator(APIConnection *client) : client_(client) {}
|
InitialStateIterator::InitialStateIterator(APIConnection *client) : client_(client) {}
|
||||||
|
|
||||||
} // namespace api
|
} // namespace api
|
||||||
|
|
|
@ -72,6 +72,9 @@ class InitialStateIterator : public ComponentIterator {
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_EVENT
|
#ifdef USE_EVENT
|
||||||
bool on_event(event::Event *event) override { return true; };
|
bool on_event(event::Event *event) override { return true; };
|
||||||
|
#endif
|
||||||
|
#ifdef USE_UPDATE
|
||||||
|
bool on_update(update::UpdateEntity *update) override;
|
||||||
#endif
|
#endif
|
||||||
protected:
|
protected:
|
||||||
APIConnection *client_;
|
APIConnection *client_;
|
||||||
|
|
|
@ -6,18 +6,24 @@ namespace climate_ir_lg {
|
||||||
|
|
||||||
static const char *const TAG = "climate.climate_ir_lg";
|
static const char *const TAG = "climate.climate_ir_lg";
|
||||||
|
|
||||||
const uint32_t COMMAND_ON = 0x00000;
|
// Commands
|
||||||
const uint32_t COMMAND_ON_AI = 0x03000;
|
const uint32_t COMMAND_MASK = 0xFF000;
|
||||||
const uint32_t COMMAND_COOL = 0x08000;
|
|
||||||
const uint32_t COMMAND_HEAT = 0x0C000;
|
|
||||||
const uint32_t COMMAND_OFF = 0xC0000;
|
const uint32_t COMMAND_OFF = 0xC0000;
|
||||||
const uint32_t COMMAND_SWING = 0x10000;
|
const uint32_t COMMAND_SWING = 0x10000;
|
||||||
// On, 25C, Mode: Auto, Fan: Auto, Zone Follow: Off, Sensor Temp: Ignore.
|
|
||||||
const uint32_t COMMAND_AUTO = 0x0B000;
|
|
||||||
const uint32_t COMMAND_DRY_FAN = 0x09000;
|
|
||||||
|
|
||||||
const uint32_t COMMAND_MASK = 0xFF000;
|
const uint32_t COMMAND_ON_COOL = 0x00000;
|
||||||
|
const uint32_t COMMAND_ON_DRY = 0x01000;
|
||||||
|
const uint32_t COMMAND_ON_FAN_ONLY = 0x02000;
|
||||||
|
const uint32_t COMMAND_ON_AI = 0x03000;
|
||||||
|
const uint32_t COMMAND_ON_HEAT = 0x04000;
|
||||||
|
|
||||||
|
const uint32_t COMMAND_COOL = 0x08000;
|
||||||
|
const uint32_t COMMAND_DRY = 0x09000;
|
||||||
|
const uint32_t COMMAND_FAN_ONLY = 0x0A000;
|
||||||
|
const uint32_t COMMAND_AI = 0x0B000;
|
||||||
|
const uint32_t COMMAND_HEAT = 0x0C000;
|
||||||
|
|
||||||
|
// Fan speed
|
||||||
const uint32_t FAN_MASK = 0xF0;
|
const uint32_t FAN_MASK = 0xF0;
|
||||||
const uint32_t FAN_AUTO = 0x50;
|
const uint32_t FAN_AUTO = 0x50;
|
||||||
const uint32_t FAN_MIN = 0x00;
|
const uint32_t FAN_MIN = 0x00;
|
||||||
|
@ -35,69 +41,67 @@ void LgIrClimate::transmit_state() {
|
||||||
uint32_t remote_state = 0x8800000;
|
uint32_t remote_state = 0x8800000;
|
||||||
|
|
||||||
// ESP_LOGD(TAG, "climate_lg_ir mode_before_ code: 0x%02X", modeBefore_);
|
// ESP_LOGD(TAG, "climate_lg_ir mode_before_ code: 0x%02X", modeBefore_);
|
||||||
|
|
||||||
|
// Set command
|
||||||
if (send_swing_cmd_) {
|
if (send_swing_cmd_) {
|
||||||
send_swing_cmd_ = false;
|
send_swing_cmd_ = false;
|
||||||
remote_state |= COMMAND_SWING;
|
remote_state |= COMMAND_SWING;
|
||||||
} else {
|
} else {
|
||||||
if (mode_before_ == climate::CLIMATE_MODE_OFF && this->mode == climate::CLIMATE_MODE_HEAT_COOL) {
|
bool climate_is_off = (mode_before_ == climate::CLIMATE_MODE_OFF);
|
||||||
remote_state |= COMMAND_ON_AI;
|
switch (this->mode) {
|
||||||
} else if (mode_before_ == climate::CLIMATE_MODE_OFF && this->mode != climate::CLIMATE_MODE_OFF) {
|
case climate::CLIMATE_MODE_COOL:
|
||||||
remote_state |= COMMAND_ON;
|
remote_state |= climate_is_off ? COMMAND_ON_COOL : COMMAND_COOL;
|
||||||
this->mode = climate::CLIMATE_MODE_COOL;
|
break;
|
||||||
} else {
|
case climate::CLIMATE_MODE_DRY:
|
||||||
switch (this->mode) {
|
remote_state |= climate_is_off ? COMMAND_ON_DRY : COMMAND_DRY;
|
||||||
case climate::CLIMATE_MODE_COOL:
|
break;
|
||||||
remote_state |= COMMAND_COOL;
|
case climate::CLIMATE_MODE_FAN_ONLY:
|
||||||
break;
|
remote_state |= climate_is_off ? COMMAND_ON_FAN_ONLY : COMMAND_FAN_ONLY;
|
||||||
case climate::CLIMATE_MODE_HEAT:
|
break;
|
||||||
remote_state |= COMMAND_HEAT;
|
case climate::CLIMATE_MODE_HEAT_COOL:
|
||||||
break;
|
remote_state |= climate_is_off ? COMMAND_ON_AI : COMMAND_AI;
|
||||||
case climate::CLIMATE_MODE_HEAT_COOL:
|
break;
|
||||||
remote_state |= COMMAND_AUTO;
|
case climate::CLIMATE_MODE_HEAT:
|
||||||
break;
|
remote_state |= climate_is_off ? COMMAND_ON_HEAT : COMMAND_HEAT;
|
||||||
case climate::CLIMATE_MODE_DRY:
|
break;
|
||||||
remote_state |= COMMAND_DRY_FAN;
|
case climate::CLIMATE_MODE_OFF:
|
||||||
break;
|
default:
|
||||||
case climate::CLIMATE_MODE_OFF:
|
remote_state |= COMMAND_OFF;
|
||||||
default:
|
break;
|
||||||
remote_state |= COMMAND_OFF;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mode_before_ = this->mode;
|
|
||||||
|
|
||||||
ESP_LOGD(TAG, "climate_lg_ir mode code: 0x%02X", this->mode);
|
|
||||||
|
|
||||||
if (this->mode == climate::CLIMATE_MODE_OFF) {
|
|
||||||
remote_state |= FAN_AUTO;
|
|
||||||
} else if (this->mode == climate::CLIMATE_MODE_COOL || this->mode == climate::CLIMATE_MODE_DRY ||
|
|
||||||
this->mode == climate::CLIMATE_MODE_HEAT) {
|
|
||||||
switch (this->fan_mode.value()) {
|
|
||||||
case climate::CLIMATE_FAN_HIGH:
|
|
||||||
remote_state |= FAN_MAX;
|
|
||||||
break;
|
|
||||||
case climate::CLIMATE_FAN_MEDIUM:
|
|
||||||
remote_state |= FAN_MED;
|
|
||||||
break;
|
|
||||||
case climate::CLIMATE_FAN_LOW:
|
|
||||||
remote_state |= FAN_MIN;
|
|
||||||
break;
|
|
||||||
case climate::CLIMATE_FAN_AUTO:
|
|
||||||
default:
|
|
||||||
remote_state |= FAN_AUTO;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this->mode == climate::CLIMATE_MODE_HEAT_COOL) {
|
|
||||||
this->fan_mode = climate::CLIMATE_FAN_AUTO;
|
|
||||||
// remote_state |= FAN_MODE_AUTO_DRY;
|
|
||||||
}
|
|
||||||
if (this->mode == climate::CLIMATE_MODE_COOL || this->mode == climate::CLIMATE_MODE_HEAT) {
|
|
||||||
auto temp = (uint8_t) roundf(clamp<float>(this->target_temperature, TEMP_MIN, TEMP_MAX));
|
|
||||||
remote_state |= ((temp - 15) << TEMP_SHIFT);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mode_before_ = this->mode;
|
||||||
|
|
||||||
|
ESP_LOGD(TAG, "climate_lg_ir mode code: 0x%02X", this->mode);
|
||||||
|
|
||||||
|
// Set fan speed
|
||||||
|
if (this->mode == climate::CLIMATE_MODE_OFF) {
|
||||||
|
remote_state |= FAN_AUTO;
|
||||||
|
} else {
|
||||||
|
switch (this->fan_mode.value()) {
|
||||||
|
case climate::CLIMATE_FAN_HIGH:
|
||||||
|
remote_state |= FAN_MAX;
|
||||||
|
break;
|
||||||
|
case climate::CLIMATE_FAN_MEDIUM:
|
||||||
|
remote_state |= FAN_MED;
|
||||||
|
break;
|
||||||
|
case climate::CLIMATE_FAN_LOW:
|
||||||
|
remote_state |= FAN_MIN;
|
||||||
|
break;
|
||||||
|
case climate::CLIMATE_FAN_AUTO:
|
||||||
|
default:
|
||||||
|
remote_state |= FAN_AUTO;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set temperature
|
||||||
|
if (this->mode == climate::CLIMATE_MODE_COOL || this->mode == climate::CLIMATE_MODE_HEAT) {
|
||||||
|
auto temp = (uint8_t) roundf(clamp<float>(this->target_temperature, TEMP_MIN, TEMP_MAX));
|
||||||
|
remote_state |= ((temp - 15) << TEMP_SHIFT);
|
||||||
|
}
|
||||||
|
|
||||||
transmit_(remote_state);
|
transmit_(remote_state);
|
||||||
this->publish_state();
|
this->publish_state();
|
||||||
}
|
}
|
||||||
|
@ -125,37 +129,42 @@ bool LgIrClimate::on_receive(remote_base::RemoteReceiveData data) {
|
||||||
if ((remote_state & 0xFF00000) != 0x8800000)
|
if ((remote_state & 0xFF00000) != 0x8800000)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if ((remote_state & COMMAND_MASK) == COMMAND_ON) {
|
// Get command
|
||||||
this->mode = climate::CLIMATE_MODE_COOL;
|
|
||||||
} else if ((remote_state & COMMAND_MASK) == COMMAND_ON_AI) {
|
|
||||||
this->mode = climate::CLIMATE_MODE_HEAT_COOL;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((remote_state & COMMAND_MASK) == COMMAND_OFF) {
|
if ((remote_state & COMMAND_MASK) == COMMAND_OFF) {
|
||||||
this->mode = climate::CLIMATE_MODE_OFF;
|
this->mode = climate::CLIMATE_MODE_OFF;
|
||||||
} else if ((remote_state & COMMAND_MASK) == COMMAND_SWING) {
|
} else if ((remote_state & COMMAND_MASK) == COMMAND_SWING) {
|
||||||
this->swing_mode =
|
this->swing_mode =
|
||||||
this->swing_mode == climate::CLIMATE_SWING_OFF ? climate::CLIMATE_SWING_VERTICAL : climate::CLIMATE_SWING_OFF;
|
this->swing_mode == climate::CLIMATE_SWING_OFF ? climate::CLIMATE_SWING_VERTICAL : climate::CLIMATE_SWING_OFF;
|
||||||
} else {
|
} else {
|
||||||
if ((remote_state & COMMAND_MASK) == COMMAND_AUTO) {
|
switch (remote_state & COMMAND_MASK) {
|
||||||
this->mode = climate::CLIMATE_MODE_HEAT_COOL;
|
case COMMAND_DRY:
|
||||||
} else if ((remote_state & COMMAND_MASK) == COMMAND_DRY_FAN) {
|
case COMMAND_ON_DRY:
|
||||||
this->mode = climate::CLIMATE_MODE_DRY;
|
this->mode = climate::CLIMATE_MODE_DRY;
|
||||||
} else if ((remote_state & COMMAND_MASK) == COMMAND_HEAT) {
|
break;
|
||||||
this->mode = climate::CLIMATE_MODE_HEAT;
|
case COMMAND_FAN_ONLY:
|
||||||
} else {
|
case COMMAND_ON_FAN_ONLY:
|
||||||
this->mode = climate::CLIMATE_MODE_COOL;
|
this->mode = climate::CLIMATE_MODE_FAN_ONLY;
|
||||||
|
break;
|
||||||
|
case COMMAND_AI:
|
||||||
|
case COMMAND_ON_AI:
|
||||||
|
this->mode = climate::CLIMATE_MODE_HEAT_COOL;
|
||||||
|
break;
|
||||||
|
case COMMAND_HEAT:
|
||||||
|
case COMMAND_ON_HEAT:
|
||||||
|
this->mode = climate::CLIMATE_MODE_HEAT;
|
||||||
|
break;
|
||||||
|
case COMMAND_COOL:
|
||||||
|
case COMMAND_ON_COOL:
|
||||||
|
default:
|
||||||
|
this->mode = climate::CLIMATE_MODE_COOL;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Temperature
|
// Get fan speed
|
||||||
if (this->mode == climate::CLIMATE_MODE_COOL || this->mode == climate::CLIMATE_MODE_HEAT)
|
|
||||||
this->target_temperature = ((remote_state & TEMP_MASK) >> TEMP_SHIFT) + 15;
|
|
||||||
|
|
||||||
// Fan Speed
|
|
||||||
if (this->mode == climate::CLIMATE_MODE_HEAT_COOL) {
|
if (this->mode == climate::CLIMATE_MODE_HEAT_COOL) {
|
||||||
this->fan_mode = climate::CLIMATE_FAN_AUTO;
|
this->fan_mode = climate::CLIMATE_FAN_AUTO;
|
||||||
} else if (this->mode == climate::CLIMATE_MODE_COOL || this->mode == climate::CLIMATE_MODE_HEAT ||
|
} else if (this->mode == climate::CLIMATE_MODE_COOL || this->mode == climate::CLIMATE_MODE_DRY ||
|
||||||
this->mode == climate::CLIMATE_MODE_DRY) {
|
this->mode == climate::CLIMATE_MODE_FAN_ONLY || this->mode == climate::CLIMATE_MODE_HEAT) {
|
||||||
if ((remote_state & FAN_MASK) == FAN_AUTO) {
|
if ((remote_state & FAN_MASK) == FAN_AUTO) {
|
||||||
this->fan_mode = climate::CLIMATE_FAN_AUTO;
|
this->fan_mode = climate::CLIMATE_FAN_AUTO;
|
||||||
} else if ((remote_state & FAN_MASK) == FAN_MIN) {
|
} else if ((remote_state & FAN_MASK) == FAN_MIN) {
|
||||||
|
@ -166,11 +175,17 @@ bool LgIrClimate::on_receive(remote_base::RemoteReceiveData data) {
|
||||||
this->fan_mode = climate::CLIMATE_FAN_HIGH;
|
this->fan_mode = climate::CLIMATE_FAN_HIGH;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get temperature
|
||||||
|
if (this->mode == climate::CLIMATE_MODE_COOL || this->mode == climate::CLIMATE_MODE_HEAT) {
|
||||||
|
this->target_temperature = ((remote_state & TEMP_MASK) >> TEMP_SHIFT) + 15;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
this->publish_state();
|
this->publish_state();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void LgIrClimate::transmit_(uint32_t value) {
|
void LgIrClimate::transmit_(uint32_t value) {
|
||||||
calc_checksum_(value);
|
calc_checksum_(value);
|
||||||
ESP_LOGD(TAG, "Sending climate_lg_ir code: 0x%02" PRIX32, value);
|
ESP_LOGD(TAG, "Sending climate_lg_ir code: 0x%02" PRIX32, value);
|
||||||
|
|
|
@ -14,7 +14,7 @@ const uint8_t TEMP_MAX = 30; // Celsius
|
||||||
class LgIrClimate : public climate_ir::ClimateIR {
|
class LgIrClimate : public climate_ir::ClimateIR {
|
||||||
public:
|
public:
|
||||||
LgIrClimate()
|
LgIrClimate()
|
||||||
: climate_ir::ClimateIR(TEMP_MIN, TEMP_MAX, 1.0f, true, false,
|
: climate_ir::ClimateIR(TEMP_MIN, TEMP_MAX, 1.0f, true, true,
|
||||||
{climate::CLIMATE_FAN_AUTO, climate::CLIMATE_FAN_LOW, climate::CLIMATE_FAN_MEDIUM,
|
{climate::CLIMATE_FAN_AUTO, climate::CLIMATE_FAN_LOW, climate::CLIMATE_FAN_MEDIUM,
|
||||||
climate::CLIMATE_FAN_HIGH},
|
climate::CLIMATE_FAN_HIGH},
|
||||||
{climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_VERTICAL}) {}
|
{climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_VERTICAL}) {}
|
||||||
|
|
|
@ -129,13 +129,13 @@ class Cover : public EntityBase, public EntityBase_DeviceClass {
|
||||||
*
|
*
|
||||||
* This is a legacy method and may be removed later, please use `.make_call()` instead.
|
* This is a legacy method and may be removed later, please use `.make_call()` instead.
|
||||||
*/
|
*/
|
||||||
ESPDEPRECATED("open() is deprecated, use make_call().set_command_open() instead.", "2021.9")
|
ESPDEPRECATED("open() is deprecated, use make_call().set_command_open().perform() instead.", "2021.9")
|
||||||
void open();
|
void open();
|
||||||
/** Close the cover.
|
/** Close the cover.
|
||||||
*
|
*
|
||||||
* This is a legacy method and may be removed later, please use `.make_call()` instead.
|
* This is a legacy method and may be removed later, please use `.make_call()` instead.
|
||||||
*/
|
*/
|
||||||
ESPDEPRECATED("close() is deprecated, use make_call().set_command_close() instead.", "2021.9")
|
ESPDEPRECATED("close() is deprecated, use make_call().set_command_close().perform() instead.", "2021.9")
|
||||||
void close();
|
void close();
|
||||||
/** Stop the cover.
|
/** Stop the cover.
|
||||||
*
|
*
|
||||||
|
|
|
@ -1,25 +1,7 @@
|
||||||
import esphome.codegen as cg
|
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome import pins
|
|
||||||
from esphome.const import CONF_ID, CONF_PIN
|
|
||||||
|
|
||||||
MULTI_CONF = True
|
MULTI_CONF = True
|
||||||
AUTO_LOAD = ["sensor"]
|
|
||||||
|
|
||||||
dallas_ns = cg.esphome_ns.namespace("dallas")
|
CONFIG_SCHEMA = cv.invalid(
|
||||||
DallasComponent = dallas_ns.class_("DallasComponent", cg.PollingComponent)
|
'The "dallas" component has been replaced by the "one_wire" component.\nhttps://esphome.io/components/one_wire'
|
||||||
|
)
|
||||||
CONFIG_SCHEMA = cv.Schema(
|
|
||||||
{
|
|
||||||
cv.GenerateID(): cv.declare_id(DallasComponent),
|
|
||||||
cv.Required(CONF_PIN): pins.internal_gpio_output_pin_schema,
|
|
||||||
}
|
|
||||||
).extend(cv.polling_component_schema("60s"))
|
|
||||||
|
|
||||||
|
|
||||||
async def to_code(config):
|
|
||||||
var = cg.new_Pvariable(config[CONF_ID])
|
|
||||||
await cg.register_component(var, config)
|
|
||||||
|
|
||||||
pin = await cg.gpio_pin_expression(config[CONF_PIN])
|
|
||||||
cg.add(var.set_pin(pin))
|
|
||||||
|
|
|
@ -1,287 +0,0 @@
|
||||||
#include "dallas_component.h"
|
|
||||||
#include "esphome/core/log.h"
|
|
||||||
|
|
||||||
namespace esphome {
|
|
||||||
namespace dallas {
|
|
||||||
|
|
||||||
static const char *const TAG = "dallas.sensor";
|
|
||||||
|
|
||||||
static const uint8_t DALLAS_MODEL_DS18S20 = 0x10;
|
|
||||||
static const uint8_t DALLAS_MODEL_DS1822 = 0x22;
|
|
||||||
static const uint8_t DALLAS_MODEL_DS18B20 = 0x28;
|
|
||||||
static const uint8_t DALLAS_MODEL_DS1825 = 0x3B;
|
|
||||||
static const uint8_t DALLAS_MODEL_DS28EA00 = 0x42;
|
|
||||||
static const uint8_t DALLAS_COMMAND_START_CONVERSION = 0x44;
|
|
||||||
static const uint8_t DALLAS_COMMAND_READ_SCRATCH_PAD = 0xBE;
|
|
||||||
static const uint8_t DALLAS_COMMAND_WRITE_SCRATCH_PAD = 0x4E;
|
|
||||||
|
|
||||||
uint16_t DallasTemperatureSensor::millis_to_wait_for_conversion() const {
|
|
||||||
switch (this->resolution_) {
|
|
||||||
case 9:
|
|
||||||
return 94;
|
|
||||||
case 10:
|
|
||||||
return 188;
|
|
||||||
case 11:
|
|
||||||
return 375;
|
|
||||||
default:
|
|
||||||
return 750;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void DallasComponent::setup() {
|
|
||||||
ESP_LOGCONFIG(TAG, "Setting up DallasComponent...");
|
|
||||||
|
|
||||||
pin_->setup();
|
|
||||||
|
|
||||||
// clear bus with 480µs high, otherwise initial reset in search_vec() fails
|
|
||||||
pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
|
|
||||||
delayMicroseconds(480);
|
|
||||||
|
|
||||||
one_wire_ = new ESPOneWire(pin_); // NOLINT(cppcoreguidelines-owning-memory)
|
|
||||||
|
|
||||||
std::vector<uint64_t> raw_sensors;
|
|
||||||
raw_sensors = this->one_wire_->search_vec();
|
|
||||||
|
|
||||||
for (auto &address : raw_sensors) {
|
|
||||||
auto *address8 = reinterpret_cast<uint8_t *>(&address);
|
|
||||||
if (crc8(address8, 7) != address8[7]) {
|
|
||||||
ESP_LOGW(TAG, "Dallas device 0x%s has invalid CRC.", format_hex(address).c_str());
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (address8[0] != DALLAS_MODEL_DS18S20 && address8[0] != DALLAS_MODEL_DS1822 &&
|
|
||||||
address8[0] != DALLAS_MODEL_DS18B20 && address8[0] != DALLAS_MODEL_DS1825 &&
|
|
||||||
address8[0] != DALLAS_MODEL_DS28EA00) {
|
|
||||||
ESP_LOGW(TAG, "Unknown device type 0x%02X.", address8[0]);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
this->found_sensors_.push_back(address);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto *sensor : this->sensors_) {
|
|
||||||
if (sensor->get_index().has_value()) {
|
|
||||||
if (*sensor->get_index() >= this->found_sensors_.size()) {
|
|
||||||
this->status_set_error("Sensor configured by index but not found");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
sensor->set_address(this->found_sensors_[*sensor->get_index()]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!sensor->setup_sensor()) {
|
|
||||||
this->status_set_error();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
void DallasComponent::dump_config() {
|
|
||||||
ESP_LOGCONFIG(TAG, "DallasComponent:");
|
|
||||||
LOG_PIN(" Pin: ", this->pin_);
|
|
||||||
LOG_UPDATE_INTERVAL(this);
|
|
||||||
|
|
||||||
if (this->found_sensors_.empty()) {
|
|
||||||
ESP_LOGW(TAG, " Found no sensors!");
|
|
||||||
} else {
|
|
||||||
ESP_LOGD(TAG, " Found sensors:");
|
|
||||||
for (auto &address : this->found_sensors_) {
|
|
||||||
ESP_LOGD(TAG, " 0x%s", format_hex(address).c_str());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto *sensor : this->sensors_) {
|
|
||||||
LOG_SENSOR(" ", "Device", sensor);
|
|
||||||
if (sensor->get_index().has_value()) {
|
|
||||||
ESP_LOGCONFIG(TAG, " Index %u", *sensor->get_index());
|
|
||||||
if (*sensor->get_index() >= this->found_sensors_.size()) {
|
|
||||||
ESP_LOGE(TAG, "Couldn't find sensor by index - not connected. Proceeding without it.");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ESP_LOGCONFIG(TAG, " Address: %s", sensor->get_address_name().c_str());
|
|
||||||
ESP_LOGCONFIG(TAG, " Resolution: %u", sensor->get_resolution());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void DallasComponent::register_sensor(DallasTemperatureSensor *sensor) { this->sensors_.push_back(sensor); }
|
|
||||||
void DallasComponent::update() {
|
|
||||||
this->status_clear_warning();
|
|
||||||
|
|
||||||
bool result;
|
|
||||||
{
|
|
||||||
InterruptLock lock;
|
|
||||||
result = this->one_wire_->reset();
|
|
||||||
}
|
|
||||||
if (!result) {
|
|
||||||
if (!this->found_sensors_.empty()) {
|
|
||||||
// Only log error if at the start sensors were found (and thus are disconnected during uptime)
|
|
||||||
ESP_LOGE(TAG, "Requesting conversion failed");
|
|
||||||
this->status_set_warning();
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto *sensor : this->sensors_) {
|
|
||||||
sensor->publish_state(NAN);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
InterruptLock lock;
|
|
||||||
this->one_wire_->skip();
|
|
||||||
this->one_wire_->write8(DALLAS_COMMAND_START_CONVERSION);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto *sensor : this->sensors_) {
|
|
||||||
if (sensor->get_address() == 0) {
|
|
||||||
ESP_LOGV(TAG, "'%s' - Indexed sensor not found at startup, skipping update", sensor->get_name().c_str());
|
|
||||||
sensor->publish_state(NAN);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
this->set_timeout(sensor->get_address_name(), sensor->millis_to_wait_for_conversion(), [this, sensor] {
|
|
||||||
bool res = sensor->read_scratch_pad();
|
|
||||||
|
|
||||||
if (!res) {
|
|
||||||
ESP_LOGW(TAG, "'%s' - Resetting bus for read failed!", sensor->get_name().c_str());
|
|
||||||
sensor->publish_state(NAN);
|
|
||||||
this->status_set_warning();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!sensor->check_scratch_pad()) {
|
|
||||||
sensor->publish_state(NAN);
|
|
||||||
this->status_set_warning();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
float tempc = sensor->get_temp_c();
|
|
||||||
ESP_LOGD(TAG, "'%s': Got Temperature=%.1f°C", sensor->get_name().c_str(), tempc);
|
|
||||||
sensor->publish_state(tempc);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void DallasTemperatureSensor::set_address(uint64_t address) { this->address_ = address; }
|
|
||||||
uint8_t DallasTemperatureSensor::get_resolution() const { return this->resolution_; }
|
|
||||||
void DallasTemperatureSensor::set_resolution(uint8_t resolution) { this->resolution_ = resolution; }
|
|
||||||
optional<uint8_t> DallasTemperatureSensor::get_index() const { return this->index_; }
|
|
||||||
void DallasTemperatureSensor::set_index(uint8_t index) { this->index_ = index; }
|
|
||||||
uint8_t *DallasTemperatureSensor::get_address8() { return reinterpret_cast<uint8_t *>(&this->address_); }
|
|
||||||
uint64_t DallasTemperatureSensor::get_address() { return this->address_; }
|
|
||||||
|
|
||||||
const std::string &DallasTemperatureSensor::get_address_name() {
|
|
||||||
if (this->address_name_.empty()) {
|
|
||||||
this->address_name_ = std::string("0x") + format_hex(this->address_);
|
|
||||||
}
|
|
||||||
|
|
||||||
return this->address_name_;
|
|
||||||
}
|
|
||||||
bool IRAM_ATTR DallasTemperatureSensor::read_scratch_pad() {
|
|
||||||
auto *wire = this->parent_->one_wire_;
|
|
||||||
|
|
||||||
{
|
|
||||||
InterruptLock lock;
|
|
||||||
|
|
||||||
if (!wire->reset()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
wire->select(this->address_);
|
|
||||||
wire->write8(DALLAS_COMMAND_READ_SCRATCH_PAD);
|
|
||||||
|
|
||||||
for (unsigned char &i : this->scratch_pad_) {
|
|
||||||
i = wire->read8();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
bool DallasTemperatureSensor::setup_sensor() {
|
|
||||||
bool r = this->read_scratch_pad();
|
|
||||||
|
|
||||||
if (!r) {
|
|
||||||
ESP_LOGE(TAG, "Reading scratchpad failed: reset");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!this->check_scratch_pad())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (this->scratch_pad_[4] == this->resolution_)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (this->get_address8()[0] == DALLAS_MODEL_DS18S20) {
|
|
||||||
// DS18S20 doesn't support resolution.
|
|
||||||
ESP_LOGW(TAG, "DS18S20 doesn't support setting resolution.");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (this->resolution_) {
|
|
||||||
case 12:
|
|
||||||
this->scratch_pad_[4] = 0x7F;
|
|
||||||
break;
|
|
||||||
case 11:
|
|
||||||
this->scratch_pad_[4] = 0x5F;
|
|
||||||
break;
|
|
||||||
case 10:
|
|
||||||
this->scratch_pad_[4] = 0x3F;
|
|
||||||
break;
|
|
||||||
case 9:
|
|
||||||
default:
|
|
||||||
this->scratch_pad_[4] = 0x1F;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto *wire = this->parent_->one_wire_;
|
|
||||||
{
|
|
||||||
InterruptLock lock;
|
|
||||||
if (wire->reset()) {
|
|
||||||
wire->select(this->address_);
|
|
||||||
wire->write8(DALLAS_COMMAND_WRITE_SCRATCH_PAD);
|
|
||||||
wire->write8(this->scratch_pad_[2]); // high alarm temp
|
|
||||||
wire->write8(this->scratch_pad_[3]); // low alarm temp
|
|
||||||
wire->write8(this->scratch_pad_[4]); // resolution
|
|
||||||
wire->reset();
|
|
||||||
|
|
||||||
// write value to EEPROM
|
|
||||||
wire->select(this->address_);
|
|
||||||
wire->write8(0x48);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
delay(20); // allow it to finish operation
|
|
||||||
wire->reset();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
bool DallasTemperatureSensor::check_scratch_pad() {
|
|
||||||
bool chksum_validity = (crc8(this->scratch_pad_, 8) == this->scratch_pad_[8]);
|
|
||||||
bool config_validity = false;
|
|
||||||
|
|
||||||
switch (this->get_address8()[0]) {
|
|
||||||
case DALLAS_MODEL_DS18B20:
|
|
||||||
config_validity = ((this->scratch_pad_[4] & 0x9F) == 0x1F);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
config_validity = ((this->scratch_pad_[4] & 0x10) == 0x10);
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef ESPHOME_LOG_LEVEL_VERY_VERBOSE
|
|
||||||
ESP_LOGVV(TAG, "Scratch pad: %02X.%02X.%02X.%02X.%02X.%02X.%02X.%02X.%02X (%02X)", this->scratch_pad_[0],
|
|
||||||
this->scratch_pad_[1], this->scratch_pad_[2], this->scratch_pad_[3], this->scratch_pad_[4],
|
|
||||||
this->scratch_pad_[5], this->scratch_pad_[6], this->scratch_pad_[7], this->scratch_pad_[8],
|
|
||||||
crc8(this->scratch_pad_, 8));
|
|
||||||
#endif
|
|
||||||
if (!chksum_validity) {
|
|
||||||
ESP_LOGW(TAG, "'%s' - Scratch pad checksum invalid!", this->get_name().c_str());
|
|
||||||
} else if (!config_validity) {
|
|
||||||
ESP_LOGW(TAG, "'%s' - Scratch pad config register invalid!", this->get_name().c_str());
|
|
||||||
}
|
|
||||||
return chksum_validity && config_validity;
|
|
||||||
}
|
|
||||||
float DallasTemperatureSensor::get_temp_c() {
|
|
||||||
int16_t temp = (int16_t(this->scratch_pad_[1]) << 11) | (int16_t(this->scratch_pad_[0]) << 3);
|
|
||||||
if (this->get_address8()[0] == DALLAS_MODEL_DS18S20) {
|
|
||||||
int diff = (this->scratch_pad_[7] - this->scratch_pad_[6]) << 7;
|
|
||||||
temp = ((temp & 0xFFF0) << 3) - 16 + (diff / this->scratch_pad_[7]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return temp / 128.0f;
|
|
||||||
}
|
|
||||||
std::string DallasTemperatureSensor::unique_id() { return "dallas-" + str_lower_case(format_hex(this->address_)); }
|
|
||||||
|
|
||||||
} // namespace dallas
|
|
||||||
} // namespace esphome
|
|
|
@ -1,79 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "esphome/core/component.h"
|
|
||||||
#include "esphome/components/sensor/sensor.h"
|
|
||||||
#include "esp_one_wire.h"
|
|
||||||
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace esphome {
|
|
||||||
namespace dallas {
|
|
||||||
|
|
||||||
class DallasTemperatureSensor;
|
|
||||||
|
|
||||||
class DallasComponent : public PollingComponent {
|
|
||||||
public:
|
|
||||||
void set_pin(InternalGPIOPin *pin) { pin_ = pin; }
|
|
||||||
void register_sensor(DallasTemperatureSensor *sensor);
|
|
||||||
|
|
||||||
void setup() override;
|
|
||||||
void dump_config() override;
|
|
||||||
float get_setup_priority() const override { return setup_priority::DATA; }
|
|
||||||
|
|
||||||
void update() override;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
friend DallasTemperatureSensor;
|
|
||||||
|
|
||||||
InternalGPIOPin *pin_;
|
|
||||||
ESPOneWire *one_wire_;
|
|
||||||
std::vector<DallasTemperatureSensor *> sensors_;
|
|
||||||
std::vector<uint64_t> found_sensors_;
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Internal class that helps us create multiple sensors for one Dallas hub.
|
|
||||||
class DallasTemperatureSensor : public sensor::Sensor {
|
|
||||||
public:
|
|
||||||
void set_parent(DallasComponent *parent) { parent_ = parent; }
|
|
||||||
/// Helper to get a pointer to the address as uint8_t.
|
|
||||||
uint8_t *get_address8();
|
|
||||||
uint64_t get_address();
|
|
||||||
/// Helper to create (and cache) the name for this sensor. For example "0xfe0000031f1eaf29".
|
|
||||||
const std::string &get_address_name();
|
|
||||||
|
|
||||||
/// Set the 64-bit unsigned address for this sensor.
|
|
||||||
void set_address(uint64_t address);
|
|
||||||
/// Get the index of this sensor. (0 if using address.)
|
|
||||||
optional<uint8_t> get_index() const;
|
|
||||||
/// Set the index of this sensor. If using index, address will be set after setup.
|
|
||||||
void set_index(uint8_t index);
|
|
||||||
/// Get the set resolution for this sensor.
|
|
||||||
uint8_t get_resolution() const;
|
|
||||||
/// Set the resolution for this sensor.
|
|
||||||
void set_resolution(uint8_t resolution);
|
|
||||||
/// Get the number of milliseconds we have to wait for the conversion phase.
|
|
||||||
uint16_t millis_to_wait_for_conversion() const;
|
|
||||||
|
|
||||||
bool setup_sensor();
|
|
||||||
bool read_scratch_pad();
|
|
||||||
|
|
||||||
bool check_scratch_pad();
|
|
||||||
|
|
||||||
float get_temp_c();
|
|
||||||
|
|
||||||
std::string unique_id() override;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
DallasComponent *parent_;
|
|
||||||
uint64_t address_;
|
|
||||||
optional<uint8_t> index_;
|
|
||||||
|
|
||||||
uint8_t resolution_;
|
|
||||||
std::string address_name_;
|
|
||||||
uint8_t scratch_pad_[9] = {
|
|
||||||
0,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace dallas
|
|
||||||
} // namespace esphome
|
|
|
@ -1,252 +0,0 @@
|
||||||
#include "esp_one_wire.h"
|
|
||||||
#include "esphome/core/log.h"
|
|
||||||
#include "esphome/core/helpers.h"
|
|
||||||
|
|
||||||
namespace esphome {
|
|
||||||
namespace dallas {
|
|
||||||
|
|
||||||
static const char *const TAG = "dallas.one_wire";
|
|
||||||
|
|
||||||
const uint8_t ONE_WIRE_ROM_SELECT = 0x55;
|
|
||||||
const int ONE_WIRE_ROM_SEARCH = 0xF0;
|
|
||||||
|
|
||||||
ESPOneWire::ESPOneWire(InternalGPIOPin *pin) { pin_ = pin->to_isr(); }
|
|
||||||
|
|
||||||
bool HOT IRAM_ATTR ESPOneWire::reset() {
|
|
||||||
// See reset here:
|
|
||||||
// https://www.maximintegrated.com/en/design/technical-documents/app-notes/1/126.html
|
|
||||||
// Wait for communication to clear (delay G)
|
|
||||||
pin_.pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
|
|
||||||
uint8_t retries = 125;
|
|
||||||
do {
|
|
||||||
if (--retries == 0)
|
|
||||||
return false;
|
|
||||||
delayMicroseconds(2);
|
|
||||||
} while (!pin_.digital_read());
|
|
||||||
|
|
||||||
// Send 480µs LOW TX reset pulse (drive bus low, delay H)
|
|
||||||
pin_.pin_mode(gpio::FLAG_OUTPUT);
|
|
||||||
pin_.digital_write(false);
|
|
||||||
delayMicroseconds(480);
|
|
||||||
|
|
||||||
// Release the bus, delay I
|
|
||||||
pin_.pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
|
|
||||||
delayMicroseconds(70);
|
|
||||||
|
|
||||||
// sample bus, 0=device(s) present, 1=no device present
|
|
||||||
bool r = !pin_.digital_read();
|
|
||||||
// delay J
|
|
||||||
delayMicroseconds(410);
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
|
|
||||||
void HOT IRAM_ATTR ESPOneWire::write_bit(bool bit) {
|
|
||||||
// drive bus low
|
|
||||||
pin_.pin_mode(gpio::FLAG_OUTPUT);
|
|
||||||
pin_.digital_write(false);
|
|
||||||
|
|
||||||
// from datasheet:
|
|
||||||
// write 0 low time: t_low0: min=60µs, max=120µs
|
|
||||||
// write 1 low time: t_low1: min=1µs, max=15µs
|
|
||||||
// time slot: t_slot: min=60µs, max=120µs
|
|
||||||
// recovery time: t_rec: min=1µs
|
|
||||||
// ds18b20 appears to read the bus after roughly 14µs
|
|
||||||
uint32_t delay0 = bit ? 6 : 60;
|
|
||||||
uint32_t delay1 = bit ? 54 : 5;
|
|
||||||
|
|
||||||
// delay A/C
|
|
||||||
delayMicroseconds(delay0);
|
|
||||||
// release bus
|
|
||||||
pin_.digital_write(true);
|
|
||||||
// delay B/D
|
|
||||||
delayMicroseconds(delay1);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool HOT IRAM_ATTR ESPOneWire::read_bit() {
|
|
||||||
// drive bus low
|
|
||||||
pin_.pin_mode(gpio::FLAG_OUTPUT);
|
|
||||||
pin_.digital_write(false);
|
|
||||||
|
|
||||||
// note: for reading we'll need very accurate timing, as the
|
|
||||||
// timing for the digital_read() is tight; according to the datasheet,
|
|
||||||
// we should read at the end of 16µs starting from the bus low
|
|
||||||
// typically, the ds18b20 pulls the line high after 11µs for a logical 1
|
|
||||||
// and 29µs for a logical 0
|
|
||||||
|
|
||||||
uint32_t start = micros();
|
|
||||||
// datasheet says >1µs
|
|
||||||
delayMicroseconds(3);
|
|
||||||
|
|
||||||
// release bus, delay E
|
|
||||||
pin_.pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
|
|
||||||
|
|
||||||
// Unfortunately some frameworks have different characteristics than others
|
|
||||||
// esp32 arduino appears to pull the bus low only after the digital_write(false),
|
|
||||||
// whereas on esp-idf it already happens during the pin_mode(OUTPUT)
|
|
||||||
// manually correct for this with these constants.
|
|
||||||
|
|
||||||
#ifdef USE_ESP32
|
|
||||||
uint32_t timing_constant = 12;
|
|
||||||
#else
|
|
||||||
uint32_t timing_constant = 14;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// measure from start value directly, to get best accurate timing no matter
|
|
||||||
// how long pin_mode/delayMicroseconds took
|
|
||||||
while (micros() - start < timing_constant)
|
|
||||||
;
|
|
||||||
|
|
||||||
// sample bus to read bit from peer
|
|
||||||
bool r = pin_.digital_read();
|
|
||||||
|
|
||||||
// read slot is at least 60µs; get as close to 60µs to spend less time with interrupts locked
|
|
||||||
uint32_t now = micros();
|
|
||||||
if (now - start < 60)
|
|
||||||
delayMicroseconds(60 - (now - start));
|
|
||||||
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
|
|
||||||
void IRAM_ATTR ESPOneWire::write8(uint8_t val) {
|
|
||||||
for (uint8_t i = 0; i < 8; i++) {
|
|
||||||
this->write_bit(bool((1u << i) & val));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void IRAM_ATTR ESPOneWire::write64(uint64_t val) {
|
|
||||||
for (uint8_t i = 0; i < 64; i++) {
|
|
||||||
this->write_bit(bool((1ULL << i) & val));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t IRAM_ATTR ESPOneWire::read8() {
|
|
||||||
uint8_t ret = 0;
|
|
||||||
for (uint8_t i = 0; i < 8; i++) {
|
|
||||||
ret |= (uint8_t(this->read_bit()) << i);
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
uint64_t IRAM_ATTR ESPOneWire::read64() {
|
|
||||||
uint64_t ret = 0;
|
|
||||||
for (uint8_t i = 0; i < 8; i++) {
|
|
||||||
ret |= (uint64_t(this->read_bit()) << i);
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
void IRAM_ATTR ESPOneWire::select(uint64_t address) {
|
|
||||||
this->write8(ONE_WIRE_ROM_SELECT);
|
|
||||||
this->write64(address);
|
|
||||||
}
|
|
||||||
void IRAM_ATTR ESPOneWire::reset_search() {
|
|
||||||
this->last_discrepancy_ = 0;
|
|
||||||
this->last_device_flag_ = false;
|
|
||||||
this->rom_number_ = 0;
|
|
||||||
}
|
|
||||||
uint64_t IRAM_ATTR ESPOneWire::search() {
|
|
||||||
if (this->last_device_flag_) {
|
|
||||||
return 0u;
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
InterruptLock lock;
|
|
||||||
if (!this->reset()) {
|
|
||||||
// Reset failed or no devices present
|
|
||||||
this->reset_search();
|
|
||||||
return 0u;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t id_bit_number = 1;
|
|
||||||
uint8_t last_zero = 0;
|
|
||||||
uint8_t rom_byte_number = 0;
|
|
||||||
bool search_result = false;
|
|
||||||
uint8_t rom_byte_mask = 1;
|
|
||||||
|
|
||||||
{
|
|
||||||
InterruptLock lock;
|
|
||||||
// Initiate search
|
|
||||||
this->write8(ONE_WIRE_ROM_SEARCH);
|
|
||||||
do {
|
|
||||||
// read bit
|
|
||||||
bool id_bit = this->read_bit();
|
|
||||||
// read its complement
|
|
||||||
bool cmp_id_bit = this->read_bit();
|
|
||||||
|
|
||||||
if (id_bit && cmp_id_bit) {
|
|
||||||
// No devices participating in search
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool branch;
|
|
||||||
|
|
||||||
if (id_bit != cmp_id_bit) {
|
|
||||||
// only chose one branch, the other one doesn't have any devices.
|
|
||||||
branch = id_bit;
|
|
||||||
} else {
|
|
||||||
// there are devices with both 0s and 1s at this bit
|
|
||||||
if (id_bit_number < this->last_discrepancy_) {
|
|
||||||
branch = (this->rom_number8_()[rom_byte_number] & rom_byte_mask) > 0;
|
|
||||||
} else {
|
|
||||||
branch = id_bit_number == this->last_discrepancy_;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!branch) {
|
|
||||||
last_zero = id_bit_number;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (branch) {
|
|
||||||
// set bit
|
|
||||||
this->rom_number8_()[rom_byte_number] |= rom_byte_mask;
|
|
||||||
} else {
|
|
||||||
// clear bit
|
|
||||||
this->rom_number8_()[rom_byte_number] &= ~rom_byte_mask;
|
|
||||||
}
|
|
||||||
|
|
||||||
// choose/announce branch
|
|
||||||
this->write_bit(branch);
|
|
||||||
id_bit_number++;
|
|
||||||
rom_byte_mask <<= 1;
|
|
||||||
if (rom_byte_mask == 0u) {
|
|
||||||
// go to next byte
|
|
||||||
rom_byte_number++;
|
|
||||||
rom_byte_mask = 1;
|
|
||||||
}
|
|
||||||
} while (rom_byte_number < 8); // loop through all bytes
|
|
||||||
}
|
|
||||||
|
|
||||||
if (id_bit_number >= 65) {
|
|
||||||
this->last_discrepancy_ = last_zero;
|
|
||||||
if (this->last_discrepancy_ == 0) {
|
|
||||||
// we're at root and have no choices left, so this was the last one.
|
|
||||||
this->last_device_flag_ = true;
|
|
||||||
}
|
|
||||||
search_result = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
search_result = search_result && (this->rom_number8_()[0] != 0);
|
|
||||||
if (!search_result) {
|
|
||||||
this->reset_search();
|
|
||||||
return 0u;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this->rom_number_;
|
|
||||||
}
|
|
||||||
std::vector<uint64_t> ESPOneWire::search_vec() {
|
|
||||||
std::vector<uint64_t> res;
|
|
||||||
|
|
||||||
this->reset_search();
|
|
||||||
uint64_t address;
|
|
||||||
while ((address = this->search()) != 0u)
|
|
||||||
res.push_back(address);
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
void IRAM_ATTR ESPOneWire::skip() {
|
|
||||||
this->write8(0xCC); // skip ROM
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t IRAM_ATTR *ESPOneWire::rom_number8_() { return reinterpret_cast<uint8_t *>(&this->rom_number_); }
|
|
||||||
|
|
||||||
} // namespace dallas
|
|
||||||
} // namespace esphome
|
|
|
@ -1,68 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "esphome/core/hal.h"
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace esphome {
|
|
||||||
namespace dallas {
|
|
||||||
|
|
||||||
extern const uint8_t ONE_WIRE_ROM_SELECT;
|
|
||||||
extern const int ONE_WIRE_ROM_SEARCH;
|
|
||||||
|
|
||||||
class ESPOneWire {
|
|
||||||
public:
|
|
||||||
explicit ESPOneWire(InternalGPIOPin *pin);
|
|
||||||
|
|
||||||
/** Reset the bus, should be done before all write operations.
|
|
||||||
*
|
|
||||||
* Takes approximately 1ms.
|
|
||||||
*
|
|
||||||
* @return Whether the operation was successful.
|
|
||||||
*/
|
|
||||||
bool reset();
|
|
||||||
|
|
||||||
/// Write a single bit to the bus, takes about 70µs.
|
|
||||||
void write_bit(bool bit);
|
|
||||||
|
|
||||||
/// Read a single bit from the bus, takes about 70µs
|
|
||||||
bool read_bit();
|
|
||||||
|
|
||||||
/// Write a word to the bus. LSB first.
|
|
||||||
void write8(uint8_t val);
|
|
||||||
|
|
||||||
/// Write a 64 bit unsigned integer to the bus. LSB first.
|
|
||||||
void write64(uint64_t val);
|
|
||||||
|
|
||||||
/// Write a command to the bus that addresses all devices by skipping the ROM.
|
|
||||||
void skip();
|
|
||||||
|
|
||||||
/// Read an 8 bit word from the bus.
|
|
||||||
uint8_t read8();
|
|
||||||
|
|
||||||
/// Read an 64-bit unsigned integer from the bus.
|
|
||||||
uint64_t read64();
|
|
||||||
|
|
||||||
/// Select a specific address on the bus for the following command.
|
|
||||||
void select(uint64_t address);
|
|
||||||
|
|
||||||
/// Reset the device search.
|
|
||||||
void reset_search();
|
|
||||||
|
|
||||||
/// Search for a 1-Wire device on the bus. Returns 0 if all devices have been found.
|
|
||||||
uint64_t search();
|
|
||||||
|
|
||||||
/// Helper that wraps search in a std::vector.
|
|
||||||
std::vector<uint64_t> search_vec();
|
|
||||||
|
|
||||||
protected:
|
|
||||||
/// Helper to get the internal 64-bit unsigned rom number as a 8-bit integer pointer.
|
|
||||||
inline uint8_t *rom_number8_();
|
|
||||||
|
|
||||||
ISRInternalGPIOPin pin_;
|
|
||||||
uint8_t last_discrepancy_{0};
|
|
||||||
bool last_device_flag_{false};
|
|
||||||
uint64_t rom_number_{0};
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace dallas
|
|
||||||
} // namespace esphome
|
|
|
@ -1,50 +1,5 @@
|
||||||
import esphome.codegen as cg
|
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.components import sensor
|
|
||||||
from esphome.const import (
|
CONFIG_SCHEMA = cv.invalid(
|
||||||
CONF_ADDRESS,
|
'The "dallas" sensor is now "dallas_temp"\nhttps://esphome.io/components/sensor/dallas_temp'
|
||||||
CONF_DALLAS_ID,
|
|
||||||
CONF_INDEX,
|
|
||||||
CONF_RESOLUTION,
|
|
||||||
DEVICE_CLASS_TEMPERATURE,
|
|
||||||
STATE_CLASS_MEASUREMENT,
|
|
||||||
UNIT_CELSIUS,
|
|
||||||
)
|
)
|
||||||
from . import DallasComponent, dallas_ns
|
|
||||||
|
|
||||||
DallasTemperatureSensor = dallas_ns.class_("DallasTemperatureSensor", sensor.Sensor)
|
|
||||||
|
|
||||||
CONFIG_SCHEMA = cv.All(
|
|
||||||
sensor.sensor_schema(
|
|
||||||
DallasTemperatureSensor,
|
|
||||||
unit_of_measurement=UNIT_CELSIUS,
|
|
||||||
accuracy_decimals=1,
|
|
||||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
|
||||||
state_class=STATE_CLASS_MEASUREMENT,
|
|
||||||
).extend(
|
|
||||||
{
|
|
||||||
cv.GenerateID(CONF_DALLAS_ID): cv.use_id(DallasComponent),
|
|
||||||
cv.Optional(CONF_ADDRESS): cv.hex_uint64_t,
|
|
||||||
cv.Optional(CONF_INDEX): cv.positive_int,
|
|
||||||
cv.Optional(CONF_RESOLUTION, default=12): cv.int_range(min=9, max=12),
|
|
||||||
}
|
|
||||||
),
|
|
||||||
cv.has_exactly_one_key(CONF_ADDRESS, CONF_INDEX),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
async def to_code(config):
|
|
||||||
hub = await cg.get_variable(config[CONF_DALLAS_ID])
|
|
||||||
var = await sensor.new_sensor(config)
|
|
||||||
|
|
||||||
if CONF_ADDRESS in config:
|
|
||||||
cg.add(var.set_address(config[CONF_ADDRESS]))
|
|
||||||
else:
|
|
||||||
cg.add(var.set_index(config[CONF_INDEX]))
|
|
||||||
|
|
||||||
if CONF_RESOLUTION in config:
|
|
||||||
cg.add(var.set_resolution(config[CONF_RESOLUTION]))
|
|
||||||
|
|
||||||
cg.add(var.set_parent(hub))
|
|
||||||
|
|
||||||
cg.add(hub.register_sensor(var))
|
|
||||||
|
|
1
esphome/components/dallas_temp/__init__.py
Normal file
1
esphome/components/dallas_temp/__init__.py
Normal file
|
@ -0,0 +1 @@
|
||||||
|
CODEOWNERS = ["@ssieb"]
|
169
esphome/components/dallas_temp/dallas_temp.cpp
Normal file
169
esphome/components/dallas_temp/dallas_temp.cpp
Normal file
|
@ -0,0 +1,169 @@
|
||||||
|
#include "dallas_temp.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace dallas_temp {
|
||||||
|
|
||||||
|
static const char *const TAG = "dallas.temp.sensor";
|
||||||
|
|
||||||
|
static const uint8_t DALLAS_MODEL_DS18S20 = 0x10;
|
||||||
|
static const uint8_t DALLAS_COMMAND_START_CONVERSION = 0x44;
|
||||||
|
static const uint8_t DALLAS_COMMAND_READ_SCRATCH_PAD = 0xBE;
|
||||||
|
static const uint8_t DALLAS_COMMAND_WRITE_SCRATCH_PAD = 0x4E;
|
||||||
|
static const uint8_t DALLAS_COMMAND_COPY_SCRATCH_PAD = 0x48;
|
||||||
|
|
||||||
|
uint16_t DallasTemperatureSensor::millis_to_wait_for_conversion_() const {
|
||||||
|
switch (this->resolution_) {
|
||||||
|
case 9:
|
||||||
|
return 94;
|
||||||
|
case 10:
|
||||||
|
return 188;
|
||||||
|
case 11:
|
||||||
|
return 375;
|
||||||
|
default:
|
||||||
|
return 750;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DallasTemperatureSensor::dump_config() {
|
||||||
|
ESP_LOGCONFIG(TAG, "Dallas Temperature Sensor:");
|
||||||
|
if (this->address_ == 0) {
|
||||||
|
ESP_LOGW(TAG, " Unable to select an address");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
LOG_ONE_WIRE_DEVICE(this);
|
||||||
|
ESP_LOGCONFIG(TAG, " Resolution: %u bits", this->resolution_);
|
||||||
|
LOG_UPDATE_INTERVAL(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DallasTemperatureSensor::update() {
|
||||||
|
if (this->address_ == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
this->status_clear_warning();
|
||||||
|
|
||||||
|
this->send_command_(DALLAS_COMMAND_START_CONVERSION);
|
||||||
|
|
||||||
|
this->set_timeout(this->get_address_name(), this->millis_to_wait_for_conversion_(), [this] {
|
||||||
|
if (!this->read_scratch_pad_() || !this->check_scratch_pad_()) {
|
||||||
|
this->publish_state(NAN);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
float tempc = this->get_temp_c_();
|
||||||
|
ESP_LOGD(TAG, "'%s': Got Temperature=%.1f°C", this->get_name().c_str(), tempc);
|
||||||
|
this->publish_state(tempc);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void IRAM_ATTR DallasTemperatureSensor::read_scratch_pad_int_() {
|
||||||
|
for (uint8_t &i : this->scratch_pad_) {
|
||||||
|
i = this->bus_->read8();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DallasTemperatureSensor::read_scratch_pad_() {
|
||||||
|
bool success;
|
||||||
|
{
|
||||||
|
InterruptLock lock;
|
||||||
|
success = this->send_command_(DALLAS_COMMAND_READ_SCRATCH_PAD);
|
||||||
|
if (success)
|
||||||
|
this->read_scratch_pad_int_();
|
||||||
|
}
|
||||||
|
if (!success) {
|
||||||
|
ESP_LOGW(TAG, "'%s' - reading scratch pad failed bus reset", this->get_name().c_str());
|
||||||
|
this->status_set_warning("bus reset failed");
|
||||||
|
}
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DallasTemperatureSensor::setup() {
|
||||||
|
ESP_LOGCONFIG(TAG, "setting up Dallas temperature sensor...");
|
||||||
|
if (!this->check_address_())
|
||||||
|
return;
|
||||||
|
if (!this->read_scratch_pad_())
|
||||||
|
return;
|
||||||
|
if (!this->check_scratch_pad_())
|
||||||
|
return;
|
||||||
|
|
||||||
|
if ((this->address_ & 0xff) == DALLAS_MODEL_DS18S20) {
|
||||||
|
// DS18S20 doesn't support resolution.
|
||||||
|
ESP_LOGW(TAG, "DS18S20 doesn't support setting resolution.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t res;
|
||||||
|
switch (this->resolution_) {
|
||||||
|
case 12:
|
||||||
|
res = 0x7F;
|
||||||
|
break;
|
||||||
|
case 11:
|
||||||
|
res = 0x5F;
|
||||||
|
break;
|
||||||
|
case 10:
|
||||||
|
res = 0x3F;
|
||||||
|
break;
|
||||||
|
case 9:
|
||||||
|
default:
|
||||||
|
res = 0x1F;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this->scratch_pad_[4] == res)
|
||||||
|
return;
|
||||||
|
this->scratch_pad_[4] = res;
|
||||||
|
|
||||||
|
{
|
||||||
|
InterruptLock lock;
|
||||||
|
if (this->send_command_(DALLAS_COMMAND_WRITE_SCRATCH_PAD)) {
|
||||||
|
this->bus_->write8(this->scratch_pad_[2]); // high alarm temp
|
||||||
|
this->bus_->write8(this->scratch_pad_[3]); // low alarm temp
|
||||||
|
this->bus_->write8(this->scratch_pad_[4]); // resolution
|
||||||
|
}
|
||||||
|
|
||||||
|
// write value to EEPROM
|
||||||
|
this->send_command_(DALLAS_COMMAND_COPY_SCRATCH_PAD);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DallasTemperatureSensor::check_scratch_pad_() {
|
||||||
|
bool chksum_validity = (crc8(this->scratch_pad_, 8) == this->scratch_pad_[8]);
|
||||||
|
|
||||||
|
#ifdef ESPHOME_LOG_LEVEL_VERY_VERBOSE
|
||||||
|
ESP_LOGVV(TAG, "Scratch pad: %02X.%02X.%02X.%02X.%02X.%02X.%02X.%02X.%02X (%02X)", this->scratch_pad_[0],
|
||||||
|
this->scratch_pad_[1], this->scratch_pad_[2], this->scratch_pad_[3], this->scratch_pad_[4],
|
||||||
|
this->scratch_pad_[5], this->scratch_pad_[6], this->scratch_pad_[7], this->scratch_pad_[8],
|
||||||
|
crc8(this->scratch_pad_, 8));
|
||||||
|
#endif
|
||||||
|
if (!chksum_validity) {
|
||||||
|
ESP_LOGW(TAG, "'%s' - Scratch pad checksum invalid!", this->get_name().c_str());
|
||||||
|
this->status_set_warning("scratch pad checksum invalid");
|
||||||
|
}
|
||||||
|
return chksum_validity;
|
||||||
|
}
|
||||||
|
|
||||||
|
float DallasTemperatureSensor::get_temp_c_() {
|
||||||
|
int16_t temp = (this->scratch_pad_[1] << 8) | this->scratch_pad_[0];
|
||||||
|
if ((this->address_ & 0xff) == DALLAS_MODEL_DS18S20) {
|
||||||
|
return (temp >> 1) + (this->scratch_pad_[7] - this->scratch_pad_[6]) / float(this->scratch_pad_[7]) - 0.25;
|
||||||
|
}
|
||||||
|
switch (this->resolution_) {
|
||||||
|
case 9:
|
||||||
|
temp &= 0xfff8;
|
||||||
|
break;
|
||||||
|
case 10:
|
||||||
|
temp &= 0xfffc;
|
||||||
|
break;
|
||||||
|
case 11:
|
||||||
|
temp &= 0xfffe;
|
||||||
|
break;
|
||||||
|
case 12:
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return temp / 16.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace dallas_temp
|
||||||
|
} // namespace esphome
|
32
esphome/components/dallas_temp/dallas_temp.h
Normal file
32
esphome/components/dallas_temp/dallas_temp.h
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
#include "esphome/components/sensor/sensor.h"
|
||||||
|
#include "esphome/components/one_wire/one_wire.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace dallas_temp {
|
||||||
|
|
||||||
|
class DallasTemperatureSensor : public PollingComponent, public sensor::Sensor, public one_wire::OneWireDevice {
|
||||||
|
public:
|
||||||
|
void setup() override;
|
||||||
|
void update() override;
|
||||||
|
void dump_config() override;
|
||||||
|
|
||||||
|
/// Set the resolution for this sensor.
|
||||||
|
void set_resolution(uint8_t resolution) { this->resolution_ = resolution; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
uint8_t resolution_;
|
||||||
|
uint8_t scratch_pad_[9] = {0};
|
||||||
|
|
||||||
|
/// Get the number of milliseconds we have to wait for the conversion phase.
|
||||||
|
uint16_t millis_to_wait_for_conversion_() const;
|
||||||
|
bool read_scratch_pad_();
|
||||||
|
void read_scratch_pad_int_();
|
||||||
|
bool check_scratch_pad_();
|
||||||
|
float get_temp_c_();
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace dallas_temp
|
||||||
|
} // namespace esphome
|
43
esphome/components/dallas_temp/sensor.py
Normal file
43
esphome/components/dallas_temp/sensor.py
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
import esphome.codegen as cg
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
from esphome.components import one_wire, sensor
|
||||||
|
from esphome.const import (
|
||||||
|
CONF_RESOLUTION,
|
||||||
|
DEVICE_CLASS_TEMPERATURE,
|
||||||
|
STATE_CLASS_MEASUREMENT,
|
||||||
|
UNIT_CELSIUS,
|
||||||
|
)
|
||||||
|
|
||||||
|
dallas_temp_ns = cg.esphome_ns.namespace("dallas_temp")
|
||||||
|
|
||||||
|
DallasTemperatureSensor = dallas_temp_ns.class_(
|
||||||
|
"DallasTemperatureSensor",
|
||||||
|
cg.PollingComponent,
|
||||||
|
sensor.Sensor,
|
||||||
|
one_wire.OneWireDevice,
|
||||||
|
)
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = (
|
||||||
|
sensor.sensor_schema(
|
||||||
|
DallasTemperatureSensor,
|
||||||
|
unit_of_measurement=UNIT_CELSIUS,
|
||||||
|
accuracy_decimals=1,
|
||||||
|
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||||
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
|
)
|
||||||
|
.extend(
|
||||||
|
{
|
||||||
|
cv.Optional(CONF_RESOLUTION, default=12): cv.int_range(min=9, max=12),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.extend(one_wire.one_wire_device_schema())
|
||||||
|
.extend(cv.polling_component_schema("60s"))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def to_code(config):
|
||||||
|
var = await sensor.new_sensor(config)
|
||||||
|
await cg.register_component(var, config)
|
||||||
|
await one_wire.register_one_wire_device(var, config)
|
||||||
|
|
||||||
|
cg.add(var.set_resolution(config[CONF_RESOLUTION]))
|
|
@ -80,6 +80,17 @@ void DateCall::validate_() {
|
||||||
|
|
||||||
void DateCall::perform() {
|
void DateCall::perform() {
|
||||||
this->validate_();
|
this->validate_();
|
||||||
|
ESP_LOGD(TAG, "'%s' - Setting", this->parent_->get_name().c_str());
|
||||||
|
|
||||||
|
if (this->year_.has_value()) {
|
||||||
|
ESP_LOGD(TAG, " Year: %d", *this->year_);
|
||||||
|
}
|
||||||
|
if (this->month_.has_value()) {
|
||||||
|
ESP_LOGD(TAG, " Month: %d", *this->month_);
|
||||||
|
}
|
||||||
|
if (this->day_.has_value()) {
|
||||||
|
ESP_LOGD(TAG, " Day: %d", *this->day_);
|
||||||
|
}
|
||||||
this->parent_->control(*this);
|
this->parent_->control(*this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,7 @@ std::string DebugComponent::get_reset_reason_() { return lt_get_reboot_reason_na
|
||||||
uint32_t DebugComponent::get_free_heap_() { return lt_heap_get_free(); }
|
uint32_t DebugComponent::get_free_heap_() { return lt_heap_get_free(); }
|
||||||
|
|
||||||
void DebugComponent::get_device_info_(std::string &device_info) {
|
void DebugComponent::get_device_info_(std::string &device_info) {
|
||||||
str::string reset_reason = get_reset_reason_();
|
std::string reset_reason = get_reset_reason_();
|
||||||
ESP_LOGD(TAG, "LibreTiny Version: %s", lt_get_version());
|
ESP_LOGD(TAG, "LibreTiny Version: %s", lt_get_version());
|
||||||
ESP_LOGD(TAG, "Chip: %s (%04x) @ %u MHz", lt_cpu_get_model_name(), lt_cpu_get_model(), lt_cpu_get_freq_mhz());
|
ESP_LOGD(TAG, "Chip: %s (%04x) @ %u MHz", lt_cpu_get_model_name(), lt_cpu_get_model(), lt_cpu_get_freq_mhz());
|
||||||
ESP_LOGD(TAG, "Chip ID: 0x%06X", lt_cpu_get_mac_id());
|
ESP_LOGD(TAG, "Chip ID: 0x%06X", lt_cpu_get_mac_id());
|
||||||
|
|
|
@ -34,10 +34,12 @@ enum WakeupPinMode {
|
||||||
WAKEUP_PIN_MODE_INVERT_WAKEUP,
|
WAKEUP_PIN_MODE_INVERT_WAKEUP,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#if defined(USE_ESP32) && !defined(USE_ESP32_VARIANT_ESP32C3)
|
||||||
struct Ext1Wakeup {
|
struct Ext1Wakeup {
|
||||||
uint64_t mask;
|
uint64_t mask;
|
||||||
esp_sleep_ext1_wakeup_mode_t wakeup_mode;
|
esp_sleep_ext1_wakeup_mode_t wakeup_mode;
|
||||||
};
|
};
|
||||||
|
#endif
|
||||||
|
|
||||||
struct WakeupCauseToRunDuration {
|
struct WakeupCauseToRunDuration {
|
||||||
// Run duration if woken up by timer or any other reason besides those below.
|
// Run duration if woken up by timer or any other reason besides those below.
|
||||||
|
@ -114,7 +116,11 @@ class DeepSleepComponent : public Component {
|
||||||
#ifdef USE_ESP32
|
#ifdef USE_ESP32
|
||||||
InternalGPIOPin *wakeup_pin_;
|
InternalGPIOPin *wakeup_pin_;
|
||||||
WakeupPinMode wakeup_pin_mode_{WAKEUP_PIN_MODE_IGNORE};
|
WakeupPinMode wakeup_pin_mode_{WAKEUP_PIN_MODE_IGNORE};
|
||||||
|
|
||||||
|
#if !defined(USE_ESP32_VARIANT_ESP32C3)
|
||||||
optional<Ext1Wakeup> ext1_wakeup_;
|
optional<Ext1Wakeup> ext1_wakeup_;
|
||||||
|
#endif
|
||||||
|
|
||||||
optional<bool> touch_wakeup_;
|
optional<bool> touch_wakeup_;
|
||||||
optional<WakeupCauseToRunDuration> wakeup_cause_to_run_duration_;
|
optional<WakeupCauseToRunDuration> wakeup_cause_to_run_duration_;
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -37,14 +37,18 @@ void DS1307Component::read_time() {
|
||||||
ESP_LOGW(TAG, "RTC halted, not syncing to system clock.");
|
ESP_LOGW(TAG, "RTC halted, not syncing to system clock.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ESPTime rtc_time{.second = uint8_t(ds1307_.reg.second + 10 * ds1307_.reg.second_10),
|
ESPTime rtc_time{
|
||||||
.minute = uint8_t(ds1307_.reg.minute + 10u * ds1307_.reg.minute_10),
|
.second = uint8_t(ds1307_.reg.second + 10 * ds1307_.reg.second_10),
|
||||||
.hour = uint8_t(ds1307_.reg.hour + 10u * ds1307_.reg.hour_10),
|
.minute = uint8_t(ds1307_.reg.minute + 10u * ds1307_.reg.minute_10),
|
||||||
.day_of_week = uint8_t(ds1307_.reg.weekday),
|
.hour = uint8_t(ds1307_.reg.hour + 10u * ds1307_.reg.hour_10),
|
||||||
.day_of_month = uint8_t(ds1307_.reg.day + 10u * ds1307_.reg.day_10),
|
.day_of_week = uint8_t(ds1307_.reg.weekday),
|
||||||
.day_of_year = 1, // ignored by recalc_timestamp_utc(false)
|
.day_of_month = uint8_t(ds1307_.reg.day + 10u * ds1307_.reg.day_10),
|
||||||
.month = uint8_t(ds1307_.reg.month + 10u * ds1307_.reg.month_10),
|
.day_of_year = 1, // ignored by recalc_timestamp_utc(false)
|
||||||
.year = uint16_t(ds1307_.reg.year + 10u * ds1307_.reg.year_10 + 2000)};
|
.month = uint8_t(ds1307_.reg.month + 10u * ds1307_.reg.month_10),
|
||||||
|
.year = uint16_t(ds1307_.reg.year + 10u * ds1307_.reg.year_10 + 2000),
|
||||||
|
.is_dst = false, // not used
|
||||||
|
.timestamp = 0 // overwritten by recalc_timestamp_utc(false)
|
||||||
|
};
|
||||||
rtc_time.recalc_timestamp_utc(false);
|
rtc_time.recalc_timestamp_utc(false);
|
||||||
if (!rtc_time.is_valid()) {
|
if (!rtc_time.is_valid()) {
|
||||||
ESP_LOGE(TAG, "Invalid RTC time, not syncing to system clock.");
|
ESP_LOGE(TAG, "Invalid RTC time, not syncing to system clock.");
|
||||||
|
|
|
@ -96,16 +96,16 @@ def get_board(core_obj=None):
|
||||||
def get_download_types(storage_json):
|
def get_download_types(storage_json):
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
"title": "Modern format",
|
"title": "Factory format (Previously Modern)",
|
||||||
"description": "For use with ESPHome Web and other tools.",
|
"description": "For use with ESPHome Web and other tools.",
|
||||||
"file": "firmware-factory.bin",
|
"file": "firmware.factory.bin",
|
||||||
"download": f"{storage_json.name}-factory.bin",
|
"download": f"{storage_json.name}.factory.bin",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "Legacy format",
|
"title": "OTA format (Previously Legacy)",
|
||||||
"description": "For use with ESPHome Flasher.",
|
"description": "For OTA updating a device.",
|
||||||
"file": "firmware.bin",
|
"file": "firmware.ota.bin",
|
||||||
"download": f"{storage_json.name}.bin",
|
"download": f"{storage_json.name}.ota.bin",
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -17,17 +17,19 @@ from SCons.Script import ARGUMENTS
|
||||||
|
|
||||||
# Copy over the default sdkconfig.
|
# Copy over the default sdkconfig.
|
||||||
from os import path
|
from os import path
|
||||||
|
|
||||||
if path.exists("./sdkconfig.defaults"):
|
if path.exists("./sdkconfig.defaults"):
|
||||||
os.makedirs(".temp", exist_ok=True)
|
os.makedirs(".temp", exist_ok=True)
|
||||||
shutil.copy("./sdkconfig.defaults", "./.temp/sdkconfig-esp32-idf")
|
shutil.copy("./sdkconfig.defaults", "./.temp/sdkconfig-esp32-idf")
|
||||||
|
|
||||||
|
|
||||||
def esp32_create_combined_bin(source, target, env):
|
def esp32_create_combined_bin(source, target, env):
|
||||||
verbose = bool(int(ARGUMENTS.get("PIOVERBOSE", "0")))
|
verbose = bool(int(ARGUMENTS.get("PIOVERBOSE", "0")))
|
||||||
if verbose:
|
if verbose:
|
||||||
print("Generating combined binary for serial flashing")
|
print("Generating combined binary for serial flashing")
|
||||||
app_offset = 0x10000
|
app_offset = 0x10000
|
||||||
|
|
||||||
new_file_name = env.subst("$BUILD_DIR/${PROGNAME}-factory.bin")
|
new_file_name = env.subst("$BUILD_DIR/${PROGNAME}.factory.bin")
|
||||||
sections = env.subst(env.get("FLASH_EXTRA_IMAGES"))
|
sections = env.subst(env.get("FLASH_EXTRA_IMAGES"))
|
||||||
firmware_name = env.subst("$BUILD_DIR/${PROGNAME}.bin")
|
firmware_name = env.subst("$BUILD_DIR/${PROGNAME}.bin")
|
||||||
chip = env.get("BOARD_MCU")
|
chip = env.get("BOARD_MCU")
|
||||||
|
@ -62,5 +64,14 @@ def esp32_create_combined_bin(source, target, env):
|
||||||
else:
|
else:
|
||||||
subprocess.run(["esptool.py", *cmd])
|
subprocess.run(["esptool.py", *cmd])
|
||||||
|
|
||||||
|
|
||||||
|
def esp32_copy_ota_bin(source, target, env):
|
||||||
|
firmware_name = env.subst("$BUILD_DIR/${PROGNAME}.bin")
|
||||||
|
new_file_name = env.subst("$BUILD_DIR/${PROGNAME}.ota.bin")
|
||||||
|
|
||||||
|
shutil.copyfile(firmware_name, new_file_name)
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=E0602
|
# pylint: disable=E0602
|
||||||
env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", esp32_create_combined_bin) # noqa
|
env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", esp32_create_combined_bin) # noqa
|
||||||
|
env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", esp32_copy_ota_bin) # noqa
|
||||||
|
|
|
@ -17,7 +17,7 @@ from esphome.const import (
|
||||||
CONF_VSYNC_PIN,
|
CONF_VSYNC_PIN,
|
||||||
)
|
)
|
||||||
from esphome.core import CORE
|
from esphome.core import CORE
|
||||||
from esphome.components.esp32 import add_idf_sdkconfig_option
|
from esphome.components.esp32 import add_idf_component
|
||||||
from esphome.cpp_helpers import setup_entity
|
from esphome.cpp_helpers import setup_entity
|
||||||
|
|
||||||
DEPENDENCIES = ["esp32"]
|
DEPENDENCIES = ["esp32"]
|
||||||
|
@ -290,8 +290,11 @@ async def to_code(config):
|
||||||
cg.add_define("USE_ESP32_CAMERA")
|
cg.add_define("USE_ESP32_CAMERA")
|
||||||
|
|
||||||
if CORE.using_esp_idf:
|
if CORE.using_esp_idf:
|
||||||
cg.add_library("espressif/esp32-camera", "1.0.0")
|
add_idf_component(
|
||||||
add_idf_sdkconfig_option("CONFIG_RTCIO_SUPPORT_RTC_GPIO_DESC", True)
|
name="esp32-camera",
|
||||||
|
repo="https://github.com/espressif/esp32-camera.git",
|
||||||
|
ref="v2.0.9",
|
||||||
|
)
|
||||||
|
|
||||||
for conf in config.get(CONF_ON_STREAM_START, []):
|
for conf in config.get(CONF_ON_STREAM_START, []):
|
||||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||||
|
|
|
@ -6,10 +6,18 @@ Import("env") # noqa
|
||||||
|
|
||||||
def esp8266_copy_factory_bin(source, target, env):
|
def esp8266_copy_factory_bin(source, target, env):
|
||||||
firmware_name = env.subst("$BUILD_DIR/${PROGNAME}.bin")
|
firmware_name = env.subst("$BUILD_DIR/${PROGNAME}.bin")
|
||||||
new_file_name = env.subst("$BUILD_DIR/${PROGNAME}-factory.bin")
|
new_file_name = env.subst("$BUILD_DIR/${PROGNAME}.factory.bin")
|
||||||
|
|
||||||
|
shutil.copyfile(firmware_name, new_file_name)
|
||||||
|
|
||||||
|
|
||||||
|
def esp8266_copy_ota_bin(source, target, env):
|
||||||
|
firmware_name = env.subst("$BUILD_DIR/${PROGNAME}.bin")
|
||||||
|
new_file_name = env.subst("$BUILD_DIR/${PROGNAME}.ota.bin")
|
||||||
|
|
||||||
shutil.copyfile(firmware_name, new_file_name)
|
shutil.copyfile(firmware_name, new_file_name)
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=E0602
|
# pylint: disable=E0602
|
||||||
env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", esp8266_copy_factory_bin) # noqa
|
env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", esp8266_copy_factory_bin) # noqa
|
||||||
|
env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", esp8266_copy_ota_bin) # noqa
|
||||||
|
|
|
@ -1,10 +1,17 @@
|
||||||
|
import logging
|
||||||
|
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
|
import esphome.final_validate as fv
|
||||||
from esphome.components.ota import BASE_OTA_SCHEMA, ota_to_code, OTAComponent
|
from esphome.components.ota import BASE_OTA_SCHEMA, ota_to_code, OTAComponent
|
||||||
|
from esphome.config_helpers import merge_config
|
||||||
from esphome.const import (
|
from esphome.const import (
|
||||||
|
CONF_ESPHOME,
|
||||||
CONF_ID,
|
CONF_ID,
|
||||||
CONF_NUM_ATTEMPTS,
|
CONF_NUM_ATTEMPTS,
|
||||||
|
CONF_OTA,
|
||||||
CONF_PASSWORD,
|
CONF_PASSWORD,
|
||||||
|
CONF_PLATFORM,
|
||||||
CONF_PORT,
|
CONF_PORT,
|
||||||
CONF_REBOOT_TIMEOUT,
|
CONF_REBOOT_TIMEOUT,
|
||||||
CONF_SAFE_MODE,
|
CONF_SAFE_MODE,
|
||||||
|
@ -12,6 +19,8 @@ from esphome.const import (
|
||||||
)
|
)
|
||||||
from esphome.core import coroutine_with_priority
|
from esphome.core import coroutine_with_priority
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
CODEOWNERS = ["@esphome/core"]
|
CODEOWNERS = ["@esphome/core"]
|
||||||
AUTO_LOAD = ["md5", "socket"]
|
AUTO_LOAD = ["md5", "socket"]
|
||||||
|
@ -21,6 +30,65 @@ esphome = cg.esphome_ns.namespace("esphome")
|
||||||
ESPHomeOTAComponent = esphome.class_("ESPHomeOTAComponent", OTAComponent)
|
ESPHomeOTAComponent = esphome.class_("ESPHomeOTAComponent", OTAComponent)
|
||||||
|
|
||||||
|
|
||||||
|
def ota_esphome_final_validate(config):
|
||||||
|
full_conf = fv.full_config.get()
|
||||||
|
full_ota_conf = full_conf[CONF_OTA]
|
||||||
|
new_ota_conf = []
|
||||||
|
merged_ota_esphome_configs_by_port = {}
|
||||||
|
ports_with_merged_configs = []
|
||||||
|
for ota_conf in full_ota_conf:
|
||||||
|
if ota_conf.get(CONF_PLATFORM) == CONF_ESPHOME:
|
||||||
|
if (
|
||||||
|
conf_port := ota_conf.get(CONF_PORT)
|
||||||
|
) not in merged_ota_esphome_configs_by_port:
|
||||||
|
merged_ota_esphome_configs_by_port[conf_port] = ota_conf
|
||||||
|
else:
|
||||||
|
if merged_ota_esphome_configs_by_port[conf_port][
|
||||||
|
CONF_VERSION
|
||||||
|
] != ota_conf.get(CONF_VERSION):
|
||||||
|
raise cv.Invalid(
|
||||||
|
f"Found multiple configurations but {CONF_VERSION} is inconsistent"
|
||||||
|
)
|
||||||
|
if (
|
||||||
|
merged_ota_esphome_configs_by_port[conf_port][CONF_ID].is_manual
|
||||||
|
and ota_conf.get(CONF_ID).is_manual
|
||||||
|
):
|
||||||
|
raise cv.Invalid(
|
||||||
|
f"Found multiple configurations but {CONF_ID} is inconsistent"
|
||||||
|
)
|
||||||
|
if (
|
||||||
|
CONF_PASSWORD in merged_ota_esphome_configs_by_port[conf_port]
|
||||||
|
and CONF_PASSWORD in ota_conf
|
||||||
|
and merged_ota_esphome_configs_by_port[conf_port][CONF_PASSWORD]
|
||||||
|
!= ota_conf.get(CONF_PASSWORD)
|
||||||
|
):
|
||||||
|
raise cv.Invalid(
|
||||||
|
f"Found multiple configurations but {CONF_PASSWORD} is inconsistent"
|
||||||
|
)
|
||||||
|
|
||||||
|
ports_with_merged_configs.append(conf_port)
|
||||||
|
merged_ota_esphome_configs_by_port[conf_port] = merge_config(
|
||||||
|
merged_ota_esphome_configs_by_port[conf_port], ota_conf
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
new_ota_conf.append(ota_conf)
|
||||||
|
|
||||||
|
for port_conf in merged_ota_esphome_configs_by_port.values():
|
||||||
|
new_ota_conf.append(port_conf)
|
||||||
|
|
||||||
|
full_conf[CONF_OTA] = new_ota_conf
|
||||||
|
fv.full_config.set(full_conf)
|
||||||
|
|
||||||
|
if len(ports_with_merged_configs) > 0:
|
||||||
|
_LOGGER.warning(
|
||||||
|
"Found and merged multiple configurations for %s %s %s port(s) %s",
|
||||||
|
CONF_OTA,
|
||||||
|
CONF_PLATFORM,
|
||||||
|
CONF_ESPHOME,
|
||||||
|
ports_with_merged_configs,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
CONFIG_SCHEMA = (
|
CONFIG_SCHEMA = (
|
||||||
cv.Schema(
|
cv.Schema(
|
||||||
{
|
{
|
||||||
|
@ -50,6 +118,8 @@ CONFIG_SCHEMA = (
|
||||||
.extend(cv.COMPONENT_SCHEMA)
|
.extend(cv.COMPONENT_SCHEMA)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
FINAL_VALIDATE_SCHEMA = ota_esphome_final_validate
|
||||||
|
|
||||||
|
|
||||||
@coroutine_with_priority(52.0)
|
@coroutine_with_priority(52.0)
|
||||||
async def to_code(config):
|
async def to_code(config):
|
||||||
|
|
|
@ -631,7 +631,7 @@ void EthernetComponent::write_phy_register_(esp_eth_mac_t *mac, PHYRegister regi
|
||||||
ESPHL_ERROR_CHECK(err, "Writing PHY Register failed");
|
ESPHL_ERROR_CHECK(err, "Writing PHY Register failed");
|
||||||
|
|
||||||
if (this->type_ == ETHERNET_TYPE_RTL8201 && register_data.page) {
|
if (this->type_ == ETHERNET_TYPE_RTL8201 && register_data.page) {
|
||||||
ESP_LOGD(TAG, "Select PHY Register Page 0x%02" PRIX32, 0x0);
|
ESP_LOGD(TAG, "Select PHY Register Page 0x00");
|
||||||
err = mac->write_phy_reg(mac, this->phy_addr_, eth_phy_psr_reg_addr, 0x0);
|
err = mac->write_phy_reg(mac, this->phy_addr_, eth_phy_psr_reg_addr, 0x0);
|
||||||
ESPHL_ERROR_CHECK(err, "Select PHY Register Page 0 failed");
|
ESPHL_ERROR_CHECK(err, "Select PHY Register Page 0 failed");
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,6 @@ from esphome.helpers import (
|
||||||
cpp_string_escape,
|
cpp_string_escape,
|
||||||
)
|
)
|
||||||
from esphome.const import (
|
from esphome.const import (
|
||||||
__version__,
|
|
||||||
CONF_FAMILY,
|
CONF_FAMILY,
|
||||||
CONF_FILE,
|
CONF_FILE,
|
||||||
CONF_GLYPHS,
|
CONF_GLYPHS,
|
||||||
|
@ -185,31 +184,6 @@ def get_font_path(value, type) -> Path:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def download_content(url: str, path: Path) -> None:
|
|
||||||
if not external_files.has_remote_file_changed(url, path):
|
|
||||||
_LOGGER.debug("Remote file has not changed %s", url)
|
|
||||||
return
|
|
||||||
|
|
||||||
_LOGGER.debug(
|
|
||||||
"Remote file has changed, downloading from %s to %s",
|
|
||||||
url,
|
|
||||||
path,
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
|
||||||
req = requests.get(
|
|
||||||
url,
|
|
||||||
timeout=external_files.NETWORK_TIMEOUT,
|
|
||||||
headers={"User-agent": f"ESPHome/{__version__} (https://esphome.io)"},
|
|
||||||
)
|
|
||||||
req.raise_for_status()
|
|
||||||
except requests.exceptions.RequestException as e:
|
|
||||||
raise cv.Invalid(f"Could not download from {url}: {e}")
|
|
||||||
|
|
||||||
path.parent.mkdir(parents=True, exist_ok=True)
|
|
||||||
path.write_bytes(req.content)
|
|
||||||
|
|
||||||
|
|
||||||
def download_gfont(value):
|
def download_gfont(value):
|
||||||
name = (
|
name = (
|
||||||
f"{value[CONF_FAMILY]}:ital,wght@{int(value[CONF_ITALIC])},{value[CONF_WEIGHT]}"
|
f"{value[CONF_FAMILY]}:ital,wght@{int(value[CONF_ITALIC])},{value[CONF_WEIGHT]}"
|
||||||
|
@ -236,7 +210,7 @@ def download_gfont(value):
|
||||||
ttf_url = match.group(1)
|
ttf_url = match.group(1)
|
||||||
_LOGGER.debug("download_gfont: ttf_url=%s", ttf_url)
|
_LOGGER.debug("download_gfont: ttf_url=%s", ttf_url)
|
||||||
|
|
||||||
download_content(ttf_url, path)
|
external_files.download_content(ttf_url, path)
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
@ -244,7 +218,7 @@ def download_web_font(value):
|
||||||
url = value[CONF_URL]
|
url = value[CONF_URL]
|
||||||
path = get_font_path(value, TYPE_WEB)
|
path = get_font_path(value, TYPE_WEB)
|
||||||
|
|
||||||
download_content(url, path)
|
external_files.download_content(url, path)
|
||||||
_LOGGER.debug("download_web_font: path=%s", path)
|
_LOGGER.debug("download_web_font: path=%s", path)
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
25
esphome/components/gpio/one_wire/__init__.py
Normal file
25
esphome/components/gpio/one_wire/__init__.py
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
import esphome.codegen as cg
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
from esphome import pins
|
||||||
|
from esphome.const import CONF_ID, CONF_PIN
|
||||||
|
from esphome.components.one_wire import OneWireBus
|
||||||
|
from .. import gpio_ns
|
||||||
|
|
||||||
|
CODEOWNERS = ["@ssieb"]
|
||||||
|
|
||||||
|
GPIOOneWireBus = gpio_ns.class_("GPIOOneWireBus", OneWireBus, cg.Component)
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = cv.Schema(
|
||||||
|
{
|
||||||
|
cv.GenerateID(): cv.declare_id(GPIOOneWireBus),
|
||||||
|
cv.Required(CONF_PIN): pins.internal_gpio_output_pin_schema,
|
||||||
|
}
|
||||||
|
).extend(cv.COMPONENT_SCHEMA)
|
||||||
|
|
||||||
|
|
||||||
|
async def to_code(config):
|
||||||
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
|
await cg.register_component(var, config)
|
||||||
|
|
||||||
|
pin = await cg.gpio_pin_expression(config[CONF_PIN])
|
||||||
|
cg.add(var.set_pin(pin))
|
205
esphome/components/gpio/one_wire/gpio_one_wire.cpp
Normal file
205
esphome/components/gpio/one_wire/gpio_one_wire.cpp
Normal file
|
@ -0,0 +1,205 @@
|
||||||
|
#include "gpio_one_wire.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
#include "esphome/core/helpers.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace gpio {
|
||||||
|
|
||||||
|
static const char *const TAG = "gpio.one_wire";
|
||||||
|
|
||||||
|
void GPIOOneWireBus::setup() {
|
||||||
|
ESP_LOGCONFIG(TAG, "Setting up 1-wire bus...");
|
||||||
|
this->t_pin_->setup();
|
||||||
|
// clear bus with 480µs high, otherwise initial reset in search might fail
|
||||||
|
this->t_pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
|
||||||
|
delayMicroseconds(480);
|
||||||
|
this->search();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GPIOOneWireBus::dump_config() {
|
||||||
|
ESP_LOGCONFIG(TAG, "GPIO 1-wire bus:");
|
||||||
|
LOG_PIN(" Pin: ", this->t_pin_);
|
||||||
|
this->dump_devices_(TAG);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HOT IRAM_ATTR GPIOOneWireBus::reset() {
|
||||||
|
// See reset here:
|
||||||
|
// https://www.maximintegrated.com/en/design/technical-documents/app-notes/1/126.html
|
||||||
|
// Wait for communication to clear (delay G)
|
||||||
|
pin_.pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
|
||||||
|
uint8_t retries = 125;
|
||||||
|
do {
|
||||||
|
if (--retries == 0)
|
||||||
|
return false;
|
||||||
|
delayMicroseconds(2);
|
||||||
|
} while (!pin_.digital_read());
|
||||||
|
|
||||||
|
bool r;
|
||||||
|
|
||||||
|
// Send 480µs LOW TX reset pulse (drive bus low, delay H)
|
||||||
|
pin_.pin_mode(gpio::FLAG_OUTPUT);
|
||||||
|
pin_.digital_write(false);
|
||||||
|
delayMicroseconds(480);
|
||||||
|
|
||||||
|
// Release the bus, delay I
|
||||||
|
pin_.pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
|
||||||
|
delayMicroseconds(70);
|
||||||
|
|
||||||
|
// sample bus, 0=device(s) present, 1=no device present
|
||||||
|
r = !pin_.digital_read();
|
||||||
|
// delay J
|
||||||
|
delayMicroseconds(410);
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
void HOT IRAM_ATTR GPIOOneWireBus::write_bit_(bool bit) {
|
||||||
|
// drive bus low
|
||||||
|
pin_.pin_mode(gpio::FLAG_OUTPUT);
|
||||||
|
pin_.digital_write(false);
|
||||||
|
|
||||||
|
// from datasheet:
|
||||||
|
// write 0 low time: t_low0: min=60µs, max=120µs
|
||||||
|
// write 1 low time: t_low1: min=1µs, max=15µs
|
||||||
|
// time slot: t_slot: min=60µs, max=120µs
|
||||||
|
// recovery time: t_rec: min=1µs
|
||||||
|
// ds18b20 appears to read the bus after roughly 14µs
|
||||||
|
uint32_t delay0 = bit ? 6 : 60;
|
||||||
|
uint32_t delay1 = bit ? 59 : 5;
|
||||||
|
|
||||||
|
// delay A/C
|
||||||
|
delayMicroseconds(delay0);
|
||||||
|
// release bus
|
||||||
|
pin_.digital_write(true);
|
||||||
|
// delay B/D
|
||||||
|
delayMicroseconds(delay1);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HOT IRAM_ATTR GPIOOneWireBus::read_bit_() {
|
||||||
|
// drive bus low
|
||||||
|
pin_.pin_mode(gpio::FLAG_OUTPUT);
|
||||||
|
pin_.digital_write(false);
|
||||||
|
|
||||||
|
// note: for reading we'll need very accurate timing, as the
|
||||||
|
// timing for the digital_read() is tight; according to the datasheet,
|
||||||
|
// we should read at the end of 16µs starting from the bus low
|
||||||
|
// typically, the ds18b20 pulls the line high after 11µs for a logical 1
|
||||||
|
// and 29µs for a logical 0
|
||||||
|
|
||||||
|
uint32_t start = micros();
|
||||||
|
// datasheet says >1µs
|
||||||
|
delayMicroseconds(2);
|
||||||
|
|
||||||
|
// release bus, delay E
|
||||||
|
pin_.pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
|
||||||
|
|
||||||
|
// measure from start value directly, to get best accurate timing no matter
|
||||||
|
// how long pin_mode/delayMicroseconds took
|
||||||
|
uint32_t now = micros();
|
||||||
|
if (now - start < 12)
|
||||||
|
delayMicroseconds(12 - (now - start));
|
||||||
|
|
||||||
|
// sample bus to read bit from peer
|
||||||
|
bool r = pin_.digital_read();
|
||||||
|
|
||||||
|
// read slot is at least 60µs; get as close to 60µs to spend less time with interrupts locked
|
||||||
|
now = micros();
|
||||||
|
if (now - start < 60)
|
||||||
|
delayMicroseconds(60 - (now - start));
|
||||||
|
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
void IRAM_ATTR GPIOOneWireBus::write8(uint8_t val) {
|
||||||
|
for (uint8_t i = 0; i < 8; i++) {
|
||||||
|
this->write_bit_(bool((1u << i) & val));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void IRAM_ATTR GPIOOneWireBus::write64(uint64_t val) {
|
||||||
|
for (uint8_t i = 0; i < 64; i++) {
|
||||||
|
this->write_bit_(bool((1ULL << i) & val));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t IRAM_ATTR GPIOOneWireBus::read8() {
|
||||||
|
uint8_t ret = 0;
|
||||||
|
for (uint8_t i = 0; i < 8; i++) {
|
||||||
|
ret |= (uint8_t(this->read_bit_()) << i);
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t IRAM_ATTR GPIOOneWireBus::read64() {
|
||||||
|
uint64_t ret = 0;
|
||||||
|
for (uint8_t i = 0; i < 8; i++) {
|
||||||
|
ret |= (uint64_t(this->read_bit_()) << i);
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GPIOOneWireBus::reset_search() {
|
||||||
|
this->last_discrepancy_ = 0;
|
||||||
|
this->last_device_flag_ = false;
|
||||||
|
this->address_ = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t IRAM_ATTR GPIOOneWireBus::search_int() {
|
||||||
|
if (this->last_device_flag_)
|
||||||
|
return 0u;
|
||||||
|
|
||||||
|
uint8_t last_zero = 0;
|
||||||
|
uint64_t bit_mask = 1;
|
||||||
|
uint64_t address = this->address_;
|
||||||
|
|
||||||
|
// Initiate search
|
||||||
|
for (int bit_number = 1; bit_number <= 64; bit_number++, bit_mask <<= 1) {
|
||||||
|
// read bit
|
||||||
|
bool id_bit = this->read_bit_();
|
||||||
|
// read its complement
|
||||||
|
bool cmp_id_bit = this->read_bit_();
|
||||||
|
|
||||||
|
if (id_bit && cmp_id_bit) {
|
||||||
|
// No devices participating in search
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool branch;
|
||||||
|
|
||||||
|
if (id_bit != cmp_id_bit) {
|
||||||
|
// only chose one branch, the other one doesn't have any devices.
|
||||||
|
branch = id_bit;
|
||||||
|
} else {
|
||||||
|
// there are devices with both 0s and 1s at this bit
|
||||||
|
if (bit_number < this->last_discrepancy_) {
|
||||||
|
branch = (address & bit_mask) > 0;
|
||||||
|
} else {
|
||||||
|
branch = bit_number == this->last_discrepancy_;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!branch) {
|
||||||
|
last_zero = bit_number;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (branch) {
|
||||||
|
address |= bit_mask;
|
||||||
|
} else {
|
||||||
|
address &= ~bit_mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
// choose/announce branch
|
||||||
|
this->write_bit_(branch);
|
||||||
|
}
|
||||||
|
|
||||||
|
this->last_discrepancy_ = last_zero;
|
||||||
|
if (this->last_discrepancy_ == 0) {
|
||||||
|
// we're at root and have no choices left, so this was the last one.
|
||||||
|
this->last_device_flag_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
this->address_ = address;
|
||||||
|
return address;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace gpio
|
||||||
|
} // namespace esphome
|
41
esphome/components/gpio/one_wire/gpio_one_wire.h
Normal file
41
esphome/components/gpio/one_wire/gpio_one_wire.h
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
#include "esphome/core/hal.h"
|
||||||
|
#include "esphome/components/one_wire/one_wire.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace gpio {
|
||||||
|
|
||||||
|
class GPIOOneWireBus : public one_wire::OneWireBus, public Component {
|
||||||
|
public:
|
||||||
|
void setup() override;
|
||||||
|
void dump_config() override;
|
||||||
|
float get_setup_priority() const override { return setup_priority::BUS; }
|
||||||
|
|
||||||
|
void set_pin(InternalGPIOPin *pin) {
|
||||||
|
this->t_pin_ = pin;
|
||||||
|
this->pin_ = pin->to_isr();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool reset() override;
|
||||||
|
void write8(uint8_t val) override;
|
||||||
|
void write64(uint64_t val) override;
|
||||||
|
uint8_t read8() override;
|
||||||
|
uint64_t read64() override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
InternalGPIOPin *t_pin_;
|
||||||
|
ISRInternalGPIOPin pin_;
|
||||||
|
uint8_t last_discrepancy_{0};
|
||||||
|
bool last_device_flag_{false};
|
||||||
|
uint64_t address_;
|
||||||
|
|
||||||
|
void reset_search() override;
|
||||||
|
uint64_t search_int() override;
|
||||||
|
void write_bit_(bool bit);
|
||||||
|
bool read_bit_();
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace gpio
|
||||||
|
} // namespace esphome
|
|
@ -56,7 +56,7 @@ SENSOR_TYPES = {
|
||||||
|
|
||||||
CONFIG_SCHEMA = cv.Schema(
|
CONFIG_SCHEMA = cv.Schema(
|
||||||
{
|
{
|
||||||
cv.Required(CONF_HAIER_ID): cv.use_id(HonClimate),
|
cv.GenerateID(CONF_HAIER_ID): cv.use_id(HonClimate),
|
||||||
}
|
}
|
||||||
).extend({cv.Optional(type): schema for type, schema in SENSOR_TYPES.items()})
|
).extend({cv.Optional(type): schema for type, schema in SENSOR_TYPES.items()})
|
||||||
|
|
||||||
|
@ -64,8 +64,8 @@ CONFIG_SCHEMA = cv.Schema(
|
||||||
async def to_code(config):
|
async def to_code(config):
|
||||||
paren = await cg.get_variable(config[CONF_HAIER_ID])
|
paren = await cg.get_variable(config[CONF_HAIER_ID])
|
||||||
|
|
||||||
for type, _ in SENSOR_TYPES.items():
|
for type_ in SENSOR_TYPES:
|
||||||
if conf := config.get(type):
|
if conf := config.get(type_):
|
||||||
sens = await binary_sensor.new_binary_sensor(conf)
|
sens = await binary_sensor.new_binary_sensor(conf)
|
||||||
binary_sensor_type = getattr(BinarySensorTypeEnum, type.upper())
|
binary_sensor_type = getattr(BinarySensorTypeEnum, type_.upper())
|
||||||
cg.add(paren.set_sub_binary_sensor(binary_sensor_type, sens))
|
cg.add(paren.set_sub_binary_sensor(binary_sensor_type, sens))
|
||||||
|
|
|
@ -21,7 +21,7 @@ ICON_SPRAY_BOTTLE = "mdi:spray-bottle"
|
||||||
|
|
||||||
CONFIG_SCHEMA = cv.Schema(
|
CONFIG_SCHEMA = cv.Schema(
|
||||||
{
|
{
|
||||||
cv.Required(CONF_HAIER_ID): cv.use_id(HonClimate),
|
cv.GenerateID(CONF_HAIER_ID): cv.use_id(HonClimate),
|
||||||
cv.Optional(CONF_SELF_CLEANING): button.button_schema(
|
cv.Optional(CONF_SELF_CLEANING): button.button_schema(
|
||||||
SelfCleaningButton,
|
SelfCleaningButton,
|
||||||
icon=ICON_SPRAY_BOTTLE,
|
icon=ICON_SPRAY_BOTTLE,
|
||||||
|
|
|
@ -183,7 +183,6 @@ BASE_CONFIG_SCHEMA = (
|
||||||
cv.Optional(
|
cv.Optional(
|
||||||
CONF_SUPPORTED_SWING_MODES,
|
CONF_SUPPORTED_SWING_MODES,
|
||||||
default=[
|
default=[
|
||||||
"OFF",
|
|
||||||
"VERTICAL",
|
"VERTICAL",
|
||||||
"HORIZONTAL",
|
"HORIZONTAL",
|
||||||
"BOTH",
|
"BOTH",
|
||||||
|
@ -211,7 +210,7 @@ CONFIG_SCHEMA = cv.All(
|
||||||
): cv.boolean,
|
): cv.boolean,
|
||||||
cv.Optional(
|
cv.Optional(
|
||||||
CONF_SUPPORTED_PRESETS,
|
CONF_SUPPORTED_PRESETS,
|
||||||
default=list(["BOOST", "COMFORT"]), # No AWAY by default
|
default=["BOOST", "COMFORT"], # No AWAY by default
|
||||||
): cv.ensure_list(
|
): cv.ensure_list(
|
||||||
cv.enum(SUPPORTED_CLIMATE_PRESETS_SMARTAIR2_OPTIONS, upper=True)
|
cv.enum(SUPPORTED_CLIMATE_PRESETS_SMARTAIR2_OPTIONS, upper=True)
|
||||||
),
|
),
|
||||||
|
@ -231,7 +230,7 @@ CONFIG_SCHEMA = cv.All(
|
||||||
): cv.int_range(min=PROTOCOL_CONTROL_PACKET_SIZE, max=50),
|
): cv.int_range(min=PROTOCOL_CONTROL_PACKET_SIZE, max=50),
|
||||||
cv.Optional(
|
cv.Optional(
|
||||||
CONF_SUPPORTED_PRESETS,
|
CONF_SUPPORTED_PRESETS,
|
||||||
default=list(["BOOST", "ECO", "SLEEP"]), # No AWAY by default
|
default=["BOOST", "ECO", "SLEEP"], # No AWAY by default
|
||||||
): cv.ensure_list(
|
): cv.ensure_list(
|
||||||
cv.enum(SUPPORTED_CLIMATE_PRESETS_HON_OPTIONS, upper=True)
|
cv.enum(SUPPORTED_CLIMATE_PRESETS_HON_OPTIONS, upper=True)
|
||||||
),
|
),
|
||||||
|
@ -427,11 +426,7 @@ def _final_validate(config):
|
||||||
"No logger component found, logging for Haier protocol is disabled"
|
"No logger component found, logging for Haier protocol is disabled"
|
||||||
)
|
)
|
||||||
cg.add_build_flag("-DHAIER_LOG_LEVEL=0")
|
cg.add_build_flag("-DHAIER_LOG_LEVEL=0")
|
||||||
if (
|
if config.get(CONF_WIFI_SIGNAL) and CONF_WIFI not in full_config:
|
||||||
(CONF_WIFI_SIGNAL in config)
|
|
||||||
and (config[CONF_WIFI_SIGNAL])
|
|
||||||
and CONF_WIFI not in full_config
|
|
||||||
):
|
|
||||||
raise cv.Invalid(
|
raise cv.Invalid(
|
||||||
f"No WiFi configured, if you want to use haier climate without WiFi add {CONF_WIFI_SIGNAL}: false to climate configuration"
|
f"No WiFi configured, if you want to use haier climate without WiFi add {CONF_WIFI_SIGNAL}: false to climate configuration"
|
||||||
)
|
)
|
||||||
|
|
|
@ -137,16 +137,16 @@ SENSOR_TYPES = {
|
||||||
|
|
||||||
CONFIG_SCHEMA = cv.Schema(
|
CONFIG_SCHEMA = cv.Schema(
|
||||||
{
|
{
|
||||||
cv.Required(CONF_HAIER_ID): cv.use_id(HonClimate),
|
cv.GenerateID(CONF_HAIER_ID): cv.use_id(HonClimate),
|
||||||
}
|
}
|
||||||
).extend({cv.Optional(type): schema for type, schema in SENSOR_TYPES.items()})
|
).extend({cv.Optional(type_): schema for type_, schema in SENSOR_TYPES.items()})
|
||||||
|
|
||||||
|
|
||||||
async def to_code(config):
|
async def to_code(config):
|
||||||
paren = await cg.get_variable(config[CONF_HAIER_ID])
|
paren = await cg.get_variable(config[CONF_HAIER_ID])
|
||||||
|
|
||||||
for type, _ in SENSOR_TYPES.items():
|
for type_ in SENSOR_TYPES:
|
||||||
if conf := config.get(type):
|
if conf := config.get(type_):
|
||||||
sens = await sensor.new_sensor(conf)
|
sens = await sensor.new_sensor(conf)
|
||||||
sensor_type = getattr(SensorTypeEnum, type.upper())
|
sensor_type = getattr(SensorTypeEnum, type_.upper())
|
||||||
cg.add(paren.set_sub_sensor(sensor_type, sens))
|
cg.add(paren.set_sub_sensor(sensor_type, sens))
|
||||||
|
|
|
@ -39,7 +39,7 @@ TEXT_SENSOR_TYPES = {
|
||||||
|
|
||||||
CONFIG_SCHEMA = cv.Schema(
|
CONFIG_SCHEMA = cv.Schema(
|
||||||
{
|
{
|
||||||
cv.Required(CONF_HAIER_ID): cv.use_id(HonClimate),
|
cv.GenerateID(CONF_HAIER_ID): cv.use_id(HonClimate),
|
||||||
}
|
}
|
||||||
).extend({cv.Optional(type): schema for type, schema in TEXT_SENSOR_TYPES.items()})
|
).extend({cv.Optional(type): schema for type, schema in TEXT_SENSOR_TYPES.items()})
|
||||||
|
|
||||||
|
@ -47,8 +47,8 @@ CONFIG_SCHEMA = cv.Schema(
|
||||||
async def to_code(config):
|
async def to_code(config):
|
||||||
paren = await cg.get_variable(config[CONF_HAIER_ID])
|
paren = await cg.get_variable(config[CONF_HAIER_ID])
|
||||||
|
|
||||||
for type, _ in TEXT_SENSOR_TYPES.items():
|
for type_ in TEXT_SENSOR_TYPES:
|
||||||
if conf := config.get(type):
|
if conf := config.get(type_):
|
||||||
sens = await text_sensor.new_text_sensor(conf)
|
sens = await text_sensor.new_text_sensor(conf)
|
||||||
text_sensor_type = getattr(TextSensorTypeEnum, type.upper())
|
text_sensor_type = getattr(TextSensorTypeEnum, type_.upper())
|
||||||
cg.add(paren.set_sub_text_sensor(text_sensor_type, sens))
|
cg.add(paren.set_sub_text_sensor(text_sensor_type, sens))
|
||||||
|
|
|
@ -56,7 +56,7 @@ void HE60rCover::endstop_reached_(CoverOperation operation) {
|
||||||
this->position = new_position;
|
this->position = new_position;
|
||||||
this->current_operation = COVER_OPERATION_IDLE;
|
this->current_operation = COVER_OPERATION_IDLE;
|
||||||
if (this->last_command_ == operation) {
|
if (this->last_command_ == operation) {
|
||||||
float dur = (now - this->start_dir_time_) / 1e3f;
|
float dur = (float) (now - this->start_dir_time_) / 1e3f;
|
||||||
ESP_LOGD(TAG, "'%s' - %s endstop reached. Took %.1fs.", this->name_.c_str(),
|
ESP_LOGD(TAG, "'%s' - %s endstop reached. Took %.1fs.", this->name_.c_str(),
|
||||||
operation == COVER_OPERATION_OPENING ? "Open" : "Close", dur);
|
operation == COVER_OPERATION_OPENING ? "Open" : "Close", dur);
|
||||||
}
|
}
|
||||||
|
@ -69,7 +69,6 @@ void HE60rCover::set_current_operation_(cover::CoverOperation operation) {
|
||||||
this->current_operation = operation;
|
this->current_operation = operation;
|
||||||
if (operation != COVER_OPERATION_IDLE)
|
if (operation != COVER_OPERATION_IDLE)
|
||||||
this->last_recompute_time_ = millis();
|
this->last_recompute_time_ = millis();
|
||||||
this->publish_state();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -129,7 +128,7 @@ void HE60rCover::update_() {
|
||||||
if (this->toggles_needed_ != 0) {
|
if (this->toggles_needed_ != 0) {
|
||||||
if ((this->counter_++ & 0x3) == 0) {
|
if ((this->counter_++ & 0x3) == 0) {
|
||||||
this->toggles_needed_--;
|
this->toggles_needed_--;
|
||||||
ESP_LOGD(TAG, "Writing byte 0x30, still needed=%" PRIu32, this->toggles_needed_);
|
ESP_LOGD(TAG, "Writing byte 0x30, still needed=%u", this->toggles_needed_);
|
||||||
this->write_byte(TOGGLE_BYTE);
|
this->write_byte(TOGGLE_BYTE);
|
||||||
} else {
|
} else {
|
||||||
this->write_byte(QUERY_BYTE);
|
this->write_byte(QUERY_BYTE);
|
||||||
|
@ -235,31 +234,28 @@ void HE60rCover::recompute_position_() {
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const uint32_t now = millis();
|
const uint32_t now = millis();
|
||||||
float dir;
|
|
||||||
float action_dur;
|
|
||||||
|
|
||||||
switch (this->current_operation) {
|
|
||||||
case COVER_OPERATION_OPENING:
|
|
||||||
dir = 1.0f;
|
|
||||||
action_dur = this->open_duration_;
|
|
||||||
break;
|
|
||||||
case COVER_OPERATION_CLOSING:
|
|
||||||
dir = -1.0f;
|
|
||||||
action_dur = this->close_duration_;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (now > this->last_recompute_time_) {
|
if (now > this->last_recompute_time_) {
|
||||||
auto diff = now - last_recompute_time_;
|
auto diff = (unsigned) (now - last_recompute_time_);
|
||||||
auto delta = dir * diff / action_dur;
|
float delta;
|
||||||
|
switch (this->current_operation) {
|
||||||
|
case COVER_OPERATION_OPENING:
|
||||||
|
delta = (float) diff / (float) this->open_duration_;
|
||||||
|
break;
|
||||||
|
case COVER_OPERATION_CLOSING:
|
||||||
|
delta = -(float) diff / (float) this->close_duration_;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// make sure our guesstimate never reaches full open or close.
|
// make sure our guesstimate never reaches full open or close.
|
||||||
this->position = clamp(delta + this->position, COVER_CLOSED + 0.01f, COVER_OPEN - 0.01f);
|
auto new_position = clamp(delta + this->position, COVER_CLOSED + 0.01f, COVER_OPEN - 0.01f);
|
||||||
ESP_LOGD(TAG, "Recompute %dms, dir=%f, action_dur=%f, delta=%f, pos=%f", (int) diff, dir, action_dur, delta,
|
ESP_LOGD(TAG, "Recompute %ums, dir=%u, delta=%f, pos=%f", diff, this->current_operation, delta, new_position);
|
||||||
this->position);
|
|
||||||
this->last_recompute_time_ = now;
|
this->last_recompute_time_ = now;
|
||||||
this->publish_state();
|
if (this->position != new_position) {
|
||||||
|
this->position = new_position;
|
||||||
|
this->publish_state();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,15 +25,14 @@ class HE60rCover : public cover::Cover, public Component, public uart::UARTDevic
|
||||||
void control(const cover::CoverCall &call) override;
|
void control(const cover::CoverCall &call) override;
|
||||||
bool is_at_target_() const;
|
bool is_at_target_() const;
|
||||||
void start_direction_(cover::CoverOperation dir);
|
void start_direction_(cover::CoverOperation dir);
|
||||||
void update_operation_(cover::CoverOperation dir);
|
|
||||||
void endstop_reached_(cover::CoverOperation operation);
|
void endstop_reached_(cover::CoverOperation operation);
|
||||||
void recompute_position_();
|
void recompute_position_();
|
||||||
void set_current_operation_(cover::CoverOperation operation);
|
void set_current_operation_(cover::CoverOperation operation);
|
||||||
void process_rx_(uint8_t data);
|
void process_rx_(uint8_t data);
|
||||||
|
|
||||||
uint32_t open_duration_{0};
|
unsigned open_duration_{0};
|
||||||
uint32_t close_duration_{0};
|
unsigned close_duration_{0};
|
||||||
uint32_t toggles_needed_{0};
|
unsigned toggles_needed_{0};
|
||||||
cover::CoverOperation next_direction_{cover::COVER_OPERATION_IDLE};
|
cover::CoverOperation next_direction_{cover::COVER_OPERATION_IDLE};
|
||||||
cover::CoverOperation last_command_{cover::COVER_OPERATION_IDLE};
|
cover::CoverOperation last_command_{cover::COVER_OPERATION_IDLE};
|
||||||
uint32_t last_recompute_time_{0};
|
uint32_t last_recompute_time_{0};
|
||||||
|
|
|
@ -34,6 +34,7 @@ PROTOCOLS = {
|
||||||
"greeyan": Protocol.PROTOCOL_GREEYAN,
|
"greeyan": Protocol.PROTOCOL_GREEYAN,
|
||||||
"greeyac": Protocol.PROTOCOL_GREEYAC,
|
"greeyac": Protocol.PROTOCOL_GREEYAC,
|
||||||
"greeyt": Protocol.PROTOCOL_GREEYT,
|
"greeyt": Protocol.PROTOCOL_GREEYT,
|
||||||
|
"greeyap": Protocol.PROTOCOL_GREEYAP,
|
||||||
"hisense_aud": Protocol.PROTOCOL_HISENSE_AUD,
|
"hisense_aud": Protocol.PROTOCOL_HISENSE_AUD,
|
||||||
"hitachi": Protocol.PROTOCOL_HITACHI,
|
"hitachi": Protocol.PROTOCOL_HITACHI,
|
||||||
"hyundai": Protocol.PROTOCOL_HYUNDAI,
|
"hyundai": Protocol.PROTOCOL_HYUNDAI,
|
||||||
|
@ -61,6 +62,11 @@ PROTOCOLS = {
|
||||||
"toshiba_daiseikai": Protocol.PROTOCOL_TOSHIBA_DAISEIKAI,
|
"toshiba_daiseikai": Protocol.PROTOCOL_TOSHIBA_DAISEIKAI,
|
||||||
"toshiba": Protocol.PROTOCOL_TOSHIBA,
|
"toshiba": Protocol.PROTOCOL_TOSHIBA,
|
||||||
"zhlt01": Protocol.PROTOCOL_ZHLT01,
|
"zhlt01": Protocol.PROTOCOL_ZHLT01,
|
||||||
|
"nibe": Protocol.PROTOCOL_NIBE,
|
||||||
|
"carrier_qlima_1": Protocol.PROTOCOL_QLIMA_1,
|
||||||
|
"carrier_qlima_2": Protocol.PROTOCOL_QLIMA_2,
|
||||||
|
"samsung_aqv12msan": Protocol.PROTOCOL_SAMSUNG_AQV12MSAN,
|
||||||
|
"zhjg01": Protocol.PROTOCOL_ZHJG01,
|
||||||
}
|
}
|
||||||
|
|
||||||
CONF_HORIZONTAL_DEFAULT = "horizontal_default"
|
CONF_HORIZONTAL_DEFAULT = "horizontal_default"
|
||||||
|
@ -116,7 +122,7 @@ def to_code(config):
|
||||||
cg.add(var.set_max_temperature(config[CONF_MAX_TEMPERATURE]))
|
cg.add(var.set_max_temperature(config[CONF_MAX_TEMPERATURE]))
|
||||||
cg.add(var.set_min_temperature(config[CONF_MIN_TEMPERATURE]))
|
cg.add(var.set_min_temperature(config[CONF_MIN_TEMPERATURE]))
|
||||||
|
|
||||||
cg.add_library("tonia/HeatpumpIR", "1.0.23")
|
cg.add_library("tonia/HeatpumpIR", "1.0.26")
|
||||||
|
|
||||||
if CORE.is_esp8266 or CORE.is_esp32:
|
if CORE.is_esp8266 or CORE.is_esp32:
|
||||||
cg.add_library("crankyoldgit/IRremoteESP8266", "2.8.4")
|
cg.add_library("crankyoldgit/IRremoteESP8266", "2.8.6")
|
||||||
|
|
|
@ -28,6 +28,7 @@ const std::map<Protocol, std::function<HeatpumpIR *()>> PROTOCOL_CONSTRUCTOR_MAP
|
||||||
{PROTOCOL_GREEYAN, []() { return new GreeYANHeatpumpIR(); }}, // NOLINT
|
{PROTOCOL_GREEYAN, []() { return new GreeYANHeatpumpIR(); }}, // NOLINT
|
||||||
{PROTOCOL_GREEYAC, []() { return new GreeYACHeatpumpIR(); }}, // NOLINT
|
{PROTOCOL_GREEYAC, []() { return new GreeYACHeatpumpIR(); }}, // NOLINT
|
||||||
{PROTOCOL_GREEYT, []() { return new GreeYTHeatpumpIR(); }}, // NOLINT
|
{PROTOCOL_GREEYT, []() { return new GreeYTHeatpumpIR(); }}, // NOLINT
|
||||||
|
{PROTOCOL_GREEYAP, []() { return new GreeYAPHeatpumpIR(); }}, // NOLINT
|
||||||
{PROTOCOL_HISENSE_AUD, []() { return new HisenseHeatpumpIR(); }}, // NOLINT
|
{PROTOCOL_HISENSE_AUD, []() { return new HisenseHeatpumpIR(); }}, // NOLINT
|
||||||
{PROTOCOL_HITACHI, []() { return new HitachiHeatpumpIR(); }}, // NOLINT
|
{PROTOCOL_HITACHI, []() { return new HitachiHeatpumpIR(); }}, // NOLINT
|
||||||
{PROTOCOL_HYUNDAI, []() { return new HyundaiHeatpumpIR(); }}, // NOLINT
|
{PROTOCOL_HYUNDAI, []() { return new HyundaiHeatpumpIR(); }}, // NOLINT
|
||||||
|
@ -55,6 +56,11 @@ const std::map<Protocol, std::function<HeatpumpIR *()>> PROTOCOL_CONSTRUCTOR_MAP
|
||||||
{PROTOCOL_TOSHIBA_DAISEIKAI, []() { return new ToshibaDaiseikaiHeatpumpIR(); }}, // NOLINT
|
{PROTOCOL_TOSHIBA_DAISEIKAI, []() { return new ToshibaDaiseikaiHeatpumpIR(); }}, // NOLINT
|
||||||
{PROTOCOL_TOSHIBA, []() { return new ToshibaHeatpumpIR(); }}, // NOLINT
|
{PROTOCOL_TOSHIBA, []() { return new ToshibaHeatpumpIR(); }}, // NOLINT
|
||||||
{PROTOCOL_ZHLT01, []() { return new ZHLT01HeatpumpIR(); }}, // NOLINT
|
{PROTOCOL_ZHLT01, []() { return new ZHLT01HeatpumpIR(); }}, // NOLINT
|
||||||
|
{PROTOCOL_NIBE, []() { return new NibeHeatpumpIR(); }}, // NOLINT
|
||||||
|
{PROTOCOL_QLIMA_1, []() { return new Qlima1HeatpumpIR(); }}, // NOLINT
|
||||||
|
{PROTOCOL_QLIMA_2, []() { return new Qlima2HeatpumpIR(); }}, // NOLINT
|
||||||
|
{PROTOCOL_SAMSUNG_AQV12MSAN, []() { return new SamsungAQV12MSANHeatpumpIR(); }}, // NOLINT
|
||||||
|
{PROTOCOL_ZHJG01, []() { return new ZHJG01HeatpumpIR(); }}, // NOLINT
|
||||||
};
|
};
|
||||||
|
|
||||||
void HeatpumpIRClimate::setup() {
|
void HeatpumpIRClimate::setup() {
|
||||||
|
|
|
@ -28,6 +28,7 @@ enum Protocol {
|
||||||
PROTOCOL_GREEYAN,
|
PROTOCOL_GREEYAN,
|
||||||
PROTOCOL_GREEYAC,
|
PROTOCOL_GREEYAC,
|
||||||
PROTOCOL_GREEYT,
|
PROTOCOL_GREEYT,
|
||||||
|
PROTOCOL_GREEYAP,
|
||||||
PROTOCOL_HISENSE_AUD,
|
PROTOCOL_HISENSE_AUD,
|
||||||
PROTOCOL_HITACHI,
|
PROTOCOL_HITACHI,
|
||||||
PROTOCOL_HYUNDAI,
|
PROTOCOL_HYUNDAI,
|
||||||
|
@ -55,6 +56,11 @@ enum Protocol {
|
||||||
PROTOCOL_TOSHIBA_DAISEIKAI,
|
PROTOCOL_TOSHIBA_DAISEIKAI,
|
||||||
PROTOCOL_TOSHIBA,
|
PROTOCOL_TOSHIBA,
|
||||||
PROTOCOL_ZHLT01,
|
PROTOCOL_ZHLT01,
|
||||||
|
PROTOCOL_NIBE,
|
||||||
|
PROTOCOL_QLIMA_1,
|
||||||
|
PROTOCOL_QLIMA_2,
|
||||||
|
PROTOCOL_SAMSUNG_AQV12MSAN,
|
||||||
|
PROTOCOL_ZHJG01,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Simple enum to represent horizontal directios
|
// Simple enum to represent horizontal directios
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
import urllib.parse as urlparse
|
|
||||||
|
|
||||||
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 automation
|
from esphome import automation
|
||||||
from esphome.const import (
|
from esphome.const import (
|
||||||
|
__version__,
|
||||||
CONF_ID,
|
CONF_ID,
|
||||||
CONF_TIMEOUT,
|
CONF_TIMEOUT,
|
||||||
CONF_METHOD,
|
CONF_METHOD,
|
||||||
|
@ -12,67 +11,91 @@ from esphome.const import (
|
||||||
CONF_ESP8266_DISABLE_SSL_SUPPORT,
|
CONF_ESP8266_DISABLE_SSL_SUPPORT,
|
||||||
)
|
)
|
||||||
from esphome.core import Lambda, CORE
|
from esphome.core import Lambda, CORE
|
||||||
|
from esphome.components import esp32
|
||||||
|
|
||||||
DEPENDENCIES = ["network"]
|
DEPENDENCIES = ["network"]
|
||||||
AUTO_LOAD = ["json"]
|
AUTO_LOAD = ["json"]
|
||||||
|
|
||||||
http_request_ns = cg.esphome_ns.namespace("http_request")
|
http_request_ns = cg.esphome_ns.namespace("http_request")
|
||||||
HttpRequestComponent = http_request_ns.class_("HttpRequestComponent", cg.Component)
|
HttpRequestComponent = http_request_ns.class_("HttpRequestComponent", cg.Component)
|
||||||
|
HttpRequestArduino = http_request_ns.class_("HttpRequestArduino", HttpRequestComponent)
|
||||||
|
HttpRequestIDF = http_request_ns.class_("HttpRequestIDF", HttpRequestComponent)
|
||||||
|
|
||||||
|
HttpContainer = http_request_ns.class_("HttpContainer")
|
||||||
|
|
||||||
HttpRequestSendAction = http_request_ns.class_(
|
HttpRequestSendAction = http_request_ns.class_(
|
||||||
"HttpRequestSendAction", automation.Action
|
"HttpRequestSendAction", automation.Action
|
||||||
)
|
)
|
||||||
HttpRequestResponseTrigger = http_request_ns.class_(
|
HttpRequestResponseTrigger = http_request_ns.class_(
|
||||||
"HttpRequestResponseTrigger", automation.Trigger
|
"HttpRequestResponseTrigger",
|
||||||
|
automation.Trigger.template(
|
||||||
|
cg.std_shared_ptr.template(HttpContainer), cg.std_string
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
CONF_HEADERS = "headers"
|
CONF_HTTP_REQUEST_ID = "http_request_id"
|
||||||
|
|
||||||
CONF_USERAGENT = "useragent"
|
CONF_USERAGENT = "useragent"
|
||||||
CONF_BODY = "body"
|
|
||||||
CONF_JSON = "json"
|
|
||||||
CONF_VERIFY_SSL = "verify_ssl"
|
CONF_VERIFY_SSL = "verify_ssl"
|
||||||
CONF_ON_RESPONSE = "on_response"
|
|
||||||
CONF_FOLLOW_REDIRECTS = "follow_redirects"
|
CONF_FOLLOW_REDIRECTS = "follow_redirects"
|
||||||
CONF_REDIRECT_LIMIT = "redirect_limit"
|
CONF_REDIRECT_LIMIT = "redirect_limit"
|
||||||
|
CONF_WATCHDOG_TIMEOUT = "watchdog_timeout"
|
||||||
|
|
||||||
|
CONF_MAX_RESPONSE_BUFFER_SIZE = "max_response_buffer_size"
|
||||||
|
CONF_ON_RESPONSE = "on_response"
|
||||||
|
CONF_HEADERS = "headers"
|
||||||
|
CONF_BODY = "body"
|
||||||
|
CONF_JSON = "json"
|
||||||
|
CONF_CAPTURE_RESPONSE = "capture_response"
|
||||||
|
|
||||||
|
|
||||||
def validate_url(value):
|
def validate_url(value):
|
||||||
value = cv.string(value)
|
value = cv.url(value)
|
||||||
try:
|
if value.startswith("http://") or value.startswith("https://"):
|
||||||
parsed = list(urlparse.urlparse(value))
|
return value
|
||||||
except Exception as err:
|
raise cv.Invalid("URL must start with 'http://' or 'https://'")
|
||||||
raise cv.Invalid("Invalid URL") from err
|
|
||||||
|
|
||||||
if not parsed[0] or not parsed[1]:
|
|
||||||
raise cv.Invalid("URL must have a URL scheme and host")
|
|
||||||
|
|
||||||
if parsed[0] not in ["http", "https"]:
|
|
||||||
raise cv.Invalid("Scheme must be http or https")
|
|
||||||
|
|
||||||
if not parsed[2]:
|
|
||||||
parsed[2] = "/"
|
|
||||||
|
|
||||||
return urlparse.urlunparse(parsed)
|
|
||||||
|
|
||||||
|
|
||||||
def validate_secure_url(config):
|
def validate_ssl_verification(config):
|
||||||
url_ = config[CONF_URL]
|
error_message = ""
|
||||||
|
|
||||||
|
if CORE.is_esp32:
|
||||||
|
if not CORE.using_esp_idf and config[CONF_VERIFY_SSL]:
|
||||||
|
error_message = "ESPHome supports certificate verification only via ESP-IDF"
|
||||||
|
|
||||||
|
if CORE.is_rp2040 and config[CONF_VERIFY_SSL]:
|
||||||
|
error_message = "ESPHome does not support certificate verification on RP2040"
|
||||||
|
|
||||||
if (
|
if (
|
||||||
config.get(CONF_VERIFY_SSL)
|
CORE.is_esp8266
|
||||||
and not isinstance(url_, Lambda)
|
and not config[CONF_ESP8266_DISABLE_SSL_SUPPORT]
|
||||||
and url_.lower().startswith("https:")
|
and config[CONF_VERIFY_SSL]
|
||||||
):
|
):
|
||||||
|
error_message = "ESPHome does not support certificate verification on ESP8266"
|
||||||
|
|
||||||
|
if len(error_message) > 0:
|
||||||
raise cv.Invalid(
|
raise cv.Invalid(
|
||||||
"Currently ESPHome doesn't support SSL verification. "
|
f"{error_message}. Set '{CONF_VERIFY_SSL}: false' to skip certificate validation and allow less secure HTTPS connections."
|
||||||
"Set 'verify_ssl: false' to make insecure HTTPS requests."
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return config
|
return config
|
||||||
|
|
||||||
|
|
||||||
|
def _declare_request_class(value):
|
||||||
|
if CORE.using_esp_idf:
|
||||||
|
return cv.declare_id(HttpRequestIDF)(value)
|
||||||
|
if CORE.is_esp8266 or CORE.is_esp32 or CORE.is_rp2040:
|
||||||
|
return cv.declare_id(HttpRequestArduino)(value)
|
||||||
|
return NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
CONFIG_SCHEMA = cv.All(
|
CONFIG_SCHEMA = cv.All(
|
||||||
cv.Schema(
|
cv.Schema(
|
||||||
{
|
{
|
||||||
cv.GenerateID(): cv.declare_id(HttpRequestComponent),
|
cv.GenerateID(): _declare_request_class,
|
||||||
cv.Optional(CONF_USERAGENT, "ESPHome"): cv.string,
|
cv.Optional(
|
||||||
|
CONF_USERAGENT, f"ESPHome/{__version__} (https://esphome.io)"
|
||||||
|
): cv.string,
|
||||||
cv.Optional(CONF_FOLLOW_REDIRECTS, True): cv.boolean,
|
cv.Optional(CONF_FOLLOW_REDIRECTS, True): cv.boolean,
|
||||||
cv.Optional(CONF_REDIRECT_LIMIT, 3): cv.int_,
|
cv.Optional(CONF_REDIRECT_LIMIT, 3): cv.int_,
|
||||||
cv.Optional(
|
cv.Optional(
|
||||||
|
@ -81,12 +104,21 @@ CONFIG_SCHEMA = cv.All(
|
||||||
cv.SplitDefault(CONF_ESP8266_DISABLE_SSL_SUPPORT, esp8266=False): cv.All(
|
cv.SplitDefault(CONF_ESP8266_DISABLE_SSL_SUPPORT, esp8266=False): cv.All(
|
||||||
cv.only_on_esp8266, cv.boolean
|
cv.only_on_esp8266, cv.boolean
|
||||||
),
|
),
|
||||||
|
cv.Optional(CONF_VERIFY_SSL, default=True): cv.boolean,
|
||||||
|
cv.Optional(CONF_WATCHDOG_TIMEOUT): cv.All(
|
||||||
|
cv.Any(cv.only_on_esp32, cv.only_on_rp2040),
|
||||||
|
cv.positive_not_null_time_period,
|
||||||
|
cv.positive_time_period_milliseconds,
|
||||||
|
),
|
||||||
}
|
}
|
||||||
).extend(cv.COMPONENT_SCHEMA),
|
).extend(cv.COMPONENT_SCHEMA),
|
||||||
cv.require_framework_version(
|
cv.require_framework_version(
|
||||||
esp8266_arduino=cv.Version(2, 5, 1),
|
esp8266_arduino=cv.Version(2, 5, 1),
|
||||||
esp32_arduino=cv.Version(0, 0, 0),
|
esp32_arduino=cv.Version(0, 0, 0),
|
||||||
|
esp_idf=cv.Version(0, 0, 0),
|
||||||
|
rp2040_arduino=cv.Version(0, 0, 0),
|
||||||
),
|
),
|
||||||
|
validate_ssl_verification,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -100,11 +132,30 @@ async def to_code(config):
|
||||||
if CORE.is_esp8266 and not config[CONF_ESP8266_DISABLE_SSL_SUPPORT]:
|
if CORE.is_esp8266 and not config[CONF_ESP8266_DISABLE_SSL_SUPPORT]:
|
||||||
cg.add_define("USE_HTTP_REQUEST_ESP8266_HTTPS")
|
cg.add_define("USE_HTTP_REQUEST_ESP8266_HTTPS")
|
||||||
|
|
||||||
|
if timeout_ms := config.get(CONF_WATCHDOG_TIMEOUT):
|
||||||
|
cg.add(var.set_watchdog_timeout(timeout_ms))
|
||||||
|
|
||||||
if CORE.is_esp32:
|
if CORE.is_esp32:
|
||||||
cg.add_library("WiFiClientSecure", None)
|
if CORE.using_esp_idf:
|
||||||
cg.add_library("HTTPClient", None)
|
esp32.add_idf_sdkconfig_option(
|
||||||
|
"CONFIG_MBEDTLS_CERTIFICATE_BUNDLE",
|
||||||
|
config.get(CONF_VERIFY_SSL),
|
||||||
|
)
|
||||||
|
esp32.add_idf_sdkconfig_option(
|
||||||
|
"CONFIG_ESP_TLS_INSECURE",
|
||||||
|
not config.get(CONF_VERIFY_SSL),
|
||||||
|
)
|
||||||
|
esp32.add_idf_sdkconfig_option(
|
||||||
|
"CONFIG_ESP_TLS_SKIP_SERVER_CERT_VERIFY",
|
||||||
|
not config.get(CONF_VERIFY_SSL),
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
cg.add_library("WiFiClientSecure", None)
|
||||||
|
cg.add_library("HTTPClient", None)
|
||||||
if CORE.is_esp8266:
|
if CORE.is_esp8266:
|
||||||
cg.add_library("ESP8266HTTPClient", None)
|
cg.add_library("ESP8266HTTPClient", None)
|
||||||
|
if CORE.is_rp2040 and CORE.using_arduino:
|
||||||
|
cg.add_library("HTTPClient", None)
|
||||||
|
|
||||||
await cg.register_component(var, config)
|
await cg.register_component(var, config)
|
||||||
|
|
||||||
|
@ -116,12 +167,16 @@ HTTP_REQUEST_ACTION_SCHEMA = cv.Schema(
|
||||||
cv.Optional(CONF_HEADERS): cv.All(
|
cv.Optional(CONF_HEADERS): cv.All(
|
||||||
cv.Schema({cv.string: cv.templatable(cv.string)})
|
cv.Schema({cv.string: cv.templatable(cv.string)})
|
||||||
),
|
),
|
||||||
cv.Optional(CONF_VERIFY_SSL, default=True): cv.boolean,
|
cv.Optional(CONF_VERIFY_SSL): cv.invalid(
|
||||||
|
f"{CONF_VERIFY_SSL} has moved to the base component configuration."
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_CAPTURE_RESPONSE, default=False): cv.boolean,
|
||||||
cv.Optional(CONF_ON_RESPONSE): automation.validate_automation(
|
cv.Optional(CONF_ON_RESPONSE): automation.validate_automation(
|
||||||
{cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(HttpRequestResponseTrigger)}
|
{cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(HttpRequestResponseTrigger)}
|
||||||
),
|
),
|
||||||
|
cv.Optional(CONF_MAX_RESPONSE_BUFFER_SIZE, default="1kB"): cv.validate_bytes,
|
||||||
}
|
}
|
||||||
).add_extra(validate_secure_url)
|
)
|
||||||
HTTP_REQUEST_GET_ACTION_SCHEMA = automation.maybe_conf(
|
HTTP_REQUEST_GET_ACTION_SCHEMA = automation.maybe_conf(
|
||||||
CONF_URL,
|
CONF_URL,
|
||||||
HTTP_REQUEST_ACTION_SCHEMA.extend(
|
HTTP_REQUEST_ACTION_SCHEMA.extend(
|
||||||
|
@ -173,6 +228,9 @@ async def http_request_action_to_code(config, action_id, template_arg, args):
|
||||||
template_ = await cg.templatable(config[CONF_URL], args, cg.std_string)
|
template_ = await cg.templatable(config[CONF_URL], args, cg.std_string)
|
||||||
cg.add(var.set_url(template_))
|
cg.add(var.set_url(template_))
|
||||||
cg.add(var.set_method(config[CONF_METHOD]))
|
cg.add(var.set_method(config[CONF_METHOD]))
|
||||||
|
cg.add(var.set_capture_response(config[CONF_CAPTURE_RESPONSE]))
|
||||||
|
cg.add(var.set_max_response_buffer_size(config[CONF_MAX_RESPONSE_BUFFER_SIZE]))
|
||||||
|
|
||||||
if CONF_BODY in config:
|
if CONF_BODY in config:
|
||||||
template_ = await cg.templatable(config[CONF_BODY], args, cg.std_string)
|
template_ = await cg.templatable(config[CONF_BODY], args, cg.std_string)
|
||||||
cg.add(var.set_body(template_))
|
cg.add(var.set_body(template_))
|
||||||
|
@ -196,7 +254,12 @@ async def http_request_action_to_code(config, action_id, template_arg, args):
|
||||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID])
|
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID])
|
||||||
cg.add(var.register_response_trigger(trigger))
|
cg.add(var.register_response_trigger(trigger))
|
||||||
await automation.build_automation(
|
await automation.build_automation(
|
||||||
trigger, [(int, "status_code"), (cg.uint32, "duration_ms")], conf
|
trigger,
|
||||||
|
[
|
||||||
|
(cg.std_shared_ptr.template(HttpContainer), "response"),
|
||||||
|
(cg.std_string_ref, "body"),
|
||||||
|
],
|
||||||
|
conf,
|
||||||
)
|
)
|
||||||
|
|
||||||
return var
|
return var
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
#ifdef USE_ARDUINO
|
|
||||||
|
|
||||||
#include "http_request.h"
|
#include "http_request.h"
|
||||||
#include "esphome/core/defines.h"
|
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
#include "esphome/components/network/util.h"
|
|
||||||
|
#include <cinttypes>
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace http_request {
|
namespace http_request {
|
||||||
|
@ -14,131 +13,12 @@ void HttpRequestComponent::dump_config() {
|
||||||
ESP_LOGCONFIG(TAG, "HTTP Request:");
|
ESP_LOGCONFIG(TAG, "HTTP Request:");
|
||||||
ESP_LOGCONFIG(TAG, " Timeout: %ums", this->timeout_);
|
ESP_LOGCONFIG(TAG, " Timeout: %ums", this->timeout_);
|
||||||
ESP_LOGCONFIG(TAG, " User-Agent: %s", this->useragent_);
|
ESP_LOGCONFIG(TAG, " User-Agent: %s", this->useragent_);
|
||||||
ESP_LOGCONFIG(TAG, " Follow Redirects: %d", this->follow_redirects_);
|
ESP_LOGCONFIG(TAG, " Follow redirects: %s", YESNO(this->follow_redirects_));
|
||||||
ESP_LOGCONFIG(TAG, " Redirect limit: %d", this->redirect_limit_);
|
ESP_LOGCONFIG(TAG, " Redirect limit: %d", this->redirect_limit_);
|
||||||
}
|
if (this->watchdog_timeout_ > 0) {
|
||||||
|
ESP_LOGCONFIG(TAG, " Watchdog Timeout: %" PRIu32 "ms", this->watchdog_timeout_);
|
||||||
void HttpRequestComponent::set_url(std::string url) {
|
|
||||||
this->url_ = std::move(url);
|
|
||||||
this->secure_ = this->url_.compare(0, 6, "https:") == 0;
|
|
||||||
|
|
||||||
if (!this->last_url_.empty() && this->url_ != this->last_url_) {
|
|
||||||
// Close connection if url has been changed
|
|
||||||
this->client_.setReuse(false);
|
|
||||||
this->client_.end();
|
|
||||||
}
|
}
|
||||||
this->client_.setReuse(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
void HttpRequestComponent::send(const std::vector<HttpRequestResponseTrigger *> &response_triggers) {
|
|
||||||
if (!network::is_connected()) {
|
|
||||||
this->client_.end();
|
|
||||||
this->status_set_warning();
|
|
||||||
ESP_LOGW(TAG, "HTTP Request failed; Not connected to network");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool begin_status = false;
|
|
||||||
const String url = this->url_.c_str();
|
|
||||||
#if defined(USE_ESP32) || (defined(USE_ESP8266) && USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 6, 0))
|
|
||||||
#if defined(USE_ESP32) || USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 7, 0)
|
|
||||||
if (this->follow_redirects_) {
|
|
||||||
this->client_.setFollowRedirects(HTTPC_FORCE_FOLLOW_REDIRECTS);
|
|
||||||
} else {
|
|
||||||
this->client_.setFollowRedirects(HTTPC_DISABLE_FOLLOW_REDIRECTS);
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
this->client_.setFollowRedirects(this->follow_redirects_);
|
|
||||||
#endif
|
|
||||||
this->client_.setRedirectLimit(this->redirect_limit_);
|
|
||||||
#endif
|
|
||||||
#if defined(USE_ESP32)
|
|
||||||
begin_status = this->client_.begin(url);
|
|
||||||
#elif defined(USE_ESP8266)
|
|
||||||
begin_status = this->client_.begin(*this->get_wifi_client_(), url);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (!begin_status) {
|
|
||||||
this->client_.end();
|
|
||||||
this->status_set_warning();
|
|
||||||
ESP_LOGW(TAG, "HTTP Request failed at the begin phase. Please check the configuration");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this->client_.setTimeout(this->timeout_);
|
|
||||||
#if defined(USE_ESP32)
|
|
||||||
this->client_.setConnectTimeout(this->timeout_);
|
|
||||||
#endif
|
|
||||||
if (this->useragent_ != nullptr) {
|
|
||||||
this->client_.setUserAgent(this->useragent_);
|
|
||||||
}
|
|
||||||
for (const auto &header : this->headers_) {
|
|
||||||
this->client_.addHeader(header.name, header.value, false, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t start_time = millis();
|
|
||||||
int http_code = this->client_.sendRequest(this->method_, this->body_.c_str());
|
|
||||||
uint32_t duration = millis() - start_time;
|
|
||||||
for (auto *trigger : response_triggers)
|
|
||||||
trigger->process(http_code, duration);
|
|
||||||
|
|
||||||
if (http_code < 0) {
|
|
||||||
ESP_LOGW(TAG, "HTTP Request failed; URL: %s; Error: %s; Duration: %u ms", this->url_.c_str(),
|
|
||||||
HTTPClient::errorToString(http_code).c_str(), duration);
|
|
||||||
this->status_set_warning();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (http_code < 200 || http_code >= 300) {
|
|
||||||
ESP_LOGW(TAG, "HTTP Request failed; URL: %s; Code: %d; Duration: %u ms", this->url_.c_str(), http_code, duration);
|
|
||||||
this->status_set_warning();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this->status_clear_warning();
|
|
||||||
ESP_LOGD(TAG, "HTTP Request completed; URL: %s; Code: %d; Duration: %u ms", this->url_.c_str(), http_code, duration);
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef USE_ESP8266
|
|
||||||
std::shared_ptr<WiFiClient> HttpRequestComponent::get_wifi_client_() {
|
|
||||||
#ifdef USE_HTTP_REQUEST_ESP8266_HTTPS
|
|
||||||
if (this->secure_) {
|
|
||||||
if (this->wifi_client_secure_ == nullptr) {
|
|
||||||
this->wifi_client_secure_ = std::make_shared<BearSSL::WiFiClientSecure>();
|
|
||||||
this->wifi_client_secure_->setInsecure();
|
|
||||||
this->wifi_client_secure_->setBufferSizes(512, 512);
|
|
||||||
}
|
|
||||||
return this->wifi_client_secure_;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (this->wifi_client_ == nullptr) {
|
|
||||||
this->wifi_client_ = std::make_shared<WiFiClient>();
|
|
||||||
}
|
|
||||||
return this->wifi_client_;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
void HttpRequestComponent::close() {
|
|
||||||
this->last_url_ = this->url_;
|
|
||||||
this->client_.end();
|
|
||||||
}
|
|
||||||
|
|
||||||
const char *HttpRequestComponent::get_string() {
|
|
||||||
#if defined(ESP32)
|
|
||||||
// The static variable is here because HTTPClient::getString() returns a String on ESP32,
|
|
||||||
// and we need something to keep a buffer alive.
|
|
||||||
static String str;
|
|
||||||
#else
|
|
||||||
// However on ESP8266, HTTPClient::getString() returns a String& to a member variable.
|
|
||||||
// Leaving this the default so that any new platform either doesn't copy, or encounters a compilation error.
|
|
||||||
auto &
|
|
||||||
#endif
|
|
||||||
str = this->client_.getString();
|
|
||||||
return str.c_str();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace http_request
|
} // namespace http_request
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|
||||||
#endif // USE_ARDUINO
|
|
||||||
|
|
|
@ -1,27 +1,18 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#ifdef USE_ARDUINO
|
|
||||||
|
|
||||||
#include "esphome/components/json/json_util.h"
|
|
||||||
#include "esphome/core/automation.h"
|
|
||||||
#include "esphome/core/component.h"
|
|
||||||
#include "esphome/core/defines.h"
|
|
||||||
|
|
||||||
#include <list>
|
#include <list>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#ifdef USE_ESP32
|
#include "esphome/components/json/json_util.h"
|
||||||
#include <HTTPClient.h>
|
#include "esphome/core/application.h"
|
||||||
#endif
|
#include "esphome/core/automation.h"
|
||||||
#ifdef USE_ESP8266
|
#include "esphome/core/component.h"
|
||||||
#include <ESP8266HTTPClient.h>
|
#include "esphome/core/defines.h"
|
||||||
#ifdef USE_HTTP_REQUEST_ESP8266_HTTPS
|
#include "esphome/core/helpers.h"
|
||||||
#include <WiFiClientSecure.h>
|
#include "esphome/core/log.h"
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace http_request {
|
namespace http_request {
|
||||||
|
@ -31,9 +22,32 @@ struct Header {
|
||||||
const char *value;
|
const char *value;
|
||||||
};
|
};
|
||||||
|
|
||||||
class HttpRequestResponseTrigger : public Trigger<int32_t, uint32_t> {
|
class HttpRequestComponent;
|
||||||
|
|
||||||
|
class HttpContainer : public Parented<HttpRequestComponent> {
|
||||||
public:
|
public:
|
||||||
void process(int32_t status_code, uint32_t duration_ms) { this->trigger(status_code, duration_ms); }
|
virtual ~HttpContainer() = default;
|
||||||
|
size_t content_length;
|
||||||
|
int status_code;
|
||||||
|
uint32_t duration_ms;
|
||||||
|
|
||||||
|
virtual int read(uint8_t *buf, size_t max_len) = 0;
|
||||||
|
virtual void end() = 0;
|
||||||
|
|
||||||
|
void set_secure(bool secure) { this->secure_ = secure; }
|
||||||
|
|
||||||
|
size_t get_bytes_read() const { return this->bytes_read_; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
size_t bytes_read_{0};
|
||||||
|
bool secure_{false};
|
||||||
|
};
|
||||||
|
|
||||||
|
class HttpRequestResponseTrigger : public Trigger<std::shared_ptr<HttpContainer>, std::string &> {
|
||||||
|
public:
|
||||||
|
void process(std::shared_ptr<HttpContainer> container, std::string &response_body) {
|
||||||
|
this->trigger(std::move(container), response_body);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
class HttpRequestComponent : public Component {
|
class HttpRequestComponent : public Component {
|
||||||
|
@ -41,37 +55,33 @@ class HttpRequestComponent : public Component {
|
||||||
void dump_config() override;
|
void dump_config() override;
|
||||||
float get_setup_priority() const override { return setup_priority::AFTER_WIFI; }
|
float get_setup_priority() const override { return setup_priority::AFTER_WIFI; }
|
||||||
|
|
||||||
void set_url(std::string url);
|
|
||||||
void set_method(const char *method) { this->method_ = method; }
|
|
||||||
void set_useragent(const char *useragent) { this->useragent_ = useragent; }
|
void set_useragent(const char *useragent) { this->useragent_ = useragent; }
|
||||||
void set_timeout(uint16_t timeout) { this->timeout_ = timeout; }
|
void set_timeout(uint16_t timeout) { this->timeout_ = timeout; }
|
||||||
|
void set_watchdog_timeout(uint32_t watchdog_timeout) { this->watchdog_timeout_ = watchdog_timeout; }
|
||||||
|
uint32_t get_watchdog_timeout() const { return this->watchdog_timeout_; }
|
||||||
void set_follow_redirects(bool follow_redirects) { this->follow_redirects_ = follow_redirects; }
|
void set_follow_redirects(bool follow_redirects) { this->follow_redirects_ = follow_redirects; }
|
||||||
void set_redirect_limit(uint16_t limit) { this->redirect_limit_ = limit; }
|
void set_redirect_limit(uint16_t limit) { this->redirect_limit_ = limit; }
|
||||||
void set_body(const std::string &body) { this->body_ = body; }
|
|
||||||
void set_headers(std::list<Header> headers) { this->headers_ = std::move(headers); }
|
std::shared_ptr<HttpContainer> get(std::string url) { return this->start(std::move(url), "GET", "", {}); }
|
||||||
void send(const std::vector<HttpRequestResponseTrigger *> &response_triggers);
|
std::shared_ptr<HttpContainer> get(std::string url, std::list<Header> headers) {
|
||||||
void close();
|
return this->start(std::move(url), "GET", "", std::move(headers));
|
||||||
const char *get_string();
|
}
|
||||||
|
std::shared_ptr<HttpContainer> post(std::string url, std::string body) {
|
||||||
|
return this->start(std::move(url), "POST", std::move(body), {});
|
||||||
|
}
|
||||||
|
std::shared_ptr<HttpContainer> post(std::string url, std::string body, std::list<Header> headers) {
|
||||||
|
return this->start(std::move(url), "POST", std::move(body), std::move(headers));
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual std::shared_ptr<HttpContainer> start(std::string url, std::string method, std::string body,
|
||||||
|
std::list<Header> headers) = 0;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
HTTPClient client_{};
|
|
||||||
std::string url_;
|
|
||||||
std::string last_url_;
|
|
||||||
const char *method_;
|
|
||||||
const char *useragent_{nullptr};
|
const char *useragent_{nullptr};
|
||||||
bool secure_;
|
|
||||||
bool follow_redirects_;
|
bool follow_redirects_;
|
||||||
uint16_t redirect_limit_;
|
uint16_t redirect_limit_;
|
||||||
uint16_t timeout_{5000};
|
uint16_t timeout_{5000};
|
||||||
std::string body_;
|
uint32_t watchdog_timeout_{0};
|
||||||
std::list<Header> headers_;
|
|
||||||
#ifdef USE_ESP8266
|
|
||||||
std::shared_ptr<WiFiClient> wifi_client_;
|
|
||||||
#ifdef USE_HTTP_REQUEST_ESP8266_HTTPS
|
|
||||||
std::shared_ptr<BearSSL::WiFiClientSecure> wifi_client_secure_;
|
|
||||||
#endif
|
|
||||||
std::shared_ptr<WiFiClient> get_wifi_client_();
|
|
||||||
#endif
|
|
||||||
};
|
};
|
||||||
|
|
||||||
template<typename... Ts> class HttpRequestSendAction : public Action<Ts...> {
|
template<typename... Ts> class HttpRequestSendAction : public Action<Ts...> {
|
||||||
|
@ -80,6 +90,7 @@ template<typename... Ts> class HttpRequestSendAction : public Action<Ts...> {
|
||||||
TEMPLATABLE_VALUE(std::string, url)
|
TEMPLATABLE_VALUE(std::string, url)
|
||||||
TEMPLATABLE_VALUE(const char *, method)
|
TEMPLATABLE_VALUE(const char *, method)
|
||||||
TEMPLATABLE_VALUE(std::string, body)
|
TEMPLATABLE_VALUE(std::string, body)
|
||||||
|
TEMPLATABLE_VALUE(bool, capture_response)
|
||||||
|
|
||||||
void add_header(const char *key, TemplatableValue<const char *, Ts...> value) { this->headers_.insert({key, value}); }
|
void add_header(const char *key, TemplatableValue<const char *, Ts...> value) { this->headers_.insert({key, value}); }
|
||||||
|
|
||||||
|
@ -89,19 +100,22 @@ template<typename... Ts> class HttpRequestSendAction : public Action<Ts...> {
|
||||||
|
|
||||||
void register_response_trigger(HttpRequestResponseTrigger *trigger) { this->response_triggers_.push_back(trigger); }
|
void register_response_trigger(HttpRequestResponseTrigger *trigger) { this->response_triggers_.push_back(trigger); }
|
||||||
|
|
||||||
|
void set_max_response_buffer_size(size_t max_response_buffer_size) {
|
||||||
|
this->max_response_buffer_size_ = max_response_buffer_size;
|
||||||
|
}
|
||||||
|
|
||||||
void play(Ts... x) override {
|
void play(Ts... x) override {
|
||||||
this->parent_->set_url(this->url_.value(x...));
|
std::string body;
|
||||||
this->parent_->set_method(this->method_.value(x...));
|
|
||||||
if (this->body_.has_value()) {
|
if (this->body_.has_value()) {
|
||||||
this->parent_->set_body(this->body_.value(x...));
|
body = this->body_.value(x...);
|
||||||
}
|
}
|
||||||
if (!this->json_.empty()) {
|
if (!this->json_.empty()) {
|
||||||
auto f = std::bind(&HttpRequestSendAction<Ts...>::encode_json_, this, x..., std::placeholders::_1);
|
auto f = std::bind(&HttpRequestSendAction<Ts...>::encode_json_, this, x..., std::placeholders::_1);
|
||||||
this->parent_->set_body(json::build_json(f));
|
body = json::build_json(f);
|
||||||
}
|
}
|
||||||
if (this->json_func_ != nullptr) {
|
if (this->json_func_ != nullptr) {
|
||||||
auto f = std::bind(&HttpRequestSendAction<Ts...>::encode_json_func_, this, x..., std::placeholders::_1);
|
auto f = std::bind(&HttpRequestSendAction<Ts...>::encode_json_func_, this, x..., std::placeholders::_1);
|
||||||
this->parent_->set_body(json::build_json(f));
|
body = json::build_json(f);
|
||||||
}
|
}
|
||||||
std::list<Header> headers;
|
std::list<Header> headers;
|
||||||
for (const auto &item : this->headers_) {
|
for (const auto &item : this->headers_) {
|
||||||
|
@ -111,10 +125,47 @@ template<typename... Ts> class HttpRequestSendAction : public Action<Ts...> {
|
||||||
header.value = val.value(x...);
|
header.value = val.value(x...);
|
||||||
headers.push_back(header);
|
headers.push_back(header);
|
||||||
}
|
}
|
||||||
this->parent_->set_headers(headers);
|
|
||||||
this->parent_->send(this->response_triggers_);
|
auto container = this->parent_->start(this->url_.value(x...), this->method_.value(x...), body, headers);
|
||||||
this->parent_->close();
|
|
||||||
this->parent_->set_body("");
|
if (container == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t content_length = container->content_length;
|
||||||
|
size_t max_length = std::min(content_length, this->max_response_buffer_size_);
|
||||||
|
|
||||||
|
std::string response_body;
|
||||||
|
if (this->capture_response_.value(x...)) {
|
||||||
|
ExternalRAMAllocator<uint8_t> allocator(ExternalRAMAllocator<uint8_t>::ALLOW_FAILURE);
|
||||||
|
uint8_t *buf = allocator.allocate(max_length);
|
||||||
|
if (buf != nullptr) {
|
||||||
|
size_t read_index = 0;
|
||||||
|
while (container->get_bytes_read() < max_length) {
|
||||||
|
int read = container->read(buf + read_index, std::min<size_t>(max_length - read_index, 512));
|
||||||
|
App.feed_wdt();
|
||||||
|
yield();
|
||||||
|
read_index += read;
|
||||||
|
}
|
||||||
|
response_body.reserve(read_index);
|
||||||
|
response_body.assign((char *) buf, read_index);
|
||||||
|
allocator.deallocate(buf, max_length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this->response_triggers_.size() == 1) {
|
||||||
|
// if there is only one trigger, no need to copy the response body
|
||||||
|
this->response_triggers_[0]->process(container, response_body);
|
||||||
|
} else {
|
||||||
|
for (auto *trigger : this->response_triggers_) {
|
||||||
|
// with multiple triggers, pass a copy of the response body to each
|
||||||
|
// one so that modifications made in one trigger are not visible to
|
||||||
|
// the others
|
||||||
|
auto response_body_copy = std::string(response_body);
|
||||||
|
trigger->process(container, response_body_copy);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
container->end();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
@ -130,9 +181,9 @@ template<typename... Ts> class HttpRequestSendAction : public Action<Ts...> {
|
||||||
std::map<const char *, TemplatableValue<std::string, Ts...>> json_{};
|
std::map<const char *, TemplatableValue<std::string, Ts...>> json_{};
|
||||||
std::function<void(Ts..., JsonObject)> json_func_{nullptr};
|
std::function<void(Ts..., JsonObject)> json_func_{nullptr};
|
||||||
std::vector<HttpRequestResponseTrigger *> response_triggers_;
|
std::vector<HttpRequestResponseTrigger *> response_triggers_;
|
||||||
|
|
||||||
|
size_t max_response_buffer_size_{SIZE_MAX};
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace http_request
|
} // namespace http_request
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|
||||||
#endif // USE_ARDUINO
|
|
||||||
|
|
161
esphome/components/http_request/http_request_arduino.cpp
Normal file
161
esphome/components/http_request/http_request_arduino.cpp
Normal file
|
@ -0,0 +1,161 @@
|
||||||
|
#include "http_request_arduino.h"
|
||||||
|
|
||||||
|
#ifdef USE_ARDUINO
|
||||||
|
|
||||||
|
#include "esphome/components/network/util.h"
|
||||||
|
#include "esphome/core/application.h"
|
||||||
|
#include "esphome/core/defines.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
|
#include "watchdog.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace http_request {
|
||||||
|
|
||||||
|
static const char *const TAG = "http_request.arduino";
|
||||||
|
|
||||||
|
std::shared_ptr<HttpContainer> HttpRequestArduino::start(std::string url, std::string method, std::string body,
|
||||||
|
std::list<Header> headers) {
|
||||||
|
if (!network::is_connected()) {
|
||||||
|
this->status_momentary_error("failed", 1000);
|
||||||
|
ESP_LOGW(TAG, "HTTP Request failed; Not connected to network");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<HttpContainerArduino> container = std::make_shared<HttpContainerArduino>();
|
||||||
|
container->set_parent(this);
|
||||||
|
|
||||||
|
const uint32_t start = millis();
|
||||||
|
|
||||||
|
bool secure = url.find("https:") != std::string::npos;
|
||||||
|
container->set_secure(secure);
|
||||||
|
|
||||||
|
watchdog::WatchdogManager wdm(this->get_watchdog_timeout());
|
||||||
|
|
||||||
|
#if defined(USE_ESP8266)
|
||||||
|
std::unique_ptr<WiFiClient> stream_ptr;
|
||||||
|
#ifdef USE_HTTP_REQUEST_ESP8266_HTTPS
|
||||||
|
if (secure) {
|
||||||
|
ESP_LOGV(TAG, "ESP8266 HTTPS connection with WiFiClientSecure");
|
||||||
|
stream_ptr = std::make_unique<WiFiClientSecure>();
|
||||||
|
WiFiClientSecure *secure_client = static_cast<WiFiClientSecure *>(stream_ptr.get());
|
||||||
|
secure_client->setBufferSizes(512, 512);
|
||||||
|
secure_client->setInsecure();
|
||||||
|
} else {
|
||||||
|
stream_ptr = std::make_unique<WiFiClient>();
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
ESP_LOGV(TAG, "ESP8266 HTTP connection with WiFiClient");
|
||||||
|
if (secure) {
|
||||||
|
ESP_LOGE(TAG, "Can't use HTTPS connection with esp8266_disable_ssl_support");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
stream_ptr = std::make_unique<WiFiClient>();
|
||||||
|
#endif // USE_HTTP_REQUEST_ESP8266_HTTPS
|
||||||
|
|
||||||
|
#if USE_ARDUINO_VERSION_CODE >= VERSION_CODE(3, 1, 0) // && USE_ARDUINO_VERSION_CODE < VERSION_CODE(?, ?, ?)
|
||||||
|
if (!secure) {
|
||||||
|
ESP_LOGW(TAG, "Using HTTP on Arduino version >= 3.1 is **very** slow. Consider setting framework version to 3.0.2 "
|
||||||
|
"in your YAML, or use HTTPS");
|
||||||
|
}
|
||||||
|
#endif // USE_ARDUINO_VERSION_CODE
|
||||||
|
|
||||||
|
container->client_.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
|
||||||
|
bool status = container->client_.begin(*stream_ptr, url.c_str());
|
||||||
|
|
||||||
|
#elif defined(USE_RP2040)
|
||||||
|
if (secure) {
|
||||||
|
container->client_.setInsecure();
|
||||||
|
}
|
||||||
|
bool status = container->client_.begin(url.c_str());
|
||||||
|
#elif defined(USE_ESP32)
|
||||||
|
bool status = container->client_.begin(url.c_str());
|
||||||
|
#endif
|
||||||
|
|
||||||
|
App.feed_wdt();
|
||||||
|
|
||||||
|
if (!status) {
|
||||||
|
ESP_LOGW(TAG, "HTTP Request failed; URL: %s", url.c_str());
|
||||||
|
container->end();
|
||||||
|
this->status_momentary_error("failed", 1000);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
container->client_.setReuse(true);
|
||||||
|
container->client_.setTimeout(this->timeout_);
|
||||||
|
#if defined(USE_ESP32)
|
||||||
|
container->client_.setConnectTimeout(this->timeout_);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (this->useragent_ != nullptr) {
|
||||||
|
container->client_.setUserAgent(this->useragent_);
|
||||||
|
}
|
||||||
|
for (const auto &header : headers) {
|
||||||
|
container->client_.addHeader(header.name, header.value, false, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// returned needed headers must be collected before the requests
|
||||||
|
static const char *header_keys[] = {"Content-Length", "Content-Type"};
|
||||||
|
static const size_t HEADER_COUNT = sizeof(header_keys) / sizeof(header_keys[0]);
|
||||||
|
container->client_.collectHeaders(header_keys, HEADER_COUNT);
|
||||||
|
|
||||||
|
container->status_code = container->client_.sendRequest(method.c_str(), body.c_str());
|
||||||
|
if (container->status_code < 0) {
|
||||||
|
ESP_LOGW(TAG, "HTTP Request failed; URL: %s; Error: %s", url.c_str(),
|
||||||
|
HTTPClient::errorToString(container->status_code).c_str());
|
||||||
|
this->status_momentary_error("failed", 1000);
|
||||||
|
container->end();
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (container->status_code < 200 || container->status_code >= 300) {
|
||||||
|
ESP_LOGE(TAG, "HTTP Request failed; URL: %s; Code: %d", url.c_str(), container->status_code);
|
||||||
|
this->status_momentary_error("failed", 1000);
|
||||||
|
container->end();
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
int content_length = container->client_.getSize();
|
||||||
|
ESP_LOGD(TAG, "Content-Length: %d", content_length);
|
||||||
|
container->content_length = (size_t) content_length;
|
||||||
|
container->duration_ms = millis() - start;
|
||||||
|
|
||||||
|
return container;
|
||||||
|
}
|
||||||
|
|
||||||
|
int HttpContainerArduino::read(uint8_t *buf, size_t max_len) {
|
||||||
|
const uint32_t start = millis();
|
||||||
|
watchdog::WatchdogManager wdm(this->parent_->get_watchdog_timeout());
|
||||||
|
|
||||||
|
WiFiClient *stream_ptr = this->client_.getStreamPtr();
|
||||||
|
if (stream_ptr == nullptr) {
|
||||||
|
ESP_LOGE(TAG, "Stream pointer vanished!");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int available_data = stream_ptr->available();
|
||||||
|
int bufsize = std::min(max_len, std::min(this->content_length - this->bytes_read_, (size_t) available_data));
|
||||||
|
|
||||||
|
if (bufsize == 0) {
|
||||||
|
this->duration_ms += (millis() - start);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
App.feed_wdt();
|
||||||
|
int read_len = stream_ptr->readBytes(buf, bufsize);
|
||||||
|
this->bytes_read_ += read_len;
|
||||||
|
|
||||||
|
this->duration_ms += (millis() - start);
|
||||||
|
|
||||||
|
return read_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
void HttpContainerArduino::end() {
|
||||||
|
watchdog::WatchdogManager wdm(this->parent_->get_watchdog_timeout());
|
||||||
|
this->client_.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace http_request
|
||||||
|
} // namespace esphome
|
||||||
|
|
||||||
|
#endif // USE_ARDUINO
|
40
esphome/components/http_request/http_request_arduino.h
Normal file
40
esphome/components/http_request/http_request_arduino.h
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "http_request.h"
|
||||||
|
|
||||||
|
#ifdef USE_ARDUINO
|
||||||
|
|
||||||
|
#if defined(USE_ESP32) || defined(USE_RP2040)
|
||||||
|
#include <HTTPClient.h>
|
||||||
|
#endif
|
||||||
|
#ifdef USE_ESP8266
|
||||||
|
#include <ESP8266HTTPClient.h>
|
||||||
|
#ifdef USE_HTTP_REQUEST_ESP8266_HTTPS
|
||||||
|
#include <WiFiClientSecure.h>
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace http_request {
|
||||||
|
|
||||||
|
class HttpRequestArduino;
|
||||||
|
class HttpContainerArduino : public HttpContainer {
|
||||||
|
public:
|
||||||
|
int read(uint8_t *buf, size_t max_len) override;
|
||||||
|
void end() override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
friend class HttpRequestArduino;
|
||||||
|
HTTPClient client_{};
|
||||||
|
};
|
||||||
|
|
||||||
|
class HttpRequestArduino : public HttpRequestComponent {
|
||||||
|
public:
|
||||||
|
std::shared_ptr<HttpContainer> start(std::string url, std::string method, std::string body,
|
||||||
|
std::list<Header> headers) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace http_request
|
||||||
|
} // namespace esphome
|
||||||
|
|
||||||
|
#endif // USE_ARDUINO
|
155
esphome/components/http_request/http_request_idf.cpp
Normal file
155
esphome/components/http_request/http_request_idf.cpp
Normal file
|
@ -0,0 +1,155 @@
|
||||||
|
#include "http_request_idf.h"
|
||||||
|
|
||||||
|
#ifdef USE_ESP_IDF
|
||||||
|
|
||||||
|
#include "esphome/components/network/util.h"
|
||||||
|
#include "esphome/core/application.h"
|
||||||
|
#include "esphome/core/defines.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
|
#if CONFIG_MBEDTLS_CERTIFICATE_BUNDLE
|
||||||
|
#include "esp_crt_bundle.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "watchdog.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace http_request {
|
||||||
|
|
||||||
|
static const char *const TAG = "http_request.idf";
|
||||||
|
|
||||||
|
std::shared_ptr<HttpContainer> HttpRequestIDF::start(std::string url, std::string method, std::string body,
|
||||||
|
std::list<Header> headers) {
|
||||||
|
if (!network::is_connected()) {
|
||||||
|
this->status_momentary_error("failed", 1000);
|
||||||
|
ESP_LOGE(TAG, "HTTP Request failed; Not connected to network");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_http_client_method_t method_idf;
|
||||||
|
if (method == "GET") {
|
||||||
|
method_idf = HTTP_METHOD_GET;
|
||||||
|
} else if (method == "POST") {
|
||||||
|
method_idf = HTTP_METHOD_POST;
|
||||||
|
} else if (method == "PUT") {
|
||||||
|
method_idf = HTTP_METHOD_PUT;
|
||||||
|
} else if (method == "DELETE") {
|
||||||
|
method_idf = HTTP_METHOD_DELETE;
|
||||||
|
} else if (method == "PATCH") {
|
||||||
|
method_idf = HTTP_METHOD_PATCH;
|
||||||
|
} else {
|
||||||
|
this->status_momentary_error("failed", 1000);
|
||||||
|
ESP_LOGE(TAG, "HTTP Request failed; Unsupported method");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool secure = url.find("https:") != std::string::npos;
|
||||||
|
|
||||||
|
esp_http_client_config_t config = {};
|
||||||
|
|
||||||
|
config.url = url.c_str();
|
||||||
|
config.method = method_idf;
|
||||||
|
config.timeout_ms = this->timeout_;
|
||||||
|
config.disable_auto_redirect = !this->follow_redirects_;
|
||||||
|
config.max_redirection_count = this->redirect_limit_;
|
||||||
|
#if CONFIG_MBEDTLS_CERTIFICATE_BUNDLE
|
||||||
|
if (secure) {
|
||||||
|
config.crt_bundle_attach = esp_crt_bundle_attach;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (this->useragent_ != nullptr) {
|
||||||
|
config.user_agent = this->useragent_;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint32_t start = millis();
|
||||||
|
watchdog::WatchdogManager wdm(this->get_watchdog_timeout());
|
||||||
|
|
||||||
|
esp_http_client_handle_t client = esp_http_client_init(&config);
|
||||||
|
|
||||||
|
std::shared_ptr<HttpContainerIDF> container = std::make_shared<HttpContainerIDF>(client);
|
||||||
|
container->set_parent(this);
|
||||||
|
|
||||||
|
container->set_secure(secure);
|
||||||
|
|
||||||
|
for (const auto &header : headers) {
|
||||||
|
esp_http_client_set_header(client, header.name, header.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
int body_len = body.length();
|
||||||
|
|
||||||
|
esp_err_t err = esp_http_client_open(client, body_len);
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
this->status_momentary_error("failed", 1000);
|
||||||
|
ESP_LOGE(TAG, "HTTP Request failed: %s", esp_err_to_name(err));
|
||||||
|
esp_http_client_cleanup(client);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (body_len > 0) {
|
||||||
|
int write_left = body_len;
|
||||||
|
int write_index = 0;
|
||||||
|
const char *buf = body.c_str();
|
||||||
|
while (write_left > 0) {
|
||||||
|
int written = esp_http_client_write(client, buf + write_index, write_left);
|
||||||
|
if (written < 0) {
|
||||||
|
err = ESP_FAIL;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
write_left -= written;
|
||||||
|
write_index += written;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
this->status_momentary_error("failed", 1000);
|
||||||
|
ESP_LOGE(TAG, "HTTP Request failed: %s", esp_err_to_name(err));
|
||||||
|
esp_http_client_cleanup(client);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
container->content_length = esp_http_client_fetch_headers(client);
|
||||||
|
const auto status_code = esp_http_client_get_status_code(client);
|
||||||
|
container->status_code = status_code;
|
||||||
|
|
||||||
|
if (status_code < 200 || status_code >= 300) {
|
||||||
|
ESP_LOGE(TAG, "HTTP Request failed; URL: %s; Code: %d", url.c_str(), status_code);
|
||||||
|
this->status_momentary_error("failed", 1000);
|
||||||
|
esp_http_client_cleanup(client);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
container->duration_ms = millis() - start;
|
||||||
|
return container;
|
||||||
|
}
|
||||||
|
|
||||||
|
int HttpContainerIDF::read(uint8_t *buf, size_t max_len) {
|
||||||
|
const uint32_t start = millis();
|
||||||
|
watchdog::WatchdogManager wdm(this->parent_->get_watchdog_timeout());
|
||||||
|
|
||||||
|
int bufsize = std::min(max_len, this->content_length - this->bytes_read_);
|
||||||
|
|
||||||
|
if (bufsize == 0) {
|
||||||
|
this->duration_ms += (millis() - start);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
App.feed_wdt();
|
||||||
|
int read_len = esp_http_client_read(this->client_, (char *) buf, bufsize);
|
||||||
|
this->bytes_read_ += read_len;
|
||||||
|
|
||||||
|
this->duration_ms += (millis() - start);
|
||||||
|
|
||||||
|
return read_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
void HttpContainerIDF::end() {
|
||||||
|
watchdog::WatchdogManager wdm(this->parent_->get_watchdog_timeout());
|
||||||
|
|
||||||
|
esp_http_client_close(this->client_);
|
||||||
|
esp_http_client_cleanup(this->client_);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace http_request
|
||||||
|
} // namespace esphome
|
||||||
|
|
||||||
|
#endif // USE_ESP_IDF
|
34
esphome/components/http_request/http_request_idf.h
Normal file
34
esphome/components/http_request/http_request_idf.h
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "http_request.h"
|
||||||
|
|
||||||
|
#ifdef USE_ESP_IDF
|
||||||
|
|
||||||
|
#include <esp_event.h>
|
||||||
|
#include <esp_http_client.h>
|
||||||
|
#include <esp_netif.h>
|
||||||
|
#include <esp_tls.h>
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace http_request {
|
||||||
|
|
||||||
|
class HttpContainerIDF : public HttpContainer {
|
||||||
|
public:
|
||||||
|
HttpContainerIDF(esp_http_client_handle_t client) : client_(client) {}
|
||||||
|
int read(uint8_t *buf, size_t max_len) override;
|
||||||
|
void end() override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
esp_http_client_handle_t client_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class HttpRequestIDF : public HttpRequestComponent {
|
||||||
|
public:
|
||||||
|
std::shared_ptr<HttpContainer> start(std::string url, std::string method, std::string body,
|
||||||
|
std::list<Header> headers) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace http_request
|
||||||
|
} // namespace esphome
|
||||||
|
|
||||||
|
#endif // USE_ESP_IDF
|
100
esphome/components/http_request/ota/__init__.py
Normal file
100
esphome/components/http_request/ota/__init__.py
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
import esphome.codegen as cg
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
from esphome import automation
|
||||||
|
from esphome.const import (
|
||||||
|
CONF_ID,
|
||||||
|
CONF_PASSWORD,
|
||||||
|
CONF_URL,
|
||||||
|
CONF_USERNAME,
|
||||||
|
)
|
||||||
|
from esphome.components.ota import BASE_OTA_SCHEMA, ota_to_code, OTAComponent
|
||||||
|
from esphome.core import coroutine_with_priority
|
||||||
|
from .. import CONF_HTTP_REQUEST_ID, http_request_ns, HttpRequestComponent
|
||||||
|
|
||||||
|
CODEOWNERS = ["@oarcher"]
|
||||||
|
|
||||||
|
AUTO_LOAD = ["md5"]
|
||||||
|
DEPENDENCIES = ["network", "http_request"]
|
||||||
|
|
||||||
|
CONF_MD5 = "md5"
|
||||||
|
CONF_MD5_URL = "md5_url"
|
||||||
|
|
||||||
|
OtaHttpRequestComponent = http_request_ns.class_(
|
||||||
|
"OtaHttpRequestComponent", OTAComponent
|
||||||
|
)
|
||||||
|
OtaHttpRequestComponentFlashAction = http_request_ns.class_(
|
||||||
|
"OtaHttpRequestComponentFlashAction", automation.Action
|
||||||
|
)
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = cv.All(
|
||||||
|
cv.Schema(
|
||||||
|
{
|
||||||
|
cv.GenerateID(): cv.declare_id(OtaHttpRequestComponent),
|
||||||
|
cv.GenerateID(CONF_HTTP_REQUEST_ID): cv.use_id(HttpRequestComponent),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.extend(BASE_OTA_SCHEMA)
|
||||||
|
.extend(cv.COMPONENT_SCHEMA),
|
||||||
|
cv.require_framework_version(
|
||||||
|
esp8266_arduino=cv.Version(2, 5, 1),
|
||||||
|
esp32_arduino=cv.Version(0, 0, 0),
|
||||||
|
esp_idf=cv.Version(0, 0, 0),
|
||||||
|
rp2040_arduino=cv.Version(0, 0, 0),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@coroutine_with_priority(52.0)
|
||||||
|
async def to_code(config):
|
||||||
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
|
await ota_to_code(var, config)
|
||||||
|
await cg.register_component(var, config)
|
||||||
|
await cg.register_parented(var, config[CONF_HTTP_REQUEST_ID])
|
||||||
|
|
||||||
|
|
||||||
|
OTA_HTTP_REQUEST_FLASH_ACTION_SCHEMA = cv.All(
|
||||||
|
cv.Schema(
|
||||||
|
{
|
||||||
|
cv.GenerateID(): cv.use_id(OtaHttpRequestComponent),
|
||||||
|
cv.Optional(CONF_MD5_URL): cv.templatable(cv.url),
|
||||||
|
cv.Optional(CONF_MD5): cv.templatable(
|
||||||
|
cv.All(cv.string, cv.Length(min=32, max=32))
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_PASSWORD): cv.templatable(cv.string),
|
||||||
|
cv.Optional(CONF_USERNAME): cv.templatable(cv.string),
|
||||||
|
cv.Required(CONF_URL): cv.templatable(cv.url),
|
||||||
|
}
|
||||||
|
),
|
||||||
|
cv.has_exactly_one_key(CONF_MD5, CONF_MD5_URL),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@automation.register_action(
|
||||||
|
"ota.http_request.flash",
|
||||||
|
OtaHttpRequestComponentFlashAction,
|
||||||
|
OTA_HTTP_REQUEST_FLASH_ACTION_SCHEMA,
|
||||||
|
)
|
||||||
|
async def ota_http_request_action_to_code(config, action_id, template_arg, args):
|
||||||
|
paren = await cg.get_variable(config[CONF_ID])
|
||||||
|
var = cg.new_Pvariable(action_id, template_arg, paren)
|
||||||
|
|
||||||
|
if md5_url := config.get(CONF_MD5_URL):
|
||||||
|
template_ = await cg.templatable(md5_url, args, cg.std_string)
|
||||||
|
cg.add(var.set_md5_url(template_))
|
||||||
|
|
||||||
|
if md5_str := config.get(CONF_MD5):
|
||||||
|
template_ = await cg.templatable(md5_str, args, cg.std_string)
|
||||||
|
cg.add(var.set_md5(template_))
|
||||||
|
|
||||||
|
if password_str := config.get(CONF_PASSWORD):
|
||||||
|
template_ = await cg.templatable(password_str, args, cg.std_string)
|
||||||
|
cg.add(var.set_password(template_))
|
||||||
|
|
||||||
|
if username_str := config.get(CONF_USERNAME):
|
||||||
|
template_ = await cg.templatable(username_str, args, cg.std_string)
|
||||||
|
cg.add(var.set_username(template_))
|
||||||
|
|
||||||
|
template_ = await cg.templatable(config[CONF_URL], args, cg.std_string)
|
||||||
|
cg.add(var.set_url(template_))
|
||||||
|
|
||||||
|
return var
|
42
esphome/components/http_request/ota/automation.h
Normal file
42
esphome/components/http_request/ota/automation.h
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
#pragma once
|
||||||
|
#include "ota_http_request.h"
|
||||||
|
|
||||||
|
#include "esphome/core/automation.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace http_request {
|
||||||
|
|
||||||
|
template<typename... Ts> class OtaHttpRequestComponentFlashAction : public Action<Ts...> {
|
||||||
|
public:
|
||||||
|
OtaHttpRequestComponentFlashAction(OtaHttpRequestComponent *parent) : parent_(parent) {}
|
||||||
|
TEMPLATABLE_VALUE(std::string, md5_url)
|
||||||
|
TEMPLATABLE_VALUE(std::string, md5)
|
||||||
|
TEMPLATABLE_VALUE(std::string, password)
|
||||||
|
TEMPLATABLE_VALUE(std::string, url)
|
||||||
|
TEMPLATABLE_VALUE(std::string, username)
|
||||||
|
|
||||||
|
void play(Ts... x) override {
|
||||||
|
if (this->md5_url_.has_value()) {
|
||||||
|
this->parent_->set_md5_url(this->md5_url_.value(x...));
|
||||||
|
}
|
||||||
|
if (this->md5_.has_value()) {
|
||||||
|
this->parent_->set_md5(this->md5_.value(x...));
|
||||||
|
}
|
||||||
|
if (this->password_.has_value()) {
|
||||||
|
this->parent_->set_password(this->password_.value(x...));
|
||||||
|
}
|
||||||
|
if (this->username_.has_value()) {
|
||||||
|
this->parent_->set_username(this->username_.value(x...));
|
||||||
|
}
|
||||||
|
this->parent_->set_url(this->url_.value(x...));
|
||||||
|
|
||||||
|
this->parent_->flash();
|
||||||
|
// Normally never reached due to reboot
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
OtaHttpRequestComponent *parent_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace http_request
|
||||||
|
} // namespace esphome
|
269
esphome/components/http_request/ota/ota_http_request.cpp
Normal file
269
esphome/components/http_request/ota/ota_http_request.cpp
Normal file
|
@ -0,0 +1,269 @@
|
||||||
|
#include "ota_http_request.h"
|
||||||
|
#include "../watchdog.h"
|
||||||
|
|
||||||
|
#include "esphome/core/application.h"
|
||||||
|
#include "esphome/core/defines.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
|
#include "esphome/components/md5/md5.h"
|
||||||
|
#include "esphome/components/ota/ota_backend.h"
|
||||||
|
#include "esphome/components/ota/ota_backend_arduino_esp32.h"
|
||||||
|
#include "esphome/components/ota/ota_backend_arduino_esp8266.h"
|
||||||
|
#include "esphome/components/ota/ota_backend_arduino_rp2040.h"
|
||||||
|
#include "esphome/components/ota/ota_backend_esp_idf.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace http_request {
|
||||||
|
|
||||||
|
static const char *const TAG = "http_request.ota";
|
||||||
|
|
||||||
|
void OtaHttpRequestComponent::setup() {
|
||||||
|
#ifdef USE_OTA_STATE_CALLBACK
|
||||||
|
ota::register_ota_platform(this);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void OtaHttpRequestComponent::dump_config() { ESP_LOGCONFIG(TAG, "Over-The-Air updates via HTTP request"); };
|
||||||
|
|
||||||
|
void OtaHttpRequestComponent::set_md5_url(const std::string &url) {
|
||||||
|
if (!this->validate_url_(url)) {
|
||||||
|
this->md5_url_.clear(); // URL was not valid; prevent flashing until it is
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this->md5_url_ = url;
|
||||||
|
this->md5_expected_.clear(); // to be retrieved later
|
||||||
|
}
|
||||||
|
|
||||||
|
void OtaHttpRequestComponent::set_url(const std::string &url) {
|
||||||
|
if (!this->validate_url_(url)) {
|
||||||
|
this->url_.clear(); // URL was not valid; prevent flashing until it is
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this->url_ = url;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OtaHttpRequestComponent::flash() {
|
||||||
|
if (this->url_.empty()) {
|
||||||
|
ESP_LOGE(TAG, "URL not set; cannot start update");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "Starting update...");
|
||||||
|
#ifdef USE_OTA_STATE_CALLBACK
|
||||||
|
this->state_callback_.call(ota::OTA_STARTED, 0.0f, 0);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
auto ota_status = this->do_ota_();
|
||||||
|
|
||||||
|
switch (ota_status) {
|
||||||
|
case ota::OTA_RESPONSE_OK:
|
||||||
|
#ifdef USE_OTA_STATE_CALLBACK
|
||||||
|
this->state_callback_.call(ota::OTA_COMPLETED, 100.0f, ota_status);
|
||||||
|
#endif
|
||||||
|
delay(10);
|
||||||
|
App.safe_reboot();
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
#ifdef USE_OTA_STATE_CALLBACK
|
||||||
|
this->state_callback_.call(ota::OTA_ERROR, 0.0f, ota_status);
|
||||||
|
#endif
|
||||||
|
this->md5_computed_.clear(); // will be reset at next attempt
|
||||||
|
this->md5_expected_.clear(); // will be reset at next attempt
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void OtaHttpRequestComponent::cleanup_(std::unique_ptr<ota::OTABackend> backend,
|
||||||
|
const std::shared_ptr<HttpContainer> &container) {
|
||||||
|
if (this->update_started_) {
|
||||||
|
ESP_LOGV(TAG, "Aborting OTA backend");
|
||||||
|
backend->abort();
|
||||||
|
}
|
||||||
|
ESP_LOGV(TAG, "Aborting HTTP connection");
|
||||||
|
container->end();
|
||||||
|
};
|
||||||
|
|
||||||
|
uint8_t OtaHttpRequestComponent::do_ota_() {
|
||||||
|
uint8_t buf[OtaHttpRequestComponent::HTTP_RECV_BUFFER + 1];
|
||||||
|
uint32_t last_progress = 0;
|
||||||
|
uint32_t update_start_time = millis();
|
||||||
|
md5::MD5Digest md5_receive;
|
||||||
|
std::unique_ptr<char[]> md5_receive_str(new char[33]);
|
||||||
|
|
||||||
|
if (this->md5_expected_.empty() && !this->http_get_md5_()) {
|
||||||
|
return OTA_MD5_INVALID;
|
||||||
|
}
|
||||||
|
|
||||||
|
ESP_LOGD(TAG, "MD5 expected: %s", this->md5_expected_.c_str());
|
||||||
|
|
||||||
|
auto url_with_auth = this->get_url_with_auth_(this->url_);
|
||||||
|
if (url_with_auth.empty()) {
|
||||||
|
return OTA_BAD_URL;
|
||||||
|
}
|
||||||
|
ESP_LOGVV(TAG, "url_with_auth: %s", url_with_auth.c_str());
|
||||||
|
ESP_LOGI(TAG, "Connecting to: %s", this->url_.c_str());
|
||||||
|
|
||||||
|
auto container = this->parent_->get(url_with_auth);
|
||||||
|
|
||||||
|
if (container == nullptr) {
|
||||||
|
return OTA_CONNECTION_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
// we will compute MD5 on the fly for verification -- Arduino OTA seems to ignore it
|
||||||
|
md5_receive.init();
|
||||||
|
ESP_LOGV(TAG, "MD5Digest initialized");
|
||||||
|
|
||||||
|
ESP_LOGV(TAG, "OTA backend begin");
|
||||||
|
auto backend = ota::make_ota_backend();
|
||||||
|
auto error_code = backend->begin(container->content_length);
|
||||||
|
if (error_code != ota::OTA_RESPONSE_OK) {
|
||||||
|
ESP_LOGW(TAG, "backend->begin error: %d", error_code);
|
||||||
|
this->cleanup_(std::move(backend), container);
|
||||||
|
return error_code;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (container->get_bytes_read() < container->content_length) {
|
||||||
|
// read a maximum of chunk_size bytes into buf. (real read size returned)
|
||||||
|
int bufsize = container->read(buf, OtaHttpRequestComponent::HTTP_RECV_BUFFER);
|
||||||
|
ESP_LOGVV(TAG, "bytes_read_ = %u, body_length_ = %u, bufsize = %i", container->get_bytes_read(),
|
||||||
|
container->content_length, bufsize);
|
||||||
|
|
||||||
|
// feed watchdog and give other tasks a chance to run
|
||||||
|
App.feed_wdt();
|
||||||
|
yield();
|
||||||
|
|
||||||
|
if (bufsize < 0) {
|
||||||
|
ESP_LOGE(TAG, "Stream closed");
|
||||||
|
this->cleanup_(std::move(backend), container);
|
||||||
|
return OTA_CONNECTION_ERROR;
|
||||||
|
} else if (bufsize > 0 && bufsize <= OtaHttpRequestComponent::HTTP_RECV_BUFFER) {
|
||||||
|
// add read bytes to MD5
|
||||||
|
md5_receive.add(buf, bufsize);
|
||||||
|
|
||||||
|
// write bytes to OTA backend
|
||||||
|
this->update_started_ = true;
|
||||||
|
error_code = backend->write(buf, bufsize);
|
||||||
|
if (error_code != ota::OTA_RESPONSE_OK) {
|
||||||
|
// error code explanation available at
|
||||||
|
// https://github.com/esphome/esphome/blob/dev/esphome/components/ota/ota_backend.h
|
||||||
|
ESP_LOGE(TAG, "Error code (%02X) writing binary data to flash at offset %d and size %d", error_code,
|
||||||
|
container->get_bytes_read() - bufsize, container->content_length);
|
||||||
|
this->cleanup_(std::move(backend), container);
|
||||||
|
return error_code;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t now = millis();
|
||||||
|
if ((now - last_progress > 1000) or (container->get_bytes_read() == container->content_length)) {
|
||||||
|
last_progress = now;
|
||||||
|
float percentage = container->get_bytes_read() * 100.0f / container->content_length;
|
||||||
|
ESP_LOGD(TAG, "Progress: %0.1f%%", percentage);
|
||||||
|
#ifdef USE_OTA_STATE_CALLBACK
|
||||||
|
this->state_callback_.call(ota::OTA_IN_PROGRESS, percentage, 0);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
} // while
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "Done in %.0f seconds", float(millis() - update_start_time) / 1000);
|
||||||
|
|
||||||
|
// verify MD5 is as expected and act accordingly
|
||||||
|
md5_receive.calculate();
|
||||||
|
md5_receive.get_hex(md5_receive_str.get());
|
||||||
|
this->md5_computed_ = md5_receive_str.get();
|
||||||
|
if (strncmp(this->md5_computed_.c_str(), this->md5_expected_.c_str(), MD5_SIZE) != 0) {
|
||||||
|
ESP_LOGE(TAG, "MD5 computed: %s - Aborting due to MD5 mismatch", this->md5_computed_.c_str());
|
||||||
|
this->cleanup_(std::move(backend), container);
|
||||||
|
return ota::OTA_RESPONSE_ERROR_MD5_MISMATCH;
|
||||||
|
} else {
|
||||||
|
backend->set_update_md5(md5_receive_str.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
container->end();
|
||||||
|
|
||||||
|
// feed watchdog and give other tasks a chance to run
|
||||||
|
App.feed_wdt();
|
||||||
|
yield();
|
||||||
|
delay(100); // NOLINT
|
||||||
|
|
||||||
|
error_code = backend->end();
|
||||||
|
if (error_code != ota::OTA_RESPONSE_OK) {
|
||||||
|
ESP_LOGW(TAG, "Error ending update! error_code: %d", error_code);
|
||||||
|
this->cleanup_(std::move(backend), container);
|
||||||
|
return error_code;
|
||||||
|
}
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "Update complete");
|
||||||
|
return ota::OTA_RESPONSE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string OtaHttpRequestComponent::get_url_with_auth_(const std::string &url) {
|
||||||
|
if (this->username_.empty() || this->password_.empty()) {
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto start_char = url.find("://");
|
||||||
|
if ((start_char == std::string::npos) || (start_char < 4)) {
|
||||||
|
ESP_LOGE(TAG, "Incorrect URL prefix");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
ESP_LOGD(TAG, "Using basic HTTP authentication");
|
||||||
|
|
||||||
|
start_char += 3; // skip '://' characters
|
||||||
|
auto url_with_auth =
|
||||||
|
url.substr(0, start_char) + this->username_ + ":" + this->password_ + "@" + url.substr(start_char);
|
||||||
|
return url_with_auth;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool OtaHttpRequestComponent::http_get_md5_() {
|
||||||
|
if (this->md5_url_.empty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto url_with_auth = this->get_url_with_auth_(this->md5_url_);
|
||||||
|
if (url_with_auth.empty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ESP_LOGVV(TAG, "url_with_auth: %s", url_with_auth.c_str());
|
||||||
|
ESP_LOGI(TAG, "Connecting to: %s", this->md5_url_.c_str());
|
||||||
|
auto container = this->parent_->get(url_with_auth);
|
||||||
|
if (container == nullptr) {
|
||||||
|
ESP_LOGE(TAG, "Failed to connect to MD5 URL");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
size_t length = container->content_length;
|
||||||
|
if (length == 0) {
|
||||||
|
container->end();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (length < MD5_SIZE) {
|
||||||
|
ESP_LOGE(TAG, "MD5 file must be %u bytes; %u bytes reported by HTTP server. Aborting", MD5_SIZE, length);
|
||||||
|
container->end();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this->md5_expected_.resize(MD5_SIZE);
|
||||||
|
int read_len = 0;
|
||||||
|
while (container->get_bytes_read() < MD5_SIZE) {
|
||||||
|
read_len = container->read((uint8_t *) this->md5_expected_.data(), MD5_SIZE);
|
||||||
|
App.feed_wdt();
|
||||||
|
yield();
|
||||||
|
}
|
||||||
|
container->end();
|
||||||
|
|
||||||
|
ESP_LOGV(TAG, "Read len: %u, MD5 expected: %u", read_len, MD5_SIZE);
|
||||||
|
return read_len == MD5_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool OtaHttpRequestComponent::validate_url_(const std::string &url) {
|
||||||
|
if ((url.length() < 8) || (url.find("http") != 0) || (url.find("://") == std::string::npos)) {
|
||||||
|
ESP_LOGE(TAG, "URL is invalid and/or must be prefixed with 'http://' or 'https://'");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace http_request
|
||||||
|
} // namespace esphome
|
61
esphome/components/http_request/ota/ota_http_request.h
Normal file
61
esphome/components/http_request/ota/ota_http_request.h
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/components/ota/ota_backend.h"
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
#include "esphome/core/defines.h"
|
||||||
|
#include "esphome/core/helpers.h"
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#include "../http_request.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace http_request {
|
||||||
|
|
||||||
|
static const uint8_t MD5_SIZE = 32;
|
||||||
|
|
||||||
|
enum OtaHttpRequestError : uint8_t {
|
||||||
|
OTA_MD5_INVALID = 0x10,
|
||||||
|
OTA_BAD_URL = 0x11,
|
||||||
|
OTA_CONNECTION_ERROR = 0x12,
|
||||||
|
};
|
||||||
|
|
||||||
|
class OtaHttpRequestComponent : public ota::OTAComponent, public Parented<HttpRequestComponent> {
|
||||||
|
public:
|
||||||
|
void setup() override;
|
||||||
|
void dump_config() override;
|
||||||
|
float get_setup_priority() const override { return setup_priority::AFTER_WIFI; }
|
||||||
|
|
||||||
|
void set_md5_url(const std::string &md5_url);
|
||||||
|
void set_md5(const std::string &md5) { this->md5_expected_ = md5; }
|
||||||
|
void set_password(const std::string &password) { this->password_ = password; }
|
||||||
|
void set_url(const std::string &url);
|
||||||
|
void set_username(const std::string &username) { this->username_ = username; }
|
||||||
|
|
||||||
|
std::string md5_computed() { return this->md5_computed_; }
|
||||||
|
std::string md5_expected() { return this->md5_expected_; }
|
||||||
|
|
||||||
|
void flash();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void cleanup_(std::unique_ptr<ota::OTABackend> backend, const std::shared_ptr<HttpContainer> &container);
|
||||||
|
uint8_t do_ota_();
|
||||||
|
std::string get_url_with_auth_(const std::string &url);
|
||||||
|
bool http_get_md5_();
|
||||||
|
bool validate_url_(const std::string &url);
|
||||||
|
|
||||||
|
std::string md5_computed_{};
|
||||||
|
std::string md5_expected_{};
|
||||||
|
std::string md5_url_{};
|
||||||
|
std::string password_{};
|
||||||
|
std::string username_{};
|
||||||
|
std::string url_{};
|
||||||
|
int status_ = -1;
|
||||||
|
bool update_started_ = false;
|
||||||
|
static const uint16_t HTTP_RECV_BUFFER = 256; // the firmware GET chunk size
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace http_request
|
||||||
|
} // namespace esphome
|
44
esphome/components/http_request/update/__init__.py
Normal file
44
esphome/components/http_request/update/__init__.py
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
import esphome.codegen as cg
|
||||||
|
|
||||||
|
from esphome.components import update
|
||||||
|
from esphome.const import (
|
||||||
|
CONF_SOURCE,
|
||||||
|
)
|
||||||
|
|
||||||
|
from .. import http_request_ns, CONF_HTTP_REQUEST_ID, HttpRequestComponent
|
||||||
|
from ..ota import OtaHttpRequestComponent
|
||||||
|
|
||||||
|
|
||||||
|
AUTO_LOAD = ["json"]
|
||||||
|
CODEOWNERS = ["@jesserockz"]
|
||||||
|
DEPENDENCIES = ["ota.http_request"]
|
||||||
|
|
||||||
|
HttpRequestUpdate = http_request_ns.class_(
|
||||||
|
"HttpRequestUpdate", update.UpdateEntity, cg.PollingComponent
|
||||||
|
)
|
||||||
|
|
||||||
|
CONF_OTA_ID = "ota_id"
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = update.UPDATE_SCHEMA.extend(
|
||||||
|
{
|
||||||
|
cv.GenerateID(): cv.declare_id(HttpRequestUpdate),
|
||||||
|
cv.GenerateID(CONF_OTA_ID): cv.use_id(OtaHttpRequestComponent),
|
||||||
|
cv.GenerateID(CONF_HTTP_REQUEST_ID): cv.use_id(HttpRequestComponent),
|
||||||
|
cv.Required(CONF_SOURCE): cv.url,
|
||||||
|
}
|
||||||
|
).extend(cv.polling_component_schema("6h"))
|
||||||
|
|
||||||
|
|
||||||
|
async def to_code(config):
|
||||||
|
var = await update.new_update(config)
|
||||||
|
ota_parent = await cg.get_variable(config[CONF_OTA_ID])
|
||||||
|
cg.add(var.set_ota_parent(ota_parent))
|
||||||
|
request_parent = await cg.get_variable(config[CONF_HTTP_REQUEST_ID])
|
||||||
|
cg.add(var.set_request_parent(request_parent))
|
||||||
|
|
||||||
|
cg.add(var.set_source_url(config[CONF_SOURCE]))
|
||||||
|
|
||||||
|
cg.add_define("USE_OTA_STATE_CALLBACK")
|
||||||
|
|
||||||
|
await cg.register_component(var, config)
|
157
esphome/components/http_request/update/http_request_update.cpp
Normal file
157
esphome/components/http_request/update/http_request_update.cpp
Normal file
|
@ -0,0 +1,157 @@
|
||||||
|
#include "http_request_update.h"
|
||||||
|
|
||||||
|
#include "esphome/core/application.h"
|
||||||
|
#include "esphome/core/version.h"
|
||||||
|
|
||||||
|
#include "esphome/components/json/json_util.h"
|
||||||
|
#include "esphome/components/network/util.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace http_request {
|
||||||
|
|
||||||
|
static const char *const TAG = "http_request.update";
|
||||||
|
|
||||||
|
static const size_t MAX_READ_SIZE = 256;
|
||||||
|
|
||||||
|
void HttpRequestUpdate::setup() {
|
||||||
|
this->ota_parent_->add_on_state_callback([this](ota::OTAState state, float progress, uint8_t err) {
|
||||||
|
if (state == ota::OTAState::OTA_IN_PROGRESS) {
|
||||||
|
this->state_ = update::UPDATE_STATE_INSTALLING;
|
||||||
|
this->update_info_.has_progress = true;
|
||||||
|
this->update_info_.progress = progress;
|
||||||
|
this->publish_state();
|
||||||
|
} else if (state == ota::OTAState::OTA_ABORT || state == ota::OTAState::OTA_ERROR) {
|
||||||
|
this->state_ = update::UPDATE_STATE_AVAILABLE;
|
||||||
|
this->status_set_error("Failed to install firmware");
|
||||||
|
this->publish_state();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void HttpRequestUpdate::update() {
|
||||||
|
auto container = this->request_parent_->get(this->source_url_);
|
||||||
|
|
||||||
|
if (container == nullptr) {
|
||||||
|
std::string msg = str_sprintf("Failed to fetch manifest from %s", this->source_url_.c_str());
|
||||||
|
this->status_set_error(msg.c_str());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ExternalRAMAllocator<uint8_t> allocator(ExternalRAMAllocator<uint8_t>::ALLOW_FAILURE);
|
||||||
|
uint8_t *data = allocator.allocate(container->content_length);
|
||||||
|
if (data == nullptr) {
|
||||||
|
std::string msg = str_sprintf("Failed to allocate %d bytes for manifest", container->content_length);
|
||||||
|
this->status_set_error(msg.c_str());
|
||||||
|
container->end();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t read_index = 0;
|
||||||
|
while (container->get_bytes_read() < container->content_length) {
|
||||||
|
int read_bytes = container->read(data + read_index, MAX_READ_SIZE);
|
||||||
|
|
||||||
|
App.feed_wdt();
|
||||||
|
yield();
|
||||||
|
|
||||||
|
read_index += read_bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string response((char *) data, read_index);
|
||||||
|
allocator.deallocate(data, container->content_length);
|
||||||
|
|
||||||
|
container->end();
|
||||||
|
|
||||||
|
bool valid = json::parse_json(response, [this](JsonObject root) -> bool {
|
||||||
|
if (!root.containsKey("name") || !root.containsKey("version") || !root.containsKey("builds")) {
|
||||||
|
ESP_LOGE(TAG, "Manifest does not contain required fields");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
this->update_info_.title = root["name"].as<std::string>();
|
||||||
|
this->update_info_.latest_version = root["version"].as<std::string>();
|
||||||
|
|
||||||
|
for (auto build : root["builds"].as<JsonArray>()) {
|
||||||
|
if (!build.containsKey("chipFamily")) {
|
||||||
|
ESP_LOGE(TAG, "Manifest does not contain required fields");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (build["chipFamily"] == ESPHOME_VARIANT) {
|
||||||
|
if (!build.containsKey("ota")) {
|
||||||
|
ESP_LOGE(TAG, "Manifest does not contain required fields");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
auto ota = build["ota"];
|
||||||
|
if (!ota.containsKey("path") || !ota.containsKey("md5")) {
|
||||||
|
ESP_LOGE(TAG, "Manifest does not contain required fields");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
this->update_info_.firmware_url = ota["path"].as<std::string>();
|
||||||
|
this->update_info_.md5 = ota["md5"].as<std::string>();
|
||||||
|
|
||||||
|
if (ota.containsKey("summary"))
|
||||||
|
this->update_info_.summary = ota["summary"].as<std::string>();
|
||||||
|
if (ota.containsKey("release_url"))
|
||||||
|
this->update_info_.release_url = ota["release_url"].as<std::string>();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!valid) {
|
||||||
|
std::string msg = str_sprintf("Failed to parse JSON from %s", this->source_url_.c_str());
|
||||||
|
this->status_set_error(msg.c_str());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge source_url_ and this->update_info_.firmware_url
|
||||||
|
if (this->update_info_.firmware_url.find("http") == std::string::npos) {
|
||||||
|
std::string path = this->update_info_.firmware_url;
|
||||||
|
if (path[0] == '/') {
|
||||||
|
std::string domain = this->source_url_.substr(0, this->source_url_.find('/', 8));
|
||||||
|
this->update_info_.firmware_url = domain + path;
|
||||||
|
} else {
|
||||||
|
std::string domain = this->source_url_.substr(0, this->source_url_.rfind('/') + 1);
|
||||||
|
this->update_info_.firmware_url = domain + path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string current_version = this->current_version_;
|
||||||
|
if (current_version.empty()) {
|
||||||
|
#ifdef ESPHOME_PROJECT_VERSION
|
||||||
|
current_version = ESPHOME_PROJECT_VERSION;
|
||||||
|
#else
|
||||||
|
current_version = ESPHOME_VERSION;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
this->update_info_.current_version = current_version;
|
||||||
|
|
||||||
|
if (this->update_info_.latest_version.empty()) {
|
||||||
|
this->state_ = update::UPDATE_STATE_NO_UPDATE;
|
||||||
|
} else if (this->update_info_.latest_version != this->current_version_) {
|
||||||
|
this->state_ = update::UPDATE_STATE_AVAILABLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
this->update_info_.has_progress = false;
|
||||||
|
this->update_info_.progress = 0.0f;
|
||||||
|
|
||||||
|
this->status_clear_error();
|
||||||
|
this->publish_state();
|
||||||
|
}
|
||||||
|
|
||||||
|
void HttpRequestUpdate::perform() {
|
||||||
|
if (this->state_ != update::UPDATE_STATE_AVAILABLE) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this->state_ = update::UPDATE_STATE_INSTALLING;
|
||||||
|
this->publish_state();
|
||||||
|
|
||||||
|
this->ota_parent_->set_md5(this->update_info.md5);
|
||||||
|
this->ota_parent_->set_url(this->update_info.firmware_url);
|
||||||
|
// Flash in the next loop
|
||||||
|
this->defer([this]() { this->ota_parent_->flash(); });
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace http_request
|
||||||
|
} // namespace esphome
|
37
esphome/components/http_request/update/http_request_update.h
Normal file
37
esphome/components/http_request/update/http_request_update.h
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
#include "esphome/core/helpers.h"
|
||||||
|
|
||||||
|
#include "esphome/components/http_request/http_request.h"
|
||||||
|
#include "esphome/components/http_request/ota/ota_http_request.h"
|
||||||
|
#include "esphome/components/update/update_entity.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace http_request {
|
||||||
|
|
||||||
|
class HttpRequestUpdate : public update::UpdateEntity, public PollingComponent {
|
||||||
|
public:
|
||||||
|
void setup() override;
|
||||||
|
void update() override;
|
||||||
|
|
||||||
|
void perform() override;
|
||||||
|
|
||||||
|
void set_source_url(const std::string &source_url) { this->source_url_ = source_url; }
|
||||||
|
|
||||||
|
void set_request_parent(HttpRequestComponent *request_parent) { this->request_parent_ = request_parent; }
|
||||||
|
void set_ota_parent(OtaHttpRequestComponent *ota_parent) { this->ota_parent_ = ota_parent; }
|
||||||
|
|
||||||
|
void set_current_version(const std::string ¤t_version) { this->current_version_ = current_version; }
|
||||||
|
|
||||||
|
float get_setup_priority() const override { return setup_priority::AFTER_WIFI; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
HttpRequestComponent *request_parent_;
|
||||||
|
OtaHttpRequestComponent *ota_parent_;
|
||||||
|
std::string source_url_;
|
||||||
|
std::string current_version_{""};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace http_request
|
||||||
|
} // namespace esphome
|
76
esphome/components/http_request/watchdog.cpp
Normal file
76
esphome/components/http_request/watchdog.cpp
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
#include "watchdog.h"
|
||||||
|
|
||||||
|
#include "esphome/core/application.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
|
#include <cinttypes>
|
||||||
|
#include <cstdint>
|
||||||
|
#ifdef USE_ESP32
|
||||||
|
#include "esp_idf_version.h"
|
||||||
|
#include "esp_task_wdt.h"
|
||||||
|
#endif
|
||||||
|
#ifdef USE_RP2040
|
||||||
|
#include "hardware/watchdog.h"
|
||||||
|
#include "pico/stdlib.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace http_request {
|
||||||
|
namespace watchdog {
|
||||||
|
|
||||||
|
static const char *const TAG = "http_request.watchdog";
|
||||||
|
|
||||||
|
WatchdogManager::WatchdogManager(uint32_t timeout_ms) : timeout_ms_(timeout_ms) {
|
||||||
|
if (timeout_ms == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this->saved_timeout_ms_ = this->get_timeout_();
|
||||||
|
this->set_timeout_(timeout_ms);
|
||||||
|
}
|
||||||
|
|
||||||
|
WatchdogManager::~WatchdogManager() {
|
||||||
|
if (this->timeout_ms_ == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this->set_timeout_(this->saved_timeout_ms_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WatchdogManager::set_timeout_(uint32_t timeout_ms) {
|
||||||
|
ESP_LOGV(TAG, "Adjusting WDT to %" PRIu32 "ms", timeout_ms);
|
||||||
|
#ifdef USE_ESP32
|
||||||
|
#if ESP_IDF_VERSION_MAJOR >= 5
|
||||||
|
esp_task_wdt_config_t wdt_config = {
|
||||||
|
.timeout_ms = timeout_ms,
|
||||||
|
.idle_core_mask = 0x03,
|
||||||
|
.trigger_panic = true,
|
||||||
|
};
|
||||||
|
esp_task_wdt_reconfigure(&wdt_config);
|
||||||
|
#else
|
||||||
|
esp_task_wdt_init(timeout_ms / 1000, true);
|
||||||
|
#endif // ESP_IDF_VERSION_MAJOR
|
||||||
|
#endif // USE_ESP32
|
||||||
|
|
||||||
|
#ifdef USE_RP2040
|
||||||
|
watchdog_enable(timeout_ms, true);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t WatchdogManager::get_timeout_() {
|
||||||
|
uint32_t timeout_ms = 0;
|
||||||
|
|
||||||
|
#ifdef USE_ESP32
|
||||||
|
timeout_ms = (uint32_t) CONFIG_ESP_TASK_WDT_TIMEOUT_S * 1000;
|
||||||
|
#endif // USE_ESP32
|
||||||
|
|
||||||
|
#ifdef USE_RP2040
|
||||||
|
timeout_ms = watchdog_get_count() / 1000;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
ESP_LOGVV(TAG, "get_timeout: %" PRIu32 "ms", timeout_ms);
|
||||||
|
|
||||||
|
return timeout_ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace watchdog
|
||||||
|
} // namespace http_request
|
||||||
|
} // namespace esphome
|
26
esphome/components/http_request/watchdog.h
Normal file
26
esphome/components/http_request/watchdog.h
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/defines.h"
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace http_request {
|
||||||
|
namespace watchdog {
|
||||||
|
|
||||||
|
class WatchdogManager {
|
||||||
|
public:
|
||||||
|
WatchdogManager(uint32_t timeout_ms);
|
||||||
|
~WatchdogManager();
|
||||||
|
|
||||||
|
private:
|
||||||
|
uint32_t get_timeout_();
|
||||||
|
void set_timeout_(uint32_t timeout_ms);
|
||||||
|
|
||||||
|
uint32_t saved_timeout_ms_{0};
|
||||||
|
uint32_t timeout_ms_{0};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace watchdog
|
||||||
|
} // namespace http_request
|
||||||
|
} // namespace esphome
|
|
@ -27,43 +27,24 @@ void I2SAudioMediaPlayer::control(const media_player::MediaPlayerCall &call) {
|
||||||
this->start();
|
this->start();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (play_state == media_player::MEDIA_PLAYER_STATE_ANNOUNCING) {
|
||||||
|
this->is_announcement_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
if (call.get_volume().has_value()) {
|
if (call.get_volume().has_value()) {
|
||||||
this->volume = call.get_volume().value();
|
this->volume = call.get_volume().value();
|
||||||
this->set_volume_(volume);
|
this->set_volume_(volume);
|
||||||
this->unmute_();
|
this->unmute_();
|
||||||
}
|
}
|
||||||
if (this->i2s_state_ != I2S_STATE_RUNNING) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (call.get_command().has_value()) {
|
if (call.get_command().has_value()) {
|
||||||
switch (call.get_command().value()) {
|
switch (call.get_command().value()) {
|
||||||
case media_player::MEDIA_PLAYER_COMMAND_PLAY:
|
|
||||||
if (!this->audio_->isRunning())
|
|
||||||
this->audio_->pauseResume();
|
|
||||||
this->state = play_state;
|
|
||||||
break;
|
|
||||||
case media_player::MEDIA_PLAYER_COMMAND_PAUSE:
|
|
||||||
if (this->audio_->isRunning())
|
|
||||||
this->audio_->pauseResume();
|
|
||||||
this->state = media_player::MEDIA_PLAYER_STATE_PAUSED;
|
|
||||||
break;
|
|
||||||
case media_player::MEDIA_PLAYER_COMMAND_STOP:
|
|
||||||
this->stop();
|
|
||||||
break;
|
|
||||||
case media_player::MEDIA_PLAYER_COMMAND_MUTE:
|
case media_player::MEDIA_PLAYER_COMMAND_MUTE:
|
||||||
this->mute_();
|
this->mute_();
|
||||||
break;
|
break;
|
||||||
case media_player::MEDIA_PLAYER_COMMAND_UNMUTE:
|
case media_player::MEDIA_PLAYER_COMMAND_UNMUTE:
|
||||||
this->unmute_();
|
this->unmute_();
|
||||||
break;
|
break;
|
||||||
case media_player::MEDIA_PLAYER_COMMAND_TOGGLE:
|
|
||||||
this->audio_->pauseResume();
|
|
||||||
if (this->audio_->isRunning()) {
|
|
||||||
this->state = media_player::MEDIA_PLAYER_STATE_PLAYING;
|
|
||||||
} else {
|
|
||||||
this->state = media_player::MEDIA_PLAYER_STATE_PAUSED;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case media_player::MEDIA_PLAYER_COMMAND_VOLUME_UP: {
|
case media_player::MEDIA_PLAYER_COMMAND_VOLUME_UP: {
|
||||||
float new_volume = this->volume + 0.1f;
|
float new_volume = this->volume + 0.1f;
|
||||||
if (new_volume > 1.0f)
|
if (new_volume > 1.0f)
|
||||||
|
@ -80,6 +61,36 @@ void I2SAudioMediaPlayer::control(const media_player::MediaPlayerCall &call) {
|
||||||
this->unmute_();
|
this->unmute_();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (this->i2s_state_ != I2S_STATE_RUNNING) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
switch (call.get_command().value()) {
|
||||||
|
case media_player::MEDIA_PLAYER_COMMAND_PLAY:
|
||||||
|
if (!this->audio_->isRunning())
|
||||||
|
this->audio_->pauseResume();
|
||||||
|
this->state = play_state;
|
||||||
|
break;
|
||||||
|
case media_player::MEDIA_PLAYER_COMMAND_PAUSE:
|
||||||
|
if (this->audio_->isRunning())
|
||||||
|
this->audio_->pauseResume();
|
||||||
|
this->state = media_player::MEDIA_PLAYER_STATE_PAUSED;
|
||||||
|
break;
|
||||||
|
case media_player::MEDIA_PLAYER_COMMAND_STOP:
|
||||||
|
this->stop();
|
||||||
|
break;
|
||||||
|
case media_player::MEDIA_PLAYER_COMMAND_TOGGLE:
|
||||||
|
this->audio_->pauseResume();
|
||||||
|
if (this->audio_->isRunning()) {
|
||||||
|
this->state = media_player::MEDIA_PLAYER_STATE_PLAYING;
|
||||||
|
} else {
|
||||||
|
this->state = media_player::MEDIA_PLAYER_STATE_PAUSED;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this->publish_state();
|
this->publish_state();
|
||||||
|
@ -171,9 +182,8 @@ void I2SAudioMediaPlayer::start_() {
|
||||||
if (this->current_url_.has_value()) {
|
if (this->current_url_.has_value()) {
|
||||||
this->audio_->connecttohost(this->current_url_.value().c_str());
|
this->audio_->connecttohost(this->current_url_.value().c_str());
|
||||||
this->state = media_player::MEDIA_PLAYER_STATE_PLAYING;
|
this->state = media_player::MEDIA_PLAYER_STATE_PLAYING;
|
||||||
if (this->is_announcement_.has_value()) {
|
if (this->is_announcement_) {
|
||||||
this->state = this->is_announcement_.value() ? media_player::MEDIA_PLAYER_STATE_ANNOUNCING
|
this->state = media_player::MEDIA_PLAYER_STATE_ANNOUNCING;
|
||||||
: media_player::MEDIA_PLAYER_STATE_PLAYING;
|
|
||||||
}
|
}
|
||||||
this->publish_state();
|
this->publish_state();
|
||||||
}
|
}
|
||||||
|
@ -202,6 +212,7 @@ void I2SAudioMediaPlayer::stop_() {
|
||||||
this->high_freq_.stop();
|
this->high_freq_.stop();
|
||||||
this->state = media_player::MEDIA_PLAYER_STATE_IDLE;
|
this->state = media_player::MEDIA_PLAYER_STATE_IDLE;
|
||||||
this->publish_state();
|
this->publish_state();
|
||||||
|
this->is_announcement_ = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
media_player::MediaPlayerTraits I2SAudioMediaPlayer::get_traits() {
|
media_player::MediaPlayerTraits I2SAudioMediaPlayer::get_traits() {
|
||||||
|
|
|
@ -78,7 +78,7 @@ class I2SAudioMediaPlayer : public Component, public media_player::MediaPlayer,
|
||||||
HighFrequencyLoopRequester high_freq_;
|
HighFrequencyLoopRequester high_freq_;
|
||||||
|
|
||||||
optional<std::string> current_url_{};
|
optional<std::string> current_url_{};
|
||||||
optional<bool> is_announcement_{};
|
bool is_announcement_{false};
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace i2s_audio
|
} // namespace i2s_audio
|
||||||
|
|
|
@ -38,15 +38,22 @@ void I2SAudioSpeaker::start() {
|
||||||
ESP_LOGE(TAG, "Cannot start audio, speaker failed to setup");
|
ESP_LOGE(TAG, "Cannot start audio, speaker failed to setup");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (this->task_created_) {
|
||||||
|
ESP_LOGW(TAG, "Called start while task has been already created.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
this->state_ = speaker::STATE_STARTING;
|
this->state_ = speaker::STATE_STARTING;
|
||||||
}
|
}
|
||||||
void I2SAudioSpeaker::start_() {
|
void I2SAudioSpeaker::start_() {
|
||||||
|
if (this->task_created_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!this->parent_->try_lock()) {
|
if (!this->parent_->try_lock()) {
|
||||||
return; // Waiting for another i2s component to return lock
|
return; // Waiting for another i2s component to return lock
|
||||||
}
|
}
|
||||||
this->state_ = speaker::STATE_RUNNING;
|
|
||||||
|
|
||||||
xTaskCreate(I2SAudioSpeaker::player_task, "speaker_task", 8192, (void *) this, 1, &this->player_task_handle_);
|
xTaskCreate(I2SAudioSpeaker::player_task, "speaker_task", 8192, (void *) this, 1, &this->player_task_handle_);
|
||||||
|
this->task_created_ = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void I2SAudioSpeaker::player_task(void *params) {
|
void I2SAudioSpeaker::player_task(void *params) {
|
||||||
|
@ -131,7 +138,16 @@ void I2SAudioSpeaker::player_task(void *params) {
|
||||||
(10 / portTICK_PERIOD_MS));
|
(10 / portTICK_PERIOD_MS));
|
||||||
if (err != ESP_OK) {
|
if (err != ESP_OK) {
|
||||||
event = {.type = TaskEventType::WARNING, .err = err};
|
event = {.type = TaskEventType::WARNING, .err = err};
|
||||||
xQueueSend(this_speaker->event_queue_, &event, portMAX_DELAY);
|
if (xQueueSend(this_speaker->event_queue_, &event, 10 / portTICK_PERIOD_MS) != pdTRUE) {
|
||||||
|
ESP_LOGW(TAG, "Failed to send WARNING event");
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (bytes_written != sizeof(sample)) {
|
||||||
|
event = {.type = TaskEventType::WARNING, .err = ESP_FAIL};
|
||||||
|
if (xQueueSend(this_speaker->event_queue_, &event, 10 / portTICK_PERIOD_MS) != pdTRUE) {
|
||||||
|
ESP_LOGW(TAG, "Failed to send WARNING event");
|
||||||
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
remaining--;
|
remaining--;
|
||||||
|
@ -139,18 +155,25 @@ void I2SAudioSpeaker::player_task(void *params) {
|
||||||
}
|
}
|
||||||
|
|
||||||
event.type = TaskEventType::PLAYING;
|
event.type = TaskEventType::PLAYING;
|
||||||
xQueueSend(this_speaker->event_queue_, &event, portMAX_DELAY);
|
event.err = current;
|
||||||
|
if (xQueueSend(this_speaker->event_queue_, &event, 10 / portTICK_PERIOD_MS) != pdTRUE) {
|
||||||
|
ESP_LOGW(TAG, "Failed to send PLAYING event");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
event.type = TaskEventType::STOPPING;
|
||||||
|
if (xQueueSend(this_speaker->event_queue_, &event, 10 / portTICK_PERIOD_MS) != pdTRUE) {
|
||||||
|
ESP_LOGW(TAG, "Failed to send STOPPING event");
|
||||||
}
|
}
|
||||||
|
|
||||||
i2s_zero_dma_buffer(this_speaker->parent_->get_port());
|
i2s_zero_dma_buffer(this_speaker->parent_->get_port());
|
||||||
|
|
||||||
event.type = TaskEventType::STOPPING;
|
|
||||||
xQueueSend(this_speaker->event_queue_, &event, portMAX_DELAY);
|
|
||||||
|
|
||||||
i2s_driver_uninstall(this_speaker->parent_->get_port());
|
i2s_driver_uninstall(this_speaker->parent_->get_port());
|
||||||
|
|
||||||
event.type = TaskEventType::STOPPED;
|
event.type = TaskEventType::STOPPED;
|
||||||
xQueueSend(this_speaker->event_queue_, &event, portMAX_DELAY);
|
if (xQueueSend(this_speaker->event_queue_, &event, 10 / portTICK_PERIOD_MS) != pdTRUE) {
|
||||||
|
ESP_LOGW(TAG, "Failed to send STOPPED event");
|
||||||
|
}
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
delay(10);
|
delay(10);
|
||||||
|
@ -181,6 +204,7 @@ void I2SAudioSpeaker::watch_() {
|
||||||
break;
|
break;
|
||||||
case TaskEventType::STARTED:
|
case TaskEventType::STARTED:
|
||||||
ESP_LOGD(TAG, "Started I2S Audio Speaker");
|
ESP_LOGD(TAG, "Started I2S Audio Speaker");
|
||||||
|
this->state_ = speaker::STATE_RUNNING;
|
||||||
break;
|
break;
|
||||||
case TaskEventType::STOPPING:
|
case TaskEventType::STOPPING:
|
||||||
ESP_LOGD(TAG, "Stopping I2S Audio Speaker");
|
ESP_LOGD(TAG, "Stopping I2S Audio Speaker");
|
||||||
|
@ -191,6 +215,7 @@ void I2SAudioSpeaker::watch_() {
|
||||||
case TaskEventType::STOPPED:
|
case TaskEventType::STOPPED:
|
||||||
this->state_ = speaker::STATE_STOPPED;
|
this->state_ = speaker::STATE_STOPPED;
|
||||||
vTaskDelete(this->player_task_handle_);
|
vTaskDelete(this->player_task_handle_);
|
||||||
|
this->task_created_ = false;
|
||||||
this->player_task_handle_ = nullptr;
|
this->player_task_handle_ = nullptr;
|
||||||
this->parent_->unlock();
|
this->parent_->unlock();
|
||||||
xQueueReset(this->buffer_queue_);
|
xQueueReset(this->buffer_queue_);
|
||||||
|
@ -208,7 +233,6 @@ void I2SAudioSpeaker::loop() {
|
||||||
switch (this->state_) {
|
switch (this->state_) {
|
||||||
case speaker::STATE_STARTING:
|
case speaker::STATE_STARTING:
|
||||||
this->start_();
|
this->start_();
|
||||||
break;
|
|
||||||
case speaker::STATE_RUNNING:
|
case speaker::STATE_RUNNING:
|
||||||
case speaker::STATE_STOPPING:
|
case speaker::STATE_STOPPING:
|
||||||
this->watch_();
|
this->watch_();
|
||||||
|
|
|
@ -60,7 +60,6 @@ class I2SAudioSpeaker : public Component, public speaker::Speaker, public I2SAud
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void start_();
|
void start_();
|
||||||
// void stop_();
|
|
||||||
void watch_();
|
void watch_();
|
||||||
|
|
||||||
static void player_task(void *params);
|
static void player_task(void *params);
|
||||||
|
@ -70,6 +69,7 @@ class I2SAudioSpeaker : public Component, public speaker::Speaker, public I2SAud
|
||||||
QueueHandle_t event_queue_;
|
QueueHandle_t event_queue_;
|
||||||
|
|
||||||
uint8_t dout_pin_{0};
|
uint8_t dout_pin_{0};
|
||||||
|
bool task_created_{false};
|
||||||
|
|
||||||
#if SOC_I2S_SUPPORTS_DAC
|
#if SOC_I2S_SUPPORTS_DAC
|
||||||
i2s_dac_mode_t internal_dac_mode_{I2S_DAC_CHANNEL_DISABLE};
|
i2s_dac_mode_t internal_dac_mode_{I2S_DAC_CHANNEL_DISABLE};
|
||||||
|
|
|
@ -69,6 +69,7 @@ MODELS = {
|
||||||
"ILI9486": ili9xxx_ns.class_("ILI9XXXILI9486", ILI9XXXDisplay),
|
"ILI9486": ili9xxx_ns.class_("ILI9XXXILI9486", ILI9XXXDisplay),
|
||||||
"ILI9488": ili9xxx_ns.class_("ILI9XXXILI9488", ILI9XXXDisplay),
|
"ILI9488": ili9xxx_ns.class_("ILI9XXXILI9488", ILI9XXXDisplay),
|
||||||
"ILI9488_A": ili9xxx_ns.class_("ILI9XXXILI9488A", ILI9XXXDisplay),
|
"ILI9488_A": ili9xxx_ns.class_("ILI9XXXILI9488A", ILI9XXXDisplay),
|
||||||
|
"ST7735": ili9xxx_ns.class_("ILI9XXXST7735", ILI9XXXDisplay),
|
||||||
"ST7796": ili9xxx_ns.class_("ILI9XXXST7796", ILI9XXXDisplay),
|
"ST7796": ili9xxx_ns.class_("ILI9XXXST7796", ILI9XXXDisplay),
|
||||||
"ST7789V": ili9xxx_ns.class_("ILI9XXXST7789V", ILI9XXXDisplay),
|
"ST7789V": ili9xxx_ns.class_("ILI9XXXST7789V", ILI9XXXDisplay),
|
||||||
"S3BOX": ili9xxx_ns.class_("ILI9XXXS3Box", ILI9XXXDisplay),
|
"S3BOX": ili9xxx_ns.class_("ILI9XXXS3Box", ILI9XXXDisplay),
|
||||||
|
@ -134,6 +135,7 @@ def _validate(config):
|
||||||
"ILI9341",
|
"ILI9341",
|
||||||
"ILI9342",
|
"ILI9342",
|
||||||
"ST7789V",
|
"ST7789V",
|
||||||
|
"ST7735",
|
||||||
]:
|
]:
|
||||||
raise cv.Invalid("Selected model can't run on ESP8266.")
|
raise cv.Invalid("Selected model can't run on ESP8266.")
|
||||||
|
|
||||||
|
|
|
@ -70,6 +70,7 @@ static const uint8_t ILI9XXX_PWCTR2 = 0xC1;
|
||||||
static const uint8_t ILI9XXX_PWCTR3 = 0xC2;
|
static const uint8_t ILI9XXX_PWCTR3 = 0xC2;
|
||||||
static const uint8_t ILI9XXX_PWCTR4 = 0xC3;
|
static const uint8_t ILI9XXX_PWCTR4 = 0xC3;
|
||||||
static const uint8_t ILI9XXX_PWCTR5 = 0xC4;
|
static const uint8_t ILI9XXX_PWCTR5 = 0xC4;
|
||||||
|
static const uint8_t ILI9XXX_PWCTR6 = 0xF6;
|
||||||
static const uint8_t ILI9XXX_VMCTR1 = 0xC5;
|
static const uint8_t ILI9XXX_VMCTR1 = 0xC5;
|
||||||
static const uint8_t ILI9XXX_IFCTR = 0xC6;
|
static const uint8_t ILI9XXX_IFCTR = 0xC6;
|
||||||
static const uint8_t ILI9XXX_VMCTR2 = 0xC7;
|
static const uint8_t ILI9XXX_VMCTR2 = 0xC7;
|
||||||
|
@ -91,6 +92,7 @@ static const uint8_t ILI9XXX_GMCTRN1 = 0xE1;
|
||||||
|
|
||||||
static const uint8_t ILI9XXX_CSCON = 0xF0;
|
static const uint8_t ILI9XXX_CSCON = 0xF0;
|
||||||
static const uint8_t ILI9XXX_ADJCTL3 = 0xF7;
|
static const uint8_t ILI9XXX_ADJCTL3 = 0xF7;
|
||||||
|
static const uint8_t ILI9XXX_DELAY = 0xFF; // followed by one byte of delay time in ms
|
||||||
|
|
||||||
} // namespace ili9xxx
|
} // namespace ili9xxx
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|
|
@ -34,8 +34,8 @@ void ILI9XXXDisplay::setup() {
|
||||||
ESP_LOGD(TAG, "Setting up ILI9xxx");
|
ESP_LOGD(TAG, "Setting up ILI9xxx");
|
||||||
|
|
||||||
this->setup_pins_();
|
this->setup_pins_();
|
||||||
this->init_lcd_(this->init_sequence_);
|
this->init_lcd(this->init_sequence_);
|
||||||
this->init_lcd_(this->extra_init_sequence_.data());
|
this->init_lcd(this->extra_init_sequence_.data());
|
||||||
switch (this->pixel_mode_) {
|
switch (this->pixel_mode_) {
|
||||||
case PIXEL_MODE_16:
|
case PIXEL_MODE_16:
|
||||||
if (this->is_18bitdisplay_) {
|
if (this->is_18bitdisplay_) {
|
||||||
|
@ -405,7 +405,29 @@ void ILI9XXXDisplay::reset_() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ILI9XXXDisplay::init_lcd_(const uint8_t *addr) {
|
void ILI9XXXDisplay::init_lcd(const uint8_t *addr) {
|
||||||
|
if (addr == nullptr)
|
||||||
|
return;
|
||||||
|
uint8_t cmd, x, num_args;
|
||||||
|
while ((cmd = *addr++) != 0) {
|
||||||
|
x = *addr++;
|
||||||
|
if (cmd == ILI9XXX_DELAY) {
|
||||||
|
ESP_LOGD(TAG, "Delay %dms", x);
|
||||||
|
delay(x);
|
||||||
|
} else {
|
||||||
|
num_args = x & 0x7F;
|
||||||
|
ESP_LOGD(TAG, "Command %02X, length %d, bits %02X", cmd, num_args, *addr);
|
||||||
|
this->send_command(cmd, addr, num_args);
|
||||||
|
addr += num_args;
|
||||||
|
if (x & 0x80) {
|
||||||
|
ESP_LOGD(TAG, "Delay 150ms");
|
||||||
|
delay(150); // NOLINT
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ILI9XXXGC9A01A::init_lcd(const uint8_t *addr) {
|
||||||
if (addr == nullptr)
|
if (addr == nullptr)
|
||||||
return;
|
return;
|
||||||
uint8_t cmd, x, num_args;
|
uint8_t cmd, x, num_args;
|
||||||
|
|
|
@ -35,7 +35,6 @@ class ILI9XXXDisplay : public display::DisplayBuffer,
|
||||||
while ((cmd = *addr++) != 0) {
|
while ((cmd = *addr++) != 0) {
|
||||||
num_args = *addr++ & 0x7F;
|
num_args = *addr++ & 0x7F;
|
||||||
bits = *addr;
|
bits = *addr;
|
||||||
esph_log_d(TAG, "Command %02X, length %d, bits %02X", cmd, num_args, bits);
|
|
||||||
switch (cmd) {
|
switch (cmd) {
|
||||||
case ILI9XXX_MADCTL: {
|
case ILI9XXX_MADCTL: {
|
||||||
this->swap_xy_ = (bits & MADCTL_MV) != 0;
|
this->swap_xy_ = (bits & MADCTL_MV) != 0;
|
||||||
|
@ -51,6 +50,9 @@ class ILI9XXXDisplay : public display::DisplayBuffer,
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case ILI9XXX_DELAY:
|
||||||
|
continue; // no args to skip
|
||||||
|
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -107,7 +109,7 @@ class ILI9XXXDisplay : public display::DisplayBuffer,
|
||||||
|
|
||||||
virtual void set_madctl();
|
virtual void set_madctl();
|
||||||
void display_();
|
void display_();
|
||||||
void init_lcd_(const uint8_t *addr);
|
virtual void init_lcd(const uint8_t *addr);
|
||||||
void set_addr_window_(uint16_t x, uint16_t y, uint16_t x2, uint16_t y2);
|
void set_addr_window_(uint16_t x, uint16_t y, uint16_t x2, uint16_t y2);
|
||||||
void reset_();
|
void reset_();
|
||||||
|
|
||||||
|
@ -267,6 +269,13 @@ class ILI9XXXS3BoxLite : public ILI9XXXDisplay {
|
||||||
class ILI9XXXGC9A01A : public ILI9XXXDisplay {
|
class ILI9XXXGC9A01A : public ILI9XXXDisplay {
|
||||||
public:
|
public:
|
||||||
ILI9XXXGC9A01A() : ILI9XXXDisplay(INITCMD_GC9A01A, 240, 240, true) {}
|
ILI9XXXGC9A01A() : ILI9XXXDisplay(INITCMD_GC9A01A, 240, 240, true) {}
|
||||||
|
void init_lcd(const uint8_t *addr) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
//----------- ILI9XXX_24_TFT display --------------
|
||||||
|
class ILI9XXXST7735 : public ILI9XXXDisplay {
|
||||||
|
public:
|
||||||
|
ILI9XXXST7735() : ILI9XXXDisplay(INITCMD_ST7735, 128, 160, false) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace ili9xxx
|
} // namespace ili9xxx
|
||||||
|
|
|
@ -370,6 +370,57 @@ static const uint8_t PROGMEM INITCMD_GC9A01A[] = {
|
||||||
0x00 // End of list
|
0x00 // End of list
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static const uint8_t PROGMEM INITCMD_ST7735[] = {
|
||||||
|
ILI9XXX_SWRESET, 0, // Soft reset, then delay 10ms
|
||||||
|
ILI9XXX_DELAY, 10,
|
||||||
|
ILI9XXX_SLPOUT , 0, // Exit Sleep, delay
|
||||||
|
ILI9XXX_DELAY, 10,
|
||||||
|
ILI9XXX_PIXFMT , 1, 0x05,
|
||||||
|
ILI9XXX_FRMCTR1, 3, // 4: Frame rate control, 3 args + delay:
|
||||||
|
0x01, 0x2C, 0x2D, // Rate = fosc/(1x2+40) * (LINE+2C+2D)
|
||||||
|
ILI9XXX_FRMCTR2, 3, // 4: Framerate ctrl - idle mode, 3 args:
|
||||||
|
0x01, 0x2C, 0x2D, // Rate = fosc/(1x2+40) * (LINE+2C+2D)
|
||||||
|
ILI9XXX_FRMCTR3, 6, // 5: Framerate - partial mode, 6 args:
|
||||||
|
0x01, 0x2C, 0x2D, // Dot inversion mode
|
||||||
|
0x01, 0x2C, 0x2D, // Line inversion mode
|
||||||
|
|
||||||
|
ILI9XXX_INVCTR, 1, // 7: Display inversion control, 1 arg:
|
||||||
|
0x7, // Line inversion
|
||||||
|
ILI9XXX_PWCTR1, 3, // 7: Power control, 3 args, no delay:
|
||||||
|
0xA2,
|
||||||
|
0x02, // -4.6V
|
||||||
|
0x84, // AUTO mode
|
||||||
|
ILI9XXX_PWCTR2, 1, // 8: Power control, 1 arg, no delay:
|
||||||
|
0xC5, // VGH25=2.4C VGSEL=-10 VGH=3 * AVDD
|
||||||
|
ILI9XXX_PWCTR3, 2, // 9: Power control, 2 args, no delay:
|
||||||
|
0x0A, // Opamp current small
|
||||||
|
0x00, // Boost frequency
|
||||||
|
ILI9XXX_PWCTR4, 2, // 10: Power control, 2 args, no delay:
|
||||||
|
0x8A, // BCLK/2,
|
||||||
|
0x2A, // opamp current small & medium low
|
||||||
|
ILI9XXX_PWCTR5, 2, // 11: Power control, 2 args, no delay:
|
||||||
|
0x8A, 0xEE,
|
||||||
|
|
||||||
|
ILI9XXX_VMCTR1, 1, // 11: Power control, 2 args + delay:
|
||||||
|
0x0E,
|
||||||
|
ILI9XXX_GMCTRP1, 16, // 13: Gamma Adjustments (pos. polarity), 16 args + delay:
|
||||||
|
0x02, 0x1c, 0x07, 0x12, // (Not entirely necessary, but provides
|
||||||
|
0x37, 0x32, 0x29, 0x2d, // accurate colors)
|
||||||
|
0x29, 0x25, 0x2B, 0x39,
|
||||||
|
0x00, 0x01, 0x03, 0x10,
|
||||||
|
ILI9XXX_GMCTRN1, 16, // 14: Gamma Adjustments (neg. polarity), 16 args + delay:
|
||||||
|
0x03, 0x1d, 0x07, 0x06, // (Not entirely necessary, but provides
|
||||||
|
0x2E, 0x2C, 0x29, 0x2D, // accurate colors)
|
||||||
|
0x2E, 0x2E, 0x37, 0x3F,
|
||||||
|
0x00, 0x00, 0x02, 0x10,
|
||||||
|
ILI9XXX_MADCTL , 1, 0x00, // Memory Access Control, BGR
|
||||||
|
ILI9XXX_NORON , 0,
|
||||||
|
ILI9XXX_DELAY, 10,
|
||||||
|
ILI9XXX_DISPON , 0, // Display on
|
||||||
|
ILI9XXX_DELAY, 10,
|
||||||
|
00, // endo of list
|
||||||
|
};
|
||||||
|
|
||||||
// clang-format on
|
// clang-format on
|
||||||
} // namespace ili9xxx
|
} // namespace ili9xxx
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|
|
@ -6,18 +6,14 @@ import hashlib
|
||||||
import io
|
import io
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import re
|
import re
|
||||||
import requests
|
|
||||||
from magic import Magic
|
from magic import Magic
|
||||||
|
|
||||||
from PIL import Image
|
|
||||||
|
|
||||||
from esphome import core
|
from esphome import core
|
||||||
from esphome.components import font
|
from esphome.components import font
|
||||||
from esphome import external_files
|
from esphome import external_files
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
from esphome.const import (
|
from esphome.const import (
|
||||||
__version__,
|
|
||||||
CONF_DITHER,
|
CONF_DITHER,
|
||||||
CONF_FILE,
|
CONF_FILE,
|
||||||
CONF_ICON,
|
CONF_ICON,
|
||||||
|
@ -68,7 +64,7 @@ def _compute_local_icon_path(value: dict) -> Path:
|
||||||
return base_dir / f"{value[CONF_ICON]}.svg"
|
return base_dir / f"{value[CONF_ICON]}.svg"
|
||||||
|
|
||||||
|
|
||||||
def _compute_local_image_path(value: dict) -> Path:
|
def compute_local_image_path(value: dict) -> Path:
|
||||||
url = value[CONF_URL]
|
url = value[CONF_URL]
|
||||||
h = hashlib.new("sha256")
|
h = hashlib.new("sha256")
|
||||||
h.update(url.encode())
|
h.update(url.encode())
|
||||||
|
@ -77,31 +73,6 @@ def _compute_local_image_path(value: dict) -> Path:
|
||||||
return base_dir / key
|
return base_dir / key
|
||||||
|
|
||||||
|
|
||||||
def download_content(url: str, path: Path) -> None:
|
|
||||||
if not external_files.has_remote_file_changed(url, path):
|
|
||||||
_LOGGER.debug("Remote file has not changed %s", url)
|
|
||||||
return
|
|
||||||
|
|
||||||
_LOGGER.debug(
|
|
||||||
"Remote file has changed, downloading from %s to %s",
|
|
||||||
url,
|
|
||||||
path,
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
|
||||||
req = requests.get(
|
|
||||||
url,
|
|
||||||
timeout=IMAGE_DOWNLOAD_TIMEOUT,
|
|
||||||
headers={"User-agent": f"ESPHome/{__version__} (https://esphome.io)"},
|
|
||||||
)
|
|
||||||
req.raise_for_status()
|
|
||||||
except requests.exceptions.RequestException as e:
|
|
||||||
raise cv.Invalid(f"Could not download from {url}: {e}")
|
|
||||||
|
|
||||||
path.parent.mkdir(parents=True, exist_ok=True)
|
|
||||||
path.write_bytes(req.content)
|
|
||||||
|
|
||||||
|
|
||||||
def download_mdi(value):
|
def download_mdi(value):
|
||||||
validate_cairosvg_installed(value)
|
validate_cairosvg_installed(value)
|
||||||
|
|
||||||
|
@ -110,16 +81,16 @@ def download_mdi(value):
|
||||||
|
|
||||||
url = f"https://raw.githubusercontent.com/Templarian/MaterialDesign/master/svg/{mdi_id}.svg"
|
url = f"https://raw.githubusercontent.com/Templarian/MaterialDesign/master/svg/{mdi_id}.svg"
|
||||||
|
|
||||||
download_content(url, path)
|
external_files.download_content(url, path, IMAGE_DOWNLOAD_TIMEOUT)
|
||||||
|
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
def download_image(value):
|
def download_image(value):
|
||||||
url = value[CONF_URL]
|
url = value[CONF_URL]
|
||||||
path = _compute_local_image_path(value)
|
path = compute_local_image_path(value)
|
||||||
|
|
||||||
download_content(url, path)
|
external_files.download_content(url, path, IMAGE_DOWNLOAD_TIMEOUT)
|
||||||
|
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
@ -267,6 +238,9 @@ CONFIG_SCHEMA = cv.All(font.validate_pillow_installed, IMAGE_SCHEMA)
|
||||||
|
|
||||||
|
|
||||||
def load_svg_image(file: bytes, resize: tuple[int, int]):
|
def load_svg_image(file: bytes, resize: tuple[int, int]):
|
||||||
|
# Local import only to allow "validate_pillow_installed" to run *before* importing it
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
# This import is only needed in case of SVG images; adding it
|
# This import is only needed in case of SVG images; adding it
|
||||||
# to the top would force configurations not using SVG to also have it
|
# to the top would force configurations not using SVG to also have it
|
||||||
# installed for no reason.
|
# installed for no reason.
|
||||||
|
@ -286,6 +260,9 @@ def load_svg_image(file: bytes, resize: tuple[int, int]):
|
||||||
|
|
||||||
|
|
||||||
async def to_code(config):
|
async def to_code(config):
|
||||||
|
# Local import only to allow "validate_pillow_installed" to run *before* importing it
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
conf_file = config[CONF_FILE]
|
conf_file = config[CONF_FILE]
|
||||||
|
|
||||||
if conf_file[CONF_SOURCE] == SOURCE_LOCAL:
|
if conf_file[CONF_SOURCE] == SOURCE_LOCAL:
|
||||||
|
@ -295,7 +272,7 @@ async def to_code(config):
|
||||||
path = _compute_local_icon_path(conf_file).as_posix()
|
path = _compute_local_icon_path(conf_file).as_posix()
|
||||||
|
|
||||||
elif conf_file[CONF_SOURCE] == SOURCE_WEB:
|
elif conf_file[CONF_SOURCE] == SOURCE_WEB:
|
||||||
path = _compute_local_image_path(conf_file).as_posix()
|
path = compute_local_image_path(conf_file).as_posix()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with open(path, "rb") as f:
|
with open(path, "rb") as f:
|
||||||
|
|
|
@ -62,7 +62,7 @@ std::string build_json(const json_build_t &f) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void parse_json(const std::string &data, const json_parse_t &f) {
|
bool parse_json(const std::string &data, const json_parse_t &f) {
|
||||||
// Here we are allocating 1.5 times the data size,
|
// Here we are allocating 1.5 times the data size,
|
||||||
// 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.
|
||||||
|
@ -76,14 +76,13 @@ void parse_json(const std::string &data, const json_parse_t &f) {
|
||||||
#elif defined(USE_LIBRETINY)
|
#elif defined(USE_LIBRETINY)
|
||||||
const size_t free_heap = lt_heap_get_free();
|
const size_t free_heap = lt_heap_get_free();
|
||||||
#endif
|
#endif
|
||||||
bool pass = false;
|
|
||||||
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));
|
||||||
do {
|
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 %u bytes, free heap: %u", request_size,
|
||||||
free_heap);
|
free_heap);
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
DeserializationError err = deserializeJson(json_document, data);
|
DeserializationError err = deserializeJson(json_document, data);
|
||||||
json_document.shrinkToFit();
|
json_document.shrinkToFit();
|
||||||
|
@ -91,21 +90,21 @@ void parse_json(const std::string &data, const json_parse_t &f) {
|
||||||
JsonObject root = json_document.as<JsonObject>();
|
JsonObject root = json_document.as<JsonObject>();
|
||||||
|
|
||||||
if (err == DeserializationError::Ok) {
|
if (err == DeserializationError::Ok) {
|
||||||
pass = true;
|
return f(root);
|
||||||
f(root);
|
|
||||||
} else if (err == DeserializationError::NoMemory) {
|
} else if (err == DeserializationError::NoMemory) {
|
||||||
if (request_size * 2 >= free_heap) {
|
if (request_size * 2 >= free_heap) {
|
||||||
ESP_LOGE(TAG, "Can not allocate more memory for deserialization. Consider making source string smaller");
|
ESP_LOGE(TAG, "Can not allocate more memory for deserialization. Consider making source string smaller");
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
ESP_LOGV(TAG, "Increasing memory allocation.");
|
ESP_LOGV(TAG, "Increasing memory allocation.");
|
||||||
request_size *= 2;
|
request_size *= 2;
|
||||||
continue;
|
continue;
|
||||||
} else {
|
} else {
|
||||||
ESP_LOGE(TAG, "JSON parse error: %s", err.c_str());
|
ESP_LOGE(TAG, "JSON parse error: %s", err.c_str());
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
} while (!pass);
|
};
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace json
|
} // namespace json
|
||||||
|
|
|
@ -14,7 +14,7 @@ namespace esphome {
|
||||||
namespace json {
|
namespace json {
|
||||||
|
|
||||||
/// Callback function typedef for parsing JsonObjects.
|
/// Callback function typedef for parsing JsonObjects.
|
||||||
using json_parse_t = std::function<void(JsonObject)>;
|
using json_parse_t = std::function<bool(JsonObject)>;
|
||||||
|
|
||||||
/// Callback function typedef for building JsonObjects.
|
/// Callback function typedef for building JsonObjects.
|
||||||
using json_build_t = std::function<void(JsonObject)>;
|
using json_build_t = std::function<void(JsonObject)>;
|
||||||
|
@ -23,7 +23,7 @@ using json_build_t = std::function<void(JsonObject)>;
|
||||||
std::string build_json(const json_build_t &f);
|
std::string build_json(const json_build_t &f);
|
||||||
|
|
||||||
/// Parse a JSON string and run the provided json parse function if it's valid.
|
/// Parse a JSON string and run the provided json parse function if it's valid.
|
||||||
void parse_json(const std::string &data, const json_parse_t &f);
|
bool parse_json(const std::string &data, const json_parse_t &f);
|
||||||
|
|
||||||
} // namespace json
|
} // namespace json
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|
|
@ -115,12 +115,15 @@ void LEDCOutput::write_state(float state) {
|
||||||
const uint32_t max_duty = (uint32_t(1) << this->bit_depth_) - 1;
|
const uint32_t max_duty = (uint32_t(1) << this->bit_depth_) - 1;
|
||||||
const float duty_rounded = roundf(state * max_duty);
|
const float duty_rounded = roundf(state * max_duty);
|
||||||
auto duty = static_cast<uint32_t>(duty_rounded);
|
auto duty = static_cast<uint32_t>(duty_rounded);
|
||||||
|
|
||||||
#ifdef USE_ARDUINO
|
#ifdef USE_ARDUINO
|
||||||
ESP_LOGV(TAG, "Setting duty: %u on channel %u", duty, this->channel_);
|
ESP_LOGV(TAG, "Setting duty: %u on channel %u", duty, this->channel_);
|
||||||
ledcWrite(this->channel_, duty);
|
ledcWrite(this->channel_, duty);
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_ESP_IDF
|
#ifdef USE_ESP_IDF
|
||||||
|
// ensure that 100% on is not 99.975% on
|
||||||
|
if ((duty == max_duty) && (max_duty != 1)) {
|
||||||
|
duty = max_duty + 1;
|
||||||
|
}
|
||||||
auto speed_mode = get_speed_mode(channel_);
|
auto speed_mode = get_speed_mode(channel_);
|
||||||
auto chan_num = static_cast<ledc_channel_t>(channel_ % 8);
|
auto chan_num = static_cast<ledc_channel_t>(channel_ % 8);
|
||||||
int hpoint = ledc_angle_to_htop(this->phase_angle_, this->bit_depth_);
|
int hpoint = ledc_angle_to_htop(this->phase_angle_, this->bit_depth_);
|
||||||
|
|
|
@ -112,11 +112,18 @@ HARDWARE_UART_TO_UART_SELECTION = {
|
||||||
}
|
}
|
||||||
|
|
||||||
HARDWARE_UART_TO_SERIAL = {
|
HARDWARE_UART_TO_SERIAL = {
|
||||||
UART0: cg.global_ns.Serial,
|
PLATFORM_ESP8266: {
|
||||||
UART0_SWAP: cg.global_ns.Serial,
|
UART0: cg.global_ns.Serial,
|
||||||
UART1: cg.global_ns.Serial1,
|
UART0_SWAP: cg.global_ns.Serial,
|
||||||
UART2: cg.global_ns.Serial2,
|
UART1: cg.global_ns.Serial1,
|
||||||
DEFAULT: cg.global_ns.Serial,
|
UART2: cg.global_ns.Serial2,
|
||||||
|
DEFAULT: cg.global_ns.Serial,
|
||||||
|
},
|
||||||
|
PLATFORM_RP2040: {
|
||||||
|
UART0: cg.global_ns.Serial1,
|
||||||
|
UART1: cg.global_ns.Serial2,
|
||||||
|
USB_CDC: cg.global_ns.Serial,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
is_log_level = cv.one_of(*LOG_LEVELS, upper=True)
|
is_log_level = cv.one_of(*LOG_LEVELS, upper=True)
|
||||||
|
@ -244,8 +251,14 @@ async def to_code(config):
|
||||||
is_at_least_very_verbose = this_severity >= very_verbose_severity
|
is_at_least_very_verbose = this_severity >= very_verbose_severity
|
||||||
has_serial_logging = baud_rate != 0
|
has_serial_logging = baud_rate != 0
|
||||||
|
|
||||||
if CORE.is_esp8266 and has_serial_logging and is_at_least_verbose:
|
if (
|
||||||
debug_serial_port = HARDWARE_UART_TO_SERIAL[config.get(CONF_HARDWARE_UART)]
|
(CORE.is_esp8266 or CORE.is_rp2040)
|
||||||
|
and has_serial_logging
|
||||||
|
and is_at_least_verbose
|
||||||
|
):
|
||||||
|
debug_serial_port = HARDWARE_UART_TO_SERIAL[CORE.target_platform][
|
||||||
|
config.get(CONF_HARDWARE_UART)
|
||||||
|
]
|
||||||
cg.add_build_flag(f"-DDEBUG_ESP_PORT={debug_serial_port}")
|
cg.add_build_flag(f"-DDEBUG_ESP_PORT={debug_serial_port}")
|
||||||
cg.add_build_flag("-DLWIP_DEBUG")
|
cg.add_build_flag("-DLWIP_DEBUG")
|
||||||
DEBUG_COMPONENTS = {
|
DEBUG_COMPONENTS = {
|
||||||
|
|
|
@ -74,6 +74,9 @@ def mdns_service(
|
||||||
|
|
||||||
@coroutine_with_priority(55.0)
|
@coroutine_with_priority(55.0)
|
||||||
async def to_code(config):
|
async def to_code(config):
|
||||||
|
if config[CONF_DISABLED] is True:
|
||||||
|
return
|
||||||
|
|
||||||
if CORE.using_arduino:
|
if CORE.using_arduino:
|
||||||
if CORE.is_esp32:
|
if CORE.is_esp32:
|
||||||
cg.add_library("ESPmDNS", None)
|
cg.add_library("ESPmDNS", None)
|
||||||
|
@ -92,9 +95,6 @@ async def to_code(config):
|
||||||
path="components/mdns",
|
path="components/mdns",
|
||||||
)
|
)
|
||||||
|
|
||||||
if config[CONF_DISABLED]:
|
|
||||||
return
|
|
||||||
|
|
||||||
cg.add_define("USE_MDNS")
|
cg.add_define("USE_MDNS")
|
||||||
|
|
||||||
var = cg.new_Pvariable(config[CONF_ID])
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
#include "mdns_component.h"
|
|
||||||
#include "esphome/core/defines.h"
|
#include "esphome/core/defines.h"
|
||||||
|
#ifdef USE_MDNS
|
||||||
|
#include "mdns_component.h"
|
||||||
#include "esphome/core/version.h"
|
#include "esphome/core/version.h"
|
||||||
#include "esphome/core/application.h"
|
#include "esphome/core/application.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
@ -125,3 +126,4 @@ void MDNSComponent::dump_config() {
|
||||||
|
|
||||||
} // namespace mdns
|
} // namespace mdns
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
#endif
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
#include "esphome/core/defines.h"
|
||||||
|
#ifdef USE_MDNS
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include "esphome/core/component.h"
|
#include "esphome/core/component.h"
|
||||||
|
@ -46,3 +47,4 @@ class MDNSComponent : public Component {
|
||||||
|
|
||||||
} // namespace mdns
|
} // namespace mdns
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
#endif
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
#ifdef USE_ESP32
|
#include "esphome/core/defines.h"
|
||||||
|
#if defined(USE_ESP32) && defined(USE_MDNS)
|
||||||
|
|
||||||
#include <mdns.h>
|
#include <mdns.h>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
#if defined(USE_ESP8266) && defined(USE_ARDUINO)
|
#include "esphome/core/defines.h"
|
||||||
|
#if defined(USE_ESP8266) && defined(USE_ARDUINO) && defined(USE_MDNS)
|
||||||
|
|
||||||
#include <ESP8266mDNS.h>
|
#include <ESP8266mDNS.h>
|
||||||
#include "esphome/components/network/ip_address.h"
|
#include "esphome/components/network/ip_address.h"
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
#ifdef USE_HOST
|
#include "esphome/core/defines.h"
|
||||||
|
#if defined(USE_HOST) && defined(USE_MDNS)
|
||||||
|
|
||||||
#include "esphome/components/network/ip_address.h"
|
#include "esphome/components/network/ip_address.h"
|
||||||
#include "esphome/components/network/util.h"
|
#include "esphome/components/network/util.h"
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
#ifdef USE_LIBRETINY
|
#include "esphome/core/defines.h"
|
||||||
|
#if defined(USE_LIBRETINY) && defined(USE_MDNS)
|
||||||
|
|
||||||
#include "esphome/components/network/ip_address.h"
|
#include "esphome/components/network/ip_address.h"
|
||||||
#include "esphome/components/network/util.h"
|
#include "esphome/components/network/util.h"
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
#ifdef USE_RP2040
|
#include "esphome/core/defines.h"
|
||||||
|
#if defined(USE_RP2040) && defined(USE_MDNS)
|
||||||
|
|
||||||
#include "esphome/components/network/ip_address.h"
|
#include "esphome/components/network/ip_address.h"
|
||||||
#include "esphome/components/network/util.h"
|
#include "esphome/components/network/util.h"
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue