commit
8051c1ca99
27
.clang-tidy
27
.clang-tidy
@ -2,9 +2,11 @@
|
||||
Checks: >-
|
||||
*,
|
||||
-abseil-*,
|
||||
-altera-*,
|
||||
-android-*,
|
||||
-boost-*,
|
||||
-bugprone-branch-clone,
|
||||
-bugprone-easily-swappable-parameters,
|
||||
-bugprone-narrowing-conversions,
|
||||
-bugprone-signed-char-misuse,
|
||||
-bugprone-too-small-loop-variable,
|
||||
@ -20,6 +22,7 @@ Checks: >-
|
||||
-clang-diagnostic-sign-compare,
|
||||
-clang-diagnostic-unused-variable,
|
||||
-clang-diagnostic-unused-const-variable,
|
||||
-concurrency-*,
|
||||
-cppcoreguidelines-avoid-c-arrays,
|
||||
-cppcoreguidelines-avoid-goto,
|
||||
-cppcoreguidelines-avoid-magic-numbers,
|
||||
@ -27,7 +30,6 @@ Checks: >-
|
||||
-cppcoreguidelines-macro-usage,
|
||||
-cppcoreguidelines-narrowing-conversions,
|
||||
-cppcoreguidelines-non-private-member-variables-in-classes,
|
||||
-cppcoreguidelines-owning-memory,
|
||||
-cppcoreguidelines-pro-bounds-array-to-pointer-decay,
|
||||
-cppcoreguidelines-pro-bounds-constant-array-index,
|
||||
-cppcoreguidelines-pro-bounds-pointer-arithmetic,
|
||||
@ -61,17 +63,21 @@ Checks: >-
|
||||
-misc-no-recursion,
|
||||
-misc-unused-parameters,
|
||||
-modernize-avoid-c-arrays,
|
||||
-modernize-avoid-bind,
|
||||
-modernize-concat-nested-namespaces,
|
||||
-modernize-return-braced-init-list,
|
||||
-modernize-use-auto,
|
||||
-modernize-use-default-member-init,
|
||||
-modernize-use-equals-default,
|
||||
-modernize-use-trailing-return-type,
|
||||
-modernize-use-nodiscard,
|
||||
-mpi-*,
|
||||
-objc-*,
|
||||
-readability-braces-around-statements,
|
||||
-readability-const-return-type,
|
||||
-readability-convert-member-functions-to-static,
|
||||
-readability-else-after-return,
|
||||
-readability-function-cognitive-complexity,
|
||||
-readability-implicit-bool-conversion,
|
||||
-readability-isolate-declaration,
|
||||
-readability-magic-numbers,
|
||||
@ -83,7 +89,6 @@ Checks: >-
|
||||
-readability-redundant-string-init,
|
||||
-readability-uppercase-literal-suffix,
|
||||
-readability-use-anyofallof,
|
||||
-warnings-as-errors
|
||||
WarningsAsErrors: '*'
|
||||
AnalyzeTemporaryDtors: false
|
||||
FormatStyle: google
|
||||
@ -108,6 +113,10 @@ CheckOptions:
|
||||
value: llvm
|
||||
- key: modernize-use-nullptr.NullMacros
|
||||
value: 'NULL'
|
||||
- key: modernize-make-unique.MakeSmartPtrFunction
|
||||
value: 'make_unique'
|
||||
- key: modernize-make-unique.MakeSmartPtrFunctionHeader
|
||||
value: 'esphome/core/helpers.h'
|
||||
- key: readability-identifier-naming.LocalVariableCase
|
||||
value: 'lower_case'
|
||||
- key: readability-identifier-naming.ClassCase
|
||||
@ -121,15 +130,19 @@ CheckOptions:
|
||||
- key: readability-identifier-naming.StaticConstantCase
|
||||
value: 'UPPER_CASE'
|
||||
- key: readability-identifier-naming.StaticVariableCase
|
||||
value: 'UPPER_CASE'
|
||||
value: 'lower_case'
|
||||
- key: readability-identifier-naming.GlobalConstantCase
|
||||
value: 'UPPER_CASE'
|
||||
- key: readability-identifier-naming.ParameterCase
|
||||
value: 'lower_case'
|
||||
- key: readability-identifier-naming.PrivateMemberPrefix
|
||||
value: 'NO_PRIVATE_MEMBERS_ALWAYS_USE_PROTECTED'
|
||||
- key: readability-identifier-naming.PrivateMethodPrefix
|
||||
value: 'NO_PRIVATE_METHODS_ALWAYS_USE_PROTECTED'
|
||||
- key: readability-identifier-naming.PrivateMemberCase
|
||||
value: 'lower_case'
|
||||
- key: readability-identifier-naming.PrivateMemberSuffix
|
||||
value: '_'
|
||||
- key: readability-identifier-naming.PrivateMethodCase
|
||||
value: 'lower_case'
|
||||
- key: readability-identifier-naming.PrivateMethodSuffix
|
||||
value: '_'
|
||||
- key: readability-identifier-naming.ClassMemberCase
|
||||
value: 'lower_case'
|
||||
- key: readability-identifier-naming.ClassMemberCase
|
||||
|
@ -1,7 +1,6 @@
|
||||
{
|
||||
"name": "ESPHome Dev",
|
||||
"context": "..",
|
||||
"dockerFile": "../docker/Dockerfile.dev",
|
||||
"image": "esphome/esphome-lint:dev",
|
||||
"postCreateCommand": [
|
||||
"script/devcontainer-post-create"
|
||||
],
|
||||
|
2
.gitattributes
vendored
Normal file
2
.gitattributes
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
# Normalize line endings to LF in the repository
|
||||
* text eol=lf
|
59
.github/stale.yml
vendored
59
.github/stale.yml
vendored
@ -1,59 +0,0 @@
|
||||
# Configuration for probot-stale - https://github.com/probot/stale
|
||||
|
||||
# Number of days of inactivity before an Issue or Pull Request becomes stale
|
||||
daysUntilStale: 60
|
||||
|
||||
# Number of days of inactivity before an Issue or Pull Request with the stale label is closed.
|
||||
# Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale.
|
||||
daysUntilClose: 7
|
||||
|
||||
# Only issues or pull requests with all of these labels are check if stale. Defaults to `[]` (disabled)
|
||||
onlyLabels: []
|
||||
|
||||
# Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable
|
||||
exemptLabels:
|
||||
- not-stale
|
||||
|
||||
# Set to true to ignore issues in a project (defaults to false)
|
||||
exemptProjects: false
|
||||
|
||||
# Set to true to ignore issues in a milestone (defaults to false)
|
||||
exemptMilestones: true
|
||||
|
||||
# Set to true to ignore issues with an assignee (defaults to false)
|
||||
exemptAssignees: false
|
||||
|
||||
# Label to use when marking as stale
|
||||
staleLabel: stale
|
||||
|
||||
# Comment to post when marking as stale. Set to `false` to disable
|
||||
markComment: >
|
||||
This issue has been automatically marked as stale because it has not had
|
||||
recent activity. It will be closed if no further activity occurs. Thank you
|
||||
for your contributions.
|
||||
|
||||
# Comment to post when removing the stale label.
|
||||
# unmarkComment: >
|
||||
# Your comment here.
|
||||
|
||||
# Comment to post when closing a stale Issue or Pull Request.
|
||||
# closeComment: >
|
||||
# Your comment here.
|
||||
|
||||
# Limit the number of actions per hour, from 1-30. Default is 30
|
||||
limitPerRun: 10
|
||||
|
||||
# Limit to only `issues` or `pulls`
|
||||
only: pulls
|
||||
|
||||
# Optionally, specify configuration settings that are specific to just 'issues' or 'pulls':
|
||||
# pulls:
|
||||
# daysUntilStale: 30
|
||||
# markComment: >
|
||||
# This pull request has been automatically marked as stale because it has not had
|
||||
# recent activity. It will be closed if no further activity occurs. Thank you
|
||||
# for your contributions.
|
||||
|
||||
# issues:
|
||||
# exemptLabels:
|
||||
# - confirmed
|
9
.github/workflows/ci-docker.yml
vendored
9
.github/workflows/ci-docker.yml
vendored
@ -7,11 +7,15 @@ on:
|
||||
paths:
|
||||
- 'docker/**'
|
||||
- '.github/workflows/**'
|
||||
- 'requirements*.txt'
|
||||
- 'platformio.ini'
|
||||
|
||||
pull_request:
|
||||
paths:
|
||||
- 'docker/**'
|
||||
- '.github/workflows/**'
|
||||
- 'requirements*.txt'
|
||||
- 'platformio.ini'
|
||||
|
||||
jobs:
|
||||
check-docker:
|
||||
@ -27,6 +31,11 @@ jobs:
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: '3.9'
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1
|
||||
|
||||
- name: Set TAG
|
||||
run: |
|
||||
echo "TAG=check" >> $GITHUB_ENV
|
||||
|
151
.github/workflows/ci.yml
vendored
151
.github/workflows/ci.yml
vendored
@ -9,58 +9,7 @@ on:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
ci-with-container:
|
||||
name: ${{ matrix.name }}
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- id: clang-format
|
||||
name: Run script/clang-format
|
||||
- id: clang-tidy
|
||||
name: Run script/clang-tidy 1/4
|
||||
split: 1
|
||||
- id: clang-tidy
|
||||
name: Run script/clang-tidy 2/4
|
||||
split: 2
|
||||
- id: clang-tidy
|
||||
name: Run script/clang-tidy 3/4
|
||||
split: 3
|
||||
- id: clang-tidy
|
||||
name: Run script/clang-tidy 4/4
|
||||
split: 4
|
||||
|
||||
# cpp lint job runs with esphome-lint docker image so that clang-format-*
|
||||
# doesn't have to be installed
|
||||
container: ghcr.io/esphome/esphome-lint:1.1
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Register problem matchers
|
||||
run: |
|
||||
echo "::add-matcher::.github/workflows/matchers/clang-tidy.json"
|
||||
echo "::add-matcher::.github/workflows/matchers/gcc.json"
|
||||
|
||||
# Also run git-diff-index so that the step is marked as failed on formatting errors,
|
||||
# since clang-format doesn't do anything but change files if -i is passed.
|
||||
- name: Run clang-format
|
||||
run: |
|
||||
script/clang-format -i
|
||||
git diff-index --quiet HEAD --
|
||||
if: ${{ matrix.id == 'clang-format' }}
|
||||
|
||||
- name: Run clang-tidy
|
||||
run: script/clang-tidy --all-headers --fix --split-num 4 --split-at ${{ matrix.split }}
|
||||
if: ${{ matrix.id == 'clang-tidy' }}
|
||||
|
||||
- name: Suggested changes
|
||||
run: script/ci-suggest-changes
|
||||
if: always()
|
||||
|
||||
ci:
|
||||
# Don't use the esphome-lint docker image because it may contain outdated requirements.
|
||||
# This way, all dependencies are cached via the cache action.
|
||||
name: ${{ matrix.name }}
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
@ -74,48 +23,87 @@ jobs:
|
||||
- id: test
|
||||
file: tests/test1.yaml
|
||||
name: Test tests/test1.yaml
|
||||
pio_cache_key: test1
|
||||
- id: test
|
||||
file: tests/test2.yaml
|
||||
name: Test tests/test2.yaml
|
||||
pio_cache_key: test2
|
||||
- id: test
|
||||
file: tests/test3.yaml
|
||||
name: Test tests/test3.yaml
|
||||
pio_cache_key: test1
|
||||
- id: test
|
||||
file: tests/test4.yaml
|
||||
name: Test tests/test4.yaml
|
||||
pio_cache_key: test4
|
||||
- id: test
|
||||
file: tests/test5.yaml
|
||||
name: Test tests/test5.yaml
|
||||
pio_cache_key: test5
|
||||
- id: pytest
|
||||
name: Run pytest
|
||||
- id: clang-format
|
||||
name: Run script/clang-format
|
||||
- id: clang-tidy
|
||||
name: Run script/clang-tidy for ESP8266
|
||||
options: --environment esp8266-tidy --grep USE_ESP8266
|
||||
pio_cache_key: tidyesp8266
|
||||
- id: clang-tidy
|
||||
name: Run script/clang-tidy for ESP32 1/4
|
||||
options: --environment esp32-tidy --split-num 4 --split-at 1
|
||||
pio_cache_key: tidyesp32
|
||||
- id: clang-tidy
|
||||
name: Run script/clang-tidy for ESP32 2/4
|
||||
options: --environment esp32-tidy --split-num 4 --split-at 2
|
||||
pio_cache_key: tidyesp32
|
||||
- id: clang-tidy
|
||||
name: Run script/clang-tidy for ESP32 3/4
|
||||
options: --environment esp32-tidy --split-num 4 --split-at 3
|
||||
pio_cache_key: tidyesp32
|
||||
- id: clang-tidy
|
||||
name: Run script/clang-tidy for ESP32 4/4
|
||||
options: --environment esp32-tidy --split-num 4 --split-at 4
|
||||
pio_cache_key: tidyesp32
|
||||
- id: clang-tidy
|
||||
name: Run script/clang-tidy for ESP32 esp-idf
|
||||
options: --environment esp32-idf-tidy --grep USE_ESP_IDF
|
||||
pio_cache_key: tidyesp32-idf
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v2
|
||||
id: python
|
||||
with:
|
||||
python-version: '3.7'
|
||||
|
||||
- name: Cache pip modules
|
||||
uses: actions/cache@v1
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.cache/pip
|
||||
key: esphome-pip-3.7-${{ hashFiles('setup.py') }}
|
||||
key: pip-${{ steps.python.outputs.python-version }}-${{ hashFiles('requirements*.txt') }}
|
||||
restore-keys: |
|
||||
esphome-pip-3.7-
|
||||
|
||||
# Use per test platformio cache because tests have different platform versions
|
||||
- name: Cache ~/.platformio
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ~/.platformio
|
||||
key: test-home-platformio-${{ matrix.file }}-${{ hashFiles('esphome/core/config.py') }}
|
||||
restore-keys: |
|
||||
test-home-platformio-${{ matrix.file }}-
|
||||
if: ${{ matrix.id == 'test' }}
|
||||
pip-${{ steps.python.outputs.python-version }}-
|
||||
|
||||
- name: Set up python environment
|
||||
run: script/setup
|
||||
run: |
|
||||
pip3 install -r requirements.txt -r requirements_optional.txt -r requirements_test.txt
|
||||
pip3 install -e .
|
||||
|
||||
# Use per check platformio cache because checks use different parts
|
||||
- name: Cache platformio
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.platformio
|
||||
key: platformio-${{ matrix.pio_cache_key }}-${{ hashFiles('platformio.ini') }}
|
||||
if: matrix.id == 'test' || matrix.id == 'clang-tidy'
|
||||
|
||||
- name: Install clang tools
|
||||
run: |
|
||||
sudo apt-get install \
|
||||
clang-format-11 \
|
||||
clang-tidy-11
|
||||
if: matrix.id == 'clang-tidy' || matrix.id == 'clang-format'
|
||||
|
||||
- name: Register problem matchers
|
||||
run: |
|
||||
@ -124,20 +112,45 @@ jobs:
|
||||
echo "::add-matcher::.github/workflows/matchers/python.json"
|
||||
echo "::add-matcher::.github/workflows/matchers/pytest.json"
|
||||
echo "::add-matcher::.github/workflows/matchers/gcc.json"
|
||||
echo "::add-matcher::.github/workflows/matchers/clang-tidy.json"
|
||||
|
||||
- name: Lint Custom
|
||||
run: |
|
||||
script/ci-custom.py
|
||||
script/build_codeowners.py --check
|
||||
if: ${{ matrix.id == 'ci-custom' }}
|
||||
if: matrix.id == 'ci-custom'
|
||||
|
||||
- name: Lint Python
|
||||
run: script/lint-python
|
||||
if: ${{ matrix.id == 'lint-python' }}
|
||||
if: matrix.id == 'lint-python'
|
||||
|
||||
- run: esphome compile ${{ matrix.file }}
|
||||
if: ${{ matrix.id == 'test' }}
|
||||
if: matrix.id == 'test'
|
||||
env:
|
||||
# Also cache libdeps, store them in a ~/.platformio subfolder
|
||||
PLATFORMIO_LIBDEPS_DIR: ~/.platformio/libdeps
|
||||
|
||||
- name: Run pytest
|
||||
run: |
|
||||
pytest -vv --tb=native tests
|
||||
if: ${{ matrix.id == 'pytest' }}
|
||||
if: matrix.id == 'pytest'
|
||||
|
||||
# Also run git-diff-index so that the step is marked as failed on formatting errors,
|
||||
# since clang-format doesn't do anything but change files if -i is passed.
|
||||
- name: Run clang-format
|
||||
run: |
|
||||
script/clang-format -i
|
||||
git diff-index --quiet HEAD --
|
||||
if: matrix.id == 'clang-format'
|
||||
|
||||
- name: Run clang-tidy
|
||||
run: |
|
||||
script/clang-tidy --all-headers --fix ${{ matrix.options }}
|
||||
if: matrix.id == 'clang-tidy'
|
||||
env:
|
||||
# Also cache libdeps, store them in a ~/.platformio subfolder
|
||||
PLATFORMIO_LIBDEPS_DIR: ~/.platformio/libdeps
|
||||
|
||||
- name: Suggested changes
|
||||
run: script/ci-suggest-changes
|
||||
if: always() && (matrix.id == 'clang-tidy' || matrix.id == 'clang-format')
|
||||
|
100
.github/workflows/docker-lint-build.yml
vendored
100
.github/workflows/docker-lint-build.yml
vendored
@ -1,100 +0,0 @@
|
||||
name: Build and publish lint docker image
|
||||
|
||||
# Only run when docker paths change
|
||||
on:
|
||||
push:
|
||||
branches: [dev]
|
||||
paths:
|
||||
- 'docker/Dockerfile.lint'
|
||||
- 'requirements.txt'
|
||||
- 'requirements_optional.txt'
|
||||
- 'requirements_test.txt'
|
||||
- 'platformio.ini'
|
||||
- '.github/workflows/docker-lint-build.yml'
|
||||
|
||||
jobs:
|
||||
deploy-docker:
|
||||
name: Build and publish docker containers
|
||||
if: github.repository == 'esphome/esphome'
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
arch: [amd64, armv7, aarch64]
|
||||
build_type: ["lint"]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: '3.9'
|
||||
- name: Set TAG
|
||||
run: |
|
||||
echo "TAG=1.1" >> $GITHUB_ENV
|
||||
|
||||
- name: Run build
|
||||
run: |
|
||||
docker/build.py \
|
||||
--tag "${TAG}" \
|
||||
--arch "${{ matrix.arch }}" \
|
||||
--build-type "${{ matrix.build_type }}" \
|
||||
build
|
||||
|
||||
- name: Log in to docker hub
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USER }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
- name: Log in to the GitHub container registry
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Run push
|
||||
run: |
|
||||
docker/build.py \
|
||||
--tag "${TAG}" \
|
||||
--arch "${{ matrix.arch }}" \
|
||||
--build-type "${{ matrix.build_type }}" \
|
||||
push
|
||||
|
||||
deploy-docker-manifest:
|
||||
if: github.repository == 'esphome/esphome'
|
||||
runs-on: ubuntu-latest
|
||||
needs: [deploy-docker]
|
||||
strategy:
|
||||
matrix:
|
||||
build_type: ["lint"]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: '3.9'
|
||||
- name: Set TAG
|
||||
run: |
|
||||
echo "TAG=1.1" >> $GITHUB_ENV
|
||||
- name: Enable experimental manifest support
|
||||
run: |
|
||||
mkdir -p ~/.docker
|
||||
echo "{\"experimental\": \"enabled\"}" > ~/.docker/config.json
|
||||
|
||||
- name: Log in to docker hub
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USER }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
- name: Log in to the GitHub container registry
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Run manifest
|
||||
run: |
|
||||
docker/build.py \
|
||||
--tag "${TAG}" \
|
||||
--build-type "${{ matrix.build_type }}" \
|
||||
manifest
|
21
.github/workflows/lock.yml
vendored
Normal file
21
.github/workflows/lock.yml
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
name: Lock
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '30 0 * * *'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
lock:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: dessant/lock-threads@v2
|
||||
with:
|
||||
github-token: ${{ github.token }}
|
||||
pr-lock-inactive-days: "1"
|
||||
pr-lock-reason: ""
|
||||
process-only: prs
|
20
.github/workflows/release.yml
vendored
20
.github/workflows/release.yml
vendored
@ -57,7 +57,7 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
arch: [amd64, armv7, aarch64]
|
||||
build_type: ["ha-addon", "docker"]
|
||||
build_type: ["ha-addon", "docker", "lint"]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up Python
|
||||
@ -65,13 +65,10 @@ jobs:
|
||||
with:
|
||||
python-version: '3.9'
|
||||
|
||||
- name: Run build
|
||||
run: |
|
||||
docker/build.py \
|
||||
--tag "${{ needs.init.outputs.tag }}" \
|
||||
--arch "${{ matrix.arch }}" \
|
||||
--build-type "${{ matrix.build_type }}" \
|
||||
build
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1
|
||||
|
||||
- name: Log in to docker hub
|
||||
uses: docker/login-action@v1
|
||||
@ -85,13 +82,14 @@ jobs:
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Run push
|
||||
- name: Build and push
|
||||
run: |
|
||||
docker/build.py \
|
||||
--tag "${{ needs.init.outputs.tag }}" \
|
||||
--arch "${{ matrix.arch }}" \
|
||||
--build-type "${{ matrix.build_type }}" \
|
||||
push
|
||||
build \
|
||||
--push
|
||||
|
||||
deploy-docker-manifest:
|
||||
if: github.repository == 'esphome/esphome'
|
||||
@ -99,7 +97,7 @@ jobs:
|
||||
needs: [init, deploy-docker]
|
||||
strategy:
|
||||
matrix:
|
||||
build_type: ["ha-addon", "docker"]
|
||||
build_type: ["ha-addon", "docker", "lint"]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up Python
|
||||
|
30
.github/workflows/stale.yml
vendored
Normal file
30
.github/workflows/stale.yml
vendored
Normal file
@ -0,0 +1,30 @@
|
||||
name: Stale
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '30 0 * * *'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/stale@v4
|
||||
with:
|
||||
repo-token: ${{ github.token }}
|
||||
days-before-pr-stale: 90
|
||||
days-before-pr-close: 7
|
||||
days-before-issue-stale: -1
|
||||
days-before-issue-close: -1
|
||||
remove-stale-when-updated: true
|
||||
stale-pr-label: "stale"
|
||||
exempt-pr-labels: "no-stale"
|
||||
stale-pr-message: >
|
||||
There hasn't been any activity on this pull request recently. This
|
||||
pull request has been automatically marked as stale because of that
|
||||
and will be closed if no further activity occurs within 7 days.
|
||||
Thank you for your contributions.
|
8
.gitignore
vendored
8
.gitignore
vendored
@ -102,10 +102,7 @@ CMakeLists.txt
|
||||
.idea/**/dynamic.xml
|
||||
|
||||
# CMake
|
||||
cmake-build-debug/
|
||||
cmake-build-livingroom8266/
|
||||
cmake-build-livingroom32/
|
||||
cmake-build-release/
|
||||
cmake-build-*/
|
||||
|
||||
CMakeCache.txt
|
||||
CMakeFiles
|
||||
@ -127,3 +124,6 @@ tests/.esphome/
|
||||
/.temp-clang-tidy.cpp
|
||||
/.temp/
|
||||
.pio/
|
||||
|
||||
sdkconfig.*
|
||||
!sdkconfig.defaults
|
||||
|
20
CODEOWNERS
20
CODEOWNERS
@ -15,6 +15,7 @@ esphome/components/ac_dimmer/* @glmnet
|
||||
esphome/components/adc/* @esphome/core
|
||||
esphome/components/addressable_light/* @justfalter
|
||||
esphome/components/airthings_ble/* @jeromelaban
|
||||
esphome/components/airthings_wave_mini/* @ncareau
|
||||
esphome/components/airthings_wave_plus/* @jeromelaban
|
||||
esphome/components/am43/* @buxtronix
|
||||
esphome/components/am43/cover/* @buxtronix
|
||||
@ -39,14 +40,19 @@ esphome/components/coolix/* @glmnet
|
||||
esphome/components/cover/* @esphome/core
|
||||
esphome/components/cs5460a/* @balrog-kun
|
||||
esphome/components/ct_clamp/* @jesserockz
|
||||
esphome/components/current_based/* @djwmarcx
|
||||
esphome/components/daly_bms/* @s1lvi0
|
||||
esphome/components/dashboard_import/* @esphome/core
|
||||
esphome/components/debug/* @OttoWinter
|
||||
esphome/components/dfplayer/* @glmnet
|
||||
esphome/components/dht/* @OttoWinter
|
||||
esphome/components/ds1307/* @badbadc0ffee
|
||||
esphome/components/dsmr/* @glmnet @zuidwijk
|
||||
esphome/components/esp32/* @esphome/core
|
||||
esphome/components/esp32_ble/* @jesserockz
|
||||
esphome/components/esp32_ble_server/* @jesserockz
|
||||
esphome/components/esp32_improv/* @jesserockz
|
||||
esphome/components/esp8266/* @esphome/core
|
||||
esphome/components/exposure_notifications/* @OttoWinter
|
||||
esphome/components/ezo/* @ssieb
|
||||
esphome/components/fastled_base/* @OttoWinter
|
||||
@ -54,9 +60,11 @@ esphome/components/fingerprint_grow/* @OnFreund @loongyh
|
||||
esphome/components/globals/* @esphome/core
|
||||
esphome/components/gpio/* @esphome/core
|
||||
esphome/components/gps/* @coogle
|
||||
esphome/components/graph/* @synco
|
||||
esphome/components/havells_solar/* @sourabhjaiswal
|
||||
esphome/components/hbridge/fan/* @WeekendWarrior
|
||||
esphome/components/hbridge/light/* @DotNetDann
|
||||
esphome/components/heatpumpir/* @rob-deutsch
|
||||
esphome/components/hitachi_ac424/* @sourabhjaiswal
|
||||
esphome/components/homeassistant/* @OttoWinter
|
||||
esphome/components/hrxl_maxsonar_wr/* @netmikey
|
||||
@ -70,6 +78,7 @@ esphome/components/json/* @OttoWinter
|
||||
esphome/components/ledc/* @OttoWinter
|
||||
esphome/components/light/* @esphome/core
|
||||
esphome/components/logger/* @esphome/core
|
||||
esphome/components/ltr390/* @sjtrny
|
||||
esphome/components/max7219digit/* @rspaargaren
|
||||
esphome/components/mcp23008/* @jesserockz
|
||||
esphome/components/mcp23017/* @jesserockz
|
||||
@ -80,8 +89,16 @@ esphome/components/mcp23x17_base/* @jesserockz
|
||||
esphome/components/mcp23xxx_base/* @jesserockz
|
||||
esphome/components/mcp2515/* @danielschramm @mvturnho
|
||||
esphome/components/mcp9808/* @k7hpn
|
||||
esphome/components/mdns/* @esphome/core
|
||||
esphome/components/midea/* @dudanov
|
||||
esphome/components/mitsubishi/* @RubyBailey
|
||||
esphome/components/modbus_controller/* @martgras
|
||||
esphome/components/modbus_controller/binary_sensor/* @martgras
|
||||
esphome/components/modbus_controller/number/* @martgras
|
||||
esphome/components/modbus_controller/output/* @martgras
|
||||
esphome/components/modbus_controller/sensor/* @martgras
|
||||
esphome/components/modbus_controller/switch/* @martgras
|
||||
esphome/components/modbus_controller/text_sensor/* @martgras
|
||||
esphome/components/network/* @esphome/core
|
||||
esphome/components/nextion/* @senexcrenshaw
|
||||
esphome/components/nextion/binary_sensor/* @senexcrenshaw
|
||||
@ -100,6 +117,7 @@ esphome/components/pn532/* @OttoWinter @jesserockz
|
||||
esphome/components/pn532_i2c/* @OttoWinter @jesserockz
|
||||
esphome/components/pn532_spi/* @OttoWinter @jesserockz
|
||||
esphome/components/power_supply/* @esphome/core
|
||||
esphome/components/preferences/* @esphome/core
|
||||
esphome/components/pulse_meter/* @stevebaxter
|
||||
esphome/components/pvvx_mithermometer/* @pasiz
|
||||
esphome/components/rc522/* @glmnet
|
||||
@ -109,6 +127,8 @@ esphome/components/restart/* @esphome/core
|
||||
esphome/components/rf_bridge/* @jesserockz
|
||||
esphome/components/rgbct/* @jesserockz
|
||||
esphome/components/rtttl/* @glmnet
|
||||
esphome/components/safe_mode/* @paulmonigatti
|
||||
esphome/components/scd4x/* @sjtrny
|
||||
esphome/components/script/* @esphome/core
|
||||
esphome/components/sdm_meter/* @jesserockz @polyfaces
|
||||
esphome/components/sdp3x/* @Azimath
|
||||
|
@ -1,10 +1,6 @@
|
||||
# Contributing to ESPHome
|
||||
|
||||
This python project is responsible for reading in YAML configuration files,
|
||||
converting them to C++ code. This code is then converted to a platformio project and compiled
|
||||
with [esphome-core](https://github.com/esphome/esphome-core), the C++ framework behind the project.
|
||||
|
||||
For a detailed guide, please see https://esphome.io/guides/contributing.html#contributing-to-esphomeyaml
|
||||
For a detailed guide, please see https://esphome.io/guides/contributing.html#contributing-to-esphome
|
||||
|
||||
Things to note when contributing:
|
||||
|
||||
|
@ -1,5 +1,60 @@
|
||||
ARG BUILD_FROM=esphome/esphome-base:latest
|
||||
FROM ${BUILD_FROM}
|
||||
# Build these with the build.py script
|
||||
# Example:
|
||||
# python3 docker/build.py --tag dev --arch amd64 --build-type docker build
|
||||
|
||||
# One of "docker", "hassio"
|
||||
ARG BASEIMGTYPE=docker
|
||||
|
||||
FROM ghcr.io/hassio-addons/debian-base/amd64:5.1.0 AS base-hassio-amd64
|
||||
FROM ghcr.io/hassio-addons/debian-base/aarch64:5.1.0 AS base-hassio-arm64
|
||||
FROM ghcr.io/hassio-addons/debian-base/armv7:5.1.0 AS base-hassio-armv7
|
||||
FROM debian:bullseye-20210902-slim AS base-docker-amd64
|
||||
FROM debian:bullseye-20210902-slim AS base-docker-arm64
|
||||
FROM debian:bullseye-20210902-slim AS base-docker-armv7
|
||||
|
||||
# Use TARGETARCH/TARGETVARIANT defined by docker
|
||||
# https://docs.docker.com/engine/reference/builder/#automatic-platform-args-in-the-global-scope
|
||||
FROM base-${BASEIMGTYPE}-${TARGETARCH}${TARGETVARIANT} AS base
|
||||
|
||||
RUN \
|
||||
apt-get update \
|
||||
# Use pinned versions so that we get updates with build caching
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
python3=3.9.2-3 \
|
||||
python3-pip=20.3.4-4 \
|
||||
python3-setuptools=52.0.0-4 \
|
||||
python3-pil=8.1.2+dfsg-0.3 \
|
||||
python3-cryptography=3.3.2-1 \
|
||||
iputils-ping=3:20210202-1 \
|
||||
git=1:2.30.2-1 \
|
||||
curl=7.74.0-1.3+b1 \
|
||||
&& rm -rf \
|
||||
/tmp/* \
|
||||
/var/{cache,log}/* \
|
||||
/var/lib/apt/lists/*
|
||||
|
||||
ENV \
|
||||
# Fix click python3 lang warning https://click.palletsprojects.com/en/7.x/python3/
|
||||
LANG=C.UTF-8 LC_ALL=C.UTF-8 \
|
||||
# Store globally installed pio libs in /piolibs
|
||||
PLATFORMIO_GLOBALLIB_DIR=/piolibs
|
||||
|
||||
RUN \
|
||||
# Ubuntu python3-pip is missing wheel
|
||||
pip3 install --no-cache-dir \
|
||||
wheel==0.36.2 \
|
||||
platformio==5.2.0 \
|
||||
# Change some platformio settings
|
||||
&& platformio settings set enable_telemetry No \
|
||||
&& platformio settings set check_libraries_interval 1000000 \
|
||||
&& platformio settings set check_platformio_interval 1000000 \
|
||||
&& platformio settings set check_platforms_interval 1000000 \
|
||||
&& mkdir -p /piolibs
|
||||
|
||||
|
||||
|
||||
# ======================= docker-type image =======================
|
||||
FROM base AS docker
|
||||
|
||||
# First install requirements to leverage caching when requirements don't change
|
||||
COPY requirements.txt requirements_optional.txt docker/platformio_install_deps.py platformio.ini /
|
||||
@ -7,9 +62,9 @@ RUN \
|
||||
pip3 install --no-cache-dir -r /requirements.txt -r /requirements_optional.txt \
|
||||
&& /platformio_install_deps.py /platformio.ini
|
||||
|
||||
# Then copy esphome and install
|
||||
COPY . .
|
||||
RUN pip3 install --no-cache-dir -e .
|
||||
# Copy esphome and install
|
||||
COPY . /esphome
|
||||
RUN pip3 install --no-cache-dir -e /esphome
|
||||
|
||||
# Settings for dashboard
|
||||
ENV USERNAME="" PASSWORD=""
|
||||
@ -17,14 +72,85 @@ ENV USERNAME="" PASSWORD=""
|
||||
# Expose the dashboard to Docker
|
||||
EXPOSE 6052
|
||||
|
||||
# Run healthcheck (heartbeat)
|
||||
HEALTHCHECK --interval=30s --timeout=30s \
|
||||
CMD curl --fail http://localhost:6052 || exit 1
|
||||
COPY docker/docker_entrypoint.sh /entrypoint.sh
|
||||
|
||||
# The directory the user should mount their configuration files to
|
||||
VOLUME /config
|
||||
WORKDIR /config
|
||||
# Set entrypoint to esphome so that the user doesn't have to type 'esphome'
|
||||
# Set entrypoint to esphome (via a script) so that the user doesn't have to type 'esphome'
|
||||
# in every docker command twice
|
||||
ENTRYPOINT ["esphome"]
|
||||
ENTRYPOINT ["/entrypoint.sh"]
|
||||
# When no arguments given, start the dashboard in the workdir
|
||||
CMD ["dashboard", "/config"]
|
||||
|
||||
|
||||
|
||||
|
||||
# ======================= hassio-type image =======================
|
||||
FROM base AS hassio
|
||||
|
||||
RUN \
|
||||
apt-get update \
|
||||
# Use pinned versions so that we get updates with build caching
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
nginx=1.18.0-6.1 \
|
||||
&& rm -rf \
|
||||
/tmp/* \
|
||||
/var/{cache,log}/* \
|
||||
/var/lib/apt/lists/*
|
||||
|
||||
ARG BUILD_VERSION=dev
|
||||
|
||||
# Copy root filesystem
|
||||
COPY docker/hassio-rootfs/ /
|
||||
|
||||
# First install requirements to leverage caching when requirements don't change
|
||||
COPY requirements.txt requirements_optional.txt docker/platformio_install_deps.py platformio.ini /
|
||||
RUN \
|
||||
pip3 install --no-cache-dir -r /requirements.txt -r /requirements_optional.txt \
|
||||
&& /platformio_install_deps.py /platformio.ini
|
||||
|
||||
# Copy esphome and install
|
||||
COPY . /esphome
|
||||
RUN pip3 install --no-cache-dir -e /esphome
|
||||
|
||||
# Labels
|
||||
LABEL \
|
||||
io.hass.name="ESPHome" \
|
||||
io.hass.description="Manage and program ESP8266/ESP32 microcontrollers through YAML configuration files" \
|
||||
io.hass.type="addon" \
|
||||
io.hass.version="${BUILD_VERSION}"
|
||||
# io.hass.arch is inherited from addon-debian-base
|
||||
|
||||
|
||||
|
||||
|
||||
# ======================= lint-type image =======================
|
||||
FROM base AS lint
|
||||
|
||||
ENV \
|
||||
PLATFORMIO_CORE_DIR=/esphome/.temp/platformio
|
||||
|
||||
RUN \
|
||||
apt-get update \
|
||||
# Use pinned versions so that we get updates with build caching
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
clang-format-11=1:11.0.1-2 \
|
||||
clang-tidy-11=1:11.0.1-2 \
|
||||
patch=2.7.6-7 \
|
||||
software-properties-common=0.96.20.2-2.1 \
|
||||
nano=5.4-2 \
|
||||
build-essential=12.9 \
|
||||
python3-dev=3.9.2-3 \
|
||||
&& rm -rf \
|
||||
/tmp/* \
|
||||
/var/{cache,log}/* \
|
||||
/var/lib/apt/lists/*
|
||||
|
||||
COPY requirements.txt requirements_optional.txt docker/platformio_install_deps.py platformio.ini /
|
||||
RUN \
|
||||
pip3 install --no-cache-dir -r /requirements.txt -r /requirements_optional.txt \
|
||||
&& /platformio_install_deps.py /platformio.ini
|
||||
|
||||
VOLUME ["/esphome"]
|
||||
WORKDIR /esphome
|
||||
|
@ -1 +0,0 @@
|
||||
FROM esphome/esphome-lint:1.1
|
@ -1,25 +0,0 @@
|
||||
ARG BUILD_FROM=esphome/esphome-hassio-base:latest
|
||||
FROM ${BUILD_FROM}
|
||||
|
||||
# First install requirements to leverage caching when requirements don't change
|
||||
COPY requirements.txt requirements_optional.txt docker/platformio_install_deps.py platformio.ini /
|
||||
RUN \
|
||||
pip3 install --no-cache-dir -r /requirements.txt -r /requirements_optional.txt \
|
||||
&& /platformio_install_deps.py /platformio.ini
|
||||
|
||||
# Copy root filesystem
|
||||
COPY docker/rootfs/ /
|
||||
|
||||
# Then copy esphome and install
|
||||
COPY . /opt/esphome/
|
||||
RUN pip3 install --no-cache-dir -e /opt/esphome
|
||||
|
||||
# Build arguments
|
||||
ARG BUILD_VERSION=dev
|
||||
|
||||
# Labels
|
||||
LABEL \
|
||||
io.hass.name="ESPHome" \
|
||||
io.hass.description="Manage and program ESP8266/ESP32 microcontrollers through YAML configuration files" \
|
||||
io.hass.type="addon" \
|
||||
io.hass.version=${BUILD_VERSION}
|
@ -1,10 +0,0 @@
|
||||
ARG BUILD_FROM=esphome/esphome-lint-base:latest
|
||||
FROM ${BUILD_FROM}
|
||||
|
||||
COPY requirements.txt requirements_optional.txt requirements_test.txt docker/platformio_install_deps.py platformio.ini /
|
||||
RUN \
|
||||
pip3 install --no-cache-dir -r /requirements.txt -r /requirements_optional.txt -r /requirements_test.txt \
|
||||
&& /platformio_install_deps.py /platformio.ini
|
||||
|
||||
VOLUME ["/esphome"]
|
||||
WORKDIR /esphome
|
100
docker/build.py
100
docker/build.py
@ -2,7 +2,7 @@
|
||||
from dataclasses import dataclass
|
||||
import subprocess
|
||||
import argparse
|
||||
import platform
|
||||
from platform import machine
|
||||
import shlex
|
||||
import re
|
||||
import sys
|
||||
@ -24,9 +24,6 @@ TYPE_LINT = 'lint'
|
||||
TYPES = [TYPE_DOCKER, TYPE_HA_ADDON, TYPE_LINT]
|
||||
|
||||
|
||||
BASE_VERSION = "4.2.0"
|
||||
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--tag", type=str, required=True, help="The main docker tag to push to. If a version number also adds latest and/or beta tag")
|
||||
parser.add_argument("--arch", choices=ARCHS, required=False, help="The architecture to build for")
|
||||
@ -34,27 +31,17 @@ parser.add_argument("--build-type", choices=TYPES, required=True, help="The type
|
||||
parser.add_argument("--dry-run", action="store_true", help="Don't run any commands, just print them")
|
||||
subparsers = parser.add_subparsers(help="Action to perform", dest="command", required=True)
|
||||
build_parser = subparsers.add_parser("build", help="Build the image")
|
||||
push_parser = subparsers.add_parser("push", help="Tag the already built image and push it to docker hub")
|
||||
build_parser.add_argument("--push", help="Also push the images", action="store_true")
|
||||
manifest_parser = subparsers.add_parser("manifest", help="Create a manifest from already pushed images")
|
||||
|
||||
|
||||
|
||||
# only lists some possibilities, doesn't have to be perfect
|
||||
# https://stackoverflow.com/a/45125525
|
||||
UNAME_TO_ARCH = {
|
||||
"x86_64": ARCH_AMD64,
|
||||
"aarch64": ARCH_AARCH64,
|
||||
"aarch64_be": ARCH_AARCH64,
|
||||
"arm": ARCH_ARMV7,
|
||||
}
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class DockerParams:
|
||||
build_from: str
|
||||
build_to: str
|
||||
manifest_to: str
|
||||
dockerfile: str
|
||||
baseimgtype: str
|
||||
platform: str
|
||||
target: str
|
||||
|
||||
@classmethod
|
||||
def for_type_arch(cls, build_type, arch):
|
||||
@ -63,18 +50,28 @@ class DockerParams:
|
||||
TYPE_HA_ADDON: "esphome/esphome-hassio",
|
||||
TYPE_LINT: "esphome/esphome-lint"
|
||||
}[build_type]
|
||||
build_from = f"ghcr.io/{prefix}-base-{arch}:{BASE_VERSION}"
|
||||
build_to = f"{prefix}-{arch}"
|
||||
dockerfile = {
|
||||
TYPE_DOCKER: "docker/Dockerfile",
|
||||
TYPE_HA_ADDON: "docker/Dockerfile.hassio",
|
||||
TYPE_LINT: "docker/Dockerfile.lint",
|
||||
baseimgtype = {
|
||||
TYPE_DOCKER: "docker",
|
||||
TYPE_HA_ADDON: "hassio",
|
||||
TYPE_LINT: "docker",
|
||||
}[build_type]
|
||||
platform = {
|
||||
ARCH_AMD64: "linux/amd64",
|
||||
ARCH_ARMV7: "linux/arm/v7",
|
||||
ARCH_AARCH64: "linux/arm64",
|
||||
}[arch]
|
||||
target = {
|
||||
TYPE_DOCKER: "docker",
|
||||
TYPE_HA_ADDON: "hassio",
|
||||
TYPE_LINT: "lint",
|
||||
}[build_type]
|
||||
return cls(
|
||||
build_from=build_from,
|
||||
build_to=build_to,
|
||||
manifest_to=prefix,
|
||||
dockerfile=dockerfile
|
||||
baseimgtype=baseimgtype,
|
||||
platform=platform,
|
||||
target=target,
|
||||
)
|
||||
|
||||
|
||||
@ -112,46 +109,31 @@ def main():
|
||||
# 1. pull cache image
|
||||
params = DockerParams.for_type_arch(args.build_type, args.arch)
|
||||
cache_tag = {
|
||||
CHANNEL_DEV: "dev",
|
||||
CHANNEL_BETA: "beta",
|
||||
CHANNEL_RELEASE: "latest",
|
||||
CHANNEL_DEV: "cache-dev",
|
||||
CHANNEL_BETA: "cache-beta",
|
||||
CHANNEL_RELEASE: "cache-latest",
|
||||
}[channel]
|
||||
cache_img = f"ghcr.io/{params.build_to}:{cache_tag}"
|
||||
run_command("docker", "pull", cache_img, ignore_error=True)
|
||||
|
||||
# 2. register QEMU binfmt (if not host arch)
|
||||
is_native = UNAME_TO_ARCH.get(platform.machine()) == args.arch
|
||||
if not is_native:
|
||||
run_command(
|
||||
"docker", "run", "--rm", "--privileged", "multiarch/qemu-user-static:5.2.0-2",
|
||||
"--reset", "-p", "yes"
|
||||
)
|
||||
|
||||
# 3. build
|
||||
run_command(
|
||||
"docker", "build",
|
||||
"--build-arg", f"BUILD_FROM={params.build_from}",
|
||||
"--build-arg", f"BUILD_VERSION={args.tag}",
|
||||
"--tag", f"{params.build_to}:{args.tag}",
|
||||
"--cache-from", cache_img,
|
||||
"--file", params.dockerfile,
|
||||
"."
|
||||
)
|
||||
elif args.command == "push":
|
||||
params = DockerParams.for_type_arch(args.build_type, args.arch)
|
||||
imgs = [f"{params.build_to}:{tag}" for tag in tags_to_push]
|
||||
imgs += [f"ghcr.io/{params.build_to}:{tag}" for tag in tags_to_push]
|
||||
src = imgs[0]
|
||||
# 1. tag images
|
||||
for img in imgs[1:]:
|
||||
run_command(
|
||||
"docker", "tag", src, img
|
||||
)
|
||||
# 2. push images
|
||||
|
||||
# 3. build
|
||||
cmd = [
|
||||
"docker", "buildx", "build",
|
||||
"--build-arg", f"BASEIMGTYPE={params.baseimgtype}",
|
||||
"--build-arg", f"BUILD_VERSION={args.tag}",
|
||||
"--cache-from", f"type=registry,ref={cache_img}",
|
||||
"--file", "docker/Dockerfile",
|
||||
"--platform", params.platform,
|
||||
"--target", params.target,
|
||||
]
|
||||
for img in imgs:
|
||||
run_command(
|
||||
"docker", "push", img
|
||||
)
|
||||
cmd += ["--tag", img]
|
||||
if args.push:
|
||||
cmd += ["--push", "--cache-to", f"type=registry,ref={cache_img},mode=max"]
|
||||
|
||||
run_command(*cmd, ".")
|
||||
elif args.command == "manifest":
|
||||
manifest = DockerParams.for_type_arch(args.build_type, ARCH_AMD64).manifest_to
|
||||
|
||||
|
24
docker/docker_entrypoint.sh
Executable file
24
docker/docker_entrypoint.sh
Executable file
@ -0,0 +1,24 @@
|
||||
#!/bin/bash
|
||||
|
||||
# If /cache is mounted, use that as PIO's coredir
|
||||
# otherwise use path in /config (so that PIO packages aren't downloaded on each compile)
|
||||
|
||||
if [[ -d /cache ]]; then
|
||||
pio_cache_base=/cache/platformio
|
||||
else
|
||||
pio_cache_base=/config/.esphome/platformio
|
||||
fi
|
||||
|
||||
if [[ ! -d "${pio_cache_base}" ]]; then
|
||||
echo "Creating cache directory ${pio_cache_base}"
|
||||
echo "You can change this behavior by mounting a directory to the container's /cache directory."
|
||||
mkdir -p "${pio_cache_base}"
|
||||
fi
|
||||
|
||||
# we can't set core_dir, because the settings file is stored in `core_dir/appstate.json`
|
||||
# setting `core_dir` would therefore prevent pio from accessing
|
||||
export PLATFORMIO_PLATFORMS_DIR="${pio_cache_base}/platforms"
|
||||
export PLATFORMIO_PACKAGES_DIR="${pio_cache_base}/packages"
|
||||
export PLATFORMIO_CACHE_DIR="${pio_cache_base}/cache"
|
||||
|
||||
exec esphome "$@"
|
9
docker/hassio-rootfs/etc/cont-init.d/30-dirs.sh
Normal file
9
docker/hassio-rootfs/etc/cont-init.d/30-dirs.sh
Normal file
@ -0,0 +1,9 @@
|
||||
#!/usr/bin/with-contenv bashio
|
||||
# ==============================================================================
|
||||
# Community Hass.io Add-ons: ESPHome
|
||||
# This files creates all directories used by esphome
|
||||
# ==============================================================================
|
||||
|
||||
pio_cache_base=/data/cache/platformio
|
||||
|
||||
mkdir -p "${pio_cache_base}"
|
@ -22,5 +22,14 @@ if bashio::config.has_value 'relative_url'; then
|
||||
export ESPHOME_DASHBOARD_RELATIVE_URL=$(bashio::config 'relative_url')
|
||||
fi
|
||||
|
||||
pio_cache_base=/data/cache/platformio
|
||||
# we can't set core_dir, because the settings file is stored in `core_dir/appstate.json`
|
||||
# setting `core_dir` would therefore prevent pio from accessing
|
||||
export PLATFORMIO_PLATFORMS_DIR="${pio_cache_base}/platforms"
|
||||
export PLATFORMIO_PACKAGES_DIR="${pio_cache_base}/packages"
|
||||
export PLATFORMIO_CACHE_DIR="${pio_cache_base}/cache"
|
||||
|
||||
export PLATFORMIO_GLOBALLIB_DIR=/piolibs
|
||||
|
||||
bashio::log.info "Starting ESPHome dashboard..."
|
||||
exec esphome dashboard /config/esphome --socket /var/run/esphome.sock --hassio
|
@ -3,18 +3,11 @@
|
||||
# all platformio libraries in the global storage
|
||||
|
||||
import configparser
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
config = configparser.ConfigParser()
|
||||
config = configparser.ConfigParser(inline_comment_prefixes=(';', ))
|
||||
config.read(sys.argv[1])
|
||||
libs = []
|
||||
for line in config['common']['lib_deps'].splitlines():
|
||||
# Format: '1655@1.0.2 ; TinyGPSPlus (has name conflict)' (includes comment)
|
||||
m = re.search(r'([a-zA-Z0-9-_/]+@[0-9\.]+)', line)
|
||||
if m is None:
|
||||
continue
|
||||
libs.append(m.group(1))
|
||||
libs = [x for x in config['common']['lib_deps'].splitlines() if len(x) != 0]
|
||||
|
||||
subprocess.check_call(['platformio', 'lib', '-g', 'install', *libs])
|
||||
|
@ -72,7 +72,7 @@ def choose_upload_log_host(default, check_default, show_ota, show_mqtt, show_api
|
||||
if default == "OTA":
|
||||
return CORE.address
|
||||
if show_mqtt and "mqtt" in CORE.config:
|
||||
options.append(("MQTT ({})".format(CORE.config["mqtt"][CONF_BROKER]), "MQTT"))
|
||||
options.append((f"MQTT ({CORE.config['mqtt'][CONF_BROKER]})", "MQTT"))
|
||||
if default == "OTA":
|
||||
return "MQTT"
|
||||
if default is not None:
|
||||
@ -184,12 +184,30 @@ def compile_program(args, config):
|
||||
|
||||
|
||||
def upload_using_esptool(config, port):
|
||||
path = CORE.firmware_bin
|
||||
from esphome import platformio_api
|
||||
|
||||
first_baudrate = config[CONF_ESPHOME][CONF_PLATFORMIO_OPTIONS].get(
|
||||
"upload_speed", 460800
|
||||
)
|
||||
|
||||
def run_esptool(baud_rate):
|
||||
idedata = platformio_api.get_idedata(config)
|
||||
|
||||
firmware_offset = "0x10000" if CORE.is_esp32 else "0x0"
|
||||
flash_images = [
|
||||
platformio_api.FlashImage(
|
||||
path=idedata.firmware_bin_path,
|
||||
offset=firmware_offset,
|
||||
),
|
||||
*idedata.extra_flash_images,
|
||||
]
|
||||
|
||||
mcu = "esp8266"
|
||||
if CORE.is_esp32:
|
||||
from esphome.components.esp32 import get_esp32_variant
|
||||
|
||||
mcu = get_esp32_variant().lower()
|
||||
|
||||
cmd = [
|
||||
"esptool.py",
|
||||
"--before",
|
||||
@ -198,14 +216,15 @@ def upload_using_esptool(config, port):
|
||||
"hard_reset",
|
||||
"--baud",
|
||||
str(baud_rate),
|
||||
"--chip",
|
||||
"esp8266",
|
||||
"--port",
|
||||
port,
|
||||
"--chip",
|
||||
mcu,
|
||||
"write_flash",
|
||||
"0x0",
|
||||
path,
|
||||
"-z",
|
||||
]
|
||||
for img in flash_images:
|
||||
cmd += [img.offset, img.path]
|
||||
|
||||
if os.environ.get("ESPHOME_USE_SUBPROCESS") is None:
|
||||
import esptool
|
||||
@ -229,11 +248,7 @@ def upload_using_esptool(config, port):
|
||||
def upload_program(config, args, host):
|
||||
# if upload is to a serial port use platformio, otherwise assume ota
|
||||
if get_port_type(host) == "SERIAL":
|
||||
from esphome import platformio_api
|
||||
|
||||
if CORE.is_esp8266:
|
||||
return upload_using_esptool(config, host)
|
||||
return platformio_api.run_upload(config, CORE.verbose, host)
|
||||
return upload_using_esptool(config, host)
|
||||
|
||||
from esphome import espota2
|
||||
|
||||
@ -245,7 +260,7 @@ def upload_program(config, args, host):
|
||||
|
||||
ota_conf = config[CONF_OTA]
|
||||
remote_port = ota_conf[CONF_PORT]
|
||||
password = ota_conf[CONF_PASSWORD]
|
||||
password = ota_conf.get(CONF_PASSWORD, "")
|
||||
return espota2.run_ota(host, remote_port, password, CORE.firmware_bin)
|
||||
|
||||
|
||||
@ -415,30 +430,30 @@ def command_update_all(args):
|
||||
click.echo(f"{half_line}{middle_text}{half_line}")
|
||||
|
||||
for f in files:
|
||||
print("Updating {}".format(color(Fore.CYAN, f)))
|
||||
print(f"Updating {color(Fore.CYAN, f)}")
|
||||
print("-" * twidth)
|
||||
print()
|
||||
rc = run_external_process(
|
||||
"esphome", "--dashboard", "run", f, "--no-logs", "--device", "OTA"
|
||||
)
|
||||
if rc == 0:
|
||||
print_bar("[{}] {}".format(color(Fore.BOLD_GREEN, "SUCCESS"), f))
|
||||
print_bar(f"[{color(Fore.BOLD_GREEN, 'SUCCESS')}] {f}")
|
||||
success[f] = True
|
||||
else:
|
||||
print_bar("[{}] {}".format(color(Fore.BOLD_RED, "ERROR"), f))
|
||||
print_bar(f"[{color(Fore.BOLD_RED, 'ERROR')}] {f}")
|
||||
success[f] = False
|
||||
|
||||
print()
|
||||
print()
|
||||
print()
|
||||
|
||||
print_bar("[{}]".format(color(Fore.BOLD_WHITE, "SUMMARY")))
|
||||
print_bar(f"[{color(Fore.BOLD_WHITE, 'SUMMARY')}]")
|
||||
failed = 0
|
||||
for f in files:
|
||||
if success[f]:
|
||||
print(" - {}: {}".format(f, color(Fore.GREEN, "SUCCESS")))
|
||||
print(f" - {f}: {color(Fore.GREEN, 'SUCCESS')}")
|
||||
else:
|
||||
print(" - {}: {}".format(f, color(Fore.BOLD_RED, "FAILED")))
|
||||
print(f" - {f}: {color(Fore.BOLD_RED, 'FAILED')}")
|
||||
failed += 1
|
||||
return failed
|
||||
|
||||
|
@ -6,6 +6,7 @@ from esphome.const import (
|
||||
CONF_ELSE,
|
||||
CONF_ID,
|
||||
CONF_THEN,
|
||||
CONF_TIMEOUT,
|
||||
CONF_TRIGGER_ID,
|
||||
CONF_TYPE_ID,
|
||||
CONF_TIME,
|
||||
@ -244,6 +245,9 @@ def validate_wait_until(value):
|
||||
schema = cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_CONDITION): validate_potentially_and_condition,
|
||||
cv.Optional(CONF_TIMEOUT): cv.templatable(
|
||||
cv.positive_time_period_milliseconds
|
||||
),
|
||||
}
|
||||
)
|
||||
if isinstance(value, dict) and CONF_CONDITION in value:
|
||||
@ -255,6 +259,9 @@ def validate_wait_until(value):
|
||||
async def wait_until_action_to_code(config, action_id, template_arg, args):
|
||||
conditions = await build_condition(config[CONF_CONDITION], template_arg, args)
|
||||
var = cg.new_Pvariable(action_id, template_arg, conditions)
|
||||
if CONF_TIMEOUT in config:
|
||||
template_ = await cg.templatable(config[CONF_TIMEOUT], args, cg.uint32)
|
||||
cg.add(var.set_timeout_value(template_))
|
||||
await cg.register_component(var, {})
|
||||
return var
|
||||
|
||||
|
@ -30,6 +30,7 @@ from esphome.cpp_generator import ( # noqa
|
||||
add_library,
|
||||
add_build_flag,
|
||||
add_define,
|
||||
add_platformio_option,
|
||||
get_variable,
|
||||
get_variable_with_full_id,
|
||||
process_lambda,
|
||||
@ -66,7 +67,7 @@ from esphome.cpp_types import ( # noqa
|
||||
NAN,
|
||||
esphome_ns,
|
||||
App,
|
||||
Nameable,
|
||||
EntityBase,
|
||||
Component,
|
||||
ComponentPtr,
|
||||
PollingComponent,
|
||||
@ -78,4 +79,6 @@ from esphome.cpp_types import ( # noqa
|
||||
JsonObjectConstRef,
|
||||
Controller,
|
||||
GPIOPin,
|
||||
InternalGPIOPin,
|
||||
gpio_Flags,
|
||||
)
|
||||
|
@ -1,7 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/esphal.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/components/stepper/stepper.h"
|
||||
|
||||
namespace esphome {
|
||||
|
@ -1,10 +1,16 @@
|
||||
#ifdef USE_ARDUINO
|
||||
|
||||
#include "ac_dimmer.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include <cmath>
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP8266
|
||||
#ifdef USE_ESP8266
|
||||
#include <core_esp8266_waveform.h>
|
||||
#endif
|
||||
#ifdef USE_ESP32_FRAMEWORK_ARDUINO
|
||||
#include <esp32-hal-timer.h>
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
namespace ac_dimmer {
|
||||
@ -17,12 +23,15 @@ static AcDimmerDataStore *all_dimmers[32]; // NOLINT(cppcoreguidelines-avoid-no
|
||||
/// Time in microseconds the gate should be held high
|
||||
/// 10µs should be long enough for most triacs
|
||||
/// For reference: BT136 datasheet says 2µs nominal (page 7)
|
||||
static const uint32_t GATE_ENABLE_TIME = 10;
|
||||
/// However other factors like gate driver propagation time
|
||||
/// are also considered and a really low value is not important
|
||||
/// See also: https://github.com/esphome/issues/issues/1632
|
||||
static const uint32_t GATE_ENABLE_TIME = 50;
|
||||
|
||||
/// Function called from timer interrupt
|
||||
/// Input is current time in microseconds (micros())
|
||||
/// Returns when next "event" is expected in µs, or 0 if no such event known.
|
||||
uint32_t ICACHE_RAM_ATTR HOT AcDimmerDataStore::timer_intr(uint32_t now) {
|
||||
uint32_t IRAM_ATTR HOT AcDimmerDataStore::timer_intr(uint32_t now) {
|
||||
// If no ZC signal received yet.
|
||||
if (this->crossed_zero_at == 0)
|
||||
return 0;
|
||||
@ -34,13 +43,13 @@ uint32_t ICACHE_RAM_ATTR HOT AcDimmerDataStore::timer_intr(uint32_t now) {
|
||||
|
||||
if (this->enable_time_us != 0 && time_since_zc >= this->enable_time_us) {
|
||||
this->enable_time_us = 0;
|
||||
this->gate_pin->digital_write(true);
|
||||
this->gate_pin.digital_write(true);
|
||||
// Prevent too short pulses
|
||||
this->disable_time_us = max(this->disable_time_us, time_since_zc + GATE_ENABLE_TIME);
|
||||
this->disable_time_us = std::max(this->disable_time_us, time_since_zc + GATE_ENABLE_TIME);
|
||||
}
|
||||
if (this->disable_time_us != 0 && time_since_zc >= this->disable_time_us) {
|
||||
this->disable_time_us = 0;
|
||||
this->gate_pin->digital_write(false);
|
||||
this->gate_pin.digital_write(false);
|
||||
}
|
||||
|
||||
if (time_since_zc < this->enable_time_us)
|
||||
@ -60,7 +69,7 @@ uint32_t ICACHE_RAM_ATTR HOT AcDimmerDataStore::timer_intr(uint32_t now) {
|
||||
}
|
||||
|
||||
/// Run timer interrupt code and return in how many µs the next event is expected
|
||||
uint32_t ICACHE_RAM_ATTR HOT timer_interrupt() {
|
||||
uint32_t IRAM_ATTR HOT timer_interrupt() {
|
||||
// run at least with 1kHz
|
||||
uint32_t min_dt_us = 1000;
|
||||
uint32_t now = micros();
|
||||
@ -77,7 +86,7 @@ uint32_t ICACHE_RAM_ATTR HOT timer_interrupt() {
|
||||
}
|
||||
|
||||
/// GPIO interrupt routine, called when ZC pin triggers
|
||||
void ICACHE_RAM_ATTR HOT AcDimmerDataStore::gpio_intr() {
|
||||
void IRAM_ATTR HOT AcDimmerDataStore::gpio_intr() {
|
||||
uint32_t prev_crossed = this->crossed_zero_at;
|
||||
|
||||
// 50Hz mains frequency should give a half cycle of 10ms a 60Hz will give 8.33ms
|
||||
@ -94,7 +103,7 @@ void ICACHE_RAM_ATTR HOT AcDimmerDataStore::gpio_intr() {
|
||||
|
||||
if (this->value == 65535) {
|
||||
// fully on, enable output immediately
|
||||
this->gate_pin->digital_write(true);
|
||||
this->gate_pin.digital_write(true);
|
||||
} else if (this->init_cycle) {
|
||||
// send a full cycle
|
||||
this->init_cycle = false;
|
||||
@ -102,29 +111,29 @@ void ICACHE_RAM_ATTR HOT AcDimmerDataStore::gpio_intr() {
|
||||
this->disable_time_us = cycle_time_us;
|
||||
} else if (this->value == 0) {
|
||||
// fully off, disable output immediately
|
||||
this->gate_pin->digital_write(false);
|
||||
this->gate_pin.digital_write(false);
|
||||
} else {
|
||||
if (this->method == DIM_METHOD_TRAILING) {
|
||||
this->enable_time_us = 1; // cannot be 0
|
||||
this->disable_time_us = max((uint32_t) 10, this->value * this->cycle_time_us / 65535);
|
||||
this->disable_time_us = std::max((uint32_t) 10, this->value * this->cycle_time_us / 65535);
|
||||
} else {
|
||||
// calculate time until enable in µs: (1.0-value)*cycle_time, but with integer arithmetic
|
||||
// also take into account min_power
|
||||
auto min_us = this->cycle_time_us * this->min_power / 1000;
|
||||
this->enable_time_us = max((uint32_t) 1, ((65535 - this->value) * (this->cycle_time_us - min_us)) / 65535);
|
||||
this->enable_time_us = std::max((uint32_t) 1, ((65535 - this->value) * (this->cycle_time_us - min_us)) / 65535);
|
||||
if (this->method == DIM_METHOD_LEADING_PULSE) {
|
||||
// Minimum pulse time should be enough for the triac to trigger when it is close to the ZC zone
|
||||
// this is for brightness near 99%
|
||||
this->disable_time_us = max(this->enable_time_us + GATE_ENABLE_TIME, (uint32_t) cycle_time_us / 10);
|
||||
this->disable_time_us = std::max(this->enable_time_us + GATE_ENABLE_TIME, (uint32_t) cycle_time_us / 10);
|
||||
} else {
|
||||
this->gate_pin->digital_write(false);
|
||||
this->gate_pin.digital_write(false);
|
||||
this->disable_time_us = this->cycle_time_us;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ICACHE_RAM_ATTR HOT AcDimmerDataStore::s_gpio_intr(AcDimmerDataStore *store) {
|
||||
void IRAM_ATTR HOT AcDimmerDataStore::s_gpio_intr(AcDimmerDataStore *store) {
|
||||
// Attaching pin interrupts on the same pin will override the previous interrupt
|
||||
// However, the user expects that multiple dimmers sharing the same ZC pin will work.
|
||||
// We solve this in a bit of a hacky way: On each pin interrupt, we check all dimmers
|
||||
@ -138,11 +147,11 @@ void ICACHE_RAM_ATTR HOT AcDimmerDataStore::s_gpio_intr(AcDimmerDataStore *store
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#ifdef USE_ESP32
|
||||
// ESP32 implementation, uses basically the same code but needs to wrap
|
||||
// timer_interrupt() function to auto-reschedule
|
||||
static hw_timer_t *dimmer_timer = nullptr;
|
||||
void ICACHE_RAM_ATTR HOT AcDimmerDataStore::s_timer_intr() { timer_interrupt(); }
|
||||
static hw_timer_t *dimmer_timer = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
void IRAM_ATTR HOT AcDimmerDataStore::s_timer_intr() { timer_interrupt(); }
|
||||
#endif
|
||||
|
||||
void AcDimmer::setup() {
|
||||
@ -171,15 +180,16 @@ void AcDimmer::setup() {
|
||||
if (setup_zero_cross_pin) {
|
||||
this->zero_cross_pin_->setup();
|
||||
this->store_.zero_cross_pin = this->zero_cross_pin_->to_isr();
|
||||
this->zero_cross_pin_->attach_interrupt(&AcDimmerDataStore::s_gpio_intr, &this->store_, FALLING);
|
||||
this->zero_cross_pin_->attach_interrupt(&AcDimmerDataStore::s_gpio_intr, &this->store_,
|
||||
gpio::INTERRUPT_FALLING_EDGE);
|
||||
}
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP8266
|
||||
#ifdef USE_ESP8266
|
||||
// Uses ESP8266 waveform (soft PWM) class
|
||||
// PWM and AcDimmer can even run at the same time this way
|
||||
setTimer1Callback(&timer_interrupt);
|
||||
#endif
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#ifdef USE_ESP32
|
||||
// 80 Divider -> 1 count=1µs
|
||||
dimmer_timer = timerBegin(0, 80, true);
|
||||
timerAttachInterrupt(dimmer_timer, &AcDimmerDataStore::s_timer_intr, true);
|
||||
@ -215,3 +225,5 @@ void AcDimmer::dump_config() {
|
||||
|
||||
} // namespace ac_dimmer
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ARDUINO
|
||||
|
@ -1,7 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef USE_ARDUINO
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/esphal.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/components/output/float_output.h"
|
||||
|
||||
namespace esphome {
|
||||
@ -11,11 +13,11 @@ enum DimMethod { DIM_METHOD_LEADING_PULSE = 0, DIM_METHOD_LEADING, DIM_METHOD_TR
|
||||
|
||||
struct AcDimmerDataStore {
|
||||
/// Zero-cross pin
|
||||
ISRInternalGPIOPin *zero_cross_pin;
|
||||
ISRInternalGPIOPin zero_cross_pin;
|
||||
/// Zero-cross pin number - used to share ZC pin across multiple dimmers
|
||||
uint8_t zero_cross_pin_number;
|
||||
/// Output pin to write to
|
||||
ISRInternalGPIOPin *gate_pin;
|
||||
ISRInternalGPIOPin gate_pin;
|
||||
/// Value of the dimmer - 0 to 65535.
|
||||
uint16_t value;
|
||||
/// Minimum power for activation
|
||||
@ -37,7 +39,7 @@ struct AcDimmerDataStore {
|
||||
|
||||
void gpio_intr();
|
||||
static void s_gpio_intr(AcDimmerDataStore *store);
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#ifdef USE_ESP32
|
||||
static void s_timer_intr();
|
||||
#endif
|
||||
};
|
||||
@ -47,16 +49,16 @@ class AcDimmer : public output::FloatOutput, public Component {
|
||||
void setup() override;
|
||||
|
||||
void dump_config() override;
|
||||
void set_gate_pin(GPIOPin *gate_pin) { gate_pin_ = gate_pin; }
|
||||
void set_zero_cross_pin(GPIOPin *zero_cross_pin) { zero_cross_pin_ = zero_cross_pin; }
|
||||
void set_gate_pin(InternalGPIOPin *gate_pin) { gate_pin_ = gate_pin; }
|
||||
void set_zero_cross_pin(InternalGPIOPin *zero_cross_pin) { zero_cross_pin_ = zero_cross_pin; }
|
||||
void set_init_with_half_cycle(bool init_with_half_cycle) { init_with_half_cycle_ = init_with_half_cycle; }
|
||||
void set_method(DimMethod method) { method_ = method; }
|
||||
|
||||
protected:
|
||||
void write_state(float state) override;
|
||||
|
||||
GPIOPin *gate_pin_;
|
||||
GPIOPin *zero_cross_pin_;
|
||||
InternalGPIOPin *gate_pin_;
|
||||
InternalGPIOPin *zero_cross_pin_;
|
||||
AcDimmerDataStore store_;
|
||||
bool init_with_half_cycle_;
|
||||
DimMethod method_;
|
||||
@ -64,3 +66,5 @@ class AcDimmer : public output::FloatOutput, public Component {
|
||||
|
||||
} // namespace ac_dimmer
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ARDUINO
|
||||
|
@ -19,17 +19,20 @@ DIM_METHODS = {
|
||||
CONF_GATE_PIN = "gate_pin"
|
||||
CONF_ZERO_CROSS_PIN = "zero_cross_pin"
|
||||
CONF_INIT_WITH_HALF_CYCLE = "init_with_half_cycle"
|
||||
CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.declare_id(AcDimmer),
|
||||
cv.Required(CONF_GATE_PIN): pins.internal_gpio_output_pin_schema,
|
||||
cv.Required(CONF_ZERO_CROSS_PIN): pins.internal_gpio_input_pin_schema,
|
||||
cv.Optional(CONF_INIT_WITH_HALF_CYCLE, default=True): cv.boolean,
|
||||
cv.Optional(CONF_METHOD, default="leading pulse"): cv.enum(
|
||||
DIM_METHODS, upper=True, space="_"
|
||||
),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
output.FLOAT_OUTPUT_SCHEMA.extend(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.declare_id(AcDimmer),
|
||||
cv.Required(CONF_GATE_PIN): pins.internal_gpio_output_pin_schema,
|
||||
cv.Required(CONF_ZERO_CROSS_PIN): pins.internal_gpio_input_pin_schema,
|
||||
cv.Optional(CONF_INIT_WITH_HALF_CYCLE, default=True): cv.boolean,
|
||||
cv.Optional(CONF_METHOD, default="leading pulse"): cv.enum(
|
||||
DIM_METHODS, upper=True, space="_"
|
||||
),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA),
|
||||
cv.only_with_arduino,
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
|
@ -1,8 +1,13 @@
|
||||
#include "adc_sensor.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#ifdef USE_ESP8266
|
||||
#ifdef USE_ADC_SENSOR_VCC
|
||||
#include <Esp.h>
|
||||
ADC_MODE(ADC_VCC)
|
||||
#else
|
||||
#include <Arduino.h>
|
||||
#endif
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
@ -10,7 +15,7 @@ namespace adc {
|
||||
|
||||
static const char *const TAG = "adc";
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#ifdef USE_ESP32
|
||||
void ADCSensor::set_attenuation(adc_atten_t attenuation) { this->attenuation_ = attenuation; }
|
||||
|
||||
inline adc1_channel_t gpio_to_adc1(uint8_t pin) {
|
||||
@ -57,28 +62,28 @@ inline adc1_channel_t gpio_to_adc1(uint8_t pin) {
|
||||
void ADCSensor::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up ADC '%s'...", this->get_name().c_str());
|
||||
#ifndef USE_ADC_SENSOR_VCC
|
||||
GPIOPin(this->pin_, INPUT).setup();
|
||||
pin_->setup();
|
||||
#endif
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
adc1_config_channel_atten(gpio_to_adc1(pin_), attenuation_);
|
||||
#ifdef USE_ESP32
|
||||
adc1_config_channel_atten(gpio_to_adc1(pin_->get_pin()), attenuation_);
|
||||
adc1_config_width(ADC_WIDTH_BIT_12);
|
||||
#if !CONFIG_IDF_TARGET_ESP32C3 && !CONFIG_IDF_TARGET_ESP32H2
|
||||
adc_gpio_init(ADC_UNIT_1, (adc_channel_t) gpio_to_adc1(pin_));
|
||||
adc_gpio_init(ADC_UNIT_1, (adc_channel_t) gpio_to_adc1(pin_->get_pin()));
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
void ADCSensor::dump_config() {
|
||||
LOG_SENSOR("", "ADC Sensor", this);
|
||||
#ifdef ARDUINO_ARCH_ESP8266
|
||||
#ifdef USE_ESP8266
|
||||
#ifdef USE_ADC_SENSOR_VCC
|
||||
ESP_LOGCONFIG(TAG, " Pin: VCC");
|
||||
#else
|
||||
ESP_LOGCONFIG(TAG, " Pin: %u", this->pin_);
|
||||
LOG_PIN(" Pin: ", pin_);
|
||||
#endif
|
||||
#endif
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
ESP_LOGCONFIG(TAG, " Pin: %u", this->pin_);
|
||||
#ifdef USE_ESP32
|
||||
LOG_PIN(" Pin: ", pin_);
|
||||
switch (this->attenuation_) {
|
||||
case ADC_ATTEN_DB_0:
|
||||
ESP_LOGCONFIG(TAG, " Attenuation: 0db (max 1.1V)");
|
||||
@ -105,8 +110,8 @@ void ADCSensor::update() {
|
||||
this->publish_state(value_v);
|
||||
}
|
||||
float ADCSensor::sample() {
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
int raw = adc1_get_raw(gpio_to_adc1(pin_));
|
||||
#ifdef USE_ESP32
|
||||
int raw = adc1_get_raw(gpio_to_adc1(pin_->get_pin()));
|
||||
float value_v = raw / 4095.0f;
|
||||
#if CONFIG_IDF_TARGET_ESP32
|
||||
switch (this->attenuation_) {
|
||||
@ -146,15 +151,15 @@ float ADCSensor::sample() {
|
||||
return value_v;
|
||||
#endif
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP8266
|
||||
#ifdef USE_ESP8266
|
||||
#ifdef USE_ADC_SENSOR_VCC
|
||||
return ESP.getVcc() / 1024.0f;
|
||||
return ESP.getVcc() / 1024.0f; // NOLINT(readability-static-accessed-through-instance)
|
||||
#else
|
||||
return analogRead(this->pin_) / 1024.0f; // NOLINT
|
||||
return analogRead(this->pin_->get_pin()) / 1024.0f; // NOLINT
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
#ifdef ARDUINO_ARCH_ESP8266
|
||||
#ifdef USE_ESP8266
|
||||
std::string ADCSensor::unique_id() { return get_mac_address() + "-adc"; }
|
||||
#endif
|
||||
|
||||
|
@ -1,12 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/esphal.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/voltage_sampler/voltage_sampler.h"
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#ifdef USE_ESP32
|
||||
#include "driver/adc.h"
|
||||
#endif
|
||||
|
||||
@ -15,7 +15,7 @@ namespace adc {
|
||||
|
||||
class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage_sampler::VoltageSampler {
|
||||
public:
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#ifdef USE_ESP32
|
||||
/// Set the attenuation for this pin. Only available on the ESP32.
|
||||
void set_attenuation(adc_atten_t attenuation);
|
||||
#endif
|
||||
@ -27,17 +27,17 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage
|
||||
void dump_config() override;
|
||||
/// `HARDWARE_LATE` setup priority.
|
||||
float get_setup_priority() const override;
|
||||
void set_pin(uint8_t pin) { this->pin_ = pin; }
|
||||
void set_pin(InternalGPIOPin *pin) { this->pin_ = pin; }
|
||||
float sample() override;
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP8266
|
||||
#ifdef USE_ESP8266
|
||||
std::string unique_id() override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
uint8_t pin_;
|
||||
InternalGPIOPin *pin_;
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#ifdef USE_ESP32
|
||||
adc_atten_t attenuation_{ADC_ATTEN_DB_0};
|
||||
#endif
|
||||
};
|
||||
|
@ -5,11 +5,13 @@ from esphome.components import sensor, voltage_sampler
|
||||
from esphome.const import (
|
||||
CONF_ATTENUATION,
|
||||
CONF_ID,
|
||||
CONF_INPUT,
|
||||
CONF_PIN,
|
||||
DEVICE_CLASS_VOLTAGE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_VOLT,
|
||||
)
|
||||
from esphome.core import CORE
|
||||
|
||||
|
||||
AUTO_LOAD = ["voltage_sampler"]
|
||||
@ -23,10 +25,34 @@ ATTENUATION_MODES = {
|
||||
|
||||
|
||||
def validate_adc_pin(value):
|
||||
vcc = str(value).upper()
|
||||
if vcc == "VCC":
|
||||
return cv.only_on_esp8266(vcc)
|
||||
return pins.analog_pin(value)
|
||||
if str(value).upper() == "VCC":
|
||||
return cv.only_on_esp8266("VCC")
|
||||
|
||||
if CORE.is_esp32:
|
||||
from esphome.components.esp32 import is_esp32c3
|
||||
|
||||
value = pins.internal_gpio_input_pin_number(value)
|
||||
if is_esp32c3():
|
||||
if not (0 <= value <= 4): # ADC1
|
||||
raise cv.Invalid("ESP32-C3: Only pins 0 though 4 support ADC.")
|
||||
if not (32 <= value <= 39): # ADC1
|
||||
raise cv.Invalid("ESP32: Only pins 32 though 39 support ADC.")
|
||||
elif CORE.is_esp8266:
|
||||
from esphome.components.esp8266.gpio import CONF_ANALOG
|
||||
|
||||
value = pins.internal_gpio_pin_number({CONF_ANALOG: True, CONF_INPUT: True})(
|
||||
value
|
||||
)
|
||||
|
||||
if value != 17: # A0
|
||||
raise cv.Invalid("ESP8266: Only pin A0 (GPIO17) supports ADC.")
|
||||
return pins.gpio_pin_schema(
|
||||
{CONF_ANALOG: True, CONF_INPUT: True}, internal=True
|
||||
)(value)
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
return pins.internal_gpio_input_pin_schema(value)
|
||||
|
||||
|
||||
adc_ns = cg.esphome_ns.namespace("adc")
|
||||
@ -62,7 +88,8 @@ async def to_code(config):
|
||||
if config[CONF_PIN] == "VCC":
|
||||
cg.add_define("USE_ADC_SENSOR_VCC")
|
||||
else:
|
||||
cg.add(var.set_pin(config[CONF_PIN]))
|
||||
pin = await cg.gpio_pin_expression(config[CONF_PIN])
|
||||
cg.add(var.set_pin(pin))
|
||||
|
||||
if CONF_ATTENUATION in config:
|
||||
cg.add(var.set_attenuation(config[CONF_ATTENUATION]))
|
||||
|
@ -8,9 +8,7 @@ static const char *const TAG = "ade7953";
|
||||
|
||||
void ADE7953::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "ADE7953:");
|
||||
if (this->has_irq_) {
|
||||
ESP_LOGCONFIG(TAG, " IRQ Pin: GPIO%u", this->irq_pin_number_);
|
||||
}
|
||||
LOG_PIN(" IRQ Pin: ", irq_pin_);
|
||||
LOG_I2C_DEVICE(this);
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
LOG_SENSOR(" ", "Voltage Sensor", this->voltage_sensor_);
|
||||
@ -20,27 +18,28 @@ void ADE7953::dump_config() {
|
||||
LOG_SENSOR(" ", "Active Power B Sensor", this->active_power_b_sensor_);
|
||||
}
|
||||
|
||||
#define ADE_PUBLISH_(name, factor) \
|
||||
if ((name) && this->name##_sensor_) { \
|
||||
float value = *(name) / (factor); \
|
||||
#define ADE_PUBLISH_(name, val, factor) \
|
||||
if (err == i2c::ERROR_OK && this->name##_sensor_) { \
|
||||
float value = (val) / (factor); \
|
||||
this->name##_sensor_->publish_state(value); \
|
||||
}
|
||||
#define ADE_PUBLISH(name, factor) ADE_PUBLISH_(name, factor)
|
||||
#define ADE_PUBLISH(name, val, factor) ADE_PUBLISH_(name, val, factor)
|
||||
|
||||
void ADE7953::update() {
|
||||
if (!this->is_setup_)
|
||||
return;
|
||||
|
||||
auto active_power_a = this->ade_read_<int32_t>(0x0312);
|
||||
ADE_PUBLISH(active_power_a, 154.0f);
|
||||
auto active_power_b = this->ade_read_<int32_t>(0x0313);
|
||||
ADE_PUBLISH(active_power_b, 154.0f);
|
||||
auto current_a = this->ade_read_<uint32_t>(0x031A);
|
||||
ADE_PUBLISH(current_a, 100000.0f);
|
||||
auto current_b = this->ade_read_<uint32_t>(0x031B);
|
||||
ADE_PUBLISH(current_b, 100000.0f);
|
||||
auto voltage = this->ade_read_<uint32_t>(0x031C);
|
||||
ADE_PUBLISH(voltage, 26000.0f);
|
||||
uint32_t val;
|
||||
i2c::ErrorCode err = ade_read_32_(0x0312, &val);
|
||||
ADE_PUBLISH(active_power_a, (int32_t) val, 154.0f);
|
||||
err = ade_read_32_(0x0313, &val);
|
||||
ADE_PUBLISH(active_power_b, (int32_t) val, 154.0f);
|
||||
err = ade_read_32_(0x031A, &val);
|
||||
ADE_PUBLISH(current_a, (uint32_t) val, 100000.0f);
|
||||
err = ade_read_32_(0x031B, &val);
|
||||
ADE_PUBLISH(current_b, (uint32_t) val, 100000.0f);
|
||||
err = ade_read_32_(0x031C, &val);
|
||||
ADE_PUBLISH(voltage, (uint32_t) val, 26000.0f);
|
||||
|
||||
// auto apparent_power_a = this->ade_read_<int32_t>(0x0310);
|
||||
// auto apparent_power_b = this->ade_read_<int32_t>(0x0311);
|
||||
|
@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
|
||||
@ -9,10 +10,7 @@ namespace ade7953 {
|
||||
|
||||
class ADE7953 : public i2c::I2CDevice, public PollingComponent {
|
||||
public:
|
||||
void set_irq_pin(uint8_t irq_pin) {
|
||||
has_irq_ = true;
|
||||
irq_pin_number_ = irq_pin;
|
||||
}
|
||||
void set_irq_pin(InternalGPIOPin *irq_pin) { irq_pin_ = irq_pin; }
|
||||
void set_voltage_sensor(sensor::Sensor *voltage_sensor) { voltage_sensor_ = voltage_sensor; }
|
||||
void set_current_a_sensor(sensor::Sensor *current_a_sensor) { current_a_sensor_ = current_a_sensor; }
|
||||
void set_current_b_sensor(sensor::Sensor *current_b_sensor) { current_b_sensor_ = current_b_sensor; }
|
||||
@ -24,15 +22,13 @@ class ADE7953 : public i2c::I2CDevice, public PollingComponent {
|
||||
}
|
||||
|
||||
void setup() override {
|
||||
if (this->has_irq_) {
|
||||
auto pin = GPIOPin(this->irq_pin_number_, INPUT);
|
||||
this->irq_pin_ = &pin;
|
||||
if (this->irq_pin_ != nullptr) {
|
||||
this->irq_pin_->setup();
|
||||
}
|
||||
this->set_timeout(100, [this]() {
|
||||
this->ade_write_<uint8_t>(0x0010, 0x04);
|
||||
this->ade_write_<uint8_t>(0x00FE, 0xAD);
|
||||
this->ade_write_<uint16_t>(0x0120, 0x0030);
|
||||
this->ade_write_8_(0x0010, 0x04);
|
||||
this->ade_write_8_(0x00FE, 0xAD);
|
||||
this->ade_write_16_(0x0120, 0x0030);
|
||||
this->is_setup_ = true;
|
||||
});
|
||||
}
|
||||
@ -42,31 +38,51 @@ class ADE7953 : public i2c::I2CDevice, public PollingComponent {
|
||||
void update() override;
|
||||
|
||||
protected:
|
||||
template<typename T> bool ade_write_(uint16_t reg, T value) {
|
||||
i2c::ErrorCode ade_write_8_(uint16_t reg, uint8_t value) {
|
||||
std::vector<uint8_t> data;
|
||||
data.push_back(reg >> 8);
|
||||
data.push_back(reg >> 0);
|
||||
for (int i = sizeof(T) - 1; i >= 0; i--)
|
||||
data.push_back(value >> (i * 8));
|
||||
return this->write_bytes_raw(data);
|
||||
data.push_back(value);
|
||||
return write(data.data(), data.size());
|
||||
}
|
||||
template<typename T> optional<T> ade_read_(uint16_t reg) {
|
||||
uint8_t hi = reg >> 8;
|
||||
uint8_t lo = reg >> 0;
|
||||
if (!this->write_bytes_raw({hi, lo}))
|
||||
return {};
|
||||
auto ret = this->read_bytes_raw<sizeof(T)>();
|
||||
if (!ret.has_value())
|
||||
return {};
|
||||
T result = 0;
|
||||
for (int i = 0, j = sizeof(T) - 1; i < sizeof(T); i++, j--)
|
||||
result |= T((*ret)[i]) << (j * 8);
|
||||
return result;
|
||||
i2c::ErrorCode ade_write_16_(uint16_t reg, uint16_t value) {
|
||||
std::vector<uint8_t> data;
|
||||
data.push_back(reg >> 8);
|
||||
data.push_back(reg >> 0);
|
||||
data.push_back(value >> 8);
|
||||
data.push_back(value >> 0);
|
||||
return write(data.data(), data.size());
|
||||
}
|
||||
i2c::ErrorCode ade_write_32_(uint16_t reg, uint32_t value) {
|
||||
std::vector<uint8_t> data;
|
||||
data.push_back(reg >> 8);
|
||||
data.push_back(reg >> 0);
|
||||
data.push_back(value >> 24);
|
||||
data.push_back(value >> 16);
|
||||
data.push_back(value >> 8);
|
||||
data.push_back(value >> 0);
|
||||
return write(data.data(), data.size());
|
||||
}
|
||||
i2c::ErrorCode ade_read_32_(uint16_t reg, uint32_t *value) {
|
||||
uint8_t reg_data[2];
|
||||
reg_data[0] = reg >> 8;
|
||||
reg_data[1] = reg >> 0;
|
||||
i2c::ErrorCode err = write(reg_data, 2);
|
||||
if (err != i2c::ERROR_OK)
|
||||
return err;
|
||||
uint8_t recv[4];
|
||||
err = read(recv, 4);
|
||||
if (err != i2c::ERROR_OK)
|
||||
return err;
|
||||
*value = 0;
|
||||
*value |= ((uint32_t) recv[0]) << 24;
|
||||
*value |= ((uint32_t) recv[1]) << 24;
|
||||
*value |= ((uint32_t) recv[2]) << 24;
|
||||
*value |= ((uint32_t) recv[3]) << 24;
|
||||
return i2c::ERROR_OK;
|
||||
}
|
||||
|
||||
bool has_irq_ = false;
|
||||
uint8_t irq_pin_number_;
|
||||
GPIOPin *irq_pin_{nullptr};
|
||||
InternalGPIOPin *irq_pin_ = nullptr;
|
||||
bool is_setup_{false};
|
||||
sensor::Sensor *voltage_sensor_{nullptr};
|
||||
sensor::Sensor *current_a_sensor_{nullptr};
|
||||
|
@ -29,7 +29,7 @@ CONFIG_SCHEMA = (
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(ADE7953),
|
||||
cv.Optional(CONF_IRQ_PIN): pins.input_pin,
|
||||
cv.Optional(CONF_IRQ_PIN): pins.internal_gpio_input_pin_schema,
|
||||
cv.Optional(CONF_VOLTAGE): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_VOLT,
|
||||
accuracy_decimals=1,
|
||||
@ -73,7 +73,8 @@ async def to_code(config):
|
||||
await i2c.register_i2c_device(var, config)
|
||||
|
||||
if CONF_IRQ_PIN in config:
|
||||
cg.add(var.set_irq_pin(config[CONF_IRQ_PIN]))
|
||||
irq_pin = await cg.gpio_pin_expression(config[CONF_IRQ_PIN])
|
||||
cg.add(var.set_irq_pin(irq_pin))
|
||||
|
||||
for key in [
|
||||
CONF_VOLTAGE,
|
||||
|
@ -1,5 +1,6 @@
|
||||
#include "ads1115.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/hal.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ads1115 {
|
||||
@ -159,7 +160,7 @@ float ADS1115Component::request_measurement(ADS1115Sensor *sensor) {
|
||||
float ADS1115Sensor::sample() { return this->parent_->request_measurement(this); }
|
||||
void ADS1115Sensor::update() {
|
||||
float v = this->parent_->request_measurement(this);
|
||||
if (!isnan(v)) {
|
||||
if (!std::isnan(v)) {
|
||||
ESP_LOGD(TAG, "'%s': Got Voltage=%fV", this->get_name().c_str(), v);
|
||||
this->publish_state(v);
|
||||
}
|
||||
|
@ -14,6 +14,7 @@
|
||||
|
||||
#include "aht10.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/hal.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace aht10 {
|
||||
@ -33,8 +34,19 @@ void AHT10Component::setup() {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
uint8_t data;
|
||||
if (!this->read_byte(0, &data, AHT10_DEFAULT_DELAY)) {
|
||||
uint8_t data = 0;
|
||||
if (this->write(&data, 1) != i2c::ERROR_OK) {
|
||||
ESP_LOGD(TAG, "Communication with AHT10 failed!");
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
delay(AHT10_DEFAULT_DELAY);
|
||||
if (this->read(&data, 1) != i2c::ERROR_OK) {
|
||||
ESP_LOGD(TAG, "Communication with AHT10 failed!");
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
if (this->read(&data, 1) != i2c::ERROR_OK) {
|
||||
ESP_LOGD(TAG, "Communication with AHT10 failed!");
|
||||
this->mark_failed();
|
||||
return;
|
||||
@ -55,15 +67,26 @@ void AHT10Component::update() {
|
||||
return;
|
||||
}
|
||||
uint8_t data[6];
|
||||
uint8_t delay = AHT10_DEFAULT_DELAY;
|
||||
uint8_t delay_ms = AHT10_DEFAULT_DELAY;
|
||||
if (this->humidity_sensor_ != nullptr)
|
||||
delay = AHT10_HUMIDITY_DELAY;
|
||||
delay_ms = AHT10_HUMIDITY_DELAY;
|
||||
bool success = false;
|
||||
for (int i = 0; i < AHT10_ATTEMPTS; ++i) {
|
||||
ESP_LOGVV(TAG, "Attempt %u at %6ld", i, millis());
|
||||
ESP_LOGVV(TAG, "Attempt %d at %6u", i, millis());
|
||||
delay_microseconds_accurate(4);
|
||||
if (!this->read_bytes(0, data, 6, delay)) {
|
||||
|
||||
uint8_t reg = 0;
|
||||
if (this->write(®, 1) != i2c::ERROR_OK) {
|
||||
ESP_LOGD(TAG, "Communication with AHT10 failed, waiting...");
|
||||
} else if ((data[0] & 0x80) == 0x80) { // Bit[7] = 0b1, device is busy
|
||||
continue;
|
||||
}
|
||||
delay(delay_ms);
|
||||
if (this->read(data, 6) != i2c::ERROR_OK) {
|
||||
ESP_LOGD(TAG, "Communication with AHT10 failed, waiting...");
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((data[0] & 0x80) == 0x80) { // Bit[7] = 0b1, device is busy
|
||||
ESP_LOGD(TAG, "AHT10 is busy, waiting...");
|
||||
} else if (data[1] == 0x0 && data[2] == 0x0 && (data[3] >> 4) == 0x0) {
|
||||
// Unrealistic humidity (0x0)
|
||||
@ -80,11 +103,12 @@ void AHT10Component::update() {
|
||||
}
|
||||
} else {
|
||||
// data is valid, we can break the loop
|
||||
ESP_LOGVV(TAG, "Answer at %6ld", millis());
|
||||
ESP_LOGVV(TAG, "Answer at %6u", millis());
|
||||
success = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ((data[0] & 0x80) == 0x80) {
|
||||
if (!success || (data[0] & 0x80) == 0x80) {
|
||||
ESP_LOGE(TAG, "Measurements reading timed-out!");
|
||||
this->status_set_warning();
|
||||
return;
|
||||
@ -105,7 +129,7 @@ void AHT10Component::update() {
|
||||
this->temperature_sensor_->publish_state(temperature);
|
||||
}
|
||||
if (this->humidity_sensor_ != nullptr) {
|
||||
if (isnan(humidity))
|
||||
if (std::isnan(humidity))
|
||||
ESP_LOGW(TAG, "Invalid humidity! Sensor reported 0%% Hum");
|
||||
this->humidity_sensor_->publish_state(humidity);
|
||||
}
|
||||
|
@ -1,12 +1,12 @@
|
||||
#include "airthings_listener.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#ifdef USE_ESP32
|
||||
|
||||
namespace esphome {
|
||||
namespace airthings_ble {
|
||||
|
||||
static const char *TAG = "airthings_ble";
|
||||
static const char *const TAG = "airthings_ble";
|
||||
|
||||
bool AirthingsListener::parse_device(const esp32_ble_tracker::ESPBTDevice &device) {
|
||||
for (auto &it : device.get_manufacturer_datas()) {
|
||||
|
@ -1,10 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
|
||||
#include <BLEDevice.h>
|
||||
|
||||
namespace esphome {
|
||||
namespace airthings_ble {
|
||||
|
1
esphome/components/airthings_wave_mini/__init__.py
Normal file
1
esphome/components/airthings_wave_mini/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
CODEOWNERS = ["@ncareau"]
|
113
esphome/components/airthings_wave_mini/airthings_wave_mini.cpp
Normal file
113
esphome/components/airthings_wave_mini/airthings_wave_mini.cpp
Normal file
@ -0,0 +1,113 @@
|
||||
#include "airthings_wave_mini.h"
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
namespace esphome {
|
||||
namespace airthings_wave_mini {
|
||||
|
||||
static const char *const TAG = "airthings_wave_mini";
|
||||
|
||||
void AirthingsWaveMini::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
||||
esp_ble_gattc_cb_param_t *param) {
|
||||
switch (event) {
|
||||
case ESP_GATTC_OPEN_EVT: {
|
||||
if (param->open.status == ESP_GATT_OK) {
|
||||
ESP_LOGI(TAG, "Connected successfully!");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case ESP_GATTC_DISCONNECT_EVT: {
|
||||
ESP_LOGW(TAG, "Disconnected!");
|
||||
break;
|
||||
}
|
||||
|
||||
case ESP_GATTC_SEARCH_CMPL_EVT: {
|
||||
this->handle_ = 0;
|
||||
auto chr = this->parent()->get_characteristic(service_uuid_, sensors_data_characteristic_uuid_);
|
||||
if (chr == nullptr) {
|
||||
ESP_LOGW(TAG, "No sensor characteristic found at service %s char %s", service_uuid_.to_string().c_str(),
|
||||
sensors_data_characteristic_uuid_.to_string().c_str());
|
||||
break;
|
||||
}
|
||||
this->handle_ = chr->handle;
|
||||
this->node_state = esp32_ble_tracker::ClientState::ESTABLISHED;
|
||||
|
||||
request_read_values_();
|
||||
break;
|
||||
}
|
||||
|
||||
case ESP_GATTC_READ_CHAR_EVT: {
|
||||
if (param->read.conn_id != this->parent()->conn_id)
|
||||
break;
|
||||
if (param->read.status != ESP_GATT_OK) {
|
||||
ESP_LOGW(TAG, "Error reading char at handle %d, status=%d", param->read.handle, param->read.status);
|
||||
break;
|
||||
}
|
||||
if (param->read.handle == this->handle_) {
|
||||
read_sensors_(param->read.value, param->read.value_len);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void AirthingsWaveMini::read_sensors_(uint8_t *raw_value, uint16_t value_len) {
|
||||
auto value = (WaveMiniReadings *) raw_value;
|
||||
|
||||
if (sizeof(WaveMiniReadings) <= value_len) {
|
||||
this->humidity_sensor_->publish_state(value->humidity / 100.0f);
|
||||
this->pressure_sensor_->publish_state(value->pressure / 50.0f);
|
||||
this->temperature_sensor_->publish_state(value->temperature / 100.0f - 273.15f);
|
||||
if (is_valid_voc_value_(value->voc)) {
|
||||
this->tvoc_sensor_->publish_state(value->voc);
|
||||
}
|
||||
|
||||
// This instance must not stay connected
|
||||
// so other clients can connect to it (e.g. the
|
||||
// mobile app).
|
||||
parent()->set_enabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
bool AirthingsWaveMini::is_valid_voc_value_(uint16_t voc) { return 0 <= voc && voc <= 16383; }
|
||||
|
||||
void AirthingsWaveMini::update() {
|
||||
if (this->node_state != esp32_ble_tracker::ClientState::ESTABLISHED) {
|
||||
if (!parent()->enabled) {
|
||||
ESP_LOGW(TAG, "Reconnecting to device");
|
||||
parent()->set_enabled(true);
|
||||
parent()->connect();
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Connection in progress");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AirthingsWaveMini::request_read_values_() {
|
||||
auto status =
|
||||
esp_ble_gattc_read_char(this->parent()->gattc_if, this->parent()->conn_id, this->handle_, ESP_GATT_AUTH_REQ_NONE);
|
||||
if (status) {
|
||||
ESP_LOGW(TAG, "Error sending read request for sensor, status=%d", status);
|
||||
}
|
||||
}
|
||||
|
||||
void AirthingsWaveMini::dump_config() {
|
||||
LOG_SENSOR(" ", "Humidity", this->humidity_sensor_);
|
||||
LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
|
||||
LOG_SENSOR(" ", "Pressure", this->pressure_sensor_);
|
||||
LOG_SENSOR(" ", "TVOC", this->tvoc_sensor_);
|
||||
}
|
||||
|
||||
AirthingsWaveMini::AirthingsWaveMini()
|
||||
: PollingComponent(10000),
|
||||
service_uuid_(esp32_ble_tracker::ESPBTUUID::from_raw(SERVICE_UUID)),
|
||||
sensors_data_characteristic_uuid_(esp32_ble_tracker::ESPBTUUID::from_raw(CHARACTERISTIC_UUID)) {}
|
||||
|
||||
} // namespace airthings_wave_mini
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ESP32
|
65
esphome/components/airthings_wave_mini/airthings_wave_mini.h
Normal file
65
esphome/components/airthings_wave_mini/airthings_wave_mini.h
Normal file
@ -0,0 +1,65 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include <esp_gattc_api.h>
|
||||
#include <algorithm>
|
||||
#include <iterator>
|
||||
#include "esphome/components/ble_client/ble_client.h"
|
||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace airthings_wave_mini {
|
||||
|
||||
static const char *const SERVICE_UUID = "b42e3882-ade7-11e4-89d3-123b93f75cba";
|
||||
static const char *const CHARACTERISTIC_UUID = "b42e3b98-ade7-11e4-89d3-123b93f75cba";
|
||||
|
||||
class AirthingsWaveMini : public PollingComponent, public ble_client::BLEClientNode {
|
||||
public:
|
||||
AirthingsWaveMini();
|
||||
|
||||
void dump_config() override;
|
||||
void update() override;
|
||||
|
||||
void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
||||
esp_ble_gattc_cb_param_t *param) override;
|
||||
|
||||
void set_temperature(sensor::Sensor *temperature) { temperature_sensor_ = temperature; }
|
||||
void set_humidity(sensor::Sensor *humidity) { humidity_sensor_ = humidity; }
|
||||
void set_pressure(sensor::Sensor *pressure) { pressure_sensor_ = pressure; }
|
||||
void set_tvoc(sensor::Sensor *tvoc) { tvoc_sensor_ = tvoc; }
|
||||
|
||||
protected:
|
||||
bool is_valid_voc_value_(uint16_t voc);
|
||||
|
||||
void read_sensors_(uint8_t *value, uint16_t value_len);
|
||||
void request_read_values_();
|
||||
|
||||
sensor::Sensor *temperature_sensor_{nullptr};
|
||||
sensor::Sensor *humidity_sensor_{nullptr};
|
||||
sensor::Sensor *pressure_sensor_{nullptr};
|
||||
sensor::Sensor *tvoc_sensor_{nullptr};
|
||||
|
||||
uint16_t handle_;
|
||||
esp32_ble_tracker::ESPBTUUID service_uuid_;
|
||||
esp32_ble_tracker::ESPBTUUID sensors_data_characteristic_uuid_;
|
||||
|
||||
struct WaveMiniReadings {
|
||||
uint16_t unused01;
|
||||
uint16_t temperature;
|
||||
uint16_t pressure;
|
||||
uint16_t humidity;
|
||||
uint16_t voc;
|
||||
uint16_t unused02;
|
||||
uint32_t unused03;
|
||||
uint32_t unused04;
|
||||
};
|
||||
};
|
||||
|
||||
} // namespace airthings_wave_mini
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ESP32
|
82
esphome/components/airthings_wave_mini/sensor.py
Normal file
82
esphome/components/airthings_wave_mini/sensor.py
Normal file
@ -0,0 +1,82 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import sensor, ble_client
|
||||
|
||||
from esphome.const import (
|
||||
DEVICE_CLASS_HUMIDITY,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
DEVICE_CLASS_PRESSURE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_PERCENT,
|
||||
UNIT_CELSIUS,
|
||||
UNIT_HECTOPASCAL,
|
||||
CONF_ID,
|
||||
CONF_HUMIDITY,
|
||||
CONF_TVOC,
|
||||
CONF_PRESSURE,
|
||||
CONF_TEMPERATURE,
|
||||
UNIT_PARTS_PER_BILLION,
|
||||
ICON_RADIATOR,
|
||||
)
|
||||
|
||||
DEPENDENCIES = ["ble_client"]
|
||||
|
||||
airthings_wave_mini_ns = cg.esphome_ns.namespace("airthings_wave_mini")
|
||||
AirthingsWaveMini = airthings_wave_mini_ns.class_(
|
||||
"AirthingsWaveMini", cg.PollingComponent, ble_client.BLEClientNode
|
||||
)
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(AirthingsWaveMini),
|
||||
cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_PERCENT,
|
||||
device_class=DEVICE_CLASS_HUMIDITY,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
accuracy_decimals=2,
|
||||
),
|
||||
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_CELSIUS,
|
||||
accuracy_decimals=2,
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_PRESSURE): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_HECTOPASCAL,
|
||||
accuracy_decimals=2,
|
||||
device_class=DEVICE_CLASS_PRESSURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_TVOC): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_PARTS_PER_BILLION,
|
||||
icon=ICON_RADIATOR,
|
||||
accuracy_decimals=0,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
}
|
||||
)
|
||||
.extend(cv.polling_component_schema("5min"))
|
||||
.extend(ble_client.BLE_CLIENT_SCHEMA),
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
|
||||
await ble_client.register_ble_node(var, config)
|
||||
|
||||
if CONF_HUMIDITY in config:
|
||||
sens = await sensor.new_sensor(config[CONF_HUMIDITY])
|
||||
cg.add(var.set_humidity(sens))
|
||||
if CONF_TEMPERATURE in config:
|
||||
sens = await sensor.new_sensor(config[CONF_TEMPERATURE])
|
||||
cg.add(var.set_temperature(sens))
|
||||
if CONF_PRESSURE in config:
|
||||
sens = await sensor.new_sensor(config[CONF_PRESSURE])
|
||||
cg.add(var.set_pressure(sens))
|
||||
if CONF_TVOC in config:
|
||||
sens = await sensor.new_sensor(config[CONF_TVOC])
|
||||
cg.add(var.set_tvoc(sens))
|
@ -1,10 +1,12 @@
|
||||
#include "airthings_wave_plus.h"
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#ifdef USE_ESP32
|
||||
|
||||
namespace esphome {
|
||||
namespace airthings_wave_plus {
|
||||
|
||||
static const char *const TAG = "airthings_wave_plus";
|
||||
|
||||
void AirthingsWavePlus::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
||||
esp_ble_gattc_cb_param_t *param) {
|
||||
switch (event) {
|
||||
@ -21,15 +23,15 @@ void AirthingsWavePlus::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt
|
||||
}
|
||||
|
||||
case ESP_GATTC_SEARCH_CMPL_EVT: {
|
||||
this->handle = 0;
|
||||
auto chr = this->parent()->get_characteristic(service_uuid, sensors_data_characteristic_uuid);
|
||||
this->handle_ = 0;
|
||||
auto chr = this->parent()->get_characteristic(service_uuid_, sensors_data_characteristic_uuid_);
|
||||
if (chr == nullptr) {
|
||||
ESP_LOGW(TAG, "No sensor characteristic found at service %s char %s", service_uuid.to_string().c_str(),
|
||||
sensors_data_characteristic_uuid.to_string().c_str());
|
||||
ESP_LOGW(TAG, "No sensor characteristic found at service %s char %s", service_uuid_.to_string().c_str(),
|
||||
sensors_data_characteristic_uuid_.to_string().c_str());
|
||||
break;
|
||||
}
|
||||
this->handle = chr->handle;
|
||||
this->node_state = espbt::ClientState::Established;
|
||||
this->handle_ = chr->handle;
|
||||
this->node_state = esp32_ble_tracker::ClientState::ESTABLISHED;
|
||||
|
||||
request_read_values_();
|
||||
break;
|
||||
@ -42,7 +44,7 @@ void AirthingsWavePlus::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt
|
||||
ESP_LOGW(TAG, "Error reading char at handle %d, status=%d", param->read.handle, param->read.status);
|
||||
break;
|
||||
}
|
||||
if (param->read.handle == this->handle) {
|
||||
if (param->read.handle == this->handle_) {
|
||||
read_sensors_(param->read.value, param->read.value_len);
|
||||
}
|
||||
break;
|
||||
@ -88,16 +90,14 @@ void AirthingsWavePlus::read_sensors_(uint8_t *raw_value, uint16_t value_len) {
|
||||
}
|
||||
}
|
||||
|
||||
bool AirthingsWavePlus::is_valid_radon_value_(short radon) { return 0 <= radon && radon <= 16383; }
|
||||
bool AirthingsWavePlus::is_valid_radon_value_(uint16_t radon) { return 0 <= radon && radon <= 16383; }
|
||||
|
||||
bool AirthingsWavePlus::is_valid_voc_value_(short voc) { return 0 <= voc && voc <= 16383; }
|
||||
bool AirthingsWavePlus::is_valid_voc_value_(uint16_t voc) { return 0 <= voc && voc <= 16383; }
|
||||
|
||||
bool AirthingsWavePlus::is_valid_co2_value_(short co2) { return 0 <= co2 && co2 <= 16383; }
|
||||
|
||||
void AirthingsWavePlus::loop() {}
|
||||
bool AirthingsWavePlus::is_valid_co2_value_(uint16_t co2) { return 0 <= co2 && co2 <= 16383; }
|
||||
|
||||
void AirthingsWavePlus::update() {
|
||||
if (this->node_state != espbt::ClientState::Established) {
|
||||
if (this->node_state != esp32_ble_tracker::ClientState::ESTABLISHED) {
|
||||
if (!parent()->enabled) {
|
||||
ESP_LOGW(TAG, "Reconnecting to device");
|
||||
parent()->set_enabled(true);
|
||||
@ -110,7 +110,7 @@ void AirthingsWavePlus::update() {
|
||||
|
||||
void AirthingsWavePlus::request_read_values_() {
|
||||
auto status =
|
||||
esp_ble_gattc_read_char(this->parent()->gattc_if, this->parent()->conn_id, this->handle, ESP_GATT_AUTH_REQ_NONE);
|
||||
esp_ble_gattc_read_char(this->parent()->gattc_if, this->parent()->conn_id, this->handle_, ESP_GATT_AUTH_REQ_NONE);
|
||||
if (status) {
|
||||
ESP_LOGW(TAG, "Error sending read request for sensor, status=%d", status);
|
||||
}
|
||||
@ -126,17 +126,12 @@ void AirthingsWavePlus::dump_config() {
|
||||
LOG_SENSOR(" ", "TVOC", this->tvoc_sensor_);
|
||||
}
|
||||
|
||||
AirthingsWavePlus::AirthingsWavePlus() : PollingComponent(10000) {
|
||||
auto service_bt = *BLEUUID::fromString(std::string("b42e1c08-ade7-11e4-89d3-123b93f75cba")).getNative();
|
||||
auto characteristic_bt = *BLEUUID::fromString(std::string("b42e2a68-ade7-11e4-89d3-123b93f75cba")).getNative();
|
||||
|
||||
service_uuid = espbt::ESPBTUUID::from_uuid(service_bt);
|
||||
sensors_data_characteristic_uuid = espbt::ESPBTUUID::from_uuid(characteristic_bt);
|
||||
}
|
||||
|
||||
void AirthingsWavePlus::setup() {}
|
||||
AirthingsWavePlus::AirthingsWavePlus()
|
||||
: PollingComponent(10000),
|
||||
service_uuid_(esp32_ble_tracker::ESPBTUUID::from_raw(SERVICE_UUID)),
|
||||
sensors_data_characteristic_uuid_(esp32_ble_tracker::ESPBTUUID::from_raw(CHARACTERISTIC_UUID)) {}
|
||||
|
||||
} // namespace airthings_wave_plus
|
||||
} // namespace esphome
|
||||
|
||||
#endif // ARDUINO_ARCH_ESP32
|
||||
#endif // USE_ESP32
|
||||
|
@ -1,32 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include <esp_gattc_api.h>
|
||||
#include <algorithm>
|
||||
#include <iterator>
|
||||
#include "esphome/components/ble_client/ble_client.h"
|
||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include <algorithm>
|
||||
#include <iterator>
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#include <esp_gattc_api.h>
|
||||
#include <BLEDevice.h>
|
||||
|
||||
using namespace esphome::ble_client;
|
||||
|
||||
namespace esphome {
|
||||
namespace airthings_wave_plus {
|
||||
|
||||
static const char *TAG = "airthings_wave_plus";
|
||||
static const char *const SERVICE_UUID = "b42e1c08-ade7-11e4-89d3-123b93f75cba";
|
||||
static const char *const CHARACTERISTIC_UUID = "b42e2a68-ade7-11e4-89d3-123b93f75cba";
|
||||
|
||||
class AirthingsWavePlus : public PollingComponent, public BLEClientNode {
|
||||
class AirthingsWavePlus : public PollingComponent, public ble_client::BLEClientNode {
|
||||
public:
|
||||
AirthingsWavePlus();
|
||||
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
void update() override;
|
||||
void loop() override;
|
||||
|
||||
void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
||||
esp_ble_gattc_cb_param_t *param) override;
|
||||
@ -40,9 +36,9 @@ class AirthingsWavePlus : public PollingComponent, public BLEClientNode {
|
||||
void set_tvoc(sensor::Sensor *tvoc) { tvoc_sensor_ = tvoc; }
|
||||
|
||||
protected:
|
||||
bool is_valid_radon_value_(short radon);
|
||||
bool is_valid_voc_value_(short voc);
|
||||
bool is_valid_co2_value_(short co2);
|
||||
bool is_valid_radon_value_(uint16_t radon);
|
||||
bool is_valid_voc_value_(uint16_t voc);
|
||||
bool is_valid_co2_value_(uint16_t co2);
|
||||
|
||||
void read_sensors_(uint8_t *value, uint16_t value_len);
|
||||
void request_read_values_();
|
||||
@ -55,9 +51,9 @@ class AirthingsWavePlus : public PollingComponent, public BLEClientNode {
|
||||
sensor::Sensor *co2_sensor_{nullptr};
|
||||
sensor::Sensor *tvoc_sensor_{nullptr};
|
||||
|
||||
uint16_t handle;
|
||||
espbt::ESPBTUUID service_uuid;
|
||||
espbt::ESPBTUUID sensors_data_characteristic_uuid;
|
||||
uint16_t handle_;
|
||||
esp32_ble_tracker::ESPBTUUID service_uuid_;
|
||||
esp32_ble_tracker::ESPBTUUID sensors_data_characteristic_uuid_;
|
||||
|
||||
struct WavePlusReadings {
|
||||
uint8_t version;
|
||||
@ -76,4 +72,4 @@ class AirthingsWavePlus : public PollingComponent, public BLEClientNode {
|
||||
} // namespace airthings_wave_plus
|
||||
} // namespace esphome
|
||||
|
||||
#endif // ARDUINO_ARCH_ESP32
|
||||
#endif // USE_ESP32
|
||||
|
@ -34,7 +34,7 @@ AirthingsWavePlus = airthings_wave_plus_ns.class_(
|
||||
)
|
||||
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(AirthingsWavePlus),
|
||||
@ -82,8 +82,8 @@ CONFIG_SCHEMA = (
|
||||
),
|
||||
}
|
||||
)
|
||||
.extend(cv.polling_component_schema("5mins"))
|
||||
.extend(ble_client.BLE_CLIENT_SCHEMA)
|
||||
.extend(cv.polling_component_schema("5min"))
|
||||
.extend(ble_client.BLE_CLIENT_SCHEMA),
|
||||
)
|
||||
|
||||
|
||||
|
@ -5,6 +5,7 @@
|
||||
|
||||
#include "am2320.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/hal.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace am2320 {
|
||||
@ -77,7 +78,7 @@ bool AM2320Component::read_bytes_(uint8_t a_register, uint8_t *data, uint8_t len
|
||||
|
||||
if (conversion > 0)
|
||||
delay(conversion);
|
||||
return this->parent_->raw_receive(this->address_, data, len);
|
||||
return this->read(data, len) == i2c::ERROR_OK;
|
||||
}
|
||||
|
||||
bool AM2320Component::read_data_(uint8_t *data) {
|
||||
|
@ -1,12 +1,13 @@
|
||||
#include "am43.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/hal.h"
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#ifdef USE_ESP32
|
||||
|
||||
namespace esphome {
|
||||
namespace am43 {
|
||||
|
||||
static const char *TAG = "am43";
|
||||
static const char *const TAG = "am43";
|
||||
|
||||
void Am43::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "AM43");
|
||||
@ -15,8 +16,8 @@ void Am43::dump_config() {
|
||||
}
|
||||
|
||||
void Am43::setup() {
|
||||
this->encoder_ = new Am43Encoder();
|
||||
this->decoder_ = new Am43Decoder();
|
||||
this->encoder_ = make_unique<Am43Encoder>();
|
||||
this->decoder_ = make_unique<Am43Decoder>();
|
||||
this->logged_in_ = false;
|
||||
this->last_battery_update_ = 0;
|
||||
this->current_sensor_ = 0;
|
||||
@ -30,7 +31,7 @@ void Am43::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_i
|
||||
}
|
||||
case ESP_GATTC_DISCONNECT_EVT: {
|
||||
this->logged_in_ = false;
|
||||
this->node_state = espbt::ClientState::Idle;
|
||||
this->node_state = espbt::ClientState::IDLE;
|
||||
if (this->battery_ != nullptr)
|
||||
this->battery_->publish_state(NAN);
|
||||
if (this->illuminance_ != nullptr)
|
||||
@ -53,7 +54,7 @@ void Am43::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_i
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
|
||||
this->node_state = espbt::ClientState::Established;
|
||||
this->node_state = espbt::ClientState::ESTABLISHED;
|
||||
this->update();
|
||||
break;
|
||||
}
|
||||
@ -92,7 +93,7 @@ void Am43::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_i
|
||||
}
|
||||
|
||||
void Am43::update() {
|
||||
if (this->node_state != espbt::ClientState::Established) {
|
||||
if (this->node_state != espbt::ClientState::ESTABLISHED) {
|
||||
ESP_LOGW(TAG, "[%s] Cannot poll, not connected", this->parent_->address_str().c_str());
|
||||
return;
|
||||
}
|
||||
|
@ -6,7 +6,7 @@
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/am43/am43_base.h"
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include <esp_gattc_api.h>
|
||||
|
||||
@ -28,8 +28,8 @@ class Am43 : public esphome::ble_client::BLEClientNode, public PollingComponent
|
||||
|
||||
protected:
|
||||
uint16_t char_handle_;
|
||||
Am43Encoder *encoder_;
|
||||
Am43Decoder *decoder_;
|
||||
std::unique_ptr<Am43Encoder> encoder_;
|
||||
std::unique_ptr<Am43Decoder> decoder_;
|
||||
bool logged_in_;
|
||||
sensor::Sensor *battery_{nullptr};
|
||||
sensor::Sensor *illuminance_{nullptr};
|
||||
|
@ -1,4 +1,6 @@
|
||||
#include "am43_base.h"
|
||||
#include <cstring>
|
||||
#include <cstdio>
|
||||
|
||||
namespace esphome {
|
||||
namespace am43 {
|
||||
|
@ -1,12 +1,12 @@
|
||||
#include "am43_cover.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#ifdef USE_ESP32
|
||||
|
||||
namespace esphome {
|
||||
namespace am43 {
|
||||
|
||||
static const char *TAG = "am43_cover";
|
||||
static const char *const TAG = "am43_cover";
|
||||
|
||||
using namespace esphome::cover;
|
||||
|
||||
@ -18,13 +18,13 @@ void Am43Component::dump_config() {
|
||||
|
||||
void Am43Component::setup() {
|
||||
this->position = COVER_OPEN;
|
||||
this->encoder_ = new Am43Encoder();
|
||||
this->decoder_ = new Am43Decoder();
|
||||
this->encoder_ = make_unique<Am43Encoder>();
|
||||
this->decoder_ = make_unique<Am43Decoder>();
|
||||
this->logged_in_ = false;
|
||||
}
|
||||
|
||||
void Am43Component::loop() {
|
||||
if (this->node_state == espbt::ClientState::Established && !this->logged_in_) {
|
||||
if (this->node_state == espbt::ClientState::ESTABLISHED && !this->logged_in_) {
|
||||
auto packet = this->encoder_->get_send_pin_request(this->pin_);
|
||||
auto status =
|
||||
esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, packet->length,
|
||||
@ -46,7 +46,7 @@ CoverTraits Am43Component::get_traits() {
|
||||
}
|
||||
|
||||
void Am43Component::control(const CoverCall &call) {
|
||||
if (this->node_state != espbt::ClientState::Established) {
|
||||
if (this->node_state != espbt::ClientState::ESTABLISHED) {
|
||||
ESP_LOGW(TAG, "[%s] Cannot send cover control, not connected", this->get_name().c_str());
|
||||
return;
|
||||
}
|
||||
@ -98,7 +98,7 @@ void Am43Component::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
|
||||
this->node_state = espbt::ClientState::Established;
|
||||
this->node_state = espbt::ClientState::ESTABLISHED;
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_NOTIFY_EVT: {
|
||||
|
@ -6,7 +6,7 @@
|
||||
#include "esphome/components/cover/cover.h"
|
||||
#include "esphome/components/am43/am43_base.h"
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include <esp_gattc_api.h>
|
||||
|
||||
@ -32,8 +32,8 @@ class Am43Component : public cover::Cover, public esphome::ble_client::BLEClient
|
||||
uint16_t char_handle_;
|
||||
uint16_t pin_;
|
||||
bool invert_position_;
|
||||
Am43Encoder *encoder_;
|
||||
Am43Decoder *decoder_;
|
||||
std::unique_ptr<Am43Encoder> encoder_;
|
||||
std::unique_ptr<Am43Decoder> decoder_;
|
||||
bool logged_in_;
|
||||
|
||||
float position_;
|
||||
|
@ -5,7 +5,7 @@ from esphome.components import display, font
|
||||
import esphome.components.image as espImage
|
||||
import esphome.config_validation as cv
|
||||
import esphome.codegen as cg
|
||||
from esphome.const import CONF_FILE, CONF_ID, CONF_TYPE, CONF_RESIZE
|
||||
from esphome.const import CONF_FILE, CONF_ID, CONF_RAW_DATA_ID, CONF_RESIZE, CONF_TYPE
|
||||
from esphome.core import CORE, HexInt
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
@ -15,8 +15,6 @@ MULTI_CONF = True
|
||||
|
||||
Animation_ = display.display_ns.class_("Animation")
|
||||
|
||||
CONF_RAW_DATA_ID = "raw_data_id"
|
||||
|
||||
ANIMATION_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.declare_id(Animation_),
|
||||
|
@ -1,19 +1,19 @@
|
||||
#include "anova.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#ifdef USE_ESP32
|
||||
|
||||
namespace esphome {
|
||||
namespace anova {
|
||||
|
||||
static const char *TAG = "anova";
|
||||
static const char *const TAG = "anova";
|
||||
|
||||
using namespace esphome::climate;
|
||||
|
||||
void Anova::dump_config() { LOG_CLIMATE("", "Anova BLE Cooker", this); }
|
||||
|
||||
void Anova::setup() {
|
||||
this->codec_ = new AnovaCodec();
|
||||
this->codec_ = make_unique<AnovaCodec>();
|
||||
this->current_request_ = 0;
|
||||
}
|
||||
|
||||
@ -72,7 +72,7 @@ void Anova::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
|
||||
this->node_state = espbt::ClientState::Established;
|
||||
this->node_state = espbt::ClientState::ESTABLISHED;
|
||||
this->current_request_ = 0;
|
||||
this->update();
|
||||
break;
|
||||
@ -129,13 +129,13 @@ void Anova::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_
|
||||
void Anova::set_unit_of_measurement(const char *unit) { this->fahrenheit_ = !strncmp(unit, "f", 1); }
|
||||
|
||||
void Anova::update() {
|
||||
if (this->node_state != espbt::ClientState::Established)
|
||||
if (this->node_state != espbt::ClientState::ESTABLISHED)
|
||||
return;
|
||||
|
||||
if (this->current_request_ < 2) {
|
||||
auto pkt = this->codec_->get_read_device_status_request();
|
||||
if (this->current_request_ == 0)
|
||||
auto pkt = this->codec_->get_set_unit_request(this->fahrenheit_ ? 'f' : 'c');
|
||||
this->codec_->get_set_unit_request(this->fahrenheit_ ? 'f' : 'c');
|
||||
auto status = esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_,
|
||||
pkt->length, pkt->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
|
||||
if (status)
|
||||
|
@ -6,7 +6,7 @@
|
||||
#include "esphome/components/climate/climate.h"
|
||||
#include "anova_base.h"
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include <esp_gattc_api.h>
|
||||
|
||||
@ -27,7 +27,7 @@ class Anova : public climate::Climate, public esphome::ble_client::BLEClientNode
|
||||
esp_ble_gattc_cb_param_t *param) override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||
climate::ClimateTraits traits() {
|
||||
climate::ClimateTraits traits() override {
|
||||
auto traits = climate::ClimateTraits();
|
||||
traits.set_supports_current_temperature(true);
|
||||
traits.set_supports_heat_mode(true);
|
||||
@ -39,7 +39,7 @@ class Anova : public climate::Climate, public esphome::ble_client::BLEClientNode
|
||||
void set_unit_of_measurement(const char *);
|
||||
|
||||
protected:
|
||||
AnovaCodec *codec_;
|
||||
std::unique_ptr<AnovaCodec> codec_;
|
||||
void control(const climate::ClimateCall &call) override;
|
||||
uint16_t char_handle_;
|
||||
uint8_t current_request_;
|
||||
|
@ -1,4 +1,6 @@
|
||||
#include "anova_base.h"
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
|
||||
namespace esphome {
|
||||
namespace anova {
|
||||
|
@ -1,5 +1,6 @@
|
||||
#include "apds9960.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/hal.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace apds9960 {
|
||||
|
@ -215,6 +215,7 @@ message ListEntitiesBinarySensorResponse {
|
||||
string device_class = 5;
|
||||
bool is_status_binary_sensor = 6;
|
||||
bool disabled_by_default = 7;
|
||||
string icon = 8;
|
||||
}
|
||||
message BinarySensorStateResponse {
|
||||
option (id) = 21;
|
||||
@ -245,6 +246,7 @@ message ListEntitiesCoverResponse {
|
||||
bool supports_tilt = 7;
|
||||
string device_class = 8;
|
||||
bool disabled_by_default = 9;
|
||||
string icon = 10;
|
||||
}
|
||||
|
||||
enum LegacyCoverState {
|
||||
@ -313,6 +315,7 @@ message ListEntitiesFanResponse {
|
||||
bool supports_direction = 7;
|
||||
int32 supported_speed_count = 8;
|
||||
bool disabled_by_default = 9;
|
||||
string icon = 10;
|
||||
}
|
||||
enum FanSpeed {
|
||||
FAN_SPEED_LOW = 0;
|
||||
@ -388,6 +391,7 @@ message ListEntitiesLightResponse {
|
||||
float max_mireds = 10;
|
||||
repeated string effects = 11;
|
||||
bool disabled_by_default = 13;
|
||||
string icon = 14;
|
||||
}
|
||||
message LightStateResponse {
|
||||
option (id) = 24;
|
||||
@ -790,6 +794,7 @@ message ListEntitiesClimateResponse {
|
||||
repeated ClimatePreset supported_presets = 16;
|
||||
repeated string supported_custom_presets = 17;
|
||||
bool disabled_by_default = 18;
|
||||
string icon = 19;
|
||||
}
|
||||
message ClimateStateResponse {
|
||||
option (id) = 47;
|
||||
|
@ -1,7 +1,9 @@
|
||||
#include "api_connection.h"
|
||||
#include "esphome/core/entity_base.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/util.h"
|
||||
#include "esphome/components/network/util.h"
|
||||
#include "esphome/core/version.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include <cerrno>
|
||||
|
||||
#ifdef USE_DEEP_SLEEP
|
||||
@ -48,7 +50,7 @@ void APIConnection::loop() {
|
||||
if (this->remove_)
|
||||
return;
|
||||
|
||||
if (!network_is_connected()) {
|
||||
if (!network::is_connected()) {
|
||||
// when network is disconnected force disconnect immediately
|
||||
// don't wait for timeout
|
||||
this->on_fatal_error();
|
||||
@ -142,8 +144,21 @@ void APIConnection::loop() {
|
||||
}
|
||||
}
|
||||
|
||||
std::string get_default_unique_id(const std::string &component_type, Nameable *nameable) {
|
||||
return App.get_name() + component_type + nameable->get_object_id();
|
||||
std::string get_default_unique_id(const std::string &component_type, EntityBase *entity) {
|
||||
return App.get_name() + component_type + entity->get_object_id();
|
||||
}
|
||||
|
||||
DisconnectResponse APIConnection::disconnect(const DisconnectRequest &msg) {
|
||||
// remote initiated disconnect_client
|
||||
// don't close yet, we still need to send the disconnect response
|
||||
// close will happen on next loop
|
||||
ESP_LOGD(TAG, "%s requested disconnected", client_info_.c_str());
|
||||
this->next_close_ = true;
|
||||
DisconnectResponse resp;
|
||||
return resp;
|
||||
}
|
||||
void APIConnection::on_disconnect_response(const DisconnectResponse &value) {
|
||||
// pass
|
||||
}
|
||||
|
||||
DisconnectResponse APIConnection::disconnect(const DisconnectRequest &msg) {
|
||||
@ -179,6 +194,7 @@ bool APIConnection::send_binary_sensor_info(binary_sensor::BinarySensor *binary_
|
||||
msg.device_class = binary_sensor->get_device_class();
|
||||
msg.is_status_binary_sensor = binary_sensor->is_status_binary_sensor();
|
||||
msg.disabled_by_default = binary_sensor->is_disabled_by_default();
|
||||
msg.icon = binary_sensor->get_icon();
|
||||
return this->send_list_entities_binary_sensor_response(msg);
|
||||
}
|
||||
#endif
|
||||
@ -211,6 +227,7 @@ bool APIConnection::send_cover_info(cover::Cover *cover) {
|
||||
msg.supports_tilt = traits.get_supports_tilt();
|
||||
msg.device_class = cover->get_device_class();
|
||||
msg.disabled_by_default = cover->is_disabled_by_default();
|
||||
msg.icon = cover->get_icon();
|
||||
return this->send_list_entities_cover_response(msg);
|
||||
}
|
||||
void APIConnection::cover_command(const CoverCommandRequest &msg) {
|
||||
@ -276,6 +293,7 @@ bool APIConnection::send_fan_info(fan::FanState *fan) {
|
||||
msg.supports_direction = traits.supports_direction();
|
||||
msg.supported_speed_count = traits.supported_speed_count();
|
||||
msg.disabled_by_default = fan->is_disabled_by_default();
|
||||
msg.icon = fan->get_icon();
|
||||
return this->send_list_entities_fan_response(msg);
|
||||
}
|
||||
void APIConnection::fan_command(const FanCommandRequest &msg) {
|
||||
@ -338,6 +356,7 @@ bool APIConnection::send_light_info(light::LightState *light) {
|
||||
msg.unique_id = get_default_unique_id("light", light);
|
||||
|
||||
msg.disabled_by_default = light->is_disabled_by_default();
|
||||
msg.icon = light->get_icon();
|
||||
|
||||
for (auto mode : traits.get_supported_color_modes())
|
||||
msg.supported_color_modes.push_back(static_cast<enums::ColorMode>(mode));
|
||||
@ -528,6 +547,7 @@ bool APIConnection::send_climate_info(climate::Climate *climate) {
|
||||
msg.unique_id = get_default_unique_id("climate", climate);
|
||||
|
||||
msg.disabled_by_default = climate->is_disabled_by_default();
|
||||
msg.icon = climate->get_icon();
|
||||
|
||||
msg.supports_current_temperature = traits.get_supports_current_temperature();
|
||||
msg.supports_two_point_target_temperature = traits.get_supports_two_point_target_temperature();
|
||||
@ -600,7 +620,7 @@ bool APIConnection::send_number_info(number::Number *number) {
|
||||
msg.object_id = number->get_object_id();
|
||||
msg.name = number->get_name();
|
||||
msg.unique_id = get_default_unique_id("number", number);
|
||||
msg.icon = number->traits.get_icon();
|
||||
msg.icon = number->get_icon();
|
||||
msg.disabled_by_default = number->is_disabled_by_default();
|
||||
|
||||
msg.min_value = number->traits.get_min_value();
|
||||
@ -637,7 +657,7 @@ bool APIConnection::send_select_info(select::Select *select) {
|
||||
msg.object_id = select->get_object_id();
|
||||
msg.name = select->get_name();
|
||||
msg.unique_id = get_default_unique_id("select", select);
|
||||
msg.icon = select->traits.get_icon();
|
||||
msg.icon = select->get_icon();
|
||||
msg.disabled_by_default = select->is_disabled_by_default();
|
||||
|
||||
for (const auto &option : select->traits.get_options())
|
||||
@ -662,7 +682,7 @@ void APIConnection::send_camera_state(std::shared_ptr<esp32_camera::CameraImage>
|
||||
return;
|
||||
if (this->image_reader_.available())
|
||||
return;
|
||||
this->image_reader_.set_image(image);
|
||||
this->image_reader_.set_image(std::move(image));
|
||||
}
|
||||
bool APIConnection::send_camera_info(esp32_camera::ESP32Camera *camera) {
|
||||
ListEntitiesCameraResponse msg;
|
||||
|
@ -3,6 +3,7 @@
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "proto.h"
|
||||
#include <cstring>
|
||||
|
||||
namespace esphome {
|
||||
namespace api {
|
||||
|
@ -1,7 +1,8 @@
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
#include <deque>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "esphome/core/defines.h"
|
||||
|
||||
@ -75,8 +76,8 @@ class APIFrameHelper {
|
||||
class APINoiseFrameHelper : public APIFrameHelper {
|
||||
public:
|
||||
APINoiseFrameHelper(std::unique_ptr<socket::Socket> socket, std::shared_ptr<APINoiseContext> ctx)
|
||||
: socket_(std::move(socket)), ctx_(ctx) {}
|
||||
~APINoiseFrameHelper();
|
||||
: socket_(std::move(socket)), ctx_(std::move(std::move(ctx))) {}
|
||||
~APINoiseFrameHelper() override;
|
||||
APIError init() override;
|
||||
APIError loop() override;
|
||||
APIError read_packet(ReadPacketBuffer *buffer) override;
|
||||
@ -136,7 +137,7 @@ class APINoiseFrameHelper : public APIFrameHelper {
|
||||
class APIPlaintextFrameHelper : public APIFrameHelper {
|
||||
public:
|
||||
APIPlaintextFrameHelper(std::unique_ptr<socket::Socket> socket) : socket_(std::move(socket)) {}
|
||||
~APIPlaintextFrameHelper() = default;
|
||||
~APIPlaintextFrameHelper() override = default;
|
||||
APIError init() override;
|
||||
APIError loop() override;
|
||||
APIError read_packet(ReadPacketBuffer *buffer) override;
|
||||
|
@ -11,7 +11,7 @@ using psk_t = std::array<uint8_t, 32>;
|
||||
|
||||
class APINoiseContext {
|
||||
public:
|
||||
void set_psk(psk_t psk) { psk_ = std::move(psk); }
|
||||
void set_psk(psk_t psk) { psk_ = psk; }
|
||||
const psk_t &get_psk() const { return psk_; }
|
||||
|
||||
protected:
|
||||
|
@ -531,6 +531,10 @@ bool ListEntitiesBinarySensorResponse::decode_length(uint32_t field_id, ProtoLen
|
||||
this->device_class = value.as_string();
|
||||
return true;
|
||||
}
|
||||
case 8: {
|
||||
this->icon = value.as_string();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@ -553,6 +557,7 @@ void ListEntitiesBinarySensorResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_string(5, this->device_class);
|
||||
buffer.encode_bool(6, this->is_status_binary_sensor);
|
||||
buffer.encode_bool(7, this->disabled_by_default);
|
||||
buffer.encode_string(8, this->icon);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void ListEntitiesBinarySensorResponse::dump_to(std::string &out) const {
|
||||
@ -586,6 +591,10 @@ void ListEntitiesBinarySensorResponse::dump_to(std::string &out) const {
|
||||
out.append(" disabled_by_default: ");
|
||||
out.append(YESNO(this->disabled_by_default));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" icon: ");
|
||||
out.append("'").append(this->icon).append("'");
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
@ -677,6 +686,10 @@ bool ListEntitiesCoverResponse::decode_length(uint32_t field_id, ProtoLengthDeli
|
||||
this->device_class = value.as_string();
|
||||
return true;
|
||||
}
|
||||
case 10: {
|
||||
this->icon = value.as_string();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@ -701,6 +714,7 @@ void ListEntitiesCoverResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_bool(7, this->supports_tilt);
|
||||
buffer.encode_string(8, this->device_class);
|
||||
buffer.encode_bool(9, this->disabled_by_default);
|
||||
buffer.encode_string(10, this->icon);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void ListEntitiesCoverResponse::dump_to(std::string &out) const {
|
||||
@ -742,6 +756,10 @@ void ListEntitiesCoverResponse::dump_to(std::string &out) const {
|
||||
out.append(" disabled_by_default: ");
|
||||
out.append(YESNO(this->disabled_by_default));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" icon: ");
|
||||
out.append("'").append(this->icon).append("'");
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
@ -948,6 +966,10 @@ bool ListEntitiesFanResponse::decode_length(uint32_t field_id, ProtoLengthDelimi
|
||||
this->unique_id = value.as_string();
|
||||
return true;
|
||||
}
|
||||
case 10: {
|
||||
this->icon = value.as_string();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@ -972,6 +994,7 @@ void ListEntitiesFanResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_bool(7, this->supports_direction);
|
||||
buffer.encode_int32(8, this->supported_speed_count);
|
||||
buffer.encode_bool(9, this->disabled_by_default);
|
||||
buffer.encode_string(10, this->icon);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void ListEntitiesFanResponse::dump_to(std::string &out) const {
|
||||
@ -1014,6 +1037,10 @@ void ListEntitiesFanResponse::dump_to(std::string &out) const {
|
||||
out.append(" disabled_by_default: ");
|
||||
out.append(YESNO(this->disabled_by_default));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" icon: ");
|
||||
out.append("'").append(this->icon).append("'");
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
@ -1262,6 +1289,10 @@ bool ListEntitiesLightResponse::decode_length(uint32_t field_id, ProtoLengthDeli
|
||||
this->effects.push_back(value.as_string());
|
||||
return true;
|
||||
}
|
||||
case 14: {
|
||||
this->icon = value.as_string();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@ -1302,6 +1333,7 @@ void ListEntitiesLightResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_string(11, it, true);
|
||||
}
|
||||
buffer.encode_bool(13, this->disabled_by_default);
|
||||
buffer.encode_string(14, this->icon);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void ListEntitiesLightResponse::dump_to(std::string &out) const {
|
||||
@ -1365,6 +1397,10 @@ void ListEntitiesLightResponse::dump_to(std::string &out) const {
|
||||
out.append(" disabled_by_default: ");
|
||||
out.append(YESNO(this->disabled_by_default));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" icon: ");
|
||||
out.append("'").append(this->icon).append("'");
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
@ -3072,6 +3108,10 @@ bool ListEntitiesClimateResponse::decode_length(uint32_t field_id, ProtoLengthDe
|
||||
this->supported_custom_presets.push_back(value.as_string());
|
||||
return true;
|
||||
}
|
||||
case 19: {
|
||||
this->icon = value.as_string();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@ -3129,6 +3169,7 @@ void ListEntitiesClimateResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_string(17, it, true);
|
||||
}
|
||||
buffer.encode_bool(18, this->disabled_by_default);
|
||||
buffer.encode_string(19, this->icon);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void ListEntitiesClimateResponse::dump_to(std::string &out) const {
|
||||
@ -3221,6 +3262,10 @@ void ListEntitiesClimateResponse::dump_to(std::string &out) const {
|
||||
out.append(" disabled_by_default: ");
|
||||
out.append(YESNO(this->disabled_by_default));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" icon: ");
|
||||
out.append("'").append(this->icon).append("'");
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
|
@ -269,6 +269,7 @@ class ListEntitiesBinarySensorResponse : public ProtoMessage {
|
||||
std::string device_class{};
|
||||
bool is_status_binary_sensor{false};
|
||||
bool disabled_by_default{false};
|
||||
std::string icon{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
@ -304,6 +305,7 @@ class ListEntitiesCoverResponse : public ProtoMessage {
|
||||
bool supports_tilt{false};
|
||||
std::string device_class{};
|
||||
bool disabled_by_default{false};
|
||||
std::string icon{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
@ -360,6 +362,7 @@ class ListEntitiesFanResponse : public ProtoMessage {
|
||||
bool supports_direction{false};
|
||||
int32_t supported_speed_count{0};
|
||||
bool disabled_by_default{false};
|
||||
std::string icon{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
@ -424,6 +427,7 @@ class ListEntitiesLightResponse : public ProtoMessage {
|
||||
float max_mireds{0.0f};
|
||||
std::vector<std::string> effects{};
|
||||
bool disabled_by_default{false};
|
||||
std::string icon{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
@ -856,6 +860,7 @@ class ListEntitiesClimateResponse : public ProtoMessage {
|
||||
std::vector<enums::ClimatePreset> supported_presets{};
|
||||
std::vector<std::string> supported_custom_presets{};
|
||||
bool disabled_by_default{false};
|
||||
std::string icon{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
|
@ -5,6 +5,8 @@
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/util.h"
|
||||
#include "esphome/core/version.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/components/network/util.h"
|
||||
#include <cerrno>
|
||||
|
||||
#ifdef USE_LOGGER
|
||||
@ -64,7 +66,7 @@ void APIServer::setup() {
|
||||
#ifdef USE_LOGGER
|
||||
if (logger::global_logger != nullptr) {
|
||||
logger::global_logger->add_on_log_callback([this](int level, const char *tag, const char *message) {
|
||||
for (auto *c : this->clients_) {
|
||||
for (auto &c : this->clients_) {
|
||||
if (!c->remove_)
|
||||
c->send_log_message(level, tag, message);
|
||||
}
|
||||
@ -76,11 +78,12 @@ void APIServer::setup() {
|
||||
|
||||
#ifdef USE_ESP32_CAMERA
|
||||
if (esp32_camera::global_esp32_camera != nullptr) {
|
||||
esp32_camera::global_esp32_camera->add_image_callback([this](std::shared_ptr<esp32_camera::CameraImage> image) {
|
||||
for (auto *c : this->clients_)
|
||||
if (!c->remove_)
|
||||
c->send_camera_state(image);
|
||||
});
|
||||
esp32_camera::global_esp32_camera->add_image_callback(
|
||||
[this](const std::shared_ptr<esp32_camera::CameraImage> &image) {
|
||||
for (auto &c : this->clients_)
|
||||
if (!c->remove_)
|
||||
c->send_camera_state(image);
|
||||
});
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@ -95,25 +98,21 @@ void APIServer::loop() {
|
||||
ESP_LOGD(TAG, "Accepted %s", sock->getpeername().c_str());
|
||||
|
||||
auto *conn = new APIConnection(std::move(sock), this);
|
||||
clients_.push_back(conn);
|
||||
clients_.emplace_back(conn);
|
||||
conn->start();
|
||||
}
|
||||
|
||||
// Partition clients into remove and active
|
||||
auto new_end =
|
||||
std::partition(this->clients_.begin(), this->clients_.end(), [](APIConnection *conn) { return !conn->remove_; });
|
||||
auto new_end = std::partition(this->clients_.begin(), this->clients_.end(),
|
||||
[](const std::unique_ptr<APIConnection> &conn) { return !conn->remove_; });
|
||||
// print disconnection messages
|
||||
for (auto it = new_end; it != this->clients_.end(); ++it) {
|
||||
ESP_LOGV(TAG, "Removing connection to %s", (*it)->client_info_.c_str());
|
||||
}
|
||||
// only then delete the pointers, otherwise log routine
|
||||
// would access freed memory
|
||||
for (auto it = new_end; it != this->clients_.end(); ++it)
|
||||
delete *it;
|
||||
// resize vector
|
||||
this->clients_.erase(new_end, this->clients_.end());
|
||||
|
||||
for (auto *client : this->clients_) {
|
||||
for (auto &client : this->clients_) {
|
||||
client->loop();
|
||||
}
|
||||
|
||||
@ -133,7 +132,7 @@ void APIServer::loop() {
|
||||
}
|
||||
void APIServer::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "API Server:");
|
||||
ESP_LOGCONFIG(TAG, " Address: %s:%u", network_get_address().c_str(), this->port_);
|
||||
ESP_LOGCONFIG(TAG, " Address: %s:%u", network::get_use_address().c_str(), this->port_);
|
||||
#ifdef USE_API_NOISE
|
||||
ESP_LOGCONFIG(TAG, " Using noise encryption: YES");
|
||||
#else
|
||||
@ -174,7 +173,7 @@ void APIServer::handle_disconnect(APIConnection *conn) {}
|
||||
void APIServer::on_binary_sensor_update(binary_sensor::BinarySensor *obj, bool state) {
|
||||
if (obj->is_internal())
|
||||
return;
|
||||
for (auto *c : this->clients_)
|
||||
for (auto &c : this->clients_)
|
||||
c->send_binary_sensor_state(obj, state);
|
||||
}
|
||||
#endif
|
||||
@ -183,7 +182,7 @@ void APIServer::on_binary_sensor_update(binary_sensor::BinarySensor *obj, bool s
|
||||
void APIServer::on_cover_update(cover::Cover *obj) {
|
||||
if (obj->is_internal())
|
||||
return;
|
||||
for (auto *c : this->clients_)
|
||||
for (auto &c : this->clients_)
|
||||
c->send_cover_state(obj);
|
||||
}
|
||||
#endif
|
||||
@ -192,7 +191,7 @@ void APIServer::on_cover_update(cover::Cover *obj) {
|
||||
void APIServer::on_fan_update(fan::FanState *obj) {
|
||||
if (obj->is_internal())
|
||||
return;
|
||||
for (auto *c : this->clients_)
|
||||
for (auto &c : this->clients_)
|
||||
c->send_fan_state(obj);
|
||||
}
|
||||
#endif
|
||||
@ -201,7 +200,7 @@ void APIServer::on_fan_update(fan::FanState *obj) {
|
||||
void APIServer::on_light_update(light::LightState *obj) {
|
||||
if (obj->is_internal())
|
||||
return;
|
||||
for (auto *c : this->clients_)
|
||||
for (auto &c : this->clients_)
|
||||
c->send_light_state(obj);
|
||||
}
|
||||
#endif
|
||||
@ -210,7 +209,7 @@ void APIServer::on_light_update(light::LightState *obj) {
|
||||
void APIServer::on_sensor_update(sensor::Sensor *obj, float state) {
|
||||
if (obj->is_internal())
|
||||
return;
|
||||
for (auto *c : this->clients_)
|
||||
for (auto &c : this->clients_)
|
||||
c->send_sensor_state(obj, state);
|
||||
}
|
||||
#endif
|
||||
@ -219,7 +218,7 @@ void APIServer::on_sensor_update(sensor::Sensor *obj, float state) {
|
||||
void APIServer::on_switch_update(switch_::Switch *obj, bool state) {
|
||||
if (obj->is_internal())
|
||||
return;
|
||||
for (auto *c : this->clients_)
|
||||
for (auto &c : this->clients_)
|
||||
c->send_switch_state(obj, state);
|
||||
}
|
||||
#endif
|
||||
@ -228,7 +227,7 @@ void APIServer::on_switch_update(switch_::Switch *obj, bool state) {
|
||||
void APIServer::on_text_sensor_update(text_sensor::TextSensor *obj, const std::string &state) {
|
||||
if (obj->is_internal())
|
||||
return;
|
||||
for (auto *c : this->clients_)
|
||||
for (auto &c : this->clients_)
|
||||
c->send_text_sensor_state(obj, state);
|
||||
}
|
||||
#endif
|
||||
@ -237,7 +236,7 @@ void APIServer::on_text_sensor_update(text_sensor::TextSensor *obj, const std::s
|
||||
void APIServer::on_climate_update(climate::Climate *obj) {
|
||||
if (obj->is_internal())
|
||||
return;
|
||||
for (auto *c : this->clients_)
|
||||
for (auto &c : this->clients_)
|
||||
c->send_climate_state(obj);
|
||||
}
|
||||
#endif
|
||||
@ -246,7 +245,7 @@ void APIServer::on_climate_update(climate::Climate *obj) {
|
||||
void APIServer::on_number_update(number::Number *obj, float state) {
|
||||
if (obj->is_internal())
|
||||
return;
|
||||
for (auto *c : this->clients_)
|
||||
for (auto &c : this->clients_)
|
||||
c->send_number_state(obj, state);
|
||||
}
|
||||
#endif
|
||||
@ -255,7 +254,7 @@ void APIServer::on_number_update(number::Number *obj, float state) {
|
||||
void APIServer::on_select_update(select::Select *obj, const std::string &state) {
|
||||
if (obj->is_internal())
|
||||
return;
|
||||
for (auto *c : this->clients_)
|
||||
for (auto &c : this->clients_)
|
||||
c->send_select_state(obj, state);
|
||||
}
|
||||
#endif
|
||||
@ -266,7 +265,7 @@ APIServer *global_api_server = nullptr; // NOLINT(cppcoreguidelines-avoid-non-c
|
||||
|
||||
void APIServer::set_password(const std::string &password) { this->password_ = password; }
|
||||
void APIServer::send_homeassistant_service_call(const HomeassistantServiceResponse &call) {
|
||||
for (auto *client : this->clients_) {
|
||||
for (auto &client : this->clients_) {
|
||||
client->send_homeassistant_service_call(call);
|
||||
}
|
||||
}
|
||||
@ -286,7 +285,7 @@ uint16_t APIServer::get_port() const { return this->port_; }
|
||||
void APIServer::set_reboot_timeout(uint32_t reboot_timeout) { this->reboot_timeout_ = reboot_timeout; }
|
||||
#ifdef USE_HOMEASSISTANT_TIME
|
||||
void APIServer::request_time() {
|
||||
for (auto *client : this->clients_) {
|
||||
for (auto &client : this->clients_) {
|
||||
if (!client->remove_ && client->connection_state_ == APIConnection::ConnectionState::CONNECTED)
|
||||
client->send_time_request();
|
||||
}
|
||||
@ -294,7 +293,7 @@ void APIServer::request_time() {
|
||||
#endif
|
||||
bool APIServer::is_connected() const { return !this->clients_.empty(); }
|
||||
void APIServer::on_shutdown() {
|
||||
for (auto *c : this->clients_) {
|
||||
for (auto &c : this->clients_) {
|
||||
c->send_disconnect_request(DisconnectRequest());
|
||||
}
|
||||
delay(10);
|
||||
|
@ -32,7 +32,7 @@ class APIServer : public Component, public Controller {
|
||||
void set_reboot_timeout(uint32_t reboot_timeout);
|
||||
|
||||
#ifdef USE_API_NOISE
|
||||
void set_noise_psk(psk_t psk) { noise_ctx_->set_psk(std::move(psk)); }
|
||||
void set_noise_psk(psk_t psk) { noise_ctx_->set_psk(psk); }
|
||||
std::shared_ptr<APINoiseContext> get_noise_ctx() { return noise_ctx_; }
|
||||
#endif // USE_API_NOISE
|
||||
|
||||
@ -91,7 +91,7 @@ class APIServer : public Component, public Controller {
|
||||
uint16_t port_{6053};
|
||||
uint32_t reboot_timeout_{300000};
|
||||
uint32_t last_connected_{0};
|
||||
std::vector<APIConnection *> clients_;
|
||||
std::vector<std::unique_ptr<APIConnection>> clients_;
|
||||
std::string password_;
|
||||
std::vector<HomeAssistantStateSubscription> state_subs_;
|
||||
std::vector<UserServiceDescriptor *> user_services_;
|
||||
|
@ -49,7 +49,7 @@ class CustomAPIDevice {
|
||||
template<typename T, typename... Ts>
|
||||
void register_service(void (T::*callback)(Ts...), const std::string &name,
|
||||
const std::array<std::string, sizeof...(Ts)> &arg_names) {
|
||||
auto *service = new CustomAPIDeviceService<T, Ts...>(name, arg_names, (T *) this, callback);
|
||||
auto *service = new CustomAPIDeviceService<T, Ts...>(name, arg_names, (T *) this, callback); // NOLINT
|
||||
global_api_server->register_user_service(service);
|
||||
}
|
||||
|
||||
@ -72,7 +72,7 @@ class CustomAPIDevice {
|
||||
* @param name The name of the arguments for the service, must match the arguments of the function.
|
||||
*/
|
||||
template<typename T> void register_service(void (T::*callback)(), const std::string &name) {
|
||||
auto *service = new CustomAPIDeviceService<T>(name, {}, (T *) this, callback);
|
||||
auto *service = new CustomAPIDeviceService<T>(name, {}, (T *) this, callback); // NOLINT
|
||||
global_api_server->register_user_service(service);
|
||||
}
|
||||
|
||||
|
@ -246,6 +246,7 @@ class ProtoWriteBuffer {
|
||||
|
||||
class ProtoMessage {
|
||||
public:
|
||||
virtual ~ProtoMessage() = default;
|
||||
virtual void encode(ProtoWriteBuffer buffer) const = 0;
|
||||
void decode(const uint8_t *buffer, size_t length);
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
|
@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/binary_sensor/binary_sensor.h"
|
||||
|
||||
|
@ -25,8 +25,12 @@ void I2CAS3935Component::write_register(uint8_t reg, uint8_t mask, uint8_t bits,
|
||||
|
||||
uint8_t I2CAS3935Component::read_register(uint8_t reg) {
|
||||
uint8_t value;
|
||||
if (!this->read_byte(reg, &value, 2)) {
|
||||
ESP_LOGW(TAG, "Read failed!");
|
||||
if (write(®, 1) != i2c::ERROR_OK) {
|
||||
ESP_LOGW(TAG, "Writing register failed!");
|
||||
return 0;
|
||||
}
|
||||
if (read(&value, 1) != i2c::ERROR_OK) {
|
||||
ESP_LOGW(TAG, "Reading register failed!");
|
||||
return 0;
|
||||
}
|
||||
return value;
|
||||
|
@ -1,9 +1,15 @@
|
||||
# Dummy integration to allow relying on AsyncTCP
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.core import CORE, coroutine_with_priority
|
||||
|
||||
CODEOWNERS = ["@OttoWinter"]
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.Schema({}),
|
||||
cv.only_with_arduino,
|
||||
)
|
||||
|
||||
|
||||
@coroutine_with_priority(200.0)
|
||||
async def to_code(config):
|
||||
@ -12,4 +18,4 @@ async def to_code(config):
|
||||
cg.add_library("esphome/AsyncTCP-esphome", "1.2.2")
|
||||
elif CORE.is_esp8266:
|
||||
# https://github.com/OttoWinter/ESPAsyncTCP
|
||||
cg.add_library("ESPAsyncTCP-esphome", "1.2.3")
|
||||
cg.add_library("ottowinter/ESPAsyncTCP-esphome", "1.2.3")
|
||||
|
@ -1,7 +1,7 @@
|
||||
#include "atc_mithermometer.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#ifdef USE_ESP32
|
||||
|
||||
namespace esphome {
|
||||
namespace atc_mithermometer {
|
||||
@ -25,14 +25,14 @@ bool ATCMiThermometer::parse_device(const esp32_ble_tracker::ESPBTDevice &device
|
||||
|
||||
bool success = false;
|
||||
for (auto &service_data : device.get_service_datas()) {
|
||||
auto res = parse_header(service_data);
|
||||
if (res->is_duplicate) {
|
||||
auto res = parse_header_(service_data);
|
||||
if (!res.has_value()) {
|
||||
continue;
|
||||
}
|
||||
if (!(parse_message(service_data.data, *res))) {
|
||||
if (!(parse_message_(service_data.data, *res))) {
|
||||
continue;
|
||||
}
|
||||
if (!(report_results(res, device.address_str()))) {
|
||||
if (!(report_results_(res, device.address_str()))) {
|
||||
continue;
|
||||
}
|
||||
if (res->temperature.has_value() && this->temperature_ != nullptr)
|
||||
@ -46,14 +46,10 @@ bool ATCMiThermometer::parse_device(const esp32_ble_tracker::ESPBTDevice &device
|
||||
success = true;
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
return success;
|
||||
}
|
||||
|
||||
optional<ParseResult> ATCMiThermometer::parse_header(const esp32_ble_tracker::ServiceData &service_data) {
|
||||
optional<ParseResult> ATCMiThermometer::parse_header_(const esp32_ble_tracker::ServiceData &service_data) {
|
||||
ParseResult result;
|
||||
if (!service_data.uuid.contains(0x1A, 0x18)) {
|
||||
ESP_LOGVV(TAG, "parse_header(): no service data UUID magic bytes.");
|
||||
@ -64,17 +60,15 @@ optional<ParseResult> ATCMiThermometer::parse_header(const esp32_ble_tracker::Se
|
||||
|
||||
static uint8_t last_frame_count = 0;
|
||||
if (last_frame_count == raw[12]) {
|
||||
ESP_LOGVV(TAG, "parse_header(): duplicate data packet received (%d).", static_cast<int>(last_frame_count));
|
||||
result.is_duplicate = true;
|
||||
ESP_LOGVV(TAG, "parse_header(): duplicate data packet received (%hhu).", last_frame_count);
|
||||
return {};
|
||||
}
|
||||
last_frame_count = raw[12];
|
||||
result.is_duplicate = false;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool ATCMiThermometer::parse_message(const std::vector<uint8_t> &message, ParseResult &result) {
|
||||
bool ATCMiThermometer::parse_message_(const std::vector<uint8_t> &message, ParseResult &result) {
|
||||
// Byte 0-5 mac in correct order
|
||||
// Byte 6-7 Temperature in uint16
|
||||
// Byte 8 Humidity in percent
|
||||
@ -107,7 +101,7 @@ bool ATCMiThermometer::parse_message(const std::vector<uint8_t> &message, ParseR
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ATCMiThermometer::report_results(const optional<ParseResult> &result, const std::string &address) {
|
||||
bool ATCMiThermometer::report_results_(const optional<ParseResult> &result, const std::string &address) {
|
||||
if (!result.has_value()) {
|
||||
ESP_LOGVV(TAG, "report_results(): no results available.");
|
||||
return false;
|
||||
|
@ -4,7 +4,7 @@
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#ifdef USE_ESP32
|
||||
|
||||
namespace esphome {
|
||||
namespace atc_mithermometer {
|
||||
@ -14,7 +14,6 @@ struct ParseResult {
|
||||
optional<float> humidity;
|
||||
optional<float> battery_level;
|
||||
optional<float> battery_voltage;
|
||||
bool is_duplicate;
|
||||
int raw_offset;
|
||||
};
|
||||
|
||||
@ -37,9 +36,9 @@ class ATCMiThermometer : public Component, public esp32_ble_tracker::ESPBTDevice
|
||||
sensor::Sensor *battery_level_{nullptr};
|
||||
sensor::Sensor *battery_voltage_{nullptr};
|
||||
|
||||
optional<ParseResult> parse_header(const esp32_ble_tracker::ServiceData &service_data);
|
||||
bool parse_message(const std::vector<uint8_t> &message, ParseResult &result);
|
||||
bool report_results(const optional<ParseResult> &result, const std::string &address);
|
||||
optional<ParseResult> parse_header_(const esp32_ble_tracker::ServiceData &service_data);
|
||||
bool parse_message_(const std::vector<uint8_t> &message, ParseResult &result);
|
||||
bool report_results_(const optional<ParseResult> &result, const std::string &address);
|
||||
};
|
||||
|
||||
} // namespace atc_mithermometer
|
||||
|
@ -265,27 +265,57 @@ float ATM90E32Component::get_power_factor_c_() {
|
||||
}
|
||||
float ATM90E32Component::get_forward_active_energy_a_() {
|
||||
uint16_t val = this->read16_(ATM90E32_REGISTER_APENERGYA);
|
||||
return (float) val * 10 / 3200; // convert register value to WattHours
|
||||
if ((UINT32_MAX - this->phase_[0].cumulative_forward_active_energy_) > val) {
|
||||
this->phase_[0].cumulative_forward_active_energy_ += val;
|
||||
} else {
|
||||
this->phase_[0].cumulative_forward_active_energy_ = val;
|
||||
}
|
||||
return ((float) this->phase_[0].cumulative_forward_active_energy_ * 10 / 3200);
|
||||
}
|
||||
float ATM90E32Component::get_forward_active_energy_b_() {
|
||||
uint16_t val = this->read16_(ATM90E32_REGISTER_APENERGYB);
|
||||
return (float) val * 10 / 3200;
|
||||
if (UINT32_MAX - this->phase_[1].cumulative_forward_active_energy_ > val) {
|
||||
this->phase_[1].cumulative_forward_active_energy_ += val;
|
||||
} else {
|
||||
this->phase_[1].cumulative_forward_active_energy_ = val;
|
||||
}
|
||||
return ((float) this->phase_[1].cumulative_forward_active_energy_ * 10 / 3200);
|
||||
}
|
||||
float ATM90E32Component::get_forward_active_energy_c_() {
|
||||
uint16_t val = this->read16_(ATM90E32_REGISTER_APENERGYC);
|
||||
return (float) val * 10 / 3200;
|
||||
if (UINT32_MAX - this->phase_[2].cumulative_forward_active_energy_ > val) {
|
||||
this->phase_[2].cumulative_forward_active_energy_ += val;
|
||||
} else {
|
||||
this->phase_[2].cumulative_forward_active_energy_ = val;
|
||||
}
|
||||
return ((float) this->phase_[2].cumulative_forward_active_energy_ * 10 / 3200);
|
||||
}
|
||||
float ATM90E32Component::get_reverse_active_energy_a_() {
|
||||
uint16_t val = this->read16_(ATM90E32_REGISTER_ANENERGYA);
|
||||
return (float) val * 10 / 3200;
|
||||
if (UINT32_MAX - this->phase_[0].cumulative_reverse_active_energy_ > val) {
|
||||
this->phase_[0].cumulative_reverse_active_energy_ += val;
|
||||
} else {
|
||||
this->phase_[0].cumulative_reverse_active_energy_ = val;
|
||||
}
|
||||
return ((float) this->phase_[0].cumulative_reverse_active_energy_ * 10 / 3200);
|
||||
}
|
||||
float ATM90E32Component::get_reverse_active_energy_b_() {
|
||||
uint16_t val = this->read16_(ATM90E32_REGISTER_ANENERGYB);
|
||||
return (float) val * 10 / 3200;
|
||||
if (UINT32_MAX - this->phase_[1].cumulative_reverse_active_energy_ > val) {
|
||||
this->phase_[1].cumulative_reverse_active_energy_ += val;
|
||||
} else {
|
||||
this->phase_[1].cumulative_reverse_active_energy_ = val;
|
||||
}
|
||||
return ((float) this->phase_[1].cumulative_reverse_active_energy_ * 10 / 3200);
|
||||
}
|
||||
float ATM90E32Component::get_reverse_active_energy_c_() {
|
||||
uint16_t val = this->read16_(ATM90E32_REGISTER_ANENERGYC);
|
||||
return (float) val * 10 / 3200;
|
||||
if (UINT32_MAX - this->phase_[2].cumulative_reverse_active_energy_ > val) {
|
||||
this->phase_[2].cumulative_reverse_active_energy_ += val;
|
||||
} else {
|
||||
this->phase_[2].cumulative_reverse_active_energy_ = val;
|
||||
}
|
||||
return ((float) this->phase_[2].cumulative_reverse_active_energy_ * 10 / 3200);
|
||||
}
|
||||
float ATM90E32Component::get_frequency_() {
|
||||
uint16_t freq = this->read16_(ATM90E32_REGISTER_FREQ);
|
||||
|
@ -77,6 +77,8 @@ class ATM90E32Component : public PollingComponent,
|
||||
sensor::Sensor *power_factor_sensor_{nullptr};
|
||||
sensor::Sensor *forward_active_energy_sensor_{nullptr};
|
||||
sensor::Sensor *reverse_active_energy_sensor_{nullptr};
|
||||
uint32_t cumulative_forward_active_energy_{0};
|
||||
uint32_t cumulative_reverse_active_energy_{0};
|
||||
} phase_[3];
|
||||
sensor::Sensor *freq_sensor_{nullptr};
|
||||
sensor::Sensor *chip_temperature_sensor_{nullptr};
|
||||
|
@ -1,7 +1,7 @@
|
||||
#include "b_parasite.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#ifdef USE_ESP32
|
||||
|
||||
namespace esphome {
|
||||
namespace b_parasite {
|
||||
@ -14,6 +14,7 @@ void BParasite::dump_config() {
|
||||
LOG_SENSOR(" ", "Temperature", this->temperature_);
|
||||
LOG_SENSOR(" ", "Humidity", this->humidity_);
|
||||
LOG_SENSOR(" ", "Soil Moisture", this->soil_moisture_);
|
||||
LOG_SENSOR(" ", "Illuminance", this->illuminance_);
|
||||
}
|
||||
|
||||
bool BParasite::parse_device(const esp32_ble_tracker::ESPBTDevice &device) {
|
||||
@ -36,6 +37,15 @@ bool BParasite::parse_device(const esp32_ble_tracker::ESPBTDevice &device) {
|
||||
|
||||
const auto &data = service_data.data;
|
||||
|
||||
const uint8_t protocol_version = data[0] >> 4;
|
||||
if (protocol_version != 1) {
|
||||
ESP_LOGE(TAG, "Unsupported protocol version: %u", protocol_version);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Some b-parasite versions have an (optional) illuminance sensor.
|
||||
bool has_illuminance = data[0] & 0x1;
|
||||
|
||||
// Counter for deduplicating messages.
|
||||
uint8_t counter = data[1] & 0x0f;
|
||||
if (last_processed_counter_ == counter) {
|
||||
@ -59,6 +69,9 @@ bool BParasite::parse_device(const esp32_ble_tracker::ESPBTDevice &device) {
|
||||
uint16_t soil_moisture = data[8] << 8 | data[9];
|
||||
float moisture_percent = (100.0f * soil_moisture) / (1 << 16);
|
||||
|
||||
// Ambient light in lux.
|
||||
float illuminance = has_illuminance ? data[16] << 8 | data[17] : 0.0f;
|
||||
|
||||
if (battery_voltage_ != nullptr) {
|
||||
battery_voltage_->publish_state(battery_voltage);
|
||||
}
|
||||
@ -71,6 +84,13 @@ bool BParasite::parse_device(const esp32_ble_tracker::ESPBTDevice &device) {
|
||||
if (soil_moisture_ != nullptr) {
|
||||
soil_moisture_->publish_state(moisture_percent);
|
||||
}
|
||||
if (illuminance_ != nullptr) {
|
||||
if (has_illuminance) {
|
||||
illuminance_->publish_state(illuminance);
|
||||
} else {
|
||||
ESP_LOGE(TAG, "No lux information is present in the BLE packet");
|
||||
}
|
||||
}
|
||||
|
||||
last_processed_counter_ = counter;
|
||||
return true;
|
||||
@ -79,4 +99,4 @@ bool BParasite::parse_device(const esp32_ble_tracker::ESPBTDevice &device) {
|
||||
} // namespace b_parasite
|
||||
} // namespace esphome
|
||||
|
||||
#endif // ARDUINO_ARCH_ESP32
|
||||
#endif // USE_ESP32
|
||||
|
@ -4,7 +4,7 @@
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#ifdef USE_ESP32
|
||||
|
||||
namespace esphome {
|
||||
namespace b_parasite {
|
||||
@ -22,6 +22,7 @@ class BParasite : public Component, public esp32_ble_tracker::ESPBTDeviceListene
|
||||
void set_temperature(sensor::Sensor *temperature) { temperature_ = temperature; }
|
||||
void set_humidity(sensor::Sensor *humidity) { humidity_ = humidity; }
|
||||
void set_soil_moisture(sensor::Sensor *soil_moisture) { soil_moisture_ = soil_moisture; }
|
||||
void set_illuminance(sensor::Sensor *illuminance) { illuminance_ = illuminance; }
|
||||
|
||||
protected:
|
||||
// The received advertisement packet contains an unsigned 4 bits wrap-around counter
|
||||
@ -32,9 +33,10 @@ class BParasite : public Component, public esp32_ble_tracker::ESPBTDeviceListene
|
||||
sensor::Sensor *temperature_{nullptr};
|
||||
sensor::Sensor *humidity_{nullptr};
|
||||
sensor::Sensor *soil_moisture_{nullptr};
|
||||
sensor::Sensor *illuminance_{nullptr};
|
||||
};
|
||||
|
||||
} // namespace b_parasite
|
||||
} // namespace esphome
|
||||
|
||||
#endif // ARDUINO_ARCH_ESP32
|
||||
#endif // USE_ESP32
|
||||
|
@ -5,14 +5,17 @@ from esphome.const import (
|
||||
CONF_BATTERY_VOLTAGE,
|
||||
CONF_HUMIDITY,
|
||||
CONF_ID,
|
||||
CONF_ILLUMINANCE,
|
||||
CONF_MOISTURE,
|
||||
CONF_MAC_ADDRESS,
|
||||
CONF_TEMPERATURE,
|
||||
DEVICE_CLASS_HUMIDITY,
|
||||
DEVICE_CLASS_ILLUMINANCE,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
DEVICE_CLASS_VOLTAGE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_CELSIUS,
|
||||
UNIT_LUX,
|
||||
UNIT_PERCENT,
|
||||
UNIT_VOLT,
|
||||
)
|
||||
@ -55,6 +58,12 @@ CONFIG_SCHEMA = (
|
||||
device_class=DEVICE_CLASS_HUMIDITY,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_ILLUMINANCE): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_LUX,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_ILLUMINANCE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
}
|
||||
)
|
||||
.extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA)
|
||||
@ -74,6 +83,7 @@ async def to_code(config):
|
||||
(CONF_HUMIDITY, var.set_humidity),
|
||||
(CONF_BATTERY_VOLTAGE, var.set_battery_voltage),
|
||||
(CONF_MOISTURE, var.set_soil_moisture),
|
||||
(CONF_ILLUMINANCE, var.set_illuminance),
|
||||
]:
|
||||
if config_key in config:
|
||||
sens = await sensor.new_sensor(config[config_key])
|
||||
|
@ -69,7 +69,8 @@ void BangBangClimate::compute_state_() {
|
||||
this->switch_to_action_(climate::CLIMATE_ACTION_OFF);
|
||||
return;
|
||||
}
|
||||
if (isnan(this->current_temperature) || isnan(this->target_temperature_low) || isnan(this->target_temperature_high)) {
|
||||
if (std::isnan(this->current_temperature) || std::isnan(this->target_temperature_low) ||
|
||||
std::isnan(this->target_temperature_high)) {
|
||||
// if any control parameters are nan, go to OFF action (not IDLE!)
|
||||
this->switch_to_action_(climate::CLIMATE_ACTION_OFF);
|
||||
return;
|
||||
|
@ -71,10 +71,11 @@ void BH1750Sensor::update() {
|
||||
float BH1750Sensor::get_setup_priority() const { return setup_priority::DATA; }
|
||||
void BH1750Sensor::read_data_() {
|
||||
uint16_t raw_value;
|
||||
if (!this->parent_->raw_receive_16(this->address_, &raw_value, 1)) {
|
||||
if (this->read(reinterpret_cast<uint8_t *>(&raw_value), 2) != i2c::ERROR_OK) {
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
raw_value = i2c::i2ctohs(raw_value);
|
||||
|
||||
float lx = float(raw_value) / 1.2f;
|
||||
lx *= 69.0f / this->measurement_duration_;
|
||||
|
@ -1,15 +1,14 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.cpp_helpers import setup_entity
|
||||
from esphome import automation, core
|
||||
from esphome.automation import Condition, maybe_simple_id
|
||||
from esphome.components import mqtt
|
||||
from esphome.const import (
|
||||
CONF_DELAY,
|
||||
CONF_DEVICE_CLASS,
|
||||
CONF_DISABLED_BY_DEFAULT,
|
||||
CONF_FILTERS,
|
||||
CONF_ID,
|
||||
CONF_INTERNAL,
|
||||
CONF_INVALID_COOLDOWN,
|
||||
CONF_INVERTED,
|
||||
CONF_MAX_LENGTH,
|
||||
@ -88,7 +87,7 @@ DEVICE_CLASSES = [
|
||||
IS_PLATFORM_COMPONENT = True
|
||||
|
||||
binary_sensor_ns = cg.esphome_ns.namespace("binary_sensor")
|
||||
BinarySensor = binary_sensor_ns.class_("BinarySensor", cg.Nameable)
|
||||
BinarySensor = binary_sensor_ns.class_("BinarySensor", cg.EntityBase)
|
||||
BinarySensorInitiallyOff = binary_sensor_ns.class_(
|
||||
"BinarySensorInitiallyOff", BinarySensor
|
||||
)
|
||||
@ -231,17 +230,16 @@ def parse_multi_click_timing_str(value):
|
||||
parts = value.lower().split(" ")
|
||||
if len(parts) != 5:
|
||||
raise cv.Invalid(
|
||||
"Multi click timing grammar consists of exactly 5 words, not {}"
|
||||
"".format(len(parts))
|
||||
f"Multi click timing grammar consists of exactly 5 words, not {len(parts)}"
|
||||
)
|
||||
try:
|
||||
state = cv.boolean(parts[0])
|
||||
except cv.Invalid:
|
||||
# pylint: disable=raise-missing-from
|
||||
raise cv.Invalid("First word must either be ON or OFF, not {}".format(parts[0]))
|
||||
raise cv.Invalid(f"First word must either be ON or OFF, not {parts[0]}")
|
||||
|
||||
if parts[1] != "for":
|
||||
raise cv.Invalid("Second word must be 'for', got {}".format(parts[1]))
|
||||
raise cv.Invalid(f"Second word must be 'for', got {parts[1]}")
|
||||
|
||||
if parts[2] == "at":
|
||||
if parts[3] == "least":
|
||||
@ -250,8 +248,7 @@ def parse_multi_click_timing_str(value):
|
||||
key = CONF_MAX_LENGTH
|
||||
else:
|
||||
raise cv.Invalid(
|
||||
"Third word after at must either be 'least' or 'most', got {}"
|
||||
"".format(parts[3])
|
||||
f"Third word after at must either be 'least' or 'most', got {parts[3]}"
|
||||
)
|
||||
try:
|
||||
length = cv.positive_time_period_milliseconds(parts[4])
|
||||
@ -296,13 +293,11 @@ def validate_multi_click_timing(value):
|
||||
new_state = v_.get(CONF_STATE, not state)
|
||||
if new_state == state:
|
||||
raise cv.Invalid(
|
||||
"Timings must have alternating state. Indices {} and {} have "
|
||||
"the same state {}".format(i, i + 1, state)
|
||||
f"Timings must have alternating state. Indices {i} and {i + 1} have the same state {state}"
|
||||
)
|
||||
if max_length is not None and max_length < min_length:
|
||||
raise cv.Invalid(
|
||||
"Max length ({}) must be larger than min length ({})."
|
||||
"".format(max_length, min_length)
|
||||
f"Max length ({max_length}) must be larger than min length ({min_length})."
|
||||
)
|
||||
|
||||
state = new_state
|
||||
@ -318,7 +313,7 @@ def validate_multi_click_timing(value):
|
||||
|
||||
device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space="_")
|
||||
|
||||
BINARY_SENSOR_SCHEMA = cv.NAMEABLE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend(
|
||||
BINARY_SENSOR_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(BinarySensor),
|
||||
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(
|
||||
@ -379,10 +374,8 @@ BINARY_SENSOR_SCHEMA = cv.NAMEABLE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).exten
|
||||
|
||||
|
||||
async def setup_binary_sensor_core_(var, config):
|
||||
cg.add(var.set_name(config[CONF_NAME]))
|
||||
cg.add(var.set_disabled_by_default(config[CONF_DISABLED_BY_DEFAULT]))
|
||||
if CONF_INTERNAL in config:
|
||||
cg.add(var.set_internal(config[CONF_INTERNAL]))
|
||||
await setup_entity(var, config)
|
||||
|
||||
if CONF_DEVICE_CLASS in config:
|
||||
cg.add(var.set_device_class(config[CONF_DEVICE_CLASS]))
|
||||
if CONF_INVERTED in config:
|
||||
|
@ -4,6 +4,7 @@
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/components/binary_sensor/binary_sensor.h"
|
||||
|
||||
namespace esphome {
|
||||
|
@ -42,7 +42,7 @@ void BinarySensor::send_state_internal(bool state, bool is_initial) {
|
||||
}
|
||||
}
|
||||
std::string BinarySensor::device_class() { return ""; }
|
||||
BinarySensor::BinarySensor(const std::string &name) : Nameable(name), state(false) {}
|
||||
BinarySensor::BinarySensor(const std::string &name) : EntityBase(name), state(false) {}
|
||||
BinarySensor::BinarySensor() : BinarySensor("") {}
|
||||
void BinarySensor::set_device_class(const std::string &device_class) { this->device_class_ = device_class; }
|
||||
std::string BinarySensor::get_device_class() {
|
||||
|
@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/entity_base.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/components/binary_sensor/filter.h"
|
||||
|
||||
@ -10,7 +11,7 @@ namespace binary_sensor {
|
||||
|
||||
#define LOG_BINARY_SENSOR(prefix, type, obj) \
|
||||
if ((obj) != nullptr) { \
|
||||
ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, type, (obj)->get_name().c_str()); \
|
||||
ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, LOG_STR_LITERAL(type), (obj)->get_name().c_str()); \
|
||||
if (!(obj)->get_device_class().empty()) { \
|
||||
ESP_LOGCONFIG(TAG, "%s Device Class: '%s'", prefix, (obj)->get_device_class().c_str()); \
|
||||
} \
|
||||
@ -22,7 +23,7 @@ namespace binary_sensor {
|
||||
* The sub classes should notify the front-end of new states via the publish_state() method which
|
||||
* handles inverted inputs for you.
|
||||
*/
|
||||
class BinarySensor : public Nameable {
|
||||
class BinarySensor : public EntityBase {
|
||||
public:
|
||||
explicit BinarySensor();
|
||||
/** Construct a binary sensor with the specified name
|
||||
|
@ -3,7 +3,7 @@
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/components/ble_client/ble_client.h"
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#ifdef USE_ESP32
|
||||
|
||||
namespace esphome {
|
||||
namespace ble_client {
|
||||
@ -11,11 +11,12 @@ class BLEClientConnectTrigger : public Trigger<>, public BLEClientNode {
|
||||
public:
|
||||
explicit BLEClientConnectTrigger(BLEClient *parent) { parent->register_ble_node(this); }
|
||||
void loop() override {}
|
||||
void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) {
|
||||
void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
||||
esp_ble_gattc_cb_param_t *param) override {
|
||||
if (event == ESP_GATTC_OPEN_EVT && param->open.status == ESP_GATT_OK)
|
||||
this->trigger();
|
||||
if (event == ESP_GATTC_SEARCH_CMPL_EVT)
|
||||
this->node_state = espbt::ClientState::Established;
|
||||
this->node_state = espbt::ClientState::ESTABLISHED;
|
||||
}
|
||||
};
|
||||
|
||||
@ -23,11 +24,12 @@ class BLEClientDisconnectTrigger : public Trigger<>, public BLEClientNode {
|
||||
public:
|
||||
explicit BLEClientDisconnectTrigger(BLEClient *parent) { parent->register_ble_node(this); }
|
||||
void loop() override {}
|
||||
void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) {
|
||||
void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
||||
esp_ble_gattc_cb_param_t *param) override {
|
||||
if (event == ESP_GATTC_DISCONNECT_EVT && memcmp(param->disconnect.remote_bda, this->parent_->remote_bda, 6) == 0)
|
||||
this->trigger();
|
||||
if (event == ESP_GATTC_SEARCH_CMPL_EVT)
|
||||
this->node_state = espbt::ClientState::Established;
|
||||
this->node_state = espbt::ClientState::ESTABLISHED;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -4,7 +4,7 @@
|
||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
|
||||
#include "ble_client.h"
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#ifdef USE_ESP32
|
||||
|
||||
namespace esphome {
|
||||
namespace ble_client {
|
||||
@ -17,12 +17,12 @@ void BLEClient::setup() {
|
||||
ESP_LOGE(TAG, "gattc app register failed. app_id=%d code=%d", this->app_id, ret);
|
||||
this->mark_failed();
|
||||
}
|
||||
this->set_states(espbt::ClientState::Idle);
|
||||
this->set_states_(espbt::ClientState::IDLE);
|
||||
this->enabled = true;
|
||||
}
|
||||
|
||||
void BLEClient::loop() {
|
||||
if (this->state() == espbt::ClientState::Discovered) {
|
||||
if (this->state() == espbt::ClientState::DISCOVERED) {
|
||||
this->connect();
|
||||
}
|
||||
for (auto *node : this->nodes_)
|
||||
@ -39,11 +39,11 @@ bool BLEClient::parse_device(const espbt::ESPBTDevice &device) {
|
||||
return false;
|
||||
if (device.address_uint64() != this->address)
|
||||
return false;
|
||||
if (this->state() != espbt::ClientState::Idle)
|
||||
if (this->state() != espbt::ClientState::IDLE)
|
||||
return false;
|
||||
|
||||
ESP_LOGD(TAG, "Found device at MAC address [%s]", device.address_str().c_str());
|
||||
this->set_states(espbt::ClientState::Discovered);
|
||||
this->set_states_(espbt::ClientState::DISCOVERED);
|
||||
|
||||
auto addr = device.address_uint64();
|
||||
this->remote_bda[0] = (addr >> 40) & 0xFF;
|
||||
@ -69,7 +69,7 @@ std::string BLEClient::address_str() const {
|
||||
void BLEClient::set_enabled(bool enabled) {
|
||||
if (enabled == this->enabled)
|
||||
return;
|
||||
if (!enabled && this->state() != espbt::ClientState::Idle) {
|
||||
if (!enabled && this->state() != espbt::ClientState::IDLE) {
|
||||
ESP_LOGI(TAG, "[%s] Disabling BLE client.", this->address_str().c_str());
|
||||
auto ret = esp_ble_gattc_close(this->gattc_if, this->conn_id);
|
||||
if (ret) {
|
||||
@ -84,9 +84,9 @@ void BLEClient::connect() {
|
||||
auto ret = esp_ble_gattc_open(this->gattc_if, this->remote_bda, BLE_ADDR_TYPE_PUBLIC, true);
|
||||
if (ret) {
|
||||
ESP_LOGW(TAG, "esp_ble_gattc_open error, address=%s status=%d", this->address_str().c_str(), ret);
|
||||
this->set_states(espbt::ClientState::Idle);
|
||||
this->set_states_(espbt::ClientState::IDLE);
|
||||
} else {
|
||||
this->set_states(espbt::ClientState::Connecting);
|
||||
this->set_states_(espbt::ClientState::CONNECTING);
|
||||
}
|
||||
}
|
||||
|
||||
@ -97,7 +97,7 @@ void BLEClient::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t es
|
||||
if (event != ESP_GATTC_REG_EVT && esp_gattc_if != ESP_GATT_IF_NONE && esp_gattc_if != this->gattc_if)
|
||||
return;
|
||||
|
||||
bool all_established = this->all_nodes_established();
|
||||
bool all_established = this->all_nodes_established_();
|
||||
|
||||
switch (event) {
|
||||
case ESP_GATTC_REG_EVT: {
|
||||
@ -113,7 +113,7 @@ void BLEClient::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t es
|
||||
ESP_LOGV(TAG, "[%s] ESP_GATTC_OPEN_EVT", this->address_str().c_str());
|
||||
if (param->open.status != ESP_GATT_OK) {
|
||||
ESP_LOGW(TAG, "connect to %s failed, status=%d", this->address_str().c_str(), param->open.status);
|
||||
this->set_states(espbt::ClientState::Idle);
|
||||
this->set_states_(espbt::ClientState::IDLE);
|
||||
break;
|
||||
}
|
||||
this->conn_id = param->open.conn_id;
|
||||
@ -126,11 +126,11 @@ void BLEClient::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t es
|
||||
case ESP_GATTC_CFG_MTU_EVT: {
|
||||
if (param->cfg_mtu.status != ESP_GATT_OK) {
|
||||
ESP_LOGW(TAG, "cfg_mtu to %s failed, status %d", this->address_str().c_str(), param->cfg_mtu.status);
|
||||
this->set_states(espbt::ClientState::Idle);
|
||||
this->set_states_(espbt::ClientState::IDLE);
|
||||
break;
|
||||
}
|
||||
ESP_LOGV(TAG, "cfg_mtu status %d, mtu %d", param->cfg_mtu.status, param->cfg_mtu.mtu);
|
||||
esp_ble_gattc_search_service(esp_gattc_if, param->cfg_mtu.conn_id, NULL);
|
||||
esp_ble_gattc_search_service(esp_gattc_if, param->cfg_mtu.conn_id, nullptr);
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_DISCONNECT_EVT: {
|
||||
@ -139,13 +139,13 @@ void BLEClient::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t es
|
||||
}
|
||||
ESP_LOGV(TAG, "[%s] ESP_GATTC_DISCONNECT_EVT", this->address_str().c_str());
|
||||
for (auto &svc : this->services_)
|
||||
delete svc;
|
||||
delete svc; // NOLINT(cppcoreguidelines-owning-memory)
|
||||
this->services_.clear();
|
||||
this->set_states(espbt::ClientState::Idle);
|
||||
this->set_states_(espbt::ClientState::IDLE);
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_SEARCH_RES_EVT: {
|
||||
BLEService *ble_service = new BLEService();
|
||||
BLEService *ble_service = new BLEService(); // NOLINT(cppcoreguidelines-owning-memory)
|
||||
ble_service->uuid = espbt::ESPBTUUID::from_uuid(param->search_res.srvc_id.uuid);
|
||||
ble_service->start_handle = param->search_res.start_handle;
|
||||
ble_service->end_handle = param->search_res.end_handle;
|
||||
@ -160,8 +160,8 @@ void BLEClient::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t es
|
||||
ESP_LOGI(TAG, " start_handle: 0x%x end_handle: 0x%x", svc->start_handle, svc->end_handle);
|
||||
svc->parse_characteristics();
|
||||
}
|
||||
this->set_states(espbt::ClientState::Connected);
|
||||
this->set_state(espbt::ClientState::Established);
|
||||
this->set_states_(espbt::ClientState::CONNECTED);
|
||||
this->set_state(espbt::ClientState::ESTABLISHED);
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
|
||||
@ -192,9 +192,9 @@ void BLEClient::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t es
|
||||
node->gattc_event_handler(event, esp_gattc_if, param);
|
||||
|
||||
// Delete characteristics after clients have used them to save RAM.
|
||||
if (!all_established && this->all_nodes_established()) {
|
||||
if (!all_established && this->all_nodes_established_()) {
|
||||
for (auto &svc : this->services_)
|
||||
delete svc;
|
||||
delete svc; // NOLINT(cppcoreguidelines-owning-memory)
|
||||
this->services_.clear();
|
||||
}
|
||||
}
|
||||
@ -307,7 +307,7 @@ BLEDescriptor *BLEClient::get_descriptor(uint16_t service, uint16_t chr, uint16_
|
||||
|
||||
BLEService::~BLEService() {
|
||||
for (auto &chr : this->characteristics)
|
||||
delete chr;
|
||||
delete chr; // NOLINT(cppcoreguidelines-owning-memory)
|
||||
}
|
||||
|
||||
void BLEService::parse_characteristics() {
|
||||
@ -329,7 +329,7 @@ void BLEService::parse_characteristics() {
|
||||
break;
|
||||
}
|
||||
|
||||
BLECharacteristic *characteristic = new BLECharacteristic();
|
||||
BLECharacteristic *characteristic = new BLECharacteristic(); // NOLINT(cppcoreguidelines-owning-memory)
|
||||
characteristic->uuid = espbt::ESPBTUUID::from_uuid(result.uuid);
|
||||
characteristic->properties = result.properties;
|
||||
characteristic->handle = result.char_handle;
|
||||
@ -344,7 +344,7 @@ void BLEService::parse_characteristics() {
|
||||
|
||||
BLECharacteristic::~BLECharacteristic() {
|
||||
for (auto &desc : this->descriptors)
|
||||
delete desc;
|
||||
delete desc; // NOLINT(cppcoreguidelines-owning-memory)
|
||||
}
|
||||
|
||||
void BLECharacteristic::parse_descriptors() {
|
||||
@ -366,7 +366,7 @@ void BLECharacteristic::parse_descriptors() {
|
||||
break;
|
||||
}
|
||||
|
||||
BLEDescriptor *desc = new BLEDescriptor();
|
||||
BLEDescriptor *desc = new BLEDescriptor(); // NOLINT(cppcoreguidelines-owning-memory)
|
||||
desc->uuid = espbt::ESPBTUUID::from_uuid(result.uuid);
|
||||
desc->handle = result.handle;
|
||||
desc->characteristic = this;
|
||||
|
@ -4,7 +4,7 @@
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include <string>
|
||||
#include <array>
|
||||
@ -82,10 +82,11 @@ class BLEClient : public espbt::ESPBTClient, public Component {
|
||||
void dump_config() override;
|
||||
void loop() override;
|
||||
|
||||
void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param);
|
||||
void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
||||
esp_ble_gattc_cb_param_t *param) override;
|
||||
bool parse_device(const espbt::ESPBTDevice &device) override;
|
||||
void on_scan_end() override {}
|
||||
void connect();
|
||||
void connect() override;
|
||||
|
||||
void set_address(uint64_t address) { this->address = address; }
|
||||
|
||||
@ -116,16 +117,16 @@ class BLEClient : public espbt::ESPBTClient, public Component {
|
||||
std::string address_str() const;
|
||||
|
||||
protected:
|
||||
void set_states(espbt::ClientState st) {
|
||||
void set_states_(espbt::ClientState st) {
|
||||
this->set_state(st);
|
||||
for (auto &node : nodes_)
|
||||
node->node_state = st;
|
||||
}
|
||||
bool all_nodes_established() {
|
||||
if (this->state() != espbt::ClientState::Established)
|
||||
bool all_nodes_established_() {
|
||||
if (this->state() != espbt::ClientState::ESTABLISHED)
|
||||
return false;
|
||||
for (auto &node : nodes_)
|
||||
if (node->node_state != espbt::ClientState::Established)
|
||||
if (node->node_state != espbt::ClientState::ESTABLISHED)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
@ -3,7 +3,7 @@
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/components/ble_client/sensor/ble_sensor.h"
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#ifdef USE_ESP32
|
||||
|
||||
namespace esphome {
|
||||
namespace ble_client {
|
||||
@ -11,10 +11,11 @@ namespace ble_client {
|
||||
class BLESensorNotifyTrigger : public Trigger<float>, public BLESensor {
|
||||
public:
|
||||
explicit BLESensorNotifyTrigger(BLESensor *sensor) { sensor_ = sensor; }
|
||||
void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) {
|
||||
void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
||||
esp_ble_gattc_cb_param_t *param) override {
|
||||
switch (event) {
|
||||
case ESP_GATTC_SEARCH_CMPL_EVT: {
|
||||
this->sensor_->node_state = espbt::ClientState::Established;
|
||||
this->sensor_->node_state = espbt::ClientState::ESTABLISHED;
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_NOTIFY_EVT: {
|
||||
|
@ -4,7 +4,7 @@
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#ifdef USE_ESP32
|
||||
|
||||
namespace esphome {
|
||||
namespace ble_client {
|
||||
@ -71,7 +71,7 @@ void BLESensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga
|
||||
ESP_LOGW(TAG, "esp_ble_gattc_register_for_notify failed, status=%d", status);
|
||||
}
|
||||
} else {
|
||||
this->node_state = espbt::ClientState::Established;
|
||||
this->node_state = espbt::ClientState::ESTABLISHED;
|
||||
}
|
||||
break;
|
||||
}
|
||||
@ -84,7 +84,7 @@ void BLESensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga
|
||||
}
|
||||
if (param->read.handle == this->handle) {
|
||||
this->status_clear_warning();
|
||||
this->publish_state(this->parse_data(param->read.value, param->read.value_len));
|
||||
this->publish_state(this->parse_data_(param->read.value, param->read.value_len));
|
||||
}
|
||||
break;
|
||||
}
|
||||
@ -93,11 +93,11 @@ void BLESensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga
|
||||
break;
|
||||
ESP_LOGV(TAG, "[%s] ESP_GATTC_NOTIFY_EVT: handle=0x%x, value=0x%x", this->get_name().c_str(),
|
||||
param->notify.handle, param->notify.value[0]);
|
||||
this->publish_state(this->parse_data(param->notify.value, param->notify.value_len));
|
||||
this->publish_state(this->parse_data_(param->notify.value, param->notify.value_len));
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
|
||||
this->node_state = espbt::ClientState::Established;
|
||||
this->node_state = espbt::ClientState::ESTABLISHED;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
@ -105,7 +105,7 @@ void BLESensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga
|
||||
}
|
||||
}
|
||||
|
||||
float BLESensor::parse_data(uint8_t *value, uint16_t value_len) {
|
||||
float BLESensor::parse_data_(uint8_t *value, uint16_t value_len) {
|
||||
if (this->data_to_value_func_.has_value()) {
|
||||
std::vector<uint8_t> data(value, value + value_len);
|
||||
return (*this->data_to_value_func_)(data);
|
||||
@ -115,7 +115,7 @@ float BLESensor::parse_data(uint8_t *value, uint16_t value_len) {
|
||||
}
|
||||
|
||||
void BLESensor::update() {
|
||||
if (this->node_state != espbt::ClientState::Established) {
|
||||
if (this->node_state != espbt::ClientState::ESTABLISHED) {
|
||||
ESP_LOGW(TAG, "[%s] Cannot poll, not connected", this->get_name().c_str());
|
||||
return;
|
||||
}
|
||||
|
@ -5,7 +5,7 @@
|
||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#ifdef USE_ESP32
|
||||
#include <esp_gattc_api.h>
|
||||
|
||||
namespace esphome {
|
||||
@ -32,13 +32,13 @@ class BLESensor : public sensor::Sensor, public PollingComponent, public BLEClie
|
||||
void set_descr_uuid16(uint16_t uuid) { this->descr_uuid_ = espbt::ESPBTUUID::from_uint16(uuid); }
|
||||
void set_descr_uuid32(uint32_t uuid) { this->descr_uuid_ = espbt::ESPBTUUID::from_uint32(uuid); }
|
||||
void set_descr_uuid128(uint8_t *uuid) { this->descr_uuid_ = espbt::ESPBTUUID::from_raw(uuid); }
|
||||
void set_data_to_value(data_to_value_t &&lambda_) { this->data_to_value_func_ = lambda_; }
|
||||
void set_data_to_value(data_to_value_t &&lambda) { this->data_to_value_func_ = lambda; }
|
||||
void set_enable_notify(bool notify) { this->notify_ = notify; }
|
||||
uint16_t handle;
|
||||
|
||||
protected:
|
||||
uint32_t hash_base() override;
|
||||
float parse_data(uint8_t *value, uint16_t value_len);
|
||||
float parse_data_(uint8_t *value, uint16_t value_len);
|
||||
optional<data_to_value_t> data_to_value_func_{};
|
||||
bool notify_;
|
||||
espbt::ESPBTUUID service_uuid_;
|
||||
|
@ -2,7 +2,7 @@
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/application.h"
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#ifdef USE_ESP32
|
||||
|
||||
namespace esphome {
|
||||
namespace ble_client {
|
||||
@ -21,10 +21,10 @@ void BLEClientSwitch::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_i
|
||||
this->publish_state(this->parent_->enabled);
|
||||
break;
|
||||
case ESP_GATTC_OPEN_EVT:
|
||||
this->node_state = espbt::ClientState::Established;
|
||||
this->node_state = espbt::ClientState::ESTABLISHED;
|
||||
break;
|
||||
case ESP_GATTC_DISCONNECT_EVT:
|
||||
this->node_state = espbt::ClientState::Idle;
|
||||
this->node_state = espbt::ClientState::IDLE;
|
||||
this->publish_state(this->parent_->enabled);
|
||||
break;
|
||||
default:
|
||||
|
@ -5,7 +5,7 @@
|
||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
|
||||
#include "esphome/components/switch/switch.h"
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#ifdef USE_ESP32
|
||||
#include <esp_gattc_api.h>
|
||||
|
||||
namespace esphome {
|
||||
|
@ -1,7 +1,7 @@
|
||||
#include "ble_presence_device.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#ifdef USE_ESP32
|
||||
|
||||
namespace esphome {
|
||||
namespace ble_presence {
|
||||
|
@ -4,7 +4,7 @@
|
||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
|
||||
#include "esphome/components/binary_sensor/binary_sensor.h"
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#ifdef USE_ESP32
|
||||
|
||||
namespace esphome {
|
||||
namespace ble_presence {
|
||||
@ -93,8 +93,8 @@ class BLEPresenceDevice : public binary_sensor::BinarySensorInitiallyOff,
|
||||
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||
|
||||
protected:
|
||||
enum MATCH_TYPE { MATCH_BY_MAC_ADDRESS, MATCH_BY_SERVICE_UUID, MATCH_BY_IBEACON_UUID };
|
||||
MATCH_TYPE match_by_;
|
||||
enum MatchType { MATCH_BY_MAC_ADDRESS, MATCH_BY_SERVICE_UUID, MATCH_BY_IBEACON_UUID };
|
||||
MatchType match_by_;
|
||||
|
||||
bool found_{false};
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
#include "ble_rssi_sensor.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#ifdef USE_ESP32
|
||||
|
||||
namespace esphome {
|
||||
namespace ble_rssi {
|
||||
|
@ -4,7 +4,7 @@
|
||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#ifdef USE_ESP32
|
||||
|
||||
namespace esphome {
|
||||
namespace ble_rssi {
|
||||
|
@ -1,7 +1,7 @@
|
||||
#include "ble_scanner.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#ifdef USE_ESP32
|
||||
|
||||
namespace esphome {
|
||||
namespace ble_scanner {
|
||||
|
@ -7,7 +7,7 @@
|
||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
|
||||
#include "esphome/components/text_sensor/text_sensor.h"
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#ifdef USE_ESP32
|
||||
|
||||
namespace esphome {
|
||||
namespace ble_scanner {
|
||||
@ -15,7 +15,7 @@ namespace ble_scanner {
|
||||
class BLEScanner : public text_sensor::TextSensor, public esp32_ble_tracker::ESPBTDeviceListener, public Component {
|
||||
public:
|
||||
bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override {
|
||||
this->publish_state("{\"timestamp\":" + to_string(::time(NULL)) +
|
||||
this->publish_state("{\"timestamp\":" + to_string(::time(nullptr)) +
|
||||
","
|
||||
"\"address\":\"" +
|
||||
device.address_str() +
|
||||
|
@ -113,14 +113,14 @@ void BME280Component::setup() {
|
||||
this->calibration_.h5 = read_u8_(BME280_REGISTER_DIG_H5 + 1) << 4 | (read_u8_(BME280_REGISTER_DIG_H5) >> 4);
|
||||
this->calibration_.h6 = read_u8_(BME280_REGISTER_DIG_H6);
|
||||
|
||||
uint8_t humid_register = 0;
|
||||
if (!this->read_byte(BME280_REGISTER_CONTROLHUMID, &humid_register)) {
|
||||
uint8_t humid_control_val = 0;
|
||||
if (!this->read_byte(BME280_REGISTER_CONTROLHUMID, &humid_control_val)) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
humid_register &= ~0b00000111;
|
||||
humid_register |= this->humidity_oversampling_ & 0b111;
|
||||
if (!this->write_byte(BME280_REGISTER_CONTROLHUMID, humid_register)) {
|
||||
humid_control_val &= ~0b00000111;
|
||||
humid_control_val |= this->humidity_oversampling_ & 0b111;
|
||||
if (!this->write_byte(BME280_REGISTER_CONTROLHUMID, humid_control_val)) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
@ -169,11 +169,11 @@ inline uint8_t oversampling_to_time(BME280Oversampling over_sampling) { return (
|
||||
void BME280Component::update() {
|
||||
// Enable sensor
|
||||
ESP_LOGV(TAG, "Sending conversion request...");
|
||||
uint8_t meas_register = 0;
|
||||
meas_register |= (this->temperature_oversampling_ & 0b111) << 5;
|
||||
meas_register |= (this->pressure_oversampling_ & 0b111) << 2;
|
||||
meas_register |= BME280_MODE_FORCED;
|
||||
if (!this->write_byte(BME280_REGISTER_CONTROL, meas_register)) {
|
||||
uint8_t meas_value = 0;
|
||||
meas_value |= (this->temperature_oversampling_ & 0b111) << 5;
|
||||
meas_value |= (this->pressure_oversampling_ & 0b111) << 2;
|
||||
meas_value |= BME280_MODE_FORCED;
|
||||
if (!this->write_byte(BME280_REGISTER_CONTROL, meas_value)) {
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
@ -186,7 +186,7 @@ void BME280Component::update() {
|
||||
this->set_timeout("data", uint32_t(ceilf(meas_time)), [this]() {
|
||||
int32_t t_fine = 0;
|
||||
float temperature = this->read_temperature_(&t_fine);
|
||||
if (isnan(temperature)) {
|
||||
if (std::isnan(temperature)) {
|
||||
ESP_LOGW(TAG, "Invalid temperature, cannot read pressure & humidity values.");
|
||||
this->status_set_warning();
|
||||
return;
|
||||
|
@ -1,5 +1,6 @@
|
||||
#include "bme680.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/hal.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace bme680 {
|
||||
|
@ -381,7 +381,7 @@ void BME680BSECComponent::delay_ms(uint32_t period) {
|
||||
|
||||
void BME680BSECComponent::load_state_() {
|
||||
uint32_t hash = fnv1_hash("bme680_bsec_state_" + to_string(this->address_));
|
||||
this->bsec_state_ = global_preferences.make_preference<uint8_t[BSEC_MAX_STATE_BLOB_SIZE]>(hash, true);
|
||||
this->bsec_state_ = global_preferences->make_preference<uint8_t[BSEC_MAX_STATE_BLOB_SIZE]>(hash, true);
|
||||
|
||||
uint8_t state[BSEC_MAX_STATE_BLOB_SIZE];
|
||||
if (this->bsec_state_.load(&state)) {
|
||||
|
@ -5,6 +5,7 @@
|
||||
#include "esphome/components/text_sensor/text_sensor.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
#include "esphome/core/preferences.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#include <map>
|
||||
|
||||
#ifdef USE_BSEC
|
||||
|
@ -123,11 +123,11 @@ inline uint8_t oversampling_to_time(BMP280Oversampling over_sampling) { return (
|
||||
void BMP280Component::update() {
|
||||
// Enable sensor
|
||||
ESP_LOGV(TAG, "Sending conversion request...");
|
||||
uint8_t meas_register = 0;
|
||||
meas_register |= (this->temperature_oversampling_ & 0b111) << 5;
|
||||
meas_register |= (this->pressure_oversampling_ & 0b111) << 2;
|
||||
meas_register |= 0b01; // Forced mode
|
||||
if (!this->write_byte(BMP280_REGISTER_CONTROL, meas_register)) {
|
||||
uint8_t meas_value = 0;
|
||||
meas_value |= (this->temperature_oversampling_ & 0b111) << 5;
|
||||
meas_value |= (this->pressure_oversampling_ & 0b111) << 2;
|
||||
meas_value |= 0b01; // Forced mode
|
||||
if (!this->write_byte(BMP280_REGISTER_CONTROL, meas_value)) {
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
@ -139,7 +139,7 @@ void BMP280Component::update() {
|
||||
this->set_timeout("data", uint32_t(ceilf(meas_time)), [this]() {
|
||||
int32_t t_fine = 0;
|
||||
float temperature = this->read_temperature_(&t_fine);
|
||||
if (isnan(temperature)) {
|
||||
if (std::isnan(temperature)) {
|
||||
ESP_LOGW(TAG, "Invalid temperature, cannot read pressure values.");
|
||||
this->status_set_warning();
|
||||
return;
|
||||
|
@ -3,7 +3,7 @@ import esphome.config_validation as cv
|
||||
from esphome.components import web_server_base
|
||||
from esphome.components.web_server_base import CONF_WEB_SERVER_BASE_ID
|
||||
from esphome.const import CONF_ID
|
||||
from esphome.core import coroutine_with_priority
|
||||
from esphome.core import coroutine_with_priority, CORE
|
||||
|
||||
AUTO_LOAD = ["web_server_base"]
|
||||
DEPENDENCIES = ["wifi"]
|
||||
@ -12,14 +12,17 @@ CODEOWNERS = ["@OttoWinter"]
|
||||
captive_portal_ns = cg.esphome_ns.namespace("captive_portal")
|
||||
CaptivePortal = captive_portal_ns.class_("CaptivePortal", cg.Component)
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(CaptivePortal),
|
||||
cv.GenerateID(CONF_WEB_SERVER_BASE_ID): cv.use_id(
|
||||
web_server_base.WebServerBase
|
||||
),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(CaptivePortal),
|
||||
cv.GenerateID(CONF_WEB_SERVER_BASE_ID): cv.use_id(
|
||||
web_server_base.WebServerBase
|
||||
),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA),
|
||||
cv.only_with_arduino,
|
||||
)
|
||||
|
||||
|
||||
@coroutine_with_priority(64.0)
|
||||
@ -29,3 +32,7 @@ async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID], paren)
|
||||
await cg.register_component(var, config)
|
||||
cg.add_define("USE_CAPTIVE_PORTAL")
|
||||
|
||||
if CORE.is_esp32:
|
||||
cg.add_library("DNSServer", None)
|
||||
cg.add_library("WiFi", None)
|
||||
|
@ -1,3 +1,5 @@
|
||||
#ifdef USE_ARDUINO
|
||||
|
||||
#include "captive_portal.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/application.h"
|
||||
@ -76,16 +78,16 @@ void CaptivePortal::start() {
|
||||
this->base_->add_ota_handler();
|
||||
}
|
||||
|
||||
this->dns_server_ = new DNSServer();
|
||||
this->dns_server_ = make_unique<DNSServer>();
|
||||
this->dns_server_->setErrorReplyCode(DNSReplyCode::NoError);
|
||||
IPAddress ip = wifi::global_wifi_component->wifi_soft_ap_ip();
|
||||
this->dns_server_->start(53, "*", ip);
|
||||
network::IPAddress ip = wifi::global_wifi_component->wifi_soft_ap_ip();
|
||||
this->dns_server_->start(53, "*", (uint32_t) ip);
|
||||
|
||||
this->base_->get_server()->onNotFound([this](AsyncWebServerRequest *req) {
|
||||
bool not_found = false;
|
||||
if (!this->active_) {
|
||||
not_found = true;
|
||||
} else if (req->host() == wifi::global_wifi_component->wifi_soft_ap_ip().toString()) {
|
||||
} else if (req->host().c_str() == wifi::global_wifi_component->wifi_soft_ap_ip().str()) {
|
||||
not_found = true;
|
||||
}
|
||||
|
||||
@ -94,8 +96,8 @@ void CaptivePortal::start() {
|
||||
return;
|
||||
}
|
||||
|
||||
auto url = "http://" + wifi::global_wifi_component->wifi_soft_ap_ip().toString();
|
||||
req->redirect(url);
|
||||
auto url = "http://" + wifi::global_wifi_component->wifi_soft_ap_ip().str();
|
||||
req->redirect(url.c_str());
|
||||
});
|
||||
|
||||
this->initialized_ = true;
|
||||
@ -151,3 +153,5 @@ CaptivePortal *global_captive_portal = nullptr; // NOLINT(cppcoreguidelines-avo
|
||||
|
||||
} // namespace captive_portal
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ARDUINO
|
||||
|
@ -1,5 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef USE_ARDUINO
|
||||
|
||||
#include <memory>
|
||||
#include <DNSServer.h>
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
@ -26,7 +29,7 @@ class CaptivePortal : public AsyncWebHandler, public Component {
|
||||
this->active_ = false;
|
||||
this->base_->deinit();
|
||||
this->dns_server_->stop();
|
||||
delete this->dns_server_;
|
||||
this->dns_server_ = nullptr;
|
||||
}
|
||||
|
||||
bool canHandle(AsyncWebServerRequest *request) override {
|
||||
@ -65,10 +68,12 @@ class CaptivePortal : public AsyncWebHandler, public Component {
|
||||
web_server_base::WebServerBase *base_;
|
||||
bool initialized_{false};
|
||||
bool active_{false};
|
||||
DNSServer *dns_server_{nullptr};
|
||||
std::unique_ptr<DNSServer> dns_server_{nullptr};
|
||||
};
|
||||
|
||||
extern CaptivePortal *global_captive_portal; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
||||
} // namespace captive_portal
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ARDUINO
|
||||
|
@ -1,5 +1,6 @@
|
||||
#include "ccs811.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/hal.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ccs811 {
|
||||
@ -85,8 +86,11 @@ void CCS811Component::setup() {
|
||||
}
|
||||
}
|
||||
void CCS811Component::update() {
|
||||
if (!this->status_has_data_())
|
||||
if (!this->status_has_data_()) {
|
||||
ESP_LOGD(TAG, "Status indicates no data ready!");
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
|
||||
// page 12 - alg result data
|
||||
auto alg_data = this->read_bytes<4>(0x02);
|
||||
@ -124,12 +128,12 @@ void CCS811Component::send_env_data_() {
|
||||
float humidity = NAN;
|
||||
if (this->humidity_ != nullptr)
|
||||
humidity = this->humidity_->state;
|
||||
if (isnan(humidity) || humidity < 0 || humidity > 100)
|
||||
if (std::isnan(humidity) || humidity < 0 || humidity > 100)
|
||||
humidity = 50;
|
||||
float temperature = NAN;
|
||||
if (this->temperature_ != nullptr)
|
||||
temperature = this->temperature_->state;
|
||||
if (isnan(temperature) || temperature < -25 || temperature > 50)
|
||||
if (std::isnan(temperature) || temperature < -25 || temperature > 50)
|
||||
temperature = 25;
|
||||
// temperature has a 25° offset to allow negative temperatures
|
||||
temperature += 25;
|
||||
|
@ -1,27 +1,41 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.cpp_helpers import setup_entity
|
||||
from esphome import automation
|
||||
from esphome.components import mqtt
|
||||
from esphome.const import (
|
||||
CONF_ACTION_STATE_TOPIC,
|
||||
CONF_AWAY,
|
||||
CONF_AWAY_COMMAND_TOPIC,
|
||||
CONF_AWAY_STATE_TOPIC,
|
||||
CONF_CURRENT_TEMPERATURE_STATE_TOPIC,
|
||||
CONF_CUSTOM_FAN_MODE,
|
||||
CONF_CUSTOM_PRESET,
|
||||
CONF_DISABLED_BY_DEFAULT,
|
||||
CONF_FAN_MODE,
|
||||
CONF_FAN_MODE_COMMAND_TOPIC,
|
||||
CONF_FAN_MODE_STATE_TOPIC,
|
||||
CONF_ID,
|
||||
CONF_INTERNAL,
|
||||
CONF_MAX_TEMPERATURE,
|
||||
CONF_MIN_TEMPERATURE,
|
||||
CONF_MODE,
|
||||
CONF_MODE_COMMAND_TOPIC,
|
||||
CONF_MODE_STATE_TOPIC,
|
||||
CONF_PRESET,
|
||||
CONF_SWING_MODE,
|
||||
CONF_SWING_MODE_COMMAND_TOPIC,
|
||||
CONF_SWING_MODE_STATE_TOPIC,
|
||||
CONF_TARGET_TEMPERATURE,
|
||||
CONF_TARGET_TEMPERATURE_COMMAND_TOPIC,
|
||||
CONF_TARGET_TEMPERATURE_STATE_TOPIC,
|
||||
CONF_TARGET_TEMPERATURE_HIGH,
|
||||
CONF_TARGET_TEMPERATURE_HIGH_COMMAND_TOPIC,
|
||||
CONF_TARGET_TEMPERATURE_HIGH_STATE_TOPIC,
|
||||
CONF_TARGET_TEMPERATURE_LOW,
|
||||
CONF_TARGET_TEMPERATURE_LOW_COMMAND_TOPIC,
|
||||
CONF_TARGET_TEMPERATURE_LOW_STATE_TOPIC,
|
||||
CONF_TEMPERATURE_STEP,
|
||||
CONF_VISUAL,
|
||||
CONF_MQTT_ID,
|
||||
CONF_NAME,
|
||||
CONF_FAN_MODE,
|
||||
CONF_SWING_MODE,
|
||||
)
|
||||
from esphome.core import CORE, coroutine_with_priority
|
||||
|
||||
@ -30,7 +44,7 @@ IS_PLATFORM_COMPONENT = True
|
||||
CODEOWNERS = ["@esphome/core"]
|
||||
climate_ns = cg.esphome_ns.namespace("climate")
|
||||
|
||||
Climate = climate_ns.class_("Climate", cg.Nameable)
|
||||
Climate = climate_ns.class_("Climate", cg.EntityBase)
|
||||
ClimateCall = climate_ns.class_("ClimateCall")
|
||||
ClimateTraits = climate_ns.class_("ClimateTraits")
|
||||
|
||||
@ -88,7 +102,7 @@ validate_climate_swing_mode = cv.enum(CLIMATE_SWING_MODES, upper=True)
|
||||
# Actions
|
||||
ControlAction = climate_ns.class_("ControlAction", automation.Action)
|
||||
|
||||
CLIMATE_SCHEMA = cv.NAMEABLE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend(
|
||||
CLIMATE_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(Climate),
|
||||
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTClimateComponent),
|
||||
@ -99,16 +113,61 @@ CLIMATE_SCHEMA = cv.NAMEABLE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).ext
|
||||
cv.Optional(CONF_TEMPERATURE_STEP): cv.temperature,
|
||||
}
|
||||
),
|
||||
# TODO: MQTT topic options
|
||||
cv.Optional(CONF_ACTION_STATE_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.publish_topic
|
||||
),
|
||||
cv.Optional(CONF_AWAY_COMMAND_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.publish_topic
|
||||
),
|
||||
cv.Optional(CONF_AWAY_STATE_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.publish_topic
|
||||
),
|
||||
cv.Optional(CONF_CURRENT_TEMPERATURE_STATE_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.publish_topic
|
||||
),
|
||||
cv.Optional(CONF_FAN_MODE_COMMAND_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.publish_topic
|
||||
),
|
||||
cv.Optional(CONF_FAN_MODE_STATE_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.publish_topic
|
||||
),
|
||||
cv.Optional(CONF_MODE_COMMAND_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.publish_topic
|
||||
),
|
||||
cv.Optional(CONF_MODE_STATE_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.publish_topic
|
||||
),
|
||||
cv.Optional(CONF_SWING_MODE_COMMAND_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.publish_topic
|
||||
),
|
||||
cv.Optional(CONF_SWING_MODE_STATE_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.publish_topic
|
||||
),
|
||||
cv.Optional(CONF_TARGET_TEMPERATURE_COMMAND_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.publish_topic
|
||||
),
|
||||
cv.Optional(CONF_TARGET_TEMPERATURE_STATE_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.publish_topic
|
||||
),
|
||||
cv.Optional(CONF_TARGET_TEMPERATURE_HIGH_COMMAND_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.publish_topic
|
||||
),
|
||||
cv.Optional(CONF_TARGET_TEMPERATURE_HIGH_STATE_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.publish_topic
|
||||
),
|
||||
cv.Optional(CONF_TARGET_TEMPERATURE_LOW_COMMAND_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.publish_topic
|
||||
),
|
||||
cv.Optional(CONF_TARGET_TEMPERATURE_LOW_STATE_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.publish_topic
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def setup_climate_core_(var, config):
|
||||
cg.add(var.set_name(config[CONF_NAME]))
|
||||
cg.add(var.set_disabled_by_default(config[CONF_DISABLED_BY_DEFAULT]))
|
||||
if CONF_INTERNAL in config:
|
||||
cg.add(var.set_internal(config[CONF_INTERNAL]))
|
||||
await setup_entity(var, config)
|
||||
|
||||
visual = config[CONF_VISUAL]
|
||||
if CONF_MIN_TEMPERATURE in visual:
|
||||
cg.add(var.set_visual_min_temperature_override(visual[CONF_MIN_TEMPERATURE]))
|
||||
@ -121,6 +180,82 @@ async def setup_climate_core_(var, config):
|
||||
mqtt_ = cg.new_Pvariable(config[CONF_MQTT_ID], var)
|
||||
await mqtt.register_mqtt_component(mqtt_, config)
|
||||
|
||||
if CONF_ACTION_STATE_TOPIC in config:
|
||||
cg.add(mqtt_.set_custom_action_state_topic(config[CONF_ACTION_STATE_TOPIC]))
|
||||
if CONF_AWAY_COMMAND_TOPIC in config:
|
||||
cg.add(mqtt_.set_custom_away_command_topic(config[CONF_AWAY_COMMAND_TOPIC]))
|
||||
if CONF_AWAY_STATE_TOPIC in config:
|
||||
cg.add(mqtt_.set_custom_away_state_topic(config[CONF_AWAY_STATE_TOPIC]))
|
||||
if CONF_CURRENT_TEMPERATURE_STATE_TOPIC in config:
|
||||
cg.add(
|
||||
mqtt_.set_custom_current_temperature_state_topic(
|
||||
config[CONF_CURRENT_TEMPERATURE_STATE_TOPIC]
|
||||
)
|
||||
)
|
||||
if CONF_FAN_MODE_COMMAND_TOPIC in config:
|
||||
cg.add(
|
||||
mqtt_.set_custom_fan_mode_command_topic(
|
||||
config[CONF_FAN_MODE_COMMAND_TOPIC]
|
||||
)
|
||||
)
|
||||
if CONF_FAN_MODE_STATE_TOPIC in config:
|
||||
cg.add(
|
||||
mqtt_.set_custom_fan_mode_state_topic(config[CONF_FAN_MODE_STATE_TOPIC])
|
||||
)
|
||||
if CONF_MODE_COMMAND_TOPIC in config:
|
||||
cg.add(mqtt_.set_custom_mode_command_topic(config[CONF_MODE_COMMAND_TOPIC]))
|
||||
if CONF_MODE_STATE_TOPIC in config:
|
||||
cg.add(mqtt_.set_custom_state_topic(config[CONF_MODE_STATE_TOPIC]))
|
||||
|
||||
if CONF_SWING_MODE_COMMAND_TOPIC in config:
|
||||
cg.add(
|
||||
mqtt_.set_custom_swing_mode_command_topic(
|
||||
config[CONF_SWING_MODE_COMMAND_TOPIC]
|
||||
)
|
||||
)
|
||||
if CONF_SWING_MODE_STATE_TOPIC in config:
|
||||
cg.add(
|
||||
mqtt_.set_custom_swing_mode_state_topic(
|
||||
config[CONF_SWING_MODE_STATE_TOPIC]
|
||||
)
|
||||
)
|
||||
if CONF_TARGET_TEMPERATURE_COMMAND_TOPIC in config:
|
||||
cg.add(
|
||||
mqtt_.set_custom_target_temperature_command_topic(
|
||||
config[CONF_TARGET_TEMPERATURE_COMMAND_TOPIC]
|
||||
)
|
||||
)
|
||||
if CONF_TARGET_TEMPERATURE_STATE_TOPIC in config:
|
||||
cg.add(
|
||||
mqtt_.set_custom_target_temperature_state_topic(
|
||||
config[CONF_TARGET_TEMPERATURE_STATE_TOPIC]
|
||||
)
|
||||
)
|
||||
if CONF_TARGET_TEMPERATURE_HIGH_COMMAND_TOPIC in config:
|
||||
cg.add(
|
||||
mqtt_.set_custom_target_temperature_high_command_topic(
|
||||
config[CONF_TARGET_TEMPERATURE_HIGH_COMMAND_TOPIC]
|
||||
)
|
||||
)
|
||||
if CONF_TARGET_TEMPERATURE_HIGH_STATE_TOPIC in config:
|
||||
cg.add(
|
||||
mqtt_.set_custom_target_temperature_high_state_topic(
|
||||
config[CONF_TARGET_TEMPERATURE_HIGH_STATE_TOPIC]
|
||||
)
|
||||
)
|
||||
if CONF_TARGET_TEMPERATURE_LOW_COMMAND_TOPIC in config:
|
||||
cg.add(
|
||||
mqtt_.set_custom_target_temperature_low_command_topic(
|
||||
config[CONF_TARGET_TEMPERATURE_LOW_COMMAND_TOPIC]
|
||||
)
|
||||
)
|
||||
if CONF_TARGET_TEMPERATURE_LOW_STATE_TOPIC in config:
|
||||
cg.add(
|
||||
mqtt_.set_custom_target_temperature_state_topic(
|
||||
config[CONF_TARGET_TEMPERATURE_LOW_STATE_TOPIC]
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
async def register_climate(var, config):
|
||||
if not CORE.has_id(config[CONF_ID]):
|
||||
|
@ -9,8 +9,8 @@ void ClimateCall::perform() {
|
||||
ESP_LOGD(TAG, "'%s' - Setting", this->parent_->get_name().c_str());
|
||||
this->validate_();
|
||||
if (this->mode_.has_value()) {
|
||||
const char *mode_s = climate_mode_to_string(*this->mode_);
|
||||
ESP_LOGD(TAG, " Mode: %s", mode_s);
|
||||
const LogString *mode_s = climate_mode_to_string(*this->mode_);
|
||||
ESP_LOGD(TAG, " Mode: %s", LOG_STR_ARG(mode_s));
|
||||
}
|
||||
if (this->custom_fan_mode_.has_value()) {
|
||||
this->fan_mode_.reset();
|
||||
@ -18,8 +18,8 @@ void ClimateCall::perform() {
|
||||
}
|
||||
if (this->fan_mode_.has_value()) {
|
||||
this->custom_fan_mode_.reset();
|
||||
const char *fan_mode_s = climate_fan_mode_to_string(*this->fan_mode_);
|
||||
ESP_LOGD(TAG, " Fan: %s", fan_mode_s);
|
||||
const LogString *fan_mode_s = climate_fan_mode_to_string(*this->fan_mode_);
|
||||
ESP_LOGD(TAG, " Fan: %s", LOG_STR_ARG(fan_mode_s));
|
||||
}
|
||||
if (this->custom_preset_.has_value()) {
|
||||
this->preset_.reset();
|
||||
@ -27,12 +27,12 @@ void ClimateCall::perform() {
|
||||
}
|
||||
if (this->preset_.has_value()) {
|
||||
this->custom_preset_.reset();
|
||||
const char *preset_s = climate_preset_to_string(*this->preset_);
|
||||
ESP_LOGD(TAG, " Preset: %s", preset_s);
|
||||
const LogString *preset_s = climate_preset_to_string(*this->preset_);
|
||||
ESP_LOGD(TAG, " Preset: %s", LOG_STR_ARG(preset_s));
|
||||
}
|
||||
if (this->swing_mode_.has_value()) {
|
||||
const char *swing_mode_s = climate_swing_mode_to_string(*this->swing_mode_);
|
||||
ESP_LOGD(TAG, " Swing: %s", swing_mode_s);
|
||||
const LogString *swing_mode_s = climate_swing_mode_to_string(*this->swing_mode_);
|
||||
ESP_LOGD(TAG, " Swing: %s", LOG_STR_ARG(swing_mode_s));
|
||||
}
|
||||
if (this->target_temperature_.has_value()) {
|
||||
ESP_LOGD(TAG, " Target Temperature: %.2f", *this->target_temperature_);
|
||||
@ -50,7 +50,7 @@ void ClimateCall::validate_() {
|
||||
if (this->mode_.has_value()) {
|
||||
auto mode = *this->mode_;
|
||||
if (!traits.supports_mode(mode)) {
|
||||
ESP_LOGW(TAG, " Mode %s is not supported by this device!", climate_mode_to_string(mode));
|
||||
ESP_LOGW(TAG, " Mode %s is not supported by this device!", LOG_STR_ARG(climate_mode_to_string(mode)));
|
||||
this->mode_.reset();
|
||||
}
|
||||
}
|
||||
@ -63,7 +63,8 @@ void ClimateCall::validate_() {
|
||||
} else if (this->fan_mode_.has_value()) {
|
||||
auto fan_mode = *this->fan_mode_;
|
||||
if (!traits.supports_fan_mode(fan_mode)) {
|
||||
ESP_LOGW(TAG, " Fan Mode %s is not supported by this device!", climate_fan_mode_to_string(fan_mode));
|
||||
ESP_LOGW(TAG, " Fan Mode %s is not supported by this device!",
|
||||
LOG_STR_ARG(climate_fan_mode_to_string(fan_mode)));
|
||||
this->fan_mode_.reset();
|
||||
}
|
||||
}
|
||||
@ -76,14 +77,15 @@ void ClimateCall::validate_() {
|
||||
} else if (this->preset_.has_value()) {
|
||||
auto preset = *this->preset_;
|
||||
if (!traits.supports_preset(preset)) {
|
||||
ESP_LOGW(TAG, " Preset %s is not supported by this device!", climate_preset_to_string(preset));
|
||||
ESP_LOGW(TAG, " Preset %s is not supported by this device!", LOG_STR_ARG(climate_preset_to_string(preset)));
|
||||
this->preset_.reset();
|
||||
}
|
||||
}
|
||||
if (this->swing_mode_.has_value()) {
|
||||
auto swing_mode = *this->swing_mode_;
|
||||
if (!traits.supports_swing_mode(swing_mode)) {
|
||||
ESP_LOGW(TAG, " Swing Mode %s is not supported by this device!", climate_swing_mode_to_string(swing_mode));
|
||||
ESP_LOGW(TAG, " Swing Mode %s is not supported by this device!",
|
||||
LOG_STR_ARG(climate_swing_mode_to_string(swing_mode)));
|
||||
this->swing_mode_.reset();
|
||||
}
|
||||
}
|
||||
@ -93,7 +95,7 @@ void ClimateCall::validate_() {
|
||||
ESP_LOGW(TAG, " Cannot set target temperature for climate device "
|
||||
"with two-point target temperature!");
|
||||
this->target_temperature_.reset();
|
||||
} else if (isnan(target)) {
|
||||
} else if (std::isnan(target)) {
|
||||
ESP_LOGW(TAG, " Target temperature must not be NAN!");
|
||||
this->target_temperature_.reset();
|
||||
}
|
||||
@ -105,11 +107,11 @@ void ClimateCall::validate_() {
|
||||
this->target_temperature_high_.reset();
|
||||
}
|
||||
}
|
||||
if (this->target_temperature_low_.has_value() && isnan(*this->target_temperature_low_)) {
|
||||
if (this->target_temperature_low_.has_value() && std::isnan(*this->target_temperature_low_)) {
|
||||
ESP_LOGW(TAG, " Target temperature low must not be NAN!");
|
||||
this->target_temperature_low_.reset();
|
||||
}
|
||||
if (this->target_temperature_high_.has_value() && isnan(*this->target_temperature_high_)) {
|
||||
if (this->target_temperature_high_.has_value() && std::isnan(*this->target_temperature_high_)) {
|
||||
ESP_LOGW(TAG, " Target temperature low must not be NAN!");
|
||||
this->target_temperature_high_.reset();
|
||||
}
|
||||
@ -316,17 +318,23 @@ void Climate::add_on_state_callback(std::function<void()> &&callback) {
|
||||
static const uint32_t RESTORE_STATE_VERSION = 0x848EA6ADUL;
|
||||
|
||||
optional<ClimateDeviceRestoreState> Climate::restore_state_() {
|
||||
this->rtc_ =
|
||||
global_preferences.make_preference<ClimateDeviceRestoreState>(this->get_object_id_hash() ^ RESTORE_STATE_VERSION);
|
||||
this->rtc_ = global_preferences->make_preference<ClimateDeviceRestoreState>(this->get_object_id_hash() ^
|
||||
RESTORE_STATE_VERSION);
|
||||
ClimateDeviceRestoreState recovered{};
|
||||
if (!this->rtc_.load(&recovered))
|
||||
return {};
|
||||
return recovered;
|
||||
}
|
||||
void Climate::save_state_() {
|
||||
#if defined(USE_ESP_IDF) && !defined(CLANG_TIDY)
|
||||
#pragma GCC diagnostic ignored "-Wclass-memaccess"
|
||||
#endif
|
||||
ClimateDeviceRestoreState state{};
|
||||
// initialize as zero to prevent random data on stack triggering erase
|
||||
memset(&state, 0, sizeof(ClimateDeviceRestoreState));
|
||||
#if USE_ESP_IDF && !defined(CLANG_TIDY)
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
|
||||
state.mode = this->mode;
|
||||
auto traits = this->get_traits();
|
||||
@ -373,24 +381,24 @@ void Climate::publish_state() {
|
||||
ESP_LOGD(TAG, "'%s' - Sending state:", this->name_.c_str());
|
||||
auto traits = this->get_traits();
|
||||
|
||||
ESP_LOGD(TAG, " Mode: %s", climate_mode_to_string(this->mode));
|
||||
ESP_LOGD(TAG, " Mode: %s", LOG_STR_ARG(climate_mode_to_string(this->mode)));
|
||||
if (traits.get_supports_action()) {
|
||||
ESP_LOGD(TAG, " Action: %s", climate_action_to_string(this->action));
|
||||
ESP_LOGD(TAG, " Action: %s", LOG_STR_ARG(climate_action_to_string(this->action)));
|
||||
}
|
||||
if (traits.get_supports_fan_modes() && this->fan_mode.has_value()) {
|
||||
ESP_LOGD(TAG, " Fan Mode: %s", climate_fan_mode_to_string(this->fan_mode.value()));
|
||||
ESP_LOGD(TAG, " Fan Mode: %s", LOG_STR_ARG(climate_fan_mode_to_string(this->fan_mode.value())));
|
||||
}
|
||||
if (!traits.get_supported_custom_fan_modes().empty() && this->custom_fan_mode.has_value()) {
|
||||
ESP_LOGD(TAG, " Custom Fan Mode: %s", this->custom_fan_mode.value().c_str());
|
||||
}
|
||||
if (traits.get_supports_presets() && this->preset.has_value()) {
|
||||
ESP_LOGD(TAG, " Preset: %s", climate_preset_to_string(this->preset.value()));
|
||||
ESP_LOGD(TAG, " Preset: %s", LOG_STR_ARG(climate_preset_to_string(this->preset.value())));
|
||||
}
|
||||
if (!traits.get_supported_custom_presets().empty() && this->custom_preset.has_value()) {
|
||||
ESP_LOGD(TAG, " Custom Preset: %s", this->custom_preset.value().c_str());
|
||||
}
|
||||
if (traits.get_supports_swing_modes()) {
|
||||
ESP_LOGD(TAG, " Swing Mode: %s", climate_swing_mode_to_string(this->swing_mode));
|
||||
ESP_LOGD(TAG, " Swing Mode: %s", LOG_STR_ARG(climate_swing_mode_to_string(this->swing_mode)));
|
||||
}
|
||||
if (traits.get_supports_current_temperature()) {
|
||||
ESP_LOGD(TAG, " Current Temperature: %.2f°C", this->current_temperature);
|
||||
@ -432,7 +440,7 @@ void Climate::set_visual_max_temperature_override(float visual_max_temperature_o
|
||||
void Climate::set_visual_temperature_step_override(float visual_temperature_step_override) {
|
||||
this->visual_temperature_step_override_ = visual_temperature_step_override;
|
||||
}
|
||||
Climate::Climate(const std::string &name) : Nameable(name) {}
|
||||
Climate::Climate(const std::string &name) : EntityBase(name) {}
|
||||
Climate::Climate() : Climate("") {}
|
||||
ClimateCall Climate::make_call() { return ClimateCall(this); }
|
||||
|
||||
@ -534,12 +542,12 @@ void Climate::dump_traits_(const char *tag) {
|
||||
if (!traits.get_supported_modes().empty()) {
|
||||
ESP_LOGCONFIG(tag, " [x] Supported modes:");
|
||||
for (ClimateMode m : traits.get_supported_modes())
|
||||
ESP_LOGCONFIG(tag, " - %s", climate_mode_to_string(m));
|
||||
ESP_LOGCONFIG(tag, " - %s", LOG_STR_ARG(climate_mode_to_string(m)));
|
||||
}
|
||||
if (!traits.get_supported_fan_modes().empty()) {
|
||||
ESP_LOGCONFIG(tag, " [x] Supported fan modes:");
|
||||
for (ClimateFanMode m : traits.get_supported_fan_modes())
|
||||
ESP_LOGCONFIG(tag, " - %s", climate_fan_mode_to_string(m));
|
||||
ESP_LOGCONFIG(tag, " - %s", LOG_STR_ARG(climate_fan_mode_to_string(m)));
|
||||
}
|
||||
if (!traits.get_supported_custom_fan_modes().empty()) {
|
||||
ESP_LOGCONFIG(tag, " [x] Supported custom fan modes:");
|
||||
@ -549,7 +557,7 @@ void Climate::dump_traits_(const char *tag) {
|
||||
if (!traits.get_supported_presets().empty()) {
|
||||
ESP_LOGCONFIG(tag, " [x] Supported presets:");
|
||||
for (ClimatePreset p : traits.get_supported_presets())
|
||||
ESP_LOGCONFIG(tag, " - %s", climate_preset_to_string(p));
|
||||
ESP_LOGCONFIG(tag, " - %s", LOG_STR_ARG(climate_preset_to_string(p)));
|
||||
}
|
||||
if (!traits.get_supported_custom_presets().empty()) {
|
||||
ESP_LOGCONFIG(tag, " [x] Supported custom presets:");
|
||||
@ -559,7 +567,7 @@ void Climate::dump_traits_(const char *tag) {
|
||||
if (!traits.get_supported_swing_modes().empty()) {
|
||||
ESP_LOGCONFIG(tag, " [x] Supported swing modes:");
|
||||
for (ClimateSwingMode m : traits.get_supported_swing_modes())
|
||||
ESP_LOGCONFIG(tag, " - %s", climate_swing_mode_to_string(m));
|
||||
ESP_LOGCONFIG(tag, " - %s", LOG_STR_ARG(climate_swing_mode_to_string(m)));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/entity_base.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/preferences.h"
|
||||
#include "esphome/core/log.h"
|
||||
@ -12,7 +13,7 @@ namespace climate {
|
||||
|
||||
#define LOG_CLIMATE(prefix, type, obj) \
|
||||
if ((obj) != nullptr) { \
|
||||
ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, type, (obj)->get_name().c_str()); \
|
||||
ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, LOG_STR_LITERAL(type), (obj)->get_name().c_str()); \
|
||||
}
|
||||
|
||||
class Climate;
|
||||
@ -163,7 +164,7 @@ struct ClimateDeviceRestoreState {
|
||||
* mode etc). These are read-only for the user and rw for integrations. The reason these are public
|
||||
* is for simple access to them from lambdas `if (id(my_climate).mode == climate::CLIMATE_MODE_HEAT_COOL) ...`
|
||||
*/
|
||||
class Climate : public Nameable {
|
||||
class Climate : public EntityBase {
|
||||
public:
|
||||
/// Construct a climate device with empty name (will be set later).
|
||||
Climate();
|
||||
|
@ -3,105 +3,105 @@
|
||||
namespace esphome {
|
||||
namespace climate {
|
||||
|
||||
const char *climate_mode_to_string(ClimateMode mode) {
|
||||
const LogString *climate_mode_to_string(ClimateMode mode) {
|
||||
switch (mode) {
|
||||
case CLIMATE_MODE_OFF:
|
||||
return "OFF";
|
||||
case CLIMATE_MODE_AUTO:
|
||||
return "AUTO";
|
||||
case CLIMATE_MODE_COOL:
|
||||
return "COOL";
|
||||
case CLIMATE_MODE_HEAT:
|
||||
return "HEAT";
|
||||
case CLIMATE_MODE_FAN_ONLY:
|
||||
return "FAN_ONLY";
|
||||
case CLIMATE_MODE_DRY:
|
||||
return "DRY";
|
||||
return LOG_STR("OFF");
|
||||
case CLIMATE_MODE_HEAT_COOL:
|
||||
return "HEAT_COOL";
|
||||
return LOG_STR("HEAT_COOL");
|
||||
case CLIMATE_MODE_AUTO:
|
||||
return LOG_STR("AUTO");
|
||||
case CLIMATE_MODE_COOL:
|
||||
return LOG_STR("COOL");
|
||||
case CLIMATE_MODE_HEAT:
|
||||
return LOG_STR("HEAT");
|
||||
case CLIMATE_MODE_FAN_ONLY:
|
||||
return LOG_STR("FAN_ONLY");
|
||||
case CLIMATE_MODE_DRY:
|
||||
return LOG_STR("DRY");
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
return LOG_STR("UNKNOWN");
|
||||
}
|
||||
}
|
||||
const char *climate_action_to_string(ClimateAction action) {
|
||||
const LogString *climate_action_to_string(ClimateAction action) {
|
||||
switch (action) {
|
||||
case CLIMATE_ACTION_OFF:
|
||||
return "OFF";
|
||||
return LOG_STR("OFF");
|
||||
case CLIMATE_ACTION_COOLING:
|
||||
return "COOLING";
|
||||
return LOG_STR("COOLING");
|
||||
case CLIMATE_ACTION_HEATING:
|
||||
return "HEATING";
|
||||
return LOG_STR("HEATING");
|
||||
case CLIMATE_ACTION_IDLE:
|
||||
return "IDLE";
|
||||
return LOG_STR("IDLE");
|
||||
case CLIMATE_ACTION_DRYING:
|
||||
return "DRYING";
|
||||
return LOG_STR("DRYING");
|
||||
case CLIMATE_ACTION_FAN:
|
||||
return "FAN";
|
||||
return LOG_STR("FAN");
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
return LOG_STR("UNKNOWN");
|
||||
}
|
||||
}
|
||||
|
||||
const char *climate_fan_mode_to_string(ClimateFanMode fan_mode) {
|
||||
const LogString *climate_fan_mode_to_string(ClimateFanMode fan_mode) {
|
||||
switch (fan_mode) {
|
||||
case climate::CLIMATE_FAN_ON:
|
||||
return "ON";
|
||||
return LOG_STR("ON");
|
||||
case climate::CLIMATE_FAN_OFF:
|
||||
return "OFF";
|
||||
return LOG_STR("OFF");
|
||||
case climate::CLIMATE_FAN_AUTO:
|
||||
return "AUTO";
|
||||
return LOG_STR("AUTO");
|
||||
case climate::CLIMATE_FAN_LOW:
|
||||
return "LOW";
|
||||
return LOG_STR("LOW");
|
||||
case climate::CLIMATE_FAN_MEDIUM:
|
||||
return "MEDIUM";
|
||||
return LOG_STR("MEDIUM");
|
||||
case climate::CLIMATE_FAN_HIGH:
|
||||
return "HIGH";
|
||||
return LOG_STR("HIGH");
|
||||
case climate::CLIMATE_FAN_MIDDLE:
|
||||
return "MIDDLE";
|
||||
return LOG_STR("MIDDLE");
|
||||
case climate::CLIMATE_FAN_FOCUS:
|
||||
return "FOCUS";
|
||||
return LOG_STR("FOCUS");
|
||||
case climate::CLIMATE_FAN_DIFFUSE:
|
||||
return "DIFFUSE";
|
||||
return LOG_STR("DIFFUSE");
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
return LOG_STR("UNKNOWN");
|
||||
}
|
||||
}
|
||||
|
||||
const char *climate_swing_mode_to_string(ClimateSwingMode swing_mode) {
|
||||
const LogString *climate_swing_mode_to_string(ClimateSwingMode swing_mode) {
|
||||
switch (swing_mode) {
|
||||
case climate::CLIMATE_SWING_OFF:
|
||||
return "OFF";
|
||||
return LOG_STR("OFF");
|
||||
case climate::CLIMATE_SWING_BOTH:
|
||||
return "BOTH";
|
||||
return LOG_STR("BOTH");
|
||||
case climate::CLIMATE_SWING_VERTICAL:
|
||||
return "VERTICAL";
|
||||
return LOG_STR("VERTICAL");
|
||||
case climate::CLIMATE_SWING_HORIZONTAL:
|
||||
return "HORIZONTAL";
|
||||
return LOG_STR("HORIZONTAL");
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
return LOG_STR("UNKNOWN");
|
||||
}
|
||||
}
|
||||
|
||||
const char *climate_preset_to_string(ClimatePreset preset) {
|
||||
const LogString *climate_preset_to_string(ClimatePreset preset) {
|
||||
switch (preset) {
|
||||
case climate::CLIMATE_PRESET_NONE:
|
||||
return "NONE";
|
||||
return LOG_STR("NONE");
|
||||
case climate::CLIMATE_PRESET_HOME:
|
||||
return "HOME";
|
||||
return LOG_STR("HOME");
|
||||
case climate::CLIMATE_PRESET_ECO:
|
||||
return "ECO";
|
||||
return LOG_STR("ECO");
|
||||
case climate::CLIMATE_PRESET_AWAY:
|
||||
return "AWAY";
|
||||
return LOG_STR("AWAY");
|
||||
case climate::CLIMATE_PRESET_BOOST:
|
||||
return "BOOST";
|
||||
return LOG_STR("BOOST");
|
||||
case climate::CLIMATE_PRESET_COMFORT:
|
||||
return "COMFORT";
|
||||
return LOG_STR("COMFORT");
|
||||
case climate::CLIMATE_PRESET_SLEEP:
|
||||
return "SLEEP";
|
||||
return LOG_STR("SLEEP");
|
||||
case climate::CLIMATE_PRESET_ACTIVITY:
|
||||
return "ACTIVITY";
|
||||
return LOG_STR("ACTIVITY");
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
return LOG_STR("UNKNOWN");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace climate {
|
||||
@ -96,19 +97,19 @@ enum ClimatePreset : uint8_t {
|
||||
};
|
||||
|
||||
/// Convert the given ClimateMode to a human-readable string.
|
||||
const char *climate_mode_to_string(ClimateMode mode);
|
||||
const LogString *climate_mode_to_string(ClimateMode mode);
|
||||
|
||||
/// Convert the given ClimateAction to a human-readable string.
|
||||
const char *climate_action_to_string(ClimateAction action);
|
||||
const LogString *climate_action_to_string(ClimateAction action);
|
||||
|
||||
/// Convert the given ClimateFanMode to a human-readable string.
|
||||
const char *climate_fan_mode_to_string(ClimateFanMode mode);
|
||||
const LogString *climate_fan_mode_to_string(ClimateFanMode mode);
|
||||
|
||||
/// Convert the given ClimateSwingMode to a human-readable string.
|
||||
const char *climate_swing_mode_to_string(ClimateSwingMode mode);
|
||||
const LogString *climate_swing_mode_to_string(ClimateSwingMode mode);
|
||||
|
||||
/// Convert the given ClimateSwingMode to a human-readable string.
|
||||
const char *climate_preset_to_string(ClimatePreset preset);
|
||||
const LogString *climate_preset_to_string(ClimatePreset preset);
|
||||
|
||||
} // namespace climate
|
||||
} // namespace esphome
|
||||
|
@ -1,5 +1,5 @@
|
||||
#include "climate_traits.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include <cstdio>
|
||||
|
||||
namespace esphome {
|
||||
namespace climate {
|
||||
|
@ -52,7 +52,7 @@ void ClimateIR::setup() {
|
||||
this->swing_mode = climate::CLIMATE_SWING_OFF;
|
||||
}
|
||||
// Never send nan to HA
|
||||
if (isnan(this->target_temperature))
|
||||
if (std::isnan(this->target_temperature))
|
||||
this->target_temperature = 24;
|
||||
}
|
||||
|
||||
|
@ -4,18 +4,21 @@ from esphome import automation
|
||||
from esphome.automation import maybe_simple_id, Condition
|
||||
from esphome.components import mqtt
|
||||
from esphome.const import (
|
||||
CONF_DISABLED_BY_DEFAULT,
|
||||
CONF_ID,
|
||||
CONF_INTERNAL,
|
||||
CONF_DEVICE_CLASS,
|
||||
CONF_STATE,
|
||||
CONF_POSITION,
|
||||
CONF_POSITION_COMMAND_TOPIC,
|
||||
CONF_POSITION_STATE_TOPIC,
|
||||
CONF_TILT,
|
||||
CONF_TILT_COMMAND_TOPIC,
|
||||
CONF_TILT_STATE_TOPIC,
|
||||
CONF_STOP,
|
||||
CONF_MQTT_ID,
|
||||
CONF_NAME,
|
||||
CONF_TRIGGER_ID,
|
||||
)
|
||||
from esphome.core import CORE, coroutine_with_priority
|
||||
from esphome.cpp_helpers import setup_entity
|
||||
|
||||
IS_PLATFORM_COMPONENT = True
|
||||
|
||||
@ -36,7 +39,7 @@ DEVICE_CLASSES = [
|
||||
|
||||
cover_ns = cg.esphome_ns.namespace("cover")
|
||||
|
||||
Cover = cover_ns.class_("Cover", cg.Nameable)
|
||||
Cover = cover_ns.class_("Cover", cg.EntityBase)
|
||||
|
||||
COVER_OPEN = cover_ns.COVER_OPEN
|
||||
COVER_CLOSED = cover_ns.COVER_CLOSED
|
||||
@ -59,33 +62,84 @@ validate_cover_operation = cv.enum(COVER_OPERATIONS, upper=True)
|
||||
OpenAction = cover_ns.class_("OpenAction", automation.Action)
|
||||
CloseAction = cover_ns.class_("CloseAction", automation.Action)
|
||||
StopAction = cover_ns.class_("StopAction", automation.Action)
|
||||
ToggleAction = cover_ns.class_("ToggleAction", automation.Action)
|
||||
ControlAction = cover_ns.class_("ControlAction", automation.Action)
|
||||
CoverPublishAction = cover_ns.class_("CoverPublishAction", automation.Action)
|
||||
CoverIsOpenCondition = cover_ns.class_("CoverIsOpenCondition", Condition)
|
||||
CoverIsClosedCondition = cover_ns.class_("CoverIsClosedCondition", Condition)
|
||||
|
||||
COVER_SCHEMA = cv.NAMEABLE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend(
|
||||
# Triggers
|
||||
CoverOpenTrigger = cover_ns.class_("CoverOpenTrigger", automation.Trigger.template())
|
||||
CoverClosedTrigger = cover_ns.class_(
|
||||
"CoverClosedTrigger", automation.Trigger.template()
|
||||
)
|
||||
|
||||
CONF_ON_OPEN = "on_open"
|
||||
CONF_ON_CLOSED = "on_closed"
|
||||
|
||||
COVER_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(Cover),
|
||||
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTCoverComponent),
|
||||
cv.Optional(CONF_DEVICE_CLASS): cv.one_of(*DEVICE_CLASSES, lower=True),
|
||||
# TODO: MQTT topic options
|
||||
cv.Optional(CONF_POSITION_COMMAND_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.subscribe_topic
|
||||
),
|
||||
cv.Optional(CONF_POSITION_STATE_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.subscribe_topic
|
||||
),
|
||||
cv.Optional(CONF_TILT_COMMAND_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.subscribe_topic
|
||||
),
|
||||
cv.Optional(CONF_TILT_STATE_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.subscribe_topic
|
||||
),
|
||||
cv.Optional(CONF_ON_OPEN): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(CoverOpenTrigger),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_CLOSED): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(CoverClosedTrigger),
|
||||
}
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def setup_cover_core_(var, config):
|
||||
cg.add(var.set_name(config[CONF_NAME]))
|
||||
cg.add(var.set_disabled_by_default(config[CONF_DISABLED_BY_DEFAULT]))
|
||||
if CONF_INTERNAL in config:
|
||||
cg.add(var.set_internal(config[CONF_INTERNAL]))
|
||||
await setup_entity(var, config)
|
||||
|
||||
if CONF_DEVICE_CLASS in config:
|
||||
cg.add(var.set_device_class(config[CONF_DEVICE_CLASS]))
|
||||
|
||||
for conf in config.get(CONF_ON_OPEN, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [], conf)
|
||||
for conf in config.get(CONF_ON_CLOSED, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [], conf)
|
||||
|
||||
if CONF_MQTT_ID in config:
|
||||
mqtt_ = cg.new_Pvariable(config[CONF_MQTT_ID], var)
|
||||
await mqtt.register_mqtt_component(mqtt_, config)
|
||||
|
||||
if CONF_POSITION_STATE_TOPIC in config:
|
||||
cg.add(
|
||||
mqtt_.set_custom_position_state_topic(config[CONF_POSITION_STATE_TOPIC])
|
||||
)
|
||||
if CONF_POSITION_COMMAND_TOPIC in config:
|
||||
cg.add(
|
||||
mqtt_.set_custom_position_command_topic(
|
||||
config[CONF_POSITION_COMMAND_TOPIC]
|
||||
)
|
||||
)
|
||||
if CONF_TILT_STATE_TOPIC in config:
|
||||
cg.add(mqtt_.set_custom_tilt_state_topic(config[CONF_TILT_STATE_TOPIC]))
|
||||
if CONF_TILT_COMMAND_TOPIC in config:
|
||||
cg.add(mqtt_.set_custom_tilt_command_topic(config[CONF_TILT_COMMAND_TOPIC]))
|
||||
|
||||
|
||||
async def register_cover(var, config):
|
||||
if not CORE.has_id(config[CONF_ID]):
|
||||
@ -119,6 +173,12 @@ async def cover_stop_to_code(config, action_id, template_arg, args):
|
||||
return cg.new_Pvariable(action_id, template_arg, paren)
|
||||
|
||||
|
||||
@automation.register_action("cover.toggle", ToggleAction, COVER_ACTION_SCHEMA)
|
||||
def cover_toggle_to_code(config, action_id, template_arg, args):
|
||||
paren = yield cg.get_variable(config[CONF_ID])
|
||||
yield cg.new_Pvariable(action_id, template_arg, paren)
|
||||
|
||||
|
||||
COVER_CONTROL_ACTION_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.use_id(Cover),
|
||||
|
@ -37,6 +37,16 @@ template<typename... Ts> class StopAction : public Action<Ts...> {
|
||||
Cover *cover_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class ToggleAction : public Action<Ts...> {
|
||||
public:
|
||||
explicit ToggleAction(Cover *cover) : cover_(cover) {}
|
||||
|
||||
void play(Ts... x) override { this->cover_->make_call().set_command_toggle().perform(); }
|
||||
|
||||
protected:
|
||||
Cover *cover_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class ControlAction : public Action<Ts...> {
|
||||
public:
|
||||
explicit ControlAction(Cover *cover) : cover_(cover) {}
|
||||
@ -89,6 +99,7 @@ template<typename... Ts> class CoverIsOpenCondition : public Condition<Ts...> {
|
||||
protected:
|
||||
Cover *cover_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class CoverIsClosedCondition : public Condition<Ts...> {
|
||||
public:
|
||||
CoverIsClosedCondition(Cover *cover) : cover_(cover) {}
|
||||
@ -98,5 +109,27 @@ template<typename... Ts> class CoverIsClosedCondition : public Condition<Ts...>
|
||||
Cover *cover_;
|
||||
};
|
||||
|
||||
class CoverOpenTrigger : public Trigger<> {
|
||||
public:
|
||||
CoverOpenTrigger(Cover *a_cover) {
|
||||
a_cover->add_on_state_callback([this, a_cover]() {
|
||||
if (a_cover->is_fully_open()) {
|
||||
this->trigger();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
class CoverClosedTrigger : public Trigger<> {
|
||||
public:
|
||||
CoverClosedTrigger(Cover *a_cover) {
|
||||
a_cover->add_on_state_callback([this, a_cover]() {
|
||||
if (a_cover->is_fully_closed()) {
|
||||
this->trigger();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace cover
|
||||
} // namespace esphome
|
||||
|
@ -31,7 +31,7 @@ const char *cover_operation_to_str(CoverOperation op) {
|
||||
}
|
||||
}
|
||||
|
||||
Cover::Cover(const std::string &name) : Nameable(name), position{COVER_OPEN} {}
|
||||
Cover::Cover(const std::string &name) : EntityBase(name), position{COVER_OPEN} {}
|
||||
|
||||
uint32_t Cover::hash_base() { return 1727367479UL; }
|
||||
|
||||
@ -43,6 +43,8 @@ CoverCall &CoverCall::set_command(const char *command) {
|
||||
this->set_command_close();
|
||||
} else if (strcasecmp(command, "STOP") == 0) {
|
||||
this->set_command_stop();
|
||||
} else if (strcasecmp(command, "TOGGLE") == 0) {
|
||||
this->set_command_toggle();
|
||||
} else {
|
||||
ESP_LOGW(TAG, "'%s' - Unrecognized command %s", this->parent_->get_name().c_str(), command);
|
||||
}
|
||||
@ -60,6 +62,10 @@ CoverCall &CoverCall::set_command_stop() {
|
||||
this->stop_ = true;
|
||||
return *this;
|
||||
}
|
||||
CoverCall &CoverCall::set_command_toggle() {
|
||||
this->toggle_ = true;
|
||||
return *this;
|
||||
}
|
||||
CoverCall &CoverCall::set_position(float position) {
|
||||
this->position_ = position;
|
||||
return *this;
|
||||
@ -85,10 +91,14 @@ void CoverCall::perform() {
|
||||
if (this->tilt_.has_value()) {
|
||||
ESP_LOGD(TAG, " Tilt: %.0f%%", *this->tilt_ * 100.0f);
|
||||
}
|
||||
if (this->toggle_.has_value()) {
|
||||
ESP_LOGD(TAG, " Command: TOGGLE");
|
||||
}
|
||||
this->parent_->control(*this);
|
||||
}
|
||||
const optional<float> &CoverCall::get_position() const { return this->position_; }
|
||||
const optional<float> &CoverCall::get_tilt() const { return this->tilt_; }
|
||||
const optional<bool> &CoverCall::get_toggle() const { return this->toggle_; }
|
||||
void CoverCall::validate_() {
|
||||
auto traits = this->parent_->get_traits();
|
||||
if (this->position_.has_value()) {
|
||||
@ -111,6 +121,12 @@ void CoverCall::validate_() {
|
||||
this->tilt_ = clamp(tilt, 0.0f, 1.0f);
|
||||
}
|
||||
}
|
||||
if (this->toggle_.has_value()) {
|
||||
if (!traits.get_supports_toggle()) {
|
||||
ESP_LOGW(TAG, "'%s' - This cover device does not support toggle!", this->parent_->get_name().c_str());
|
||||
this->toggle_.reset();
|
||||
}
|
||||
}
|
||||
if (this->stop_) {
|
||||
if (this->position_.has_value()) {
|
||||
ESP_LOGW(TAG, "Cannot set position when stopping a cover!");
|
||||
@ -120,6 +136,10 @@ void CoverCall::validate_() {
|
||||
ESP_LOGW(TAG, "Cannot set tilt when stopping a cover!");
|
||||
this->tilt_.reset();
|
||||
}
|
||||
if (this->toggle_.has_value()) {
|
||||
ESP_LOGW(TAG, "Cannot set toggle when stopping a cover!");
|
||||
this->toggle_.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
CoverCall &CoverCall::set_stop(bool stop) {
|
||||
@ -180,7 +200,7 @@ void Cover::publish_state(bool save) {
|
||||
}
|
||||
}
|
||||
optional<CoverRestoreState> Cover::restore_state_() {
|
||||
this->rtc_ = global_preferences.make_preference<CoverRestoreState>(this->get_object_id_hash());
|
||||
this->rtc_ = global_preferences->make_preference<CoverRestoreState>(this->get_object_id_hash());
|
||||
CoverRestoreState recovered{};
|
||||
if (!this->rtc_.load(&recovered))
|
||||
return {};
|
||||
|
@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/entity_base.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/preferences.h"
|
||||
#include "cover_traits.h"
|
||||
@ -13,7 +14,7 @@ const extern float COVER_CLOSED;
|
||||
|
||||
#define LOG_COVER(prefix, type, obj) \
|
||||
if ((obj) != nullptr) { \
|
||||
ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, type, (obj)->get_name().c_str()); \
|
||||
ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, LOG_STR_LITERAL(type), (obj)->get_name().c_str()); \
|
||||
auto traits_ = (obj)->get_traits(); \
|
||||
if (traits_.get_is_assumed_state()) { \
|
||||
ESP_LOGCONFIG(TAG, "%s Assumed State: YES", prefix); \
|
||||
@ -29,7 +30,7 @@ class CoverCall {
|
||||
public:
|
||||
CoverCall(Cover *parent);
|
||||
|
||||
/// Set the command as a string, "STOP", "OPEN", "CLOSE".
|
||||
/// Set the command as a string, "STOP", "OPEN", "CLOSE", "TOGGLE".
|
||||
CoverCall &set_command(const char *command);
|
||||
/// Set the command to open the cover.
|
||||
CoverCall &set_command_open();
|
||||
@ -37,6 +38,8 @@ class CoverCall {
|
||||
CoverCall &set_command_close();
|
||||
/// Set the command to stop the cover.
|
||||
CoverCall &set_command_stop();
|
||||
/// Set the command to toggle the cover.
|
||||
CoverCall &set_command_toggle();
|
||||
/// Set the call to a certain target position.
|
||||
CoverCall &set_position(float position);
|
||||
/// Set the call to a certain target tilt.
|
||||
@ -50,6 +53,7 @@ class CoverCall {
|
||||
const optional<float> &get_position() const;
|
||||
bool get_stop() const;
|
||||
const optional<float> &get_tilt() const;
|
||||
const optional<bool> &get_toggle() const;
|
||||
|
||||
protected:
|
||||
void validate_();
|
||||
@ -58,6 +62,7 @@ class CoverCall {
|
||||
bool stop_{false};
|
||||
optional<float> position_{};
|
||||
optional<float> tilt_{};
|
||||
optional<bool> toggle_{};
|
||||
};
|
||||
|
||||
/// Struct used to store the restored state of a cover
|
||||
@ -103,7 +108,7 @@ const char *cover_operation_to_str(CoverOperation op);
|
||||
* to control all values of the cover. Also implement get_traits() to return what operations
|
||||
* the cover supports.
|
||||
*/
|
||||
class Cover : public Nameable {
|
||||
class Cover : public EntityBase {
|
||||
public:
|
||||
explicit Cover();
|
||||
explicit Cover(const std::string &name);
|
||||
|
@ -13,11 +13,14 @@ class CoverTraits {
|
||||
void set_supports_position(bool supports_position) { this->supports_position_ = supports_position; }
|
||||
bool get_supports_tilt() const { return this->supports_tilt_; }
|
||||
void set_supports_tilt(bool supports_tilt) { this->supports_tilt_ = supports_tilt; }
|
||||
bool get_supports_toggle() const { return this->supports_toggle_; }
|
||||
void set_supports_toggle(bool supports_toggle) { this->supports_toggle_ = supports_toggle; }
|
||||
|
||||
protected:
|
||||
bool is_assumed_state_{false};
|
||||
bool supports_position_{false};
|
||||
bool supports_tilt_{false};
|
||||
bool supports_toggle_{false};
|
||||
};
|
||||
|
||||
} // namespace cover
|
||||
|
@ -8,18 +8,6 @@ namespace ct_clamp {
|
||||
|
||||
static const char *const TAG = "ct_clamp";
|
||||
|
||||
void CTClampSensor::setup() {
|
||||
this->is_calibrating_offset_ = true;
|
||||
this->high_freq_.start();
|
||||
this->set_timeout("calibrate_offset", this->sample_duration_, [this]() {
|
||||
this->high_freq_.stop();
|
||||
this->is_calibrating_offset_ = false;
|
||||
if (this->num_samples_ != 0) {
|
||||
this->offset_ = this->sample_sum_ / this->num_samples_;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void CTClampSensor::dump_config() {
|
||||
LOG_SENSOR("", "CT Clamp Sensor", this);
|
||||
ESP_LOGCONFIG(TAG, " Sample Duration: %.2fs", this->sample_duration_ / 1e3f);
|
||||
@ -27,9 +15,6 @@ void CTClampSensor::dump_config() {
|
||||
}
|
||||
|
||||
void CTClampSensor::update() {
|
||||
if (this->is_calibrating_offset_)
|
||||
return;
|
||||
|
||||
// Update only starts the sampling phase, in loop() the actual sampling is happening.
|
||||
|
||||
// Request a high loop() execution interval during sampling phase.
|
||||
@ -46,44 +31,39 @@ void CTClampSensor::update() {
|
||||
return;
|
||||
}
|
||||
|
||||
float raw = this->sample_sum_ / this->num_samples_;
|
||||
float irms = std::sqrt(raw);
|
||||
ESP_LOGD(TAG, "'%s' - Raw Value: %.2fA", this->name_.c_str(), irms);
|
||||
this->publish_state(irms);
|
||||
const float rms_ac_dc_squared = this->sample_squared_sum_ / this->num_samples_;
|
||||
const float rms_dc = this->sample_sum_ / this->num_samples_;
|
||||
const float rms_ac = std::sqrt(rms_ac_dc_squared - rms_dc * rms_dc);
|
||||
ESP_LOGD(TAG, "'%s' - Raw AC Value: %.3fA after %d different samples (%d SPS)", this->name_.c_str(), rms_ac,
|
||||
this->num_samples_, 1000 * this->num_samples_ / this->sample_duration_);
|
||||
this->publish_state(rms_ac);
|
||||
});
|
||||
|
||||
// Set sampling values
|
||||
this->is_sampling_ = true;
|
||||
this->last_value_ = 0.0;
|
||||
this->num_samples_ = 0;
|
||||
this->sample_sum_ = 0.0f;
|
||||
this->sample_squared_sum_ = 0.0f;
|
||||
this->is_sampling_ = true;
|
||||
}
|
||||
|
||||
void CTClampSensor::loop() {
|
||||
if (!this->is_sampling_ && !this->is_calibrating_offset_)
|
||||
if (!this->is_sampling_)
|
||||
return;
|
||||
|
||||
// Perform a single sample
|
||||
float value = this->source_->sample();
|
||||
if (isnan(value))
|
||||
if (std::isnan(value))
|
||||
return;
|
||||
|
||||
if (this->is_calibrating_offset_) {
|
||||
this->sample_sum_ += value;
|
||||
this->num_samples_++;
|
||||
// Assuming a sine wave, avoid requesting values faster than the ADC can provide them
|
||||
if (this->last_value_ == value)
|
||||
return;
|
||||
}
|
||||
this->last_value_ = value;
|
||||
|
||||
// Adjust DC offset via low pass filter (exponential moving average)
|
||||
const float alpha = 0.001f;
|
||||
this->offset_ = this->offset_ * (1 - alpha) + value * alpha;
|
||||
|
||||
// Filtered value centered around the mid-point (0V)
|
||||
float filtered = value - this->offset_;
|
||||
|
||||
// IRMS is sqrt(∑v_i²)
|
||||
float sq = filtered * filtered;
|
||||
this->sample_sum_ += sq;
|
||||
this->num_samples_++;
|
||||
this->sample_sum_ += value;
|
||||
this->sample_squared_sum_ += value * value;
|
||||
}
|
||||
|
||||
} // namespace ct_clamp
|
||||
|
@ -1,7 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/esphal.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/voltage_sampler/voltage_sampler.h"
|
||||
|
||||
@ -10,7 +10,6 @@ namespace ct_clamp {
|
||||
|
||||
class CTClampSensor : public sensor::Sensor, public PollingComponent {
|
||||
public:
|
||||
void setup() override;
|
||||
void update() override;
|
||||
void loop() override;
|
||||
void dump_config() override;
|
||||
@ -35,17 +34,20 @@ class CTClampSensor : public sensor::Sensor, public PollingComponent {
|
||||
*
|
||||
* Diagram: https://learn.openenergymonitor.org/electricity-monitoring/ct-sensors/interface-with-arduino
|
||||
*
|
||||
* This is automatically calculated with an exponential moving average/digital low pass filter.
|
||||
*
|
||||
* 0.5 is a good initial approximation to start with for most ESP8266 setups.
|
||||
* The current clamp only measures AC, so any DC component is an unwanted artifact from the
|
||||
* sampling circuit. The AC component is essentially the same as the calculating the Standard-Deviation,
|
||||
* which can be done by cumulating 3 values per sample:
|
||||
* 1) Number of samples
|
||||
* 2) Sum of samples
|
||||
* 3) Sum of sample squared
|
||||
* https://en.wikipedia.org/wiki/Root_mean_square
|
||||
*/
|
||||
float offset_ = 0.5f;
|
||||
|
||||
float last_value_ = 0.0f;
|
||||
float sample_sum_ = 0.0f;
|
||||
float sample_squared_sum_ = 0.0f;
|
||||
uint32_t num_samples_ = 0;
|
||||
bool is_sampling_ = false;
|
||||
/// Calibrate offset value once at boot
|
||||
bool is_calibrating_offset_ = false;
|
||||
};
|
||||
|
||||
} // namespace ct_clamp
|
||||
|
1
esphome/components/current_based/__init__.py
Normal file
1
esphome/components/current_based/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
CODEOWNERS = ["@djwmarcx"]
|
124
esphome/components/current_based/cover.py
Normal file
124
esphome/components/current_based/cover.py
Normal file
@ -0,0 +1,124 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import automation
|
||||
from esphome.components import sensor, cover
|
||||
from esphome.const import (
|
||||
CONF_CLOSE_ACTION,
|
||||
CONF_CLOSE_DURATION,
|
||||
CONF_ID,
|
||||
CONF_OPEN_ACTION,
|
||||
CONF_OPEN_DURATION,
|
||||
CONF_STOP_ACTION,
|
||||
CONF_MAX_DURATION,
|
||||
)
|
||||
|
||||
|
||||
CONF_OPEN_SENSOR = "open_sensor"
|
||||
CONF_OPEN_MOVING_CURRENT_THRESHOLD = "open_moving_current_threshold"
|
||||
CONF_OPEN_OBSTACLE_CURRENT_THRESHOLD = "open_obstacle_current_threshold"
|
||||
|
||||
CONF_CLOSE_SENSOR = "close_sensor"
|
||||
CONF_CLOSE_MOVING_CURRENT_THRESHOLD = "close_moving_current_threshold"
|
||||
CONF_CLOSE_OBSTACLE_CURRENT_THRESHOLD = "close_obstacle_current_threshold"
|
||||
|
||||
CONF_OBSTACLE_ROLLBACK = "obstacle_rollback"
|
||||
CONF_MALFUNCTION_DETECTION = "malfunction_detection"
|
||||
CONF_MALFUNCTION_ACTION = "malfunction_action"
|
||||
CONF_START_SENSING_DELAY = "start_sensing_delay"
|
||||
|
||||
current_based_ns = cg.esphome_ns.namespace("current_based")
|
||||
CurrentBasedCover = current_based_ns.class_(
|
||||
"CurrentBasedCover", cover.Cover, cg.Component
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = cover.COVER_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(CurrentBasedCover),
|
||||
cv.Required(CONF_STOP_ACTION): automation.validate_automation(single=True),
|
||||
cv.Required(CONF_OPEN_SENSOR): cv.use_id(sensor.Sensor),
|
||||
cv.Required(CONF_OPEN_MOVING_CURRENT_THRESHOLD): cv.float_range(
|
||||
min=0, min_included=False
|
||||
),
|
||||
cv.Optional(CONF_OPEN_OBSTACLE_CURRENT_THRESHOLD): cv.float_range(
|
||||
min=0, min_included=False
|
||||
),
|
||||
cv.Required(CONF_OPEN_ACTION): automation.validate_automation(single=True),
|
||||
cv.Required(CONF_OPEN_DURATION): cv.positive_time_period_milliseconds,
|
||||
cv.Required(CONF_CLOSE_SENSOR): cv.use_id(sensor.Sensor),
|
||||
cv.Required(CONF_CLOSE_MOVING_CURRENT_THRESHOLD): cv.float_range(
|
||||
min=0, min_included=False
|
||||
),
|
||||
cv.Optional(CONF_CLOSE_OBSTACLE_CURRENT_THRESHOLD): cv.float_range(
|
||||
min=0, min_included=False
|
||||
),
|
||||
cv.Required(CONF_CLOSE_ACTION): automation.validate_automation(single=True),
|
||||
cv.Required(CONF_CLOSE_DURATION): cv.positive_time_period_milliseconds,
|
||||
cv.Optional(CONF_OBSTACLE_ROLLBACK, default="10%"): cv.percentage,
|
||||
cv.Optional(CONF_MAX_DURATION): cv.positive_time_period_milliseconds,
|
||||
cv.Optional(CONF_MALFUNCTION_DETECTION, default=True): cv.boolean,
|
||||
cv.Optional(CONF_MALFUNCTION_ACTION): automation.validate_automation(
|
||||
single=True
|
||||
),
|
||||
cv.Optional(
|
||||
CONF_START_SENSING_DELAY, default="500ms"
|
||||
): cv.positive_time_period_milliseconds,
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
yield cg.register_component(var, config)
|
||||
yield cover.register_cover(var, config)
|
||||
|
||||
yield automation.build_automation(
|
||||
var.get_stop_trigger(), [], config[CONF_STOP_ACTION]
|
||||
)
|
||||
|
||||
# OPEN
|
||||
bin = yield cg.get_variable(config[CONF_OPEN_SENSOR])
|
||||
cg.add(var.set_open_sensor(bin))
|
||||
cg.add(
|
||||
var.set_open_moving_current_threshold(
|
||||
config[CONF_OPEN_MOVING_CURRENT_THRESHOLD]
|
||||
)
|
||||
)
|
||||
if CONF_OPEN_OBSTACLE_CURRENT_THRESHOLD in config:
|
||||
cg.add(
|
||||
var.set_open_obstacle_current_threshold(
|
||||
config[CONF_OPEN_OBSTACLE_CURRENT_THRESHOLD]
|
||||
)
|
||||
)
|
||||
cg.add(var.set_open_duration(config[CONF_OPEN_DURATION]))
|
||||
yield automation.build_automation(
|
||||
var.get_open_trigger(), [], config[CONF_OPEN_ACTION]
|
||||
)
|
||||
|
||||
# CLOSE
|
||||
bin = yield cg.get_variable(config[CONF_CLOSE_SENSOR])
|
||||
cg.add(var.set_close_sensor(bin))
|
||||
cg.add(
|
||||
var.set_close_moving_current_threshold(
|
||||
config[CONF_CLOSE_MOVING_CURRENT_THRESHOLD]
|
||||
)
|
||||
)
|
||||
if CONF_CLOSE_OBSTACLE_CURRENT_THRESHOLD in config:
|
||||
cg.add(
|
||||
var.set_close_obstacle_current_threshold(
|
||||
config[CONF_CLOSE_OBSTACLE_CURRENT_THRESHOLD]
|
||||
)
|
||||
)
|
||||
cg.add(var.set_close_duration(config[CONF_CLOSE_DURATION]))
|
||||
yield automation.build_automation(
|
||||
var.get_close_trigger(), [], config[CONF_CLOSE_ACTION]
|
||||
)
|
||||
|
||||
cg.add(var.set_obstacle_rollback(config[CONF_OBSTACLE_ROLLBACK]))
|
||||
if CONF_MAX_DURATION in config:
|
||||
cg.add(var.set_max_duration(config[CONF_MAX_DURATION]))
|
||||
cg.add(var.set_malfunction_detection(config[CONF_MALFUNCTION_DETECTION]))
|
||||
if CONF_MALFUNCTION_ACTION in config:
|
||||
yield automation.build_automation(
|
||||
var.get_malfunction_trigger(), [], config[CONF_MALFUNCTION_ACTION]
|
||||
)
|
||||
cg.add(var.set_start_sensing_delay(config[CONF_START_SENSING_DELAY]))
|
251
esphome/components/current_based/current_based_cover.cpp
Normal file
251
esphome/components/current_based/current_based_cover.cpp
Normal file
@ -0,0 +1,251 @@
|
||||
#include "current_based_cover.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include <cfloat>
|
||||
|
||||
namespace esphome {
|
||||
namespace current_based {
|
||||
|
||||
static const char *const TAG = "current_based.cover";
|
||||
|
||||
using namespace esphome::cover;
|
||||
|
||||
CoverTraits CurrentBasedCover::get_traits() {
|
||||
auto traits = CoverTraits();
|
||||
traits.set_supports_position(true);
|
||||
traits.set_is_assumed_state(false);
|
||||
return traits;
|
||||
}
|
||||
void CurrentBasedCover::control(const CoverCall &call) {
|
||||
if (call.get_stop()) {
|
||||
this->direction_idle_();
|
||||
}
|
||||
if (call.get_position().has_value()) {
|
||||
auto pos = *call.get_position();
|
||||
if (pos == this->position) {
|
||||
// already at target
|
||||
} else {
|
||||
auto op = pos < this->position ? COVER_OPERATION_CLOSING : COVER_OPERATION_OPENING;
|
||||
this->target_position_ = pos;
|
||||
this->start_direction_(op);
|
||||
}
|
||||
}
|
||||
}
|
||||
void CurrentBasedCover::setup() {
|
||||
auto restore = this->restore_state_();
|
||||
if (restore.has_value()) {
|
||||
restore->apply(this);
|
||||
} else {
|
||||
this->position = 0.5f;
|
||||
}
|
||||
}
|
||||
|
||||
void CurrentBasedCover::loop() {
|
||||
if (this->current_operation == COVER_OPERATION_IDLE)
|
||||
return;
|
||||
|
||||
const uint32_t now = millis();
|
||||
|
||||
if (this->current_operation == COVER_OPERATION_OPENING) {
|
||||
if (this->malfunction_detection_ && this->is_closing_()) { // Malfunction
|
||||
this->direction_idle_();
|
||||
this->malfunction_trigger_->trigger();
|
||||
ESP_LOGI(TAG, "'%s' - Malfunction detected during opening. Current flow detected in close circuit",
|
||||
this->name_.c_str());
|
||||
} else if (this->is_opening_blocked_()) { // Blocked
|
||||
ESP_LOGD(TAG, "'%s' - Obstacle detected during opening.", this->name_.c_str());
|
||||
this->direction_idle_();
|
||||
if (this->obstacle_rollback_ != 0) {
|
||||
this->set_timeout("rollback", 300, [this]() {
|
||||
ESP_LOGD(TAG, "'%s' - Rollback.", this->name_.c_str());
|
||||
this->target_position_ = clamp(this->position - this->obstacle_rollback_, 0.0F, 1.0F);
|
||||
this->start_direction_(COVER_OPERATION_CLOSING);
|
||||
});
|
||||
}
|
||||
} else if (this->is_initial_delay_finished_() && !this->is_opening_()) { // End reached
|
||||
auto dur = (now - this->start_dir_time_) / 1e3f;
|
||||
ESP_LOGD(TAG, "'%s' - Open position reached. Took %.1fs.", this->name_.c_str(), dur);
|
||||
this->direction_idle_(COVER_OPEN);
|
||||
}
|
||||
} else if (this->current_operation == COVER_OPERATION_CLOSING) {
|
||||
if (this->malfunction_detection_ && this->is_opening_()) { // Malfunction
|
||||
this->direction_idle_();
|
||||
this->malfunction_trigger_->trigger();
|
||||
ESP_LOGI(TAG, "'%s' - Malfunction detected during closing. Current flow detected in open circuit",
|
||||
this->name_.c_str());
|
||||
} else if (this->is_closing_blocked_()) { // Blocked
|
||||
ESP_LOGD(TAG, "'%s' - Obstacle detected during closing.", this->name_.c_str());
|
||||
this->direction_idle_();
|
||||
if (this->obstacle_rollback_ != 0) {
|
||||
this->set_timeout("rollback", 300, [this]() {
|
||||
ESP_LOGD(TAG, "'%s' - Rollback.", this->name_.c_str());
|
||||
this->target_position_ = clamp(this->position + this->obstacle_rollback_, 0.0F, 1.0F);
|
||||
this->start_direction_(COVER_OPERATION_OPENING);
|
||||
});
|
||||
}
|
||||
} else if (this->is_initial_delay_finished_() && !this->is_closing_()) { // End reached
|
||||
auto dur = (now - this->start_dir_time_) / 1e3f;
|
||||
ESP_LOGD(TAG, "'%s' - Close position reached. Took %.1fs.", this->name_.c_str(), dur);
|
||||
this->direction_idle_(COVER_CLOSED);
|
||||
}
|
||||
} else if (now - this->start_dir_time_ > this->max_duration_) {
|
||||
ESP_LOGD(TAG, "'%s' - Max duration reached. Stopping cover.", this->name_.c_str());
|
||||
this->direction_idle_();
|
||||
}
|
||||
|
||||
// Recompute position every loop cycle
|
||||
this->recompute_position_();
|
||||
|
||||
if (this->current_operation != COVER_OPERATION_IDLE && this->is_at_target_()) {
|
||||
this->direction_idle_();
|
||||
}
|
||||
|
||||
// Send current position every second
|
||||
if (this->current_operation != COVER_OPERATION_IDLE && now - this->last_publish_time_ > 1000) {
|
||||
this->publish_state(false);
|
||||
this->last_publish_time_ = now;
|
||||
}
|
||||
}
|
||||
|
||||
void CurrentBasedCover::direction_idle_(float new_position) {
|
||||
this->start_direction_(COVER_OPERATION_IDLE);
|
||||
if (new_position != FLT_MAX) {
|
||||
this->position = new_position;
|
||||
}
|
||||
this->publish_state();
|
||||
}
|
||||
|
||||
void CurrentBasedCover::dump_config() {
|
||||
LOG_COVER("", "Endstop Cover", this);
|
||||
LOG_SENSOR(" ", "Open Sensor", this->open_sensor_);
|
||||
ESP_LOGCONFIG(TAG, " Open moving current threshold: %.11fA", this->open_moving_current_threshold_);
|
||||
if (this->open_obstacle_current_threshold_ != FLT_MAX) {
|
||||
ESP_LOGCONFIG(TAG, " Open obstacle current threshold: %.11fA", this->open_obstacle_current_threshold_);
|
||||
}
|
||||
ESP_LOGCONFIG(TAG, " Open Duration: %.1fs", this->open_duration_ / 1e3f);
|
||||
LOG_SENSOR(" ", "Close Sensor", this->close_sensor_);
|
||||
ESP_LOGCONFIG(TAG, " Close moving current threshold: %.11fA", this->close_moving_current_threshold_);
|
||||
if (this->close_obstacle_current_threshold_ != FLT_MAX) {
|
||||
ESP_LOGCONFIG(TAG, " Close obstacle current threshold: %.11fA", this->close_obstacle_current_threshold_);
|
||||
}
|
||||
ESP_LOGCONFIG(TAG, " Close Duration: %.1fs", this->close_duration_ / 1e3f);
|
||||
ESP_LOGCONFIG(TAG, "Obstacle Rollback: %.1f%%", this->obstacle_rollback_ * 100);
|
||||
if (this->max_duration_ != UINT32_MAX) {
|
||||
ESP_LOGCONFIG(TAG, "Maximun duration: %.1fs", this->max_duration_ / 1e3f);
|
||||
}
|
||||
ESP_LOGCONFIG(TAG, "Start sensing delay: %.1fs", this->start_sensing_delay_ / 1e3f);
|
||||
ESP_LOGCONFIG(TAG, "Malfunction detection: %s", YESNO(this->malfunction_detection_));
|
||||
}
|
||||
|
||||
float CurrentBasedCover::get_setup_priority() const { return setup_priority::DATA; }
|
||||
void CurrentBasedCover::stop_prev_trigger_() {
|
||||
if (this->prev_command_trigger_ != nullptr) {
|
||||
this->prev_command_trigger_->stop_action();
|
||||
this->prev_command_trigger_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
bool CurrentBasedCover::is_opening_() const {
|
||||
return this->open_sensor_->get_state() > this->open_moving_current_threshold_;
|
||||
}
|
||||
|
||||
bool CurrentBasedCover::is_opening_blocked_() const {
|
||||
if (this->open_obstacle_current_threshold_ == FLT_MAX) {
|
||||
return false;
|
||||
}
|
||||
return this->open_sensor_->get_state() > this->open_obstacle_current_threshold_;
|
||||
}
|
||||
|
||||
bool CurrentBasedCover::is_closing_() const {
|
||||
return this->close_sensor_->get_state() > this->close_moving_current_threshold_;
|
||||
}
|
||||
|
||||
bool CurrentBasedCover::is_closing_blocked_() const {
|
||||
if (this->close_obstacle_current_threshold_ == FLT_MAX) {
|
||||
return false;
|
||||
}
|
||||
return this->open_sensor_->get_state() > this->open_obstacle_current_threshold_;
|
||||
}
|
||||
bool CurrentBasedCover::is_initial_delay_finished_() const {
|
||||
return millis() - this->start_dir_time_ > this->start_sensing_delay_;
|
||||
}
|
||||
|
||||
bool CurrentBasedCover::is_at_target_() const {
|
||||
switch (this->current_operation) {
|
||||
case COVER_OPERATION_OPENING:
|
||||
if (this->target_position_ == COVER_OPEN) {
|
||||
if (!this->is_initial_delay_finished_()) // During initial delay, state is assumed
|
||||
return false;
|
||||
return !this->is_opening_();
|
||||
}
|
||||
return this->position >= this->target_position_;
|
||||
case COVER_OPERATION_CLOSING:
|
||||
if (this->target_position_ == COVER_CLOSED) {
|
||||
if (!this->is_initial_delay_finished_()) // During initial delay, state is assumed
|
||||
return false;
|
||||
return !this->is_closing_();
|
||||
}
|
||||
return this->position <= this->target_position_;
|
||||
case COVER_OPERATION_IDLE:
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
void CurrentBasedCover::start_direction_(CoverOperation dir) {
|
||||
if (dir == this->current_operation)
|
||||
return;
|
||||
|
||||
this->recompute_position_();
|
||||
Trigger<> *trig;
|
||||
switch (dir) {
|
||||
case COVER_OPERATION_IDLE:
|
||||
trig = this->stop_trigger_;
|
||||
break;
|
||||
case COVER_OPERATION_OPENING:
|
||||
trig = this->open_trigger_;
|
||||
break;
|
||||
case COVER_OPERATION_CLOSING:
|
||||
trig = this->close_trigger_;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
this->current_operation = dir;
|
||||
|
||||
this->stop_prev_trigger_();
|
||||
trig->trigger();
|
||||
this->prev_command_trigger_ = trig;
|
||||
|
||||
const auto now = millis();
|
||||
this->start_dir_time_ = now;
|
||||
this->last_recompute_time_ = now;
|
||||
}
|
||||
void CurrentBasedCover::recompute_position_() {
|
||||
if (this->current_operation == COVER_OPERATION_IDLE)
|
||||
return;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
const auto now = millis();
|
||||
this->position += dir * (now - this->last_recompute_time_) / action_dur;
|
||||
this->position = clamp(this->position, 0.0F, 1.0F);
|
||||
|
||||
this->last_recompute_time_ = now;
|
||||
}
|
||||
|
||||
} // namespace current_based
|
||||
} // namespace esphome
|
95
esphome/components/current_based/current_based_cover.h
Normal file
95
esphome/components/current_based/current_based_cover.h
Normal file
@ -0,0 +1,95 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/cover/cover.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include <cfloat>
|
||||
|
||||
namespace esphome {
|
||||
namespace current_based {
|
||||
|
||||
class CurrentBasedCover : public cover::Cover, public Component {
|
||||
public:
|
||||
void setup() override;
|
||||
void loop() override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override;
|
||||
|
||||
Trigger<> *get_stop_trigger() const { return this->stop_trigger_; }
|
||||
|
||||
Trigger<> *get_open_trigger() const { return this->open_trigger_; }
|
||||
void set_open_sensor(sensor::Sensor *open_sensor) { this->open_sensor_ = open_sensor; }
|
||||
void set_open_moving_current_threshold(float open_moving_current_threshold) {
|
||||
this->open_moving_current_threshold_ = open_moving_current_threshold;
|
||||
}
|
||||
void set_open_obstacle_current_threshold(float open_obstacle_current_threshold) {
|
||||
this->open_obstacle_current_threshold_ = open_obstacle_current_threshold;
|
||||
}
|
||||
void set_open_duration(uint32_t open_duration) { this->open_duration_ = open_duration; }
|
||||
|
||||
Trigger<> *get_close_trigger() const { return this->close_trigger_; }
|
||||
void set_close_sensor(sensor::Sensor *close_sensor) { this->close_sensor_ = close_sensor; }
|
||||
void set_close_moving_current_threshold(float close_moving_current_threshold) {
|
||||
this->close_moving_current_threshold_ = close_moving_current_threshold;
|
||||
}
|
||||
void set_close_obstacle_current_threshold(float close_obstacle_current_threshold) {
|
||||
this->close_obstacle_current_threshold_ = close_obstacle_current_threshold;
|
||||
}
|
||||
void set_close_duration(uint32_t close_duration) { this->close_duration_ = close_duration; }
|
||||
|
||||
void set_max_duration(uint32_t max_duration) { this->max_duration_ = max_duration; }
|
||||
void set_obstacle_rollback(float obstacle_rollback) { this->obstacle_rollback_ = obstacle_rollback; }
|
||||
|
||||
void set_malfunction_detection(bool malfunction_detection) { this->malfunction_detection_ = malfunction_detection; }
|
||||
void set_start_sensing_delay(uint32_t start_sensing_delay) { this->start_sensing_delay_ = start_sensing_delay; }
|
||||
|
||||
Trigger<> *get_malfunction_trigger() const { return this->malfunction_trigger_; }
|
||||
|
||||
cover::CoverTraits get_traits() override;
|
||||
|
||||
protected:
|
||||
void control(const cover::CoverCall &call) override;
|
||||
void stop_prev_trigger_();
|
||||
|
||||
bool is_at_target_() const;
|
||||
bool is_opening_() const;
|
||||
bool is_opening_blocked_() const;
|
||||
bool is_closing_() const;
|
||||
bool is_closing_blocked_() const;
|
||||
bool is_initial_delay_finished_() const;
|
||||
|
||||
void direction_idle_(float new_position = FLT_MAX);
|
||||
void start_direction_(cover::CoverOperation dir);
|
||||
|
||||
void recompute_position_();
|
||||
|
||||
Trigger<> *stop_trigger_{new Trigger<>()};
|
||||
|
||||
sensor::Sensor *open_sensor_{nullptr};
|
||||
Trigger<> *open_trigger_{new Trigger<>()};
|
||||
float open_moving_current_threshold_;
|
||||
float open_obstacle_current_threshold_{FLT_MAX};
|
||||
uint32_t open_duration_;
|
||||
|
||||
sensor::Sensor *close_sensor_{nullptr};
|
||||
Trigger<> *close_trigger_{new Trigger<>()};
|
||||
float close_moving_current_threshold_;
|
||||
float close_obstacle_current_threshold_{FLT_MAX};
|
||||
uint32_t close_duration_;
|
||||
|
||||
uint32_t max_duration_{UINT32_MAX};
|
||||
bool malfunction_detection_{true};
|
||||
Trigger<> *malfunction_trigger_{new Trigger<>()};
|
||||
uint32_t start_sensing_delay_;
|
||||
float obstacle_rollback_;
|
||||
|
||||
Trigger<> *prev_command_trigger_{nullptr};
|
||||
uint32_t last_recompute_time_{0};
|
||||
uint32_t start_dir_time_{0};
|
||||
uint32_t last_publish_time_{0};
|
||||
float target_position_{0};
|
||||
};
|
||||
|
||||
} // namespace current_based
|
||||
} // namespace esphome
|
@ -11,13 +11,17 @@ dallas_ns = cg.esphome_ns.namespace("dallas")
|
||||
DallasComponent = dallas_ns.class_("DallasComponent", cg.PollingComponent)
|
||||
ESPOneWire = dallas_ns.class_("ESPOneWire")
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(DallasComponent),
|
||||
cv.GenerateID(CONF_ONE_WIRE_ID): cv.declare_id(ESPOneWire),
|
||||
cv.Required(CONF_PIN): pins.internal_gpio_output_pin_schema,
|
||||
}
|
||||
).extend(cv.polling_component_schema("60s"))
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(DallasComponent),
|
||||
cv.GenerateID(CONF_ONE_WIRE_ID): cv.declare_id(ESPOneWire),
|
||||
cv.Required(CONF_PIN): pins.internal_gpio_output_pin_schema,
|
||||
}
|
||||
).extend(cv.polling_component_schema("60s")),
|
||||
# pin_mode call logs in esp-idf, but InterruptLock is active -> crash
|
||||
cv.only_with_arduino,
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
|
@ -97,16 +97,7 @@ void DallasComponent::dump_config() {
|
||||
}
|
||||
}
|
||||
|
||||
DallasTemperatureSensor *DallasComponent::get_sensor_by_address(uint64_t address, uint8_t resolution) {
|
||||
auto s = new DallasTemperatureSensor(address, resolution, this);
|
||||
this->sensors_.push_back(s);
|
||||
return s;
|
||||
}
|
||||
DallasTemperatureSensor *DallasComponent::get_sensor_by_index(uint8_t index, uint8_t resolution) {
|
||||
auto s = this->get_sensor_by_address(0, resolution);
|
||||
s->set_index(index);
|
||||
return s;
|
||||
}
|
||||
void DallasComponent::register_sensor(DallasTemperatureSensor *sensor) { this->sensors_.push_back(sensor); }
|
||||
void DallasComponent::update() {
|
||||
this->status_clear_warning();
|
||||
|
||||
@ -157,11 +148,6 @@ void DallasComponent::update() {
|
||||
}
|
||||
DallasComponent::DallasComponent(ESPOneWire *one_wire) : one_wire_(one_wire) {}
|
||||
|
||||
DallasTemperatureSensor::DallasTemperatureSensor(uint64_t address, uint8_t resolution, DallasComponent *parent)
|
||||
: parent_(parent) {
|
||||
this->set_address(address);
|
||||
this->set_resolution(resolution);
|
||||
}
|
||||
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; }
|
||||
@ -175,7 +161,7 @@ const std::string &DallasTemperatureSensor::get_address_name() {
|
||||
|
||||
return this->address_name_;
|
||||
}
|
||||
bool ICACHE_RAM_ATTR DallasTemperatureSensor::read_scratch_pad() {
|
||||
bool IRAM_ATTR DallasTemperatureSensor::read_scratch_pad() {
|
||||
ESPOneWire *wire = this->parent_->one_wire_;
|
||||
if (!wire->reset()) {
|
||||
return false;
|
||||
|
@ -13,8 +13,7 @@ class DallasComponent : public PollingComponent {
|
||||
public:
|
||||
explicit DallasComponent(ESPOneWire *one_wire);
|
||||
|
||||
DallasTemperatureSensor *get_sensor_by_address(uint64_t address, uint8_t resolution);
|
||||
DallasTemperatureSensor *get_sensor_by_index(uint8_t index, uint8_t resolution);
|
||||
void register_sensor(DallasTemperatureSensor *sensor);
|
||||
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
@ -33,8 +32,7 @@ class DallasComponent : public PollingComponent {
|
||||
/// Internal class that helps us create multiple sensors for one Dallas hub.
|
||||
class DallasTemperatureSensor : public sensor::Sensor {
|
||||
public:
|
||||
DallasTemperatureSensor(uint64_t address, uint8_t resolution, DallasComponent *parent);
|
||||
|
||||
void set_parent(DallasComponent *parent) { parent_ = parent; }
|
||||
/// Helper to get a pointer to the address as uint8_t.
|
||||
uint8_t *get_address8();
|
||||
/// Helper to create (and cache) the name for this sensor. For example "0xfe0000031f1eaf29".
|
||||
|
@ -12,11 +12,11 @@ const int ONE_WIRE_ROM_SEARCH = 0xF0;
|
||||
|
||||
ESPOneWire::ESPOneWire(GPIOPin *pin) : pin_(pin) {}
|
||||
|
||||
bool HOT ICACHE_RAM_ATTR ESPOneWire::reset() {
|
||||
bool HOT IRAM_ATTR ESPOneWire::reset() {
|
||||
uint8_t retries = 125;
|
||||
|
||||
// Wait for communication to clear
|
||||
this->pin_->pin_mode(INPUT_PULLUP);
|
||||
this->pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
|
||||
do {
|
||||
if (--retries == 0)
|
||||
return false;
|
||||
@ -24,12 +24,12 @@ bool HOT ICACHE_RAM_ATTR ESPOneWire::reset() {
|
||||
} while (!this->pin_->digital_read());
|
||||
|
||||
// Send 480µs LOW TX reset pulse
|
||||
this->pin_->pin_mode(OUTPUT);
|
||||
this->pin_->pin_mode(gpio::FLAG_OUTPUT);
|
||||
this->pin_->digital_write(false);
|
||||
delayMicroseconds(480);
|
||||
|
||||
// Switch into RX mode, letting the pin float
|
||||
this->pin_->pin_mode(INPUT_PULLUP);
|
||||
this->pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
|
||||
// after 15µs-60µs wait time, responder pulls low for 60µs-240µs
|
||||
// let's have 70µs just in case
|
||||
delayMicroseconds(70);
|
||||
@ -39,9 +39,9 @@ bool HOT ICACHE_RAM_ATTR ESPOneWire::reset() {
|
||||
return r;
|
||||
}
|
||||
|
||||
void HOT ICACHE_RAM_ATTR ESPOneWire::write_bit(bool bit) {
|
||||
void HOT IRAM_ATTR ESPOneWire::write_bit(bool bit) {
|
||||
// Initiate write/read by pulling low.
|
||||
this->pin_->pin_mode(OUTPUT);
|
||||
this->pin_->pin_mode(gpio::FLAG_OUTPUT);
|
||||
this->pin_->digital_write(false);
|
||||
|
||||
// bus sampled within 15µs and 60µs after pulling LOW.
|
||||
@ -60,14 +60,14 @@ void HOT ICACHE_RAM_ATTR ESPOneWire::write_bit(bool bit) {
|
||||
}
|
||||
}
|
||||
|
||||
bool HOT ICACHE_RAM_ATTR ESPOneWire::read_bit() {
|
||||
bool HOT IRAM_ATTR ESPOneWire::read_bit() {
|
||||
// Initiate read slot by pulling LOW for at least 1µs
|
||||
this->pin_->pin_mode(OUTPUT);
|
||||
this->pin_->pin_mode(gpio::FLAG_OUTPUT);
|
||||
this->pin_->digital_write(false);
|
||||
delayMicroseconds(3);
|
||||
|
||||
// release bus, we have to sample within 15µs of pulling low
|
||||
this->pin_->pin_mode(INPUT_PULLUP);
|
||||
this->pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
|
||||
delayMicroseconds(10);
|
||||
|
||||
bool r = this->pin_->digital_read();
|
||||
@ -76,43 +76,43 @@ bool HOT ICACHE_RAM_ATTR ESPOneWire::read_bit() {
|
||||
return r;
|
||||
}
|
||||
|
||||
void ICACHE_RAM_ATTR ESPOneWire::write8(uint8_t val) {
|
||||
void IRAM_ATTR ESPOneWire::write8(uint8_t val) {
|
||||
for (uint8_t i = 0; i < 8; i++) {
|
||||
this->write_bit(bool((1u << i) & val));
|
||||
}
|
||||
}
|
||||
|
||||
void ICACHE_RAM_ATTR ESPOneWire::write64(uint64_t 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 ICACHE_RAM_ATTR ESPOneWire::read8() {
|
||||
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 ICACHE_RAM_ATTR ESPOneWire::read64() {
|
||||
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 ICACHE_RAM_ATTR ESPOneWire::select(uint64_t address) {
|
||||
void IRAM_ATTR ESPOneWire::select(uint64_t address) {
|
||||
this->write8(ONE_WIRE_ROM_SELECT);
|
||||
this->write64(address);
|
||||
}
|
||||
void ICACHE_RAM_ATTR ESPOneWire::reset_search() {
|
||||
void IRAM_ATTR ESPOneWire::reset_search() {
|
||||
this->last_discrepancy_ = 0;
|
||||
this->last_device_flag_ = false;
|
||||
this->last_family_discrepancy_ = 0;
|
||||
this->rom_number_ = 0;
|
||||
}
|
||||
uint64_t HOT ICACHE_RAM_ATTR ESPOneWire::search() {
|
||||
uint64_t HOT IRAM_ATTR ESPOneWire::search() {
|
||||
if (this->last_device_flag_) {
|
||||
return 0u;
|
||||
}
|
||||
@ -196,7 +196,7 @@ uint64_t HOT ICACHE_RAM_ATTR ESPOneWire::search() {
|
||||
|
||||
return this->rom_number_;
|
||||
}
|
||||
std::vector<uint64_t> ICACHE_RAM_ATTR ESPOneWire::search_vec() {
|
||||
std::vector<uint64_t> IRAM_ATTR ESPOneWire::search_vec() {
|
||||
std::vector<uint64_t> res;
|
||||
|
||||
this->reset_search();
|
||||
@ -206,12 +206,12 @@ std::vector<uint64_t> ICACHE_RAM_ATTR ESPOneWire::search_vec() {
|
||||
|
||||
return res;
|
||||
}
|
||||
void ICACHE_RAM_ATTR ESPOneWire::skip() {
|
||||
void IRAM_ATTR ESPOneWire::skip() {
|
||||
this->write8(0xCC); // skip ROM
|
||||
}
|
||||
GPIOPin *ESPOneWire::get_pin() { return this->pin_; }
|
||||
|
||||
uint8_t ICACHE_RAM_ATTR *ESPOneWire::rom_number8_() { return reinterpret_cast<uint8_t *>(&this->rom_number_); }
|
||||
uint8_t IRAM_ATTR *ESPOneWire::rom_number8_() { return reinterpret_cast<uint8_t *>(&this->rom_number_); }
|
||||
|
||||
} // namespace dallas
|
||||
} // namespace esphome
|
||||
|
@ -1,7 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/esphal.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include <vector>
|
||||
|
||||
namespace esphome {
|
||||
namespace dallas {
|
||||
|
@ -36,10 +36,17 @@ CONFIG_SCHEMA = cv.All(
|
||||
|
||||
async def to_code(config):
|
||||
hub = await cg.get_variable(config[CONF_DALLAS_ID])
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
|
||||
if CONF_ADDRESS in config:
|
||||
address = config[CONF_ADDRESS]
|
||||
rhs = hub.Pget_sensor_by_address(address, config.get(CONF_RESOLUTION))
|
||||
cg.add(var.set_address(config[CONF_ADDRESS]))
|
||||
else:
|
||||
rhs = hub.Pget_sensor_by_index(config[CONF_INDEX], config.get(CONF_RESOLUTION))
|
||||
var = cg.Pvariable(config[CONF_ID], rhs)
|
||||
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))
|
||||
await sensor.register_sensor(var, config)
|
||||
|
27
esphome/components/daly_bms/__init__.py
Normal file
27
esphome/components/daly_bms/__init__.py
Normal file
@ -0,0 +1,27 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import uart
|
||||
from esphome.const import CONF_ID
|
||||
|
||||
CODEOWNERS = ["@s1lvi0"]
|
||||
DEPENDENCIES = ["uart"]
|
||||
AUTO_LOAD = ["sensor", "text_sensor", "binary_sensor"]
|
||||
|
||||
CONF_BMS_DALY_ID = "bms_daly_id"
|
||||
|
||||
daly_bms = cg.esphome_ns.namespace("daly_bms")
|
||||
DalyBmsComponent = daly_bms.class_(
|
||||
"DalyBmsComponent", cg.PollingComponent, uart.UARTDevice
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema({cv.GenerateID(): cv.declare_id(DalyBmsComponent)})
|
||||
.extend(uart.UART_DEVICE_SCHEMA)
|
||||
.extend(cv.polling_component_schema("30s"))
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await uart.register_uart_device(var, config)
|
49
esphome/components/daly_bms/binary_sensor.py
Normal file
49
esphome/components/daly_bms/binary_sensor.py
Normal file
@ -0,0 +1,49 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import binary_sensor
|
||||
from esphome.const import CONF_ID
|
||||
from . import DalyBmsComponent, CONF_BMS_DALY_ID
|
||||
|
||||
CONF_CHARGING_MOS_ENABLED = "charging_mos_enabled"
|
||||
CONF_DISCHARGING_MOS_ENABLED = "discharging_mos_enabled"
|
||||
|
||||
TYPES = [
|
||||
CONF_CHARGING_MOS_ENABLED,
|
||||
CONF_DISCHARGING_MOS_ENABLED,
|
||||
]
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(CONF_BMS_DALY_ID): cv.use_id(DalyBmsComponent),
|
||||
cv.Optional(
|
||||
CONF_CHARGING_MOS_ENABLED
|
||||
): binary_sensor.BINARY_SENSOR_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(binary_sensor.BinarySensor),
|
||||
}
|
||||
),
|
||||
cv.Optional(
|
||||
CONF_DISCHARGING_MOS_ENABLED
|
||||
): binary_sensor.BINARY_SENSOR_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(binary_sensor.BinarySensor),
|
||||
}
|
||||
),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
)
|
||||
|
||||
|
||||
async def setup_conf(config, key, hub):
|
||||
if key in config:
|
||||
conf = config[key]
|
||||
sens = cg.new_Pvariable(conf[CONF_ID])
|
||||
await binary_sensor.register_binary_sensor(sens, conf)
|
||||
cg.add(getattr(hub, f"set_{key}_binary_sensor")(sens))
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
hub = await cg.get_variable(config[CONF_BMS_DALY_ID])
|
||||
for key in TYPES:
|
||||
await setup_conf(config, key, hub)
|
181
esphome/components/daly_bms/daly_bms.cpp
Normal file
181
esphome/components/daly_bms/daly_bms.cpp
Normal file
@ -0,0 +1,181 @@
|
||||
#include "daly_bms.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include <vector>
|
||||
|
||||
namespace esphome {
|
||||
namespace daly_bms {
|
||||
|
||||
static const char *const TAG = "daly_bms";
|
||||
|
||||
static const uint8_t DALY_FRAME_SIZE = 13;
|
||||
static const uint8_t DALY_TEMPERATURE_OFFSET = 40;
|
||||
static const uint16_t DALY_CURRENT_OFFSET = 30000;
|
||||
|
||||
static const uint8_t DALY_REQUEST_BATTERY_LEVEL = 0x90;
|
||||
static const uint8_t DALY_REQUEST_MIN_MAX_VOLTAGE = 0x91;
|
||||
static const uint8_t DALY_REQUEST_MIN_MAX_TEMPERATURE = 0x92;
|
||||
static const uint8_t DALY_REQUEST_MOS = 0x93;
|
||||
static const uint8_t DALY_REQUEST_STATUS = 0x94;
|
||||
static const uint8_t DALY_REQUEST_TEMPERATURE = 0x96;
|
||||
|
||||
void DalyBmsComponent::setup() {}
|
||||
|
||||
void DalyBmsComponent::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "Daly BMS:");
|
||||
this->check_uart_settings(9600);
|
||||
}
|
||||
|
||||
void DalyBmsComponent::update() {
|
||||
this->request_data_(DALY_REQUEST_BATTERY_LEVEL);
|
||||
this->request_data_(DALY_REQUEST_MIN_MAX_VOLTAGE);
|
||||
this->request_data_(DALY_REQUEST_MIN_MAX_TEMPERATURE);
|
||||
this->request_data_(DALY_REQUEST_MOS);
|
||||
this->request_data_(DALY_REQUEST_STATUS);
|
||||
this->request_data_(DALY_REQUEST_TEMPERATURE);
|
||||
|
||||
std::vector<uint8_t> get_battery_level_data;
|
||||
int available_data = this->available();
|
||||
if (available_data >= DALY_FRAME_SIZE) {
|
||||
get_battery_level_data.resize(available_data);
|
||||
this->read_array(get_battery_level_data.data(), available_data);
|
||||
this->decode_data_(get_battery_level_data);
|
||||
}
|
||||
}
|
||||
|
||||
float DalyBmsComponent::get_setup_priority() const { return setup_priority::DATA; }
|
||||
|
||||
void DalyBmsComponent::request_data_(uint8_t data_id) {
|
||||
uint8_t request_message[DALY_FRAME_SIZE];
|
||||
|
||||
request_message[0] = 0xA5; // Start Flag
|
||||
request_message[1] = 0x80; // Communication Module Address
|
||||
request_message[2] = data_id; // Data ID
|
||||
request_message[3] = 0x08; // Data Length (Fixed)
|
||||
request_message[4] = 0x00; // Empty Data
|
||||
request_message[5] = 0x00; // |
|
||||
request_message[6] = 0x00; // |
|
||||
request_message[7] = 0x00; // |
|
||||
request_message[8] = 0x00; // |
|
||||
request_message[9] = 0x00; // |
|
||||
request_message[10] = 0x00; // |
|
||||
request_message[11] = 0x00; // Empty Data
|
||||
request_message[12] = (uint8_t)(request_message[0] + request_message[1] + request_message[2] +
|
||||
request_message[3]); // Checksum (Lower byte of the other bytes sum)
|
||||
|
||||
this->write_array(request_message, sizeof(request_message));
|
||||
this->flush();
|
||||
}
|
||||
|
||||
void DalyBmsComponent::decode_data_(std::vector<uint8_t> data) {
|
||||
auto it = data.begin();
|
||||
|
||||
while ((it = std::find(it, data.end(), 0xA5)) != data.end()) {
|
||||
if (data.end() - it >= DALY_FRAME_SIZE && it[1] == 0x01) {
|
||||
uint8_t checksum;
|
||||
int sum = 0;
|
||||
for (int i = 0; i < 12; i++) {
|
||||
sum += it[i];
|
||||
}
|
||||
checksum = sum;
|
||||
|
||||
if (checksum == it[12]) {
|
||||
switch (it[2]) {
|
||||
case DALY_REQUEST_BATTERY_LEVEL:
|
||||
if (this->voltage_sensor_) {
|
||||
this->voltage_sensor_->publish_state((float) encode_uint16(it[4], it[5]) / 10);
|
||||
}
|
||||
if (this->current_sensor_) {
|
||||
this->current_sensor_->publish_state(((float) (encode_uint16(it[8], it[9]) - DALY_CURRENT_OFFSET) / 10));
|
||||
}
|
||||
if (this->battery_level_sensor_) {
|
||||
this->battery_level_sensor_->publish_state((float) encode_uint16(it[10], it[11]) / 10);
|
||||
}
|
||||
break;
|
||||
|
||||
case DALY_REQUEST_MIN_MAX_VOLTAGE:
|
||||
if (this->max_cell_voltage_) {
|
||||
this->max_cell_voltage_->publish_state((float) encode_uint16(it[4], it[5]) / 1000);
|
||||
}
|
||||
if (this->max_cell_voltage_number_) {
|
||||
this->max_cell_voltage_number_->publish_state(it[6]);
|
||||
}
|
||||
if (this->min_cell_voltage_) {
|
||||
this->min_cell_voltage_->publish_state((float) encode_uint16(it[7], it[8]) / 1000);
|
||||
}
|
||||
if (this->min_cell_voltage_number_) {
|
||||
this->min_cell_voltage_number_->publish_state(it[9]);
|
||||
}
|
||||
break;
|
||||
|
||||
case DALY_REQUEST_MIN_MAX_TEMPERATURE:
|
||||
if (this->max_temperature_) {
|
||||
this->max_temperature_->publish_state(it[4] - DALY_TEMPERATURE_OFFSET);
|
||||
}
|
||||
if (this->max_temperature_probe_number_) {
|
||||
this->max_temperature_probe_number_->publish_state(it[5]);
|
||||
}
|
||||
if (this->min_temperature_) {
|
||||
this->min_temperature_->publish_state(it[6] - DALY_TEMPERATURE_OFFSET);
|
||||
}
|
||||
if (this->min_temperature_probe_number_) {
|
||||
this->min_temperature_probe_number_->publish_state(it[7]);
|
||||
}
|
||||
break;
|
||||
|
||||
case DALY_REQUEST_MOS:
|
||||
if (this->status_text_sensor_ != nullptr) {
|
||||
switch (it[4]) {
|
||||
case 0:
|
||||
this->status_text_sensor_->publish_state("Stationary");
|
||||
break;
|
||||
case 1:
|
||||
this->status_text_sensor_->publish_state("Charging");
|
||||
break;
|
||||
case 2:
|
||||
this->status_text_sensor_->publish_state("Discharging");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (this->charging_mos_enabled_) {
|
||||
this->charging_mos_enabled_->publish_state(it[5]);
|
||||
}
|
||||
if (this->discharging_mos_enabled_) {
|
||||
this->discharging_mos_enabled_->publish_state(it[6]);
|
||||
}
|
||||
if (this->remaining_capacity_) {
|
||||
this->remaining_capacity_->publish_state((float) encode_uint32(it[8], it[9], it[10], it[11]) / 1000);
|
||||
}
|
||||
break;
|
||||
|
||||
case DALY_REQUEST_STATUS:
|
||||
if (this->cells_number_) {
|
||||
this->cells_number_->publish_state(it[4]);
|
||||
}
|
||||
break;
|
||||
|
||||
case DALY_REQUEST_TEMPERATURE:
|
||||
if (it[4] == 1) {
|
||||
if (this->temperature_1_sensor_) {
|
||||
this->temperature_1_sensor_->publish_state(it[5] - DALY_TEMPERATURE_OFFSET);
|
||||
}
|
||||
if (this->temperature_2_sensor_) {
|
||||
this->temperature_2_sensor_->publish_state(it[6] - DALY_TEMPERATURE_OFFSET);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
std::advance(it, DALY_FRAME_SIZE);
|
||||
} else {
|
||||
std::advance(it, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace daly_bms
|
||||
} // namespace esphome
|
83
esphome/components/daly_bms/daly_bms.h
Normal file
83
esphome/components/daly_bms/daly_bms.h
Normal file
@ -0,0 +1,83 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/text_sensor/text_sensor.h"
|
||||
#include "esphome/components/binary_sensor/binary_sensor.h"
|
||||
#include "esphome/components/uart/uart.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace daly_bms {
|
||||
|
||||
class DalyBmsComponent : public PollingComponent, public uart::UARTDevice {
|
||||
public:
|
||||
DalyBmsComponent() = default;
|
||||
|
||||
// SENSORS
|
||||
void set_voltage_sensor(sensor::Sensor *voltage_sensor) { voltage_sensor_ = voltage_sensor; }
|
||||
void set_current_sensor(sensor::Sensor *current_sensor) { current_sensor_ = current_sensor; }
|
||||
void set_battery_level_sensor(sensor::Sensor *battery_level_sensor) { battery_level_sensor_ = battery_level_sensor; }
|
||||
void set_max_cell_voltage_sensor(sensor::Sensor *max_cell_voltage) { max_cell_voltage_ = max_cell_voltage; }
|
||||
void set_max_cell_voltage_number_sensor(sensor::Sensor *max_cell_voltage_number) {
|
||||
max_cell_voltage_number_ = max_cell_voltage_number;
|
||||
}
|
||||
void set_min_cell_voltage_sensor(sensor::Sensor *min_cell_voltage) { min_cell_voltage_ = min_cell_voltage; }
|
||||
void set_min_cell_voltage_number_sensor(sensor::Sensor *min_cell_voltage_number) {
|
||||
min_cell_voltage_number_ = min_cell_voltage_number;
|
||||
}
|
||||
void set_max_temperature_sensor(sensor::Sensor *max_temperature) { max_temperature_ = max_temperature; }
|
||||
void set_max_temperature_probe_number_sensor(sensor::Sensor *max_temperature_probe_number) {
|
||||
max_temperature_probe_number_ = max_temperature_probe_number;
|
||||
}
|
||||
void set_min_temperature_sensor(sensor::Sensor *min_temperature) { min_temperature_ = min_temperature; }
|
||||
void set_min_temperature_probe_number_sensor(sensor::Sensor *min_temperature_probe_number) {
|
||||
min_temperature_probe_number_ = min_temperature_probe_number;
|
||||
}
|
||||
void set_remaining_capacity_sensor(sensor::Sensor *remaining_capacity) { remaining_capacity_ = remaining_capacity; }
|
||||
void set_cells_number_sensor(sensor::Sensor *cells_number) { cells_number_ = cells_number; }
|
||||
void set_temperature_1_sensor(sensor::Sensor *temperature_1_sensor) { temperature_1_sensor_ = temperature_1_sensor; }
|
||||
void set_temperature_2_sensor(sensor::Sensor *temperature_2_sensor) { temperature_2_sensor_ = temperature_2_sensor; }
|
||||
// TEXT_SENSORS
|
||||
void set_status_text_sensor(text_sensor::TextSensor *status_text_sensor) { status_text_sensor_ = status_text_sensor; }
|
||||
// BINARY_SENSORS
|
||||
void set_charging_mos_enabled_binary_sensor(binary_sensor::BinarySensor *charging_mos_enabled) {
|
||||
charging_mos_enabled_ = charging_mos_enabled;
|
||||
}
|
||||
void set_discharging_mos_enabled_binary_sensor(binary_sensor::BinarySensor *discharging_mos_enabled) {
|
||||
discharging_mos_enabled_ = discharging_mos_enabled;
|
||||
}
|
||||
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
void update() override;
|
||||
|
||||
float get_setup_priority() const override;
|
||||
|
||||
protected:
|
||||
void request_data_(uint8_t data_id);
|
||||
void decode_data_(std::vector<uint8_t> data);
|
||||
|
||||
sensor::Sensor *voltage_sensor_{nullptr};
|
||||
sensor::Sensor *current_sensor_{nullptr};
|
||||
sensor::Sensor *battery_level_sensor_{nullptr};
|
||||
sensor::Sensor *max_cell_voltage_{nullptr};
|
||||
sensor::Sensor *max_cell_voltage_number_{nullptr};
|
||||
sensor::Sensor *min_cell_voltage_{nullptr};
|
||||
sensor::Sensor *min_cell_voltage_number_{nullptr};
|
||||
sensor::Sensor *max_temperature_{nullptr};
|
||||
sensor::Sensor *max_temperature_probe_number_{nullptr};
|
||||
sensor::Sensor *min_temperature_{nullptr};
|
||||
sensor::Sensor *min_temperature_probe_number_{nullptr};
|
||||
sensor::Sensor *remaining_capacity_{nullptr};
|
||||
sensor::Sensor *cells_number_{nullptr};
|
||||
sensor::Sensor *temperature_1_sensor_{nullptr};
|
||||
sensor::Sensor *temperature_2_sensor_{nullptr};
|
||||
|
||||
text_sensor::TextSensor *status_text_sensor_{nullptr};
|
||||
|
||||
binary_sensor::BinarySensor *charging_mos_enabled_{nullptr};
|
||||
binary_sensor::BinarySensor *discharging_mos_enabled_{nullptr};
|
||||
};
|
||||
|
||||
} // namespace daly_bms
|
||||
} // namespace esphome
|
192
esphome/components/daly_bms/sensor.py
Normal file
192
esphome/components/daly_bms/sensor.py
Normal file
@ -0,0 +1,192 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import sensor
|
||||
from esphome.const import (
|
||||
CONF_VOLTAGE,
|
||||
CONF_CURRENT,
|
||||
CONF_BATTERY_LEVEL,
|
||||
CONF_MAX_TEMPERATURE,
|
||||
CONF_MIN_TEMPERATURE,
|
||||
DEVICE_CLASS_VOLTAGE,
|
||||
DEVICE_CLASS_CURRENT,
|
||||
DEVICE_CLASS_BATTERY,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
DEVICE_CLASS_EMPTY,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
STATE_CLASS_NONE,
|
||||
UNIT_VOLT,
|
||||
UNIT_AMPERE,
|
||||
UNIT_PERCENT,
|
||||
UNIT_CELSIUS,
|
||||
UNIT_EMPTY,
|
||||
ICON_FLASH,
|
||||
ICON_PERCENT,
|
||||
ICON_COUNTER,
|
||||
ICON_THERMOMETER,
|
||||
ICON_GAUGE,
|
||||
)
|
||||
from . import DalyBmsComponent, CONF_BMS_DALY_ID
|
||||
|
||||
CONF_MAX_CELL_VOLTAGE = "max_cell_voltage"
|
||||
CONF_MAX_CELL_VOLTAGE_NUMBER = "max_cell_voltage_number"
|
||||
CONF_MIN_CELL_VOLTAGE = "min_cell_voltage"
|
||||
CONF_MIN_CELL_VOLTAGE_NUMBER = "min_cell_voltage_number"
|
||||
CONF_MAX_TEMPERATURE_PROBE_NUMBER = "max_temperature_probe_number"
|
||||
CONF_MIN_TEMPERATURE_PROBE_NUMBER = "min_temperature_probe_number"
|
||||
CONF_CELLS_NUMBER = "cells_number"
|
||||
|
||||
CONF_REMAINING_CAPACITY = "remaining_capacity"
|
||||
CONF_TEMPERATURE_1 = "temperature_1"
|
||||
CONF_TEMPERATURE_2 = "temperature_2"
|
||||
|
||||
ICON_CURRENT_DC = "mdi:current-dc"
|
||||
ICON_BATTERY_OUTLINE = "mdi:battery-outline"
|
||||
ICON_THERMOMETER_CHEVRON_UP = "mdi:thermometer-chevron-up"
|
||||
ICON_THERMOMETER_CHEVRON_DOWN = "mdi:thermometer-chevron-down"
|
||||
ICON_CAR_BATTERY = "mdi:car-battery"
|
||||
|
||||
UNIT_AMPERE_HOUR = "Ah"
|
||||
|
||||
TYPES = [
|
||||
CONF_VOLTAGE,
|
||||
CONF_CURRENT,
|
||||
CONF_BATTERY_LEVEL,
|
||||
CONF_MAX_CELL_VOLTAGE,
|
||||
CONF_MAX_CELL_VOLTAGE_NUMBER,
|
||||
CONF_MIN_CELL_VOLTAGE,
|
||||
CONF_MIN_CELL_VOLTAGE_NUMBER,
|
||||
CONF_MAX_TEMPERATURE,
|
||||
CONF_MAX_TEMPERATURE_PROBE_NUMBER,
|
||||
CONF_MIN_TEMPERATURE,
|
||||
CONF_MIN_TEMPERATURE_PROBE_NUMBER,
|
||||
CONF_CELLS_NUMBER,
|
||||
CONF_REMAINING_CAPACITY,
|
||||
CONF_TEMPERATURE_1,
|
||||
CONF_TEMPERATURE_2,
|
||||
]
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(CONF_BMS_DALY_ID): cv.use_id(DalyBmsComponent),
|
||||
cv.Optional(CONF_VOLTAGE): sensor.sensor_schema(
|
||||
UNIT_VOLT,
|
||||
ICON_FLASH,
|
||||
1,
|
||||
DEVICE_CLASS_VOLTAGE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_CURRENT): sensor.sensor_schema(
|
||||
UNIT_AMPERE,
|
||||
ICON_CURRENT_DC,
|
||||
1,
|
||||
DEVICE_CLASS_CURRENT,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema(
|
||||
UNIT_PERCENT,
|
||||
ICON_PERCENT,
|
||||
1,
|
||||
DEVICE_CLASS_BATTERY,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_MAX_CELL_VOLTAGE): sensor.sensor_schema(
|
||||
UNIT_VOLT,
|
||||
ICON_FLASH,
|
||||
2,
|
||||
DEVICE_CLASS_VOLTAGE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_MAX_CELL_VOLTAGE_NUMBER): sensor.sensor_schema(
|
||||
UNIT_EMPTY,
|
||||
ICON_COUNTER,
|
||||
0,
|
||||
DEVICE_CLASS_EMPTY,
|
||||
STATE_CLASS_NONE,
|
||||
),
|
||||
cv.Optional(CONF_MIN_CELL_VOLTAGE): sensor.sensor_schema(
|
||||
UNIT_VOLT,
|
||||
ICON_FLASH,
|
||||
2,
|
||||
DEVICE_CLASS_VOLTAGE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_MIN_CELL_VOLTAGE_NUMBER): sensor.sensor_schema(
|
||||
UNIT_EMPTY,
|
||||
ICON_COUNTER,
|
||||
0,
|
||||
DEVICE_CLASS_EMPTY,
|
||||
STATE_CLASS_NONE,
|
||||
),
|
||||
cv.Optional(CONF_MAX_TEMPERATURE): sensor.sensor_schema(
|
||||
UNIT_CELSIUS,
|
||||
ICON_THERMOMETER_CHEVRON_UP,
|
||||
0,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_MAX_TEMPERATURE_PROBE_NUMBER): sensor.sensor_schema(
|
||||
UNIT_EMPTY,
|
||||
ICON_COUNTER,
|
||||
0,
|
||||
DEVICE_CLASS_EMPTY,
|
||||
STATE_CLASS_NONE,
|
||||
),
|
||||
cv.Optional(CONF_MIN_TEMPERATURE): sensor.sensor_schema(
|
||||
UNIT_CELSIUS,
|
||||
ICON_THERMOMETER_CHEVRON_DOWN,
|
||||
0,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_MIN_TEMPERATURE_PROBE_NUMBER): sensor.sensor_schema(
|
||||
UNIT_EMPTY,
|
||||
ICON_COUNTER,
|
||||
0,
|
||||
DEVICE_CLASS_EMPTY,
|
||||
STATE_CLASS_NONE,
|
||||
),
|
||||
cv.Optional(CONF_REMAINING_CAPACITY): sensor.sensor_schema(
|
||||
UNIT_AMPERE_HOUR,
|
||||
ICON_GAUGE,
|
||||
2,
|
||||
DEVICE_CLASS_VOLTAGE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_CELLS_NUMBER): sensor.sensor_schema(
|
||||
UNIT_EMPTY,
|
||||
ICON_COUNTER,
|
||||
0,
|
||||
DEVICE_CLASS_EMPTY,
|
||||
STATE_CLASS_NONE,
|
||||
),
|
||||
cv.Optional(CONF_TEMPERATURE_1): sensor.sensor_schema(
|
||||
UNIT_CELSIUS,
|
||||
ICON_THERMOMETER,
|
||||
0,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_TEMPERATURE_2): sensor.sensor_schema(
|
||||
UNIT_CELSIUS,
|
||||
ICON_THERMOMETER,
|
||||
0,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
)
|
||||
|
||||
|
||||
async def setup_conf(config, key, hub):
|
||||
if key in config:
|
||||
conf = config[key]
|
||||
sens = await sensor.new_sensor(conf)
|
||||
cg.add(getattr(hub, f"set_{key}_sensor")(sens))
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
hub = await cg.get_variable(config[CONF_BMS_DALY_ID])
|
||||
for key in TYPES:
|
||||
await setup_conf(config, key, hub)
|
39
esphome/components/daly_bms/text_sensor.py
Normal file
39
esphome/components/daly_bms/text_sensor.py
Normal file
@ -0,0 +1,39 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import text_sensor
|
||||
from esphome.const import CONF_ICON, CONF_ID, CONF_STATUS
|
||||
from . import DalyBmsComponent, CONF_BMS_DALY_ID
|
||||
|
||||
ICON_CAR_BATTERY = "mdi:car-battery"
|
||||
|
||||
TYPES = [
|
||||
CONF_STATUS,
|
||||
]
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(CONF_BMS_DALY_ID): cv.use_id(DalyBmsComponent),
|
||||
cv.Optional(CONF_STATUS): text_sensor.TEXT_SENSOR_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(text_sensor.TextSensor),
|
||||
cv.Optional(CONF_ICON, default=ICON_CAR_BATTERY): cv.icon,
|
||||
}
|
||||
),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
)
|
||||
|
||||
|
||||
async def setup_conf(config, key, hub):
|
||||
if key in config:
|
||||
conf = config[key]
|
||||
sens = cg.new_Pvariable(conf[CONF_ID])
|
||||
await text_sensor.register_text_sensor(sens, conf)
|
||||
cg.add(getattr(hub, f"set_{key}_text_sensor")(sens))
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
hub = await cg.get_variable(config[CONF_BMS_DALY_ID])
|
||||
for key in TYPES:
|
||||
await setup_conf(config, key, hub)
|
45
esphome/components/dashboard_import/__init__.py
Normal file
45
esphome/components/dashboard_import/__init__.py
Normal file
@ -0,0 +1,45 @@
|
||||
from pathlib import Path
|
||||
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components.packages import validate_source_shorthand
|
||||
from esphome.yaml_util import dump
|
||||
|
||||
|
||||
dashboard_import_ns = cg.esphome_ns.namespace("dashboard_import")
|
||||
|
||||
# payload is in `esphomelib` mdns record, which only exists if api
|
||||
# is enabled
|
||||
DEPENDENCIES = ["api"]
|
||||
CODEOWNERS = ["@esphome/core"]
|
||||
|
||||
|
||||
def validate_import_url(value):
|
||||
value = cv.string_strict(value)
|
||||
value = cv.Length(max=255)(value)
|
||||
# ignore result, only check if it's a valid shorthand
|
||||
validate_source_shorthand(value)
|
||||
return value
|
||||
|
||||
|
||||
CONF_PACKAGE_IMPORT_URL = "package_import_url"
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_PACKAGE_IMPORT_URL): validate_import_url,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
cg.add_define("USE_DASHBOARD_IMPORT")
|
||||
cg.add(dashboard_import_ns.set_package_import_url(config[CONF_PACKAGE_IMPORT_URL]))
|
||||
|
||||
|
||||
def import_config(path: str, name: str, project_name: str, import_url: str) -> None:
|
||||
p = Path(path)
|
||||
|
||||
if p.exists():
|
||||
raise FileExistsError
|
||||
|
||||
config = {"substitutions": {"name": name}, "packages": {project_name: import_url}}
|
||||
p.write_text(dump(config), encoding="utf8")
|
12
esphome/components/dashboard_import/dashboard_import.cpp
Normal file
12
esphome/components/dashboard_import/dashboard_import.cpp
Normal file
@ -0,0 +1,12 @@
|
||||
#include "dashboard_import.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace dashboard_import {
|
||||
|
||||
static std::string g_package_import_url; // NOLINT
|
||||
|
||||
std::string get_package_import_url() { return g_package_import_url; }
|
||||
void set_package_import_url(std::string url) { g_package_import_url = std::move(url); }
|
||||
|
||||
} // namespace dashboard_import
|
||||
} // namespace esphome
|
12
esphome/components/dashboard_import/dashboard_import.h
Normal file
12
esphome/components/dashboard_import/dashboard_import.h
Normal file
@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace esphome {
|
||||
namespace dashboard_import {
|
||||
|
||||
std::string get_package_import_url();
|
||||
void set_package_import_url(std::string url);
|
||||
|
||||
} // namespace dashboard_import
|
||||
} // namespace esphome
|
@ -4,8 +4,18 @@
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/version.h"
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#ifdef USE_ESP32
|
||||
#include <rom/rtc.h>
|
||||
#include <esp_idf_version.h>
|
||||
#endif
|
||||
|
||||
#ifdef USE_ARDUINO
|
||||
#include <Esp.h>
|
||||
#endif
|
||||
|
||||
#ifdef USE_ESP_IDF
|
||||
#include <esp_heap_caps.h>
|
||||
#include <esp_system.h>
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
@ -21,11 +31,16 @@ void DebugComponent::dump_config() {
|
||||
#endif
|
||||
|
||||
ESP_LOGD(TAG, "ESPHome version %s", ESPHOME_VERSION);
|
||||
this->free_heap_ = ESP.getFreeHeap();
|
||||
#ifdef USE_ARDUINO
|
||||
this->free_heap_ = ESP.getFreeHeap(); // NOLINT(readability-static-accessed-through-instance)
|
||||
#elif defined(USE_ESP_IDF)
|
||||
this->free_heap_ = heap_caps_get_free_size(MALLOC_CAP_INTERNAL);
|
||||
#endif
|
||||
ESP_LOGD(TAG, "Free Heap Size: %u bytes", this->free_heap_);
|
||||
|
||||
#ifdef USE_ARDUINO
|
||||
const char *flash_mode;
|
||||
switch (ESP.getFlashChipMode()) {
|
||||
switch (ESP.getFlashChipMode()) { // NOLINT(readability-static-accessed-through-instance)
|
||||
case FM_QIO:
|
||||
flash_mode = "QIO";
|
||||
break;
|
||||
@ -38,7 +53,7 @@ void DebugComponent::dump_config() {
|
||||
case FM_DOUT:
|
||||
flash_mode = "DOUT";
|
||||
break;
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#ifdef USE_ESP32
|
||||
case FM_FAST_READ:
|
||||
flash_mode = "FAST_READ";
|
||||
break;
|
||||
@ -49,10 +64,12 @@ void DebugComponent::dump_config() {
|
||||
default:
|
||||
flash_mode = "UNKNOWN";
|
||||
}
|
||||
// NOLINTNEXTLINE(readability-static-accessed-through-instance)
|
||||
ESP_LOGD(TAG, "Flash Chip: Size=%ukB Speed=%uMHz Mode=%s", ESP.getFlashChipSize() / 1024,
|
||||
ESP.getFlashChipSpeed() / 1000000, flash_mode);
|
||||
#endif // USE_ARDUINO
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#ifdef USE_ESP32
|
||||
esp_chip_info_t info;
|
||||
esp_chip_info(&info);
|
||||
const char *model;
|
||||
@ -87,8 +104,7 @@ void DebugComponent::dump_config() {
|
||||
|
||||
ESP_LOGD(TAG, "ESP-IDF Version: %s", esp_get_idf_version());
|
||||
|
||||
std::string mac = uint64_to_string(ESP.getEfuseMac());
|
||||
ESP_LOGD(TAG, "EFuse MAC: %s", mac.c_str());
|
||||
ESP_LOGD(TAG, "EFuse MAC: %s", get_mac_address_pretty().c_str());
|
||||
|
||||
const char *reset_reason;
|
||||
switch (rtc_get_reset_reason(0)) {
|
||||
@ -186,7 +202,7 @@ void DebugComponent::dump_config() {
|
||||
ESP_LOGD(TAG, "Wakeup Reason: %s", wakeup_reason);
|
||||
#endif
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP8266
|
||||
#if defined(USE_ESP8266) && !defined(CLANG_TIDY)
|
||||
ESP_LOGD(TAG, "Chip ID: 0x%08X", ESP.getChipId());
|
||||
ESP_LOGD(TAG, "SDK Version: %s", ESP.getSdkVersion());
|
||||
ESP_LOGD(TAG, "Core Version: %s", ESP.getCoreVersion().c_str());
|
||||
@ -198,7 +214,11 @@ void DebugComponent::dump_config() {
|
||||
#endif
|
||||
}
|
||||
void DebugComponent::loop() {
|
||||
uint32_t new_free_heap = ESP.getFreeHeap();
|
||||
#ifdef USE_ARDUINO
|
||||
uint32_t new_free_heap = ESP.getFreeHeap(); // NOLINT(readability-static-accessed-through-instance)
|
||||
#elif defined(USE_ESP_IDF)
|
||||
uint32_t new_free_heap = heap_caps_get_free_size(MALLOC_CAP_INTERNAL);
|
||||
#endif
|
||||
if (new_free_heap < this->free_heap_ / 2) {
|
||||
this->free_heap_ = new_free_heap;
|
||||
ESP_LOGD(TAG, "Free Heap Size: %u bytes", this->free_heap_);
|
||||
|
@ -16,8 +16,7 @@ def validate_pin_number(value):
|
||||
valid_pins = [0, 2, 4, 12, 13, 14, 15, 25, 26, 27, 32, 33, 34, 35, 36, 37, 38, 39]
|
||||
if value[CONF_NUMBER] not in valid_pins:
|
||||
raise cv.Invalid(
|
||||
"Only pins {} support wakeup"
|
||||
"".format(", ".join(str(x) for x in valid_pins))
|
||||
f"Only pins {', '.join(str(x) for x in valid_pins)} support wakeup"
|
||||
)
|
||||
return value
|
||||
|
||||
@ -45,6 +44,7 @@ EXT1_WAKEUP_MODES = {
|
||||
|
||||
CONF_WAKEUP_PIN_MODE = "wakeup_pin_mode"
|
||||
CONF_ESP32_EXT1_WAKEUP = "esp32_ext1_wakeup"
|
||||
CONF_TOUCH_WAKEUP = "touch_wakeup"
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
@ -62,12 +62,13 @@ CONFIG_SCHEMA = cv.Schema(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_PINS): cv.ensure_list(
|
||||
pins.shorthand_input_pin, validate_pin_number
|
||||
pins.internal_gpio_input_pin_schema, validate_pin_number
|
||||
),
|
||||
cv.Required(CONF_MODE): cv.enum(EXT1_WAKEUP_MODES, upper=True),
|
||||
}
|
||||
),
|
||||
),
|
||||
cv.Optional(CONF_TOUCH_WAKEUP): cv.All(cv.only_on_esp32, cv.boolean),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
@ -96,6 +97,9 @@ async def to_code(config):
|
||||
)
|
||||
cg.add(var.set_ext1_wakeup(struct))
|
||||
|
||||
if CONF_TOUCH_WAKEUP in config:
|
||||
cg.add(var.set_touch_wakeup(config[CONF_TOUCH_WAKEUP]))
|
||||
|
||||
cg.add_define("USE_DEEP_SLEEP")
|
||||
|
||||
|
||||
|
@ -2,6 +2,10 @@
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/application.h"
|
||||
|
||||
#ifdef USE_ESP8266
|
||||
#include <Esp.h>
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
namespace deep_sleep {
|
||||
|
||||
@ -25,9 +29,9 @@ void DeepSleepComponent::dump_config() {
|
||||
if (this->run_duration_.has_value()) {
|
||||
ESP_LOGCONFIG(TAG, " Run Duration: %u ms", *this->run_duration_);
|
||||
}
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
if (this->wakeup_pin_.has_value()) {
|
||||
LOG_PIN(" Wakeup Pin: ", *this->wakeup_pin_);
|
||||
#ifdef USE_ESP32
|
||||
if (wakeup_pin_ != nullptr) {
|
||||
LOG_PIN(" Wakeup Pin: ", this->wakeup_pin_);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@ -39,11 +43,12 @@ float DeepSleepComponent::get_loop_priority() const {
|
||||
return -100.0f; // run after everything else is ready
|
||||
}
|
||||
void DeepSleepComponent::set_sleep_duration(uint32_t time_ms) { this->sleep_duration_ = uint64_t(time_ms) * 1000; }
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#ifdef USE_ESP32
|
||||
void DeepSleepComponent::set_wakeup_pin_mode(WakeupPinMode wakeup_pin_mode) {
|
||||
this->wakeup_pin_mode_ = wakeup_pin_mode;
|
||||
}
|
||||
void DeepSleepComponent::set_ext1_wakeup(Ext1Wakeup ext1_wakeup) { this->ext1_wakeup_ = ext1_wakeup; }
|
||||
void DeepSleepComponent::set_touch_wakeup(bool touch_wakeup) { this->touch_wakeup_ = touch_wakeup; }
|
||||
#endif
|
||||
void DeepSleepComponent::set_run_duration(uint32_t time_ms) { this->run_duration_ = time_ms; }
|
||||
void DeepSleepComponent::begin_sleep(bool manual) {
|
||||
@ -51,9 +56,9 @@ void DeepSleepComponent::begin_sleep(bool manual) {
|
||||
this->next_enter_deep_sleep_ = true;
|
||||
return;
|
||||
}
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
if (this->wakeup_pin_mode_ == WAKEUP_PIN_MODE_KEEP_AWAKE && this->wakeup_pin_.has_value() &&
|
||||
!this->sleep_duration_.has_value() && (*this->wakeup_pin_)->digital_read()) {
|
||||
#ifdef USE_ESP32
|
||||
if (this->wakeup_pin_mode_ == WAKEUP_PIN_MODE_KEEP_AWAKE && this->wakeup_pin_ != nullptr &&
|
||||
!this->sleep_duration_.has_value() && this->wakeup_pin_->digital_read()) {
|
||||
// Defer deep sleep until inactive
|
||||
if (!this->next_enter_deep_sleep_) {
|
||||
this->status_set_warning();
|
||||
@ -68,23 +73,29 @@ void DeepSleepComponent::begin_sleep(bool manual) {
|
||||
|
||||
App.run_safe_shutdown_hooks();
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#ifdef USE_ESP32
|
||||
if (this->sleep_duration_.has_value())
|
||||
esp_sleep_enable_timer_wakeup(*this->sleep_duration_);
|
||||
if (this->wakeup_pin_.has_value()) {
|
||||
bool level = !(*this->wakeup_pin_)->is_inverted();
|
||||
if (this->wakeup_pin_mode_ == WAKEUP_PIN_MODE_INVERT_WAKEUP && (*this->wakeup_pin_)->digital_read())
|
||||
if (this->wakeup_pin_ != nullptr) {
|
||||
bool level = this->wakeup_pin_->is_inverted();
|
||||
if (this->wakeup_pin_mode_ == WAKEUP_PIN_MODE_INVERT_WAKEUP && this->wakeup_pin_->digital_read())
|
||||
level = !level;
|
||||
esp_sleep_enable_ext0_wakeup(gpio_num_t((*this->wakeup_pin_)->get_pin()), level);
|
||||
esp_sleep_enable_ext0_wakeup(gpio_num_t(this->wakeup_pin_->get_pin()), level);
|
||||
}
|
||||
if (this->ext1_wakeup_.has_value()) {
|
||||
esp_sleep_enable_ext1_wakeup(this->ext1_wakeup_->mask, this->ext1_wakeup_->wakeup_mode);
|
||||
}
|
||||
|
||||
if (this->touch_wakeup_.has_value() && *(this->touch_wakeup_)) {
|
||||
esp_sleep_enable_touchpad_wakeup();
|
||||
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON);
|
||||
}
|
||||
|
||||
esp_deep_sleep_start();
|
||||
#endif
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP8266
|
||||
ESP.deepSleep(*this->sleep_duration_);
|
||||
#ifdef USE_ESP8266
|
||||
ESP.deepSleep(*this->sleep_duration_); // NOLINT(readability-static-accessed-through-instance)
|
||||
#endif
|
||||
}
|
||||
float DeepSleepComponent::get_setup_priority() const { return setup_priority::LATE; }
|
||||
|
@ -3,11 +3,16 @@
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/hal.h"
|
||||
|
||||
#ifdef USE_ESP32
|
||||
#include <esp_sleep.h>
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
namespace deep_sleep {
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#ifdef USE_ESP32
|
||||
|
||||
/** The values of this enum define what should be done if deep sleep is set up with a wakeup pin on the ESP32
|
||||
* and the scenario occurs that the wakeup pin is already in the wakeup state.
|
||||
@ -43,15 +48,17 @@ class DeepSleepComponent : public Component {
|
||||
public:
|
||||
/// Set the duration in ms the component should sleep once it's in deep sleep mode.
|
||||
void set_sleep_duration(uint32_t time_ms);
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#ifdef USE_ESP32
|
||||
/** Set the pin to wake up to on the ESP32 once it's in deep sleep mode.
|
||||
* Use the inverted property to set the wakeup level.
|
||||
*/
|
||||
void set_wakeup_pin(GPIOPin *pin) { this->wakeup_pin_ = pin; }
|
||||
void set_wakeup_pin(InternalGPIOPin *pin) { this->wakeup_pin_ = pin; }
|
||||
|
||||
void set_wakeup_pin_mode(WakeupPinMode wakeup_pin_mode);
|
||||
|
||||
void set_ext1_wakeup(Ext1Wakeup ext1_wakeup);
|
||||
|
||||
void set_touch_wakeup(bool touch_wakeup);
|
||||
#endif
|
||||
/// Set a duration in ms for how long the code should run before entering deep sleep mode.
|
||||
void set_run_duration(uint32_t time_ms);
|
||||
@ -69,10 +76,11 @@ class DeepSleepComponent : public Component {
|
||||
|
||||
protected:
|
||||
optional<uint64_t> sleep_duration_;
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
optional<GPIOPin *> wakeup_pin_;
|
||||
#ifdef USE_ESP32
|
||||
InternalGPIOPin *wakeup_pin_;
|
||||
WakeupPinMode wakeup_pin_mode_{WAKEUP_PIN_MODE_IGNORE};
|
||||
optional<Ext1Wakeup> ext1_wakeup_;
|
||||
optional<bool> touch_wakeup_;
|
||||
#endif
|
||||
optional<uint32_t> run_duration_;
|
||||
bool next_enter_deep_sleep_{false};
|
||||
|
@ -13,7 +13,7 @@ class DemoSensor : public sensor::Sensor, public PollingComponent {
|
||||
float val = random_float();
|
||||
bool increasing = this->get_state_class() == sensor::STATE_CLASS_TOTAL_INCREASING;
|
||||
if (increasing) {
|
||||
float base = isnan(this->state) ? 0.0f : this->state;
|
||||
float base = std::isnan(this->state) ? 0.0f : this->state;
|
||||
this->publish_state(base + val * 10);
|
||||
} else {
|
||||
if (val < 0.1)
|
||||
|
@ -71,7 +71,7 @@ void DHT::set_dht_model(DHTModel model) {
|
||||
this->model_ = model;
|
||||
this->is_auto_detect_ = model == DHT_MODEL_AUTO_DETECT;
|
||||
}
|
||||
bool HOT ICACHE_RAM_ATTR DHT::read_sensor_(float *temperature, float *humidity, bool report_errors) {
|
||||
bool HOT IRAM_ATTR DHT::read_sensor_(float *temperature, float *humidity, bool report_errors) {
|
||||
*humidity = NAN;
|
||||
*temperature = NAN;
|
||||
|
||||
@ -79,28 +79,27 @@ bool HOT ICACHE_RAM_ATTR DHT::read_sensor_(float *temperature, float *humidity,
|
||||
int8_t i = 0;
|
||||
uint8_t data[5] = {0, 0, 0, 0, 0};
|
||||
|
||||
this->pin_->digital_write(false);
|
||||
this->pin_->pin_mode(gpio::FLAG_OUTPUT);
|
||||
this->pin_->digital_write(false);
|
||||
|
||||
if (this->model_ == DHT_MODEL_DHT11) {
|
||||
delayMicroseconds(18000);
|
||||
} else if (this->model_ == DHT_MODEL_SI7021) {
|
||||
delayMicroseconds(500);
|
||||
this->pin_->digital_write(true);
|
||||
delayMicroseconds(40);
|
||||
} else if (this->model_ == DHT_MODEL_DHT22_TYPE2) {
|
||||
delayMicroseconds(2000);
|
||||
} else if (this->model_ == DHT_MODEL_AM2302) {
|
||||
delayMicroseconds(1000);
|
||||
} else {
|
||||
delayMicroseconds(800);
|
||||
}
|
||||
this->pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
|
||||
|
||||
{
|
||||
InterruptLock lock;
|
||||
|
||||
this->pin_->digital_write(false);
|
||||
this->pin_->pin_mode(OUTPUT);
|
||||
this->pin_->digital_write(false);
|
||||
|
||||
if (this->model_ == DHT_MODEL_DHT11) {
|
||||
delayMicroseconds(18000);
|
||||
} else if (this->model_ == DHT_MODEL_SI7021) {
|
||||
delayMicroseconds(500);
|
||||
this->pin_->digital_write(true);
|
||||
delayMicroseconds(40);
|
||||
} else if (this->model_ == DHT_MODEL_DHT22_TYPE2) {
|
||||
delayMicroseconds(2000);
|
||||
} else if (this->model_ == DHT_MODEL_AM2302) {
|
||||
delayMicroseconds(1000);
|
||||
} else {
|
||||
delayMicroseconds(800);
|
||||
}
|
||||
this->pin_->pin_mode(INPUT_PULLUP);
|
||||
|
||||
// Host pull up 20-40us then DHT response 80us
|
||||
// Start waiting for initial rising edge at the center when we
|
||||
// expect the DHT response (30us+40us)
|
||||
@ -122,6 +121,8 @@ bool HOT ICACHE_RAM_ATTR DHT::read_sensor_(float *temperature, float *humidity,
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (error_code != 0)
|
||||
break;
|
||||
|
||||
start_time = micros();
|
||||
uint32_t end_time = start_time;
|
||||
@ -136,6 +137,8 @@ bool HOT ICACHE_RAM_ATTR DHT::read_sensor_(float *temperature, float *humidity,
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (error_code != 0)
|
||||
break;
|
||||
|
||||
if (i < 0)
|
||||
continue;
|
||||
|
@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
|
||||
namespace esphome {
|
||||
@ -35,7 +36,7 @@ class DHT : public PollingComponent {
|
||||
*/
|
||||
void set_dht_model(DHTModel model);
|
||||
|
||||
void set_pin(GPIOPin *pin) { pin_ = pin; }
|
||||
void set_pin(InternalGPIOPin *pin) { pin_ = pin; }
|
||||
void set_model(DHTModel model) { model_ = model; }
|
||||
void set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_sensor; }
|
||||
void set_humidity_sensor(sensor::Sensor *humidity_sensor) { humidity_sensor_ = humidity_sensor; }
|
||||
@ -51,7 +52,7 @@ class DHT : public PollingComponent {
|
||||
protected:
|
||||
bool read_sensor_(float *temperature, float *humidity, bool report_errors);
|
||||
|
||||
GPIOPin *pin_;
|
||||
InternalGPIOPin *pin_;
|
||||
DHTModel model_{DHT_MODEL_AUTO_DETECT};
|
||||
bool is_auto_detect_{false};
|
||||
sensor::Sensor *temperature_sensor_{nullptr};
|
||||
|
@ -1,9 +1,10 @@
|
||||
#include "display_buffer.h"
|
||||
|
||||
#include <utility>
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/color.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include <utility>
|
||||
#include "esphome/core/hal.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace display {
|
||||
@ -14,7 +15,7 @@ const Color COLOR_OFF(0, 0, 0, 0);
|
||||
const Color COLOR_ON(255, 255, 255, 255);
|
||||
|
||||
void DisplayBuffer::init_internal_(uint32_t buffer_length) {
|
||||
this->buffer_ = new (std::nothrow) uint8_t[buffer_length];
|
||||
this->buffer_ = new (std::nothrow) uint8_t[buffer_length]; // NOLINT
|
||||
if (this->buffer_ == nullptr) {
|
||||
ESP_LOGE(TAG, "Could not allocate buffer for display!");
|
||||
return;
|
||||
@ -233,6 +234,13 @@ void DisplayBuffer::image(int x, int y, Image *image, Color color_on, Color colo
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef USE_GRAPH
|
||||
void DisplayBuffer::graph(int x, int y, graph::Graph *graph, Color color_on) { graph->draw(this, x, y, color_on); }
|
||||
void DisplayBuffer::legend(int x, int y, graph::Graph *graph, Color color_on) {
|
||||
graph->draw_legend(this, x, y, color_on);
|
||||
}
|
||||
#endif // USE_GRAPH
|
||||
|
||||
void DisplayBuffer::get_text_bounds(int x, int y, const char *text, Font *font, TextAlign align, int *x1, int *y1,
|
||||
int *width, int *height) {
|
||||
int x_offset, baseline;
|
||||
@ -365,7 +373,7 @@ bool Glyph::get_pixel(int x, int y) const {
|
||||
return false;
|
||||
const uint32_t width_8 = ((this->glyph_data_->width + 7u) / 8u) * 8u;
|
||||
const uint32_t pos = x_data + y_data * width_8;
|
||||
return pgm_read_byte(this->glyph_data_->data + (pos / 8u)) & (0x80 >> (pos % 8u));
|
||||
return progmem_read_byte(this->glyph_data_->data + (pos / 8u)) & (0x80 >> (pos % 8u));
|
||||
}
|
||||
const char *Glyph::get_char() const { return this->glyph_data_->a_char; }
|
||||
bool Glyph::compare_to(const char *str) const {
|
||||
@ -457,22 +465,22 @@ bool Image::get_pixel(int x, int y) const {
|
||||
return false;
|
||||
const uint32_t width_8 = ((this->width_ + 7u) / 8u) * 8u;
|
||||
const uint32_t pos = x + y * width_8;
|
||||
return pgm_read_byte(this->data_start_ + (pos / 8u)) & (0x80 >> (pos % 8u));
|
||||
return progmem_read_byte(this->data_start_ + (pos / 8u)) & (0x80 >> (pos % 8u));
|
||||
}
|
||||
Color Image::get_color_pixel(int x, int y) const {
|
||||
if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_)
|
||||
return Color::BLACK;
|
||||
const uint32_t pos = (x + y * this->width_) * 3;
|
||||
const uint32_t color32 = (pgm_read_byte(this->data_start_ + pos + 2) << 0) |
|
||||
(pgm_read_byte(this->data_start_ + pos + 1) << 8) |
|
||||
(pgm_read_byte(this->data_start_ + pos + 0) << 16);
|
||||
const uint32_t color32 = (progmem_read_byte(this->data_start_ + pos + 2) << 0) |
|
||||
(progmem_read_byte(this->data_start_ + pos + 1) << 8) |
|
||||
(progmem_read_byte(this->data_start_ + pos + 0) << 16);
|
||||
return Color(color32);
|
||||
}
|
||||
Color Image::get_grayscale_pixel(int x, int y) const {
|
||||
if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_)
|
||||
return Color::BLACK;
|
||||
const uint32_t pos = (x + y * this->width_);
|
||||
const uint8_t gray = pgm_read_byte(this->data_start_ + pos);
|
||||
const uint8_t gray = progmem_read_byte(this->data_start_ + pos);
|
||||
return Color(gray | gray << 8 | gray << 16 | gray << 24);
|
||||
}
|
||||
int Image::get_width() const { return this->width_; }
|
||||
@ -489,7 +497,7 @@ bool Animation::get_pixel(int x, int y) const {
|
||||
if (frame_index >= this->width_ * this->height_ * this->animation_frame_count_)
|
||||
return false;
|
||||
const uint32_t pos = x + y * width_8 + frame_index;
|
||||
return pgm_read_byte(this->data_start_ + (pos / 8u)) & (0x80 >> (pos % 8u));
|
||||
return progmem_read_byte(this->data_start_ + (pos / 8u)) & (0x80 >> (pos % 8u));
|
||||
}
|
||||
Color Animation::get_color_pixel(int x, int y) const {
|
||||
if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_)
|
||||
@ -498,9 +506,9 @@ Color Animation::get_color_pixel(int x, int y) const {
|
||||
if (frame_index >= this->width_ * this->height_ * this->animation_frame_count_)
|
||||
return Color::BLACK;
|
||||
const uint32_t pos = (x + y * this->width_ + frame_index) * 3;
|
||||
const uint32_t color32 = (pgm_read_byte(this->data_start_ + pos + 2) << 0) |
|
||||
(pgm_read_byte(this->data_start_ + pos + 1) << 8) |
|
||||
(pgm_read_byte(this->data_start_ + pos + 0) << 16);
|
||||
const uint32_t color32 = (progmem_read_byte(this->data_start_ + pos + 2) << 0) |
|
||||
(progmem_read_byte(this->data_start_ + pos + 1) << 8) |
|
||||
(progmem_read_byte(this->data_start_ + pos + 0) << 16);
|
||||
return Color(color32);
|
||||
}
|
||||
Color Animation::get_grayscale_pixel(int x, int y) const {
|
||||
@ -510,13 +518,11 @@ Color Animation::get_grayscale_pixel(int x, int y) const {
|
||||
if (frame_index >= this->width_ * this->height_ * this->animation_frame_count_)
|
||||
return Color::BLACK;
|
||||
const uint32_t pos = (x + y * this->width_ + frame_index);
|
||||
const uint8_t gray = pgm_read_byte(this->data_start_ + pos);
|
||||
const uint8_t gray = progmem_read_byte(this->data_start_ + pos);
|
||||
return Color(gray | gray << 8 | gray << 16 | gray << 24);
|
||||
}
|
||||
Animation::Animation(const uint8_t *data_start, int width, int height, uint32_t animation_frame_count, ImageType type)
|
||||
: Image(data_start, width, height, type), animation_frame_count_(animation_frame_count) {
|
||||
current_frame_ = 0;
|
||||
}
|
||||
: Image(data_start, width, height, type), current_frame_(0), animation_frame_count_(animation_frame_count) {}
|
||||
int Animation::get_animation_frame_count() const { return this->animation_frame_count_; }
|
||||
int Animation::get_current_frame() const { return this->current_frame_; }
|
||||
void Animation::next_frame() {
|
||||
|
@ -4,11 +4,16 @@
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "display_color_utils.h"
|
||||
#include <cstdarg>
|
||||
|
||||
#ifdef USE_TIME
|
||||
#include "esphome/components/time/real_time_clock.h"
|
||||
#endif
|
||||
|
||||
#ifdef USE_GRAPH
|
||||
#include "esphome/components/graph/graph.h"
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
namespace display {
|
||||
|
||||
@ -273,6 +278,30 @@ class DisplayBuffer {
|
||||
*/
|
||||
void image(int x, int y, Image *image, Color color_on = COLOR_ON, Color color_off = COLOR_OFF);
|
||||
|
||||
#ifdef USE_GRAPH
|
||||
/** Draw the `graph` with the top-left corner at [x,y] to the screen.
|
||||
*
|
||||
* @param x The x coordinate of the upper left corner.
|
||||
* @param y The y coordinate of the upper left corner.
|
||||
* @param graph The graph id to draw
|
||||
* @param color_on The color to replace in binary images for the on bits.
|
||||
*/
|
||||
void graph(int x, int y, graph::Graph *graph, Color color_on = COLOR_ON);
|
||||
|
||||
/** Draw the `legend` for graph with the top-left corner at [x,y] to the screen.
|
||||
*
|
||||
* @param x The x coordinate of the upper left corner.
|
||||
* @param y The y coordinate of the upper left corner.
|
||||
* @param graph The graph id for which the legend applies to
|
||||
* @param graph The graph id for which the legend applies to
|
||||
* @param graph The graph id for which the legend applies to
|
||||
* @param name_font The font used for the trace name
|
||||
* @param value_font The font used for the trace value and units
|
||||
* @param color_on The color of the border
|
||||
*/
|
||||
void legend(int x, int y, graph::Graph *graph, Color color_on = COLOR_ON);
|
||||
#endif // USE_GRAPH
|
||||
|
||||
/** Get the text bounds of the given string.
|
||||
*
|
||||
* @param x The x coordinate to place the string at, can be 0 if only interested in dimensions.
|
||||
|
@ -13,6 +13,8 @@ AUTO_LOAD = ["sensor", "text_sensor"]
|
||||
|
||||
CONF_DSMR_ID = "dsmr_id"
|
||||
CONF_DECRYPTION_KEY = "decryption_key"
|
||||
CONF_CRC_CHECK = "crc_check"
|
||||
CONF_GAS_MBUS_ID = "gas_mbus_id"
|
||||
|
||||
# Hack to prevent compile error due to ambiguity with lib namespace
|
||||
dsmr_ns = cg.esphome_ns.namespace("esphome::dsmr")
|
||||
@ -37,23 +39,30 @@ def _validate_key(value):
|
||||
return "".join(f"{part:02X}" for part in parts_int)
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(Dsmr),
|
||||
cv.Optional(CONF_DECRYPTION_KEY): _validate_key,
|
||||
}
|
||||
).extend(uart.UART_DEVICE_SCHEMA)
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(Dsmr),
|
||||
cv.Optional(CONF_DECRYPTION_KEY): _validate_key,
|
||||
cv.Optional(CONF_CRC_CHECK, default=True): cv.boolean,
|
||||
cv.Optional(CONF_GAS_MBUS_ID, default=1): cv.int_,
|
||||
}
|
||||
).extend(uart.UART_DEVICE_SCHEMA),
|
||||
cv.only_with_arduino,
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
uart_component = await cg.get_variable(config[CONF_UART_ID])
|
||||
var = cg.new_Pvariable(config[CONF_ID], uart_component)
|
||||
var = cg.new_Pvariable(config[CONF_ID], uart_component, config[CONF_CRC_CHECK])
|
||||
if CONF_DECRYPTION_KEY in config:
|
||||
cg.add(var.set_decryption_key(config[CONF_DECRYPTION_KEY]))
|
||||
await cg.register_component(var, config)
|
||||
|
||||
cg.add_define("DSMR_GAS_MBUS_ID", config[CONF_GAS_MBUS_ID])
|
||||
|
||||
# DSMR Parser
|
||||
cg.add_library("glmnet/Dsmr", "0.3")
|
||||
cg.add_library("glmnet/Dsmr", "0.5")
|
||||
|
||||
# Crypto
|
||||
cg.add_library("rweather/Crypto", "0.2.0")
|
||||
|
@ -1,3 +1,5 @@
|
||||
#ifdef USE_ARDUINO
|
||||
|
||||
#include "dsmr.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
@ -18,37 +20,50 @@ void Dsmr::loop() {
|
||||
}
|
||||
|
||||
void Dsmr::receive_telegram_() {
|
||||
while (available()) {
|
||||
int count = MAX_BYTES_PER_LOOP;
|
||||
while (available() && count-- > 0) {
|
||||
const char c = read();
|
||||
|
||||
if (c == '/') { // header: forward slash
|
||||
// Find a new telegram header, i.e. forward slash.
|
||||
if (c == '/') {
|
||||
ESP_LOGV(TAG, "Header found");
|
||||
header_found_ = true;
|
||||
footer_found_ = false;
|
||||
telegram_len_ = 0;
|
||||
}
|
||||
|
||||
if (!header_found_)
|
||||
continue;
|
||||
if (telegram_len_ >= MAX_TELEGRAM_LENGTH) { // Buffer overflow
|
||||
|
||||
// Check for buffer overflow.
|
||||
if (telegram_len_ >= MAX_TELEGRAM_LENGTH) {
|
||||
header_found_ = false;
|
||||
footer_found_ = false;
|
||||
ESP_LOGE(TAG, "Error: Message larger than buffer");
|
||||
return;
|
||||
}
|
||||
|
||||
// Some v2.2 or v3 meters will send a new value which starts with '('
|
||||
// in a new line while the value belongs to the previous ObisId. For
|
||||
// proper parsing remove these new line characters
|
||||
while (c == '(' && (telegram_[telegram_len_ - 1] == '\n' || telegram_[telegram_len_ - 1] == '\r'))
|
||||
telegram_len_--;
|
||||
|
||||
// Store the byte in the buffer.
|
||||
telegram_[telegram_len_] = c;
|
||||
telegram_len_++;
|
||||
if (c == '!') { // footer: exclamation mark
|
||||
|
||||
// Check for a footer, i.e. exlamation mark, followed by a hex checksum.
|
||||
if (c == '!') {
|
||||
ESP_LOGV(TAG, "Footer found");
|
||||
footer_found_ = true;
|
||||
} else {
|
||||
if (footer_found_ && c == 10) { // last \n after footer
|
||||
header_found_ = false;
|
||||
// Parse message
|
||||
if (parse_telegram())
|
||||
return;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
// Check for the end of the hex checksum, i.e. a newline.
|
||||
if (footer_found_ && c == '\n') {
|
||||
header_found_ = false;
|
||||
// Parse the telegram and publish sensor values.
|
||||
if (parse_telegram())
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -105,7 +120,7 @@ void Dsmr::receive_encrypted_() {
|
||||
&buffer[18],
|
||||
// cipher size
|
||||
buffer_length - 17);
|
||||
delete gcmaes128;
|
||||
delete gcmaes128; // NOLINT(cppcoreguidelines-owning-memory)
|
||||
|
||||
telegram_len_ = strnlen(this->telegram_, sizeof(this->telegram_));
|
||||
ESP_LOGV(TAG, "Decrypted data length: %d", telegram_len_);
|
||||
@ -122,16 +137,17 @@ void Dsmr::receive_encrypted_() {
|
||||
delay(4); // Wait for data
|
||||
}
|
||||
}
|
||||
if (buffer_length > 0)
|
||||
if (buffer_length > 0) {
|
||||
ESP_LOGW(TAG, "Timeout while waiting for encrypted data or invalid data received.");
|
||||
}
|
||||
}
|
||||
|
||||
bool Dsmr::parse_telegram() {
|
||||
MyData data;
|
||||
ESP_LOGV(TAG, "Trying to parse");
|
||||
::dsmr::ParseResult<void> res =
|
||||
::dsmr::P1Parser::parse(&data, telegram_, telegram_len_,
|
||||
false); // Parse telegram according to data definition. Ignore unknown values.
|
||||
::dsmr::P1Parser::parse(&data, telegram_, telegram_len_, false,
|
||||
this->crc_check_); // Parse telegram according to data definition. Ignore unknown values.
|
||||
if (res.err) {
|
||||
// Parsing error, show it
|
||||
auto err_str = res.fullError(telegram_, telegram_ + telegram_len_);
|
||||
@ -180,3 +196,5 @@ void Dsmr::set_decryption_key(const std::string &decryption_key) {
|
||||
|
||||
} // namespace dsmr
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ARDUINO
|
||||
|
@ -1,5 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef USE_ARDUINO
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/text_sensor/text_sensor.h"
|
||||
@ -15,6 +17,7 @@ namespace esphome {
|
||||
namespace dsmr {
|
||||
|
||||
static constexpr uint32_t MAX_TELEGRAM_LENGTH = 1500;
|
||||
static constexpr uint32_t MAX_BYTES_PER_LOOP = 50;
|
||||
static constexpr uint32_t POLL_TIMEOUT = 1000;
|
||||
|
||||
using namespace ::dsmr::fields;
|
||||
@ -48,7 +51,7 @@ using MyData = ::dsmr::ParsedData<DSMR_TEXT_SENSOR_LIST(DSMR_DATA_SENSOR, DSMR_C
|
||||
|
||||
class Dsmr : public Component, public uart::UARTDevice {
|
||||
public:
|
||||
Dsmr(uart::UARTComponent *uart) : uart::UARTDevice(uart) {}
|
||||
Dsmr(uart::UARTComponent *uart, bool crc_check) : uart::UARTDevice(uart), crc_check_(crc_check) {}
|
||||
|
||||
void loop() override;
|
||||
|
||||
@ -99,6 +102,9 @@ class Dsmr : public Component, public uart::UARTDevice {
|
||||
DSMR_TEXT_SENSOR_LIST(DSMR_DECLARE_TEXT_SENSOR, )
|
||||
|
||||
std::vector<uint8_t> decryption_key_{};
|
||||
bool crc_check_;
|
||||
};
|
||||
} // namespace dsmr
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ARDUINO
|
||||
|
@ -244,4 +244,7 @@ async def to_code(config):
|
||||
cg.add(getattr(hub, f"set_{key}")(s))
|
||||
sensors.append(f"F({key})")
|
||||
|
||||
cg.add_define("DSMR_SENSOR_LIST(F, sep)", cg.RawExpression(" sep ".join(sensors)))
|
||||
if sensors:
|
||||
cg.add_define(
|
||||
"DSMR_SENSOR_LIST(F, sep)", cg.RawExpression(" sep ".join(sensors))
|
||||
)
|
||||
|
@ -71,6 +71,11 @@ CONFIG_SCHEMA = cv.Schema(
|
||||
cv.GenerateID(): cv.declare_id(text_sensor.TextSensor),
|
||||
}
|
||||
),
|
||||
cv.Optional("gas_delivered_text"): text_sensor.TEXT_SENSOR_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(text_sensor.TextSensor),
|
||||
}
|
||||
),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
@ -89,6 +94,8 @@ async def to_code(config):
|
||||
cg.add(getattr(hub, f"set_{key}")(var))
|
||||
text_sensors.append(f"F({key})")
|
||||
|
||||
cg.add_define(
|
||||
"DSMR_TEXT_SENSOR_LIST(F, sep)", cg.RawExpression(" sep ".join(text_sensors))
|
||||
)
|
||||
if text_sensors:
|
||||
cg.add_define(
|
||||
"DSMR_TEXT_SENSOR_LIST(F, sep)",
|
||||
cg.RawExpression(" sep ".join(text_sensors)),
|
||||
)
|
||||
|
@ -15,7 +15,7 @@ void DutyCycleSensor::setup() {
|
||||
this->last_update_ = micros();
|
||||
this->store_.last_interrupt = micros();
|
||||
|
||||
this->pin_->attach_interrupt(DutyCycleSensorStore::gpio_intr, &this->store_, CHANGE);
|
||||
this->pin_->attach_interrupt(DutyCycleSensorStore::gpio_intr, &this->store_, gpio::INTERRUPT_ANY_EDGE);
|
||||
}
|
||||
void DutyCycleSensor::dump_config() {
|
||||
LOG_SENSOR("", "Duty Cycle Sensor", this);
|
||||
@ -44,8 +44,8 @@ void DutyCycleSensor::update() {
|
||||
|
||||
float DutyCycleSensor::get_setup_priority() const { return setup_priority::DATA; }
|
||||
|
||||
void ICACHE_RAM_ATTR DutyCycleSensorStore::gpio_intr(DutyCycleSensorStore *arg) {
|
||||
const bool new_level = arg->pin->digital_read();
|
||||
void IRAM_ATTR DutyCycleSensorStore::gpio_intr(DutyCycleSensorStore *arg) {
|
||||
const bool new_level = arg->pin.digital_read();
|
||||
if (new_level == arg->last_level)
|
||||
return;
|
||||
arg->last_level = new_level;
|
||||
|
@ -1,7 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/esphal.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
|
||||
namespace esphome {
|
||||
@ -12,14 +12,14 @@ struct DutyCycleSensorStore {
|
||||
volatile uint32_t last_interrupt{0};
|
||||
volatile uint32_t on_time{0};
|
||||
volatile bool last_level{false};
|
||||
ISRInternalGPIOPin *pin;
|
||||
ISRInternalGPIOPin pin;
|
||||
|
||||
static void gpio_intr(DutyCycleSensorStore *arg);
|
||||
};
|
||||
|
||||
class DutyCycleSensor : public sensor::Sensor, public PollingComponent {
|
||||
public:
|
||||
void set_pin(GPIOPin *pin) { pin_ = pin; }
|
||||
void set_pin(InternalGPIOPin *pin) { pin_ = pin; }
|
||||
|
||||
void setup() override;
|
||||
float get_setup_priority() const override;
|
||||
@ -27,7 +27,7 @@ class DutyCycleSensor : public sensor::Sensor, public PollingComponent {
|
||||
void update() override;
|
||||
|
||||
protected:
|
||||
GPIOPin *pin_;
|
||||
InternalGPIOPin *pin_;
|
||||
|
||||
DutyCycleSensorStore store_{};
|
||||
uint32_t last_update_;
|
||||
|
@ -25,9 +25,7 @@ CONFIG_SCHEMA = (
|
||||
.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(DutyCycleSensor),
|
||||
cv.Required(CONF_PIN): cv.All(
|
||||
pins.internal_gpio_input_pin_schema, pins.validate_has_interrupt
|
||||
),
|
||||
cv.Required(CONF_PIN): cv.All(pins.internal_gpio_input_pin_schema),
|
||||
}
|
||||
)
|
||||
.extend(cv.polling_component_schema("60s"))
|
||||
|
@ -4,6 +4,8 @@ from esphome.components.light.types import AddressableLightEffect
|
||||
from esphome.components.light.effects import register_addressable_effect
|
||||
from esphome.const import CONF_ID, CONF_NAME, CONF_METHOD, CONF_CHANNELS
|
||||
|
||||
DEPENDENCIES = ["network"]
|
||||
|
||||
e131_ns = cg.esphome_ns.namespace("e131")
|
||||
E131AddressableLightEffect = e131_ns.class_(
|
||||
"E131AddressableLightEffect", AddressableLightEffect
|
||||
@ -21,11 +23,16 @@ CHANNELS = {
|
||||
CONF_UNIVERSE = "universe"
|
||||
CONF_E131_ID = "e131_id"
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(E131Component),
|
||||
cv.Optional(CONF_METHOD, default="MULTICAST"): cv.one_of(*METHODS, upper=True),
|
||||
}
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(E131Component),
|
||||
cv.Optional(CONF_METHOD, default="MULTICAST"): cv.one_of(
|
||||
*METHODS, upper=True
|
||||
),
|
||||
}
|
||||
),
|
||||
cv.only_with_arduino,
|
||||
)
|
||||
|
||||
|
||||
|
@ -1,12 +1,14 @@
|
||||
#ifdef USE_ARDUINO
|
||||
|
||||
#include "e131.h"
|
||||
#include "e131_addressable_light_effect.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#ifdef USE_ESP32
|
||||
#include <WiFi.h>
|
||||
#endif
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP8266
|
||||
#ifdef USE_ESP8266
|
||||
#include <ESP8266WiFi.h>
|
||||
#include <WiFiUdp.h>
|
||||
#endif
|
||||
@ -26,7 +28,7 @@ E131Component::~E131Component() {
|
||||
}
|
||||
|
||||
void E131Component::setup() {
|
||||
udp_.reset(new WiFiUDP());
|
||||
udp_ = make_unique<WiFiUDP>();
|
||||
|
||||
if (!udp_->begin(PORT)) {
|
||||
ESP_LOGE(TAG, "Cannot bind E131 to %d.", PORT);
|
||||
@ -104,3 +106,5 @@ bool E131Component::process_(int universe, const E131Packet &packet) {
|
||||
|
||||
} // namespace e131
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ARDUINO
|
||||
|
@ -1,5 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef USE_ARDUINO
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
|
||||
#include <memory>
|
||||
@ -55,3 +57,5 @@ class E131Component : public esphome::Component {
|
||||
|
||||
} // namespace e131
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ARDUINO
|
||||
|
@ -1,3 +1,5 @@
|
||||
#ifdef USE_ARDUINO
|
||||
|
||||
#include "e131.h"
|
||||
#include "e131_addressable_light_effect.h"
|
||||
#include "esphome/core/log.h"
|
||||
@ -90,3 +92,5 @@ bool E131AddressableLightEffect::process_(int universe, const E131Packet &packet
|
||||
|
||||
} // namespace e131
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ARDUINO
|
||||
|
@ -1,5 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef USE_ARDUINO
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/light/addressable_light_effect.h"
|
||||
|
||||
@ -46,3 +48,5 @@ class E131AddressableLightEffect : public light::AddressableLightEffect {
|
||||
|
||||
} // namespace e131
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ARDUINO
|
||||
|
@ -1,8 +1,14 @@
|
||||
#ifdef USE_ARDUINO
|
||||
|
||||
#include "e131.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/util.h"
|
||||
#include "esphome/components/network/ip_address.h"
|
||||
#include <cstring>
|
||||
|
||||
#include <lwip/init.h>
|
||||
#include <lwip/ip_addr.h>
|
||||
#include <lwip/ip4_addr.h>
|
||||
#include <lwip/igmp.h>
|
||||
|
||||
namespace esphome {
|
||||
@ -63,8 +69,8 @@ bool E131Component::join_igmp_groups_() {
|
||||
if (!universe.second)
|
||||
continue;
|
||||
|
||||
ip4_addr_t multicast_addr = {
|
||||
static_cast<uint32_t>(IPAddress(239, 255, ((universe.first >> 8) & 0xff), ((universe.first >> 0) & 0xff)))};
|
||||
ip4_addr_t multicast_addr = {static_cast<uint32_t>(
|
||||
network::IPAddress(239, 255, ((universe.first >> 8) & 0xff), ((universe.first >> 0) & 0xff)))};
|
||||
|
||||
auto err = igmp_joingroup(IP4_ADDR_ANY4, &multicast_addr);
|
||||
|
||||
@ -98,7 +104,7 @@ void E131Component::leave_(int universe) {
|
||||
|
||||
if (listen_method_ == E131_MULTICAST) {
|
||||
ip4_addr_t multicast_addr = {
|
||||
static_cast<uint32_t>(IPAddress(239, 255, ((universe >> 8) & 0xff), ((universe >> 0) & 0xff)))};
|
||||
static_cast<uint32_t>(network::IPAddress(239, 255, ((universe >> 8) & 0xff), ((universe >> 0) & 0xff)))};
|
||||
|
||||
igmp_leavegroup(IP4_ADDR_ANY4, &multicast_addr);
|
||||
}
|
||||
@ -134,3 +140,5 @@ bool E131Component::packet_(const std::vector<uint8_t> &data, int &universe, E13
|
||||
|
||||
} // namespace e131
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ARDUINO
|
||||
|
@ -1,5 +1,6 @@
|
||||
#include "endstop_cover.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/hal.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace endstop {
|
||||
|
395
esphome/components/esp32/__init__.py
Normal file
395
esphome/components/esp32/__init__.py
Normal file
@ -0,0 +1,395 @@
|
||||
from dataclasses import dataclass
|
||||
from typing import Union
|
||||
from pathlib import Path
|
||||
import logging
|
||||
|
||||
from esphome.helpers import write_file_if_changed
|
||||
from esphome.const import (
|
||||
CONF_BOARD,
|
||||
CONF_FRAMEWORK,
|
||||
CONF_TYPE,
|
||||
CONF_VARIANT,
|
||||
CONF_VERSION,
|
||||
CONF_ADVANCED,
|
||||
CONF_IGNORE_EFUSE_MAC_CRC,
|
||||
KEY_CORE,
|
||||
KEY_FRAMEWORK_VERSION,
|
||||
KEY_TARGET_FRAMEWORK,
|
||||
KEY_TARGET_PLATFORM,
|
||||
)
|
||||
from esphome.core import CORE, HexInt
|
||||
import esphome.config_validation as cv
|
||||
import esphome.codegen as cg
|
||||
|
||||
from .const import (
|
||||
KEY_BOARD,
|
||||
KEY_ESP32,
|
||||
KEY_SDKCONFIG_OPTIONS,
|
||||
KEY_VARIANT,
|
||||
VARIANT_ESP32C3,
|
||||
VARIANTS,
|
||||
)
|
||||
|
||||
# force import gpio to register pin schema
|
||||
from .gpio import esp32_pin_to_code # noqa
|
||||
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
CODEOWNERS = ["@esphome/core"]
|
||||
AUTO_LOAD = ["preferences"]
|
||||
|
||||
|
||||
def set_core_data(config):
|
||||
CORE.data[KEY_ESP32] = {}
|
||||
CORE.data[KEY_CORE][KEY_TARGET_PLATFORM] = "esp32"
|
||||
conf = config[CONF_FRAMEWORK]
|
||||
if conf[CONF_TYPE] == FRAMEWORK_ESP_IDF:
|
||||
CORE.data[KEY_CORE][KEY_TARGET_FRAMEWORK] = "esp-idf"
|
||||
CORE.data[KEY_ESP32][KEY_SDKCONFIG_OPTIONS] = {}
|
||||
elif conf[CONF_TYPE] == FRAMEWORK_ARDUINO:
|
||||
CORE.data[KEY_CORE][KEY_TARGET_FRAMEWORK] = "arduino"
|
||||
CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] = cv.Version.parse(
|
||||
config[CONF_FRAMEWORK][CONF_VERSION_HINT]
|
||||
)
|
||||
CORE.data[KEY_ESP32][KEY_BOARD] = config[CONF_BOARD]
|
||||
CORE.data[KEY_ESP32][KEY_VARIANT] = config[CONF_VARIANT]
|
||||
return config
|
||||
|
||||
|
||||
def get_esp32_variant():
|
||||
return CORE.data[KEY_ESP32][KEY_VARIANT]
|
||||
|
||||
|
||||
def is_esp32c3():
|
||||
return get_esp32_variant() == VARIANT_ESP32C3
|
||||
|
||||
|
||||
@dataclass
|
||||
class RawSdkconfigValue:
|
||||
"""An sdkconfig value that won't be auto-formatted"""
|
||||
|
||||
value: str
|
||||
|
||||
|
||||
SdkconfigValueType = Union[bool, int, HexInt, str, RawSdkconfigValue]
|
||||
|
||||
|
||||
def add_idf_sdkconfig_option(name: str, value: SdkconfigValueType):
|
||||
"""Set an esp-idf sdkconfig value."""
|
||||
if not CORE.using_esp_idf:
|
||||
raise ValueError("Not an esp-idf project")
|
||||
CORE.data[KEY_ESP32][KEY_SDKCONFIG_OPTIONS][name] = value
|
||||
|
||||
|
||||
def _format_framework_arduino_version(ver: cv.Version) -> str:
|
||||
# format the given arduino (https://github.com/espressif/arduino-esp32/releases) version to
|
||||
# a PIO platformio/framework-arduinoespressif32 value
|
||||
# List of package versions: https://api.registry.platformio.org/v3/packages/platformio/tool/framework-arduinoespressif32
|
||||
if ver <= cv.Version(1, 0, 3):
|
||||
return f"~2.{ver.major}{ver.minor:02d}{ver.patch:02d}.0"
|
||||
return f"~3.{ver.major}{ver.minor:02d}{ver.patch:02d}.0"
|
||||
|
||||
|
||||
# NOTE: Keep this in mind when updating the recommended version:
|
||||
# * New framework historically have had some regressions, especially for WiFi.
|
||||
# The new version needs to be thoroughly validated before changing the
|
||||
# recommended version as otherwise a bunch of devices could be bricked
|
||||
# * For all constants below, update platformio.ini (in this repo)
|
||||
# and platformio.ini/platformio-lint.ini in the esphome-docker-base repository
|
||||
|
||||
# The default/recommended arduino framework version
|
||||
# - https://github.com/espressif/arduino-esp32/releases
|
||||
# - https://api.registry.platformio.org/v3/packages/platformio/tool/framework-arduinoespressif32
|
||||
RECOMMENDED_ARDUINO_FRAMEWORK_VERSION = cv.Version(1, 0, 6)
|
||||
# The platformio/espressif32 version to use for arduino frameworks
|
||||
# - https://github.com/platformio/platform-espressif32/releases
|
||||
# - https://api.registry.platformio.org/v3/packages/platformio/platform/espressif32
|
||||
ARDUINO_PLATFORM_VERSION = cv.Version(3, 3, 2)
|
||||
|
||||
# The default/recommended esp-idf framework version
|
||||
# - https://github.com/espressif/esp-idf/releases
|
||||
# - https://api.registry.platformio.org/v3/packages/platformio/tool/framework-espidf
|
||||
RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION = cv.Version(4, 3, 0)
|
||||
# The platformio/espressif32 version to use for esp-idf frameworks
|
||||
# - https://github.com/platformio/platform-espressif32/releases
|
||||
# - https://api.registry.platformio.org/v3/packages/platformio/platform/espressif32
|
||||
ESP_IDF_PLATFORM_VERSION = cv.Version(3, 3, 2)
|
||||
|
||||
|
||||
def _arduino_check_versions(value):
|
||||
value = value.copy()
|
||||
lookups = {
|
||||
"dev": ("https://github.com/espressif/arduino-esp32.git", cv.Version(2, 0, 0)),
|
||||
"latest": ("", cv.Version(1, 0, 3)),
|
||||
"recommended": (
|
||||
_format_framework_arduino_version(RECOMMENDED_ARDUINO_FRAMEWORK_VERSION),
|
||||
RECOMMENDED_ARDUINO_FRAMEWORK_VERSION,
|
||||
),
|
||||
}
|
||||
ver_value = value[CONF_VERSION]
|
||||
default_ver_hint = None
|
||||
if ver_value.lower() in lookups:
|
||||
default_ver_hint = str(lookups[ver_value.lower()][1])
|
||||
ver_value = lookups[ver_value.lower()][0]
|
||||
else:
|
||||
with cv.suppress_invalid():
|
||||
ver = cv.Version.parse(cv.version_number(value))
|
||||
if ver <= cv.Version(1, 0, 3):
|
||||
ver_value = f"~2.{ver.major}{ver.minor:02d}{ver.patch:02d}.0"
|
||||
else:
|
||||
ver_value = f"~3.{ver.major}{ver.minor:02d}{ver.patch:02d}.0"
|
||||
default_ver_hint = str(ver)
|
||||
value[CONF_VERSION] = ver_value
|
||||
|
||||
if CONF_VERSION_HINT not in value and default_ver_hint is None:
|
||||
raise cv.Invalid("Needs a version hint to understand the framework version")
|
||||
|
||||
ver_hint_s = value.get(CONF_VERSION_HINT, default_ver_hint)
|
||||
value[CONF_VERSION_HINT] = ver_hint_s
|
||||
plat_ver = value.get(CONF_PLATFORM_VERSION, ARDUINO_PLATFORM_VERSION)
|
||||
value[CONF_PLATFORM_VERSION] = str(plat_ver)
|
||||
|
||||
if cv.Version.parse(ver_hint_s) != RECOMMENDED_ARDUINO_FRAMEWORK_VERSION:
|
||||
_LOGGER.warning(
|
||||
"The selected arduino framework version is not the recommended one"
|
||||
)
|
||||
_LOGGER.warning(
|
||||
"If there are connectivity or build issues please remove the manual version"
|
||||
)
|
||||
|
||||
return value
|
||||
|
||||
|
||||
def _format_framework_espidf_version(ver: cv.Version) -> str:
|
||||
# format the given arduino (https://github.com/espressif/esp-idf/releases) version to
|
||||
# a PIO platformio/framework-espidf value
|
||||
# List of package versions: https://api.registry.platformio.org/v3/packages/platformio/tool/framework-espidf
|
||||
return f"~3.{ver.major}{ver.minor:02d}{ver.patch:02d}.0"
|
||||
|
||||
|
||||
def _esp_idf_check_versions(value):
|
||||
value = value.copy()
|
||||
lookups = {
|
||||
"dev": ("https://github.com/espressif/esp-idf.git", cv.Version(4, 3, 1)),
|
||||
"latest": ("", cv.Version(4, 3, 0)),
|
||||
"recommended": (
|
||||
_format_framework_espidf_version(RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION),
|
||||
RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION,
|
||||
),
|
||||
}
|
||||
ver_value = value[CONF_VERSION]
|
||||
default_ver_hint = None
|
||||
if ver_value.lower() in lookups:
|
||||
default_ver_hint = str(lookups[ver_value.lower()][1])
|
||||
ver_value = lookups[ver_value.lower()][0]
|
||||
else:
|
||||
with cv.suppress_invalid():
|
||||
ver = cv.Version.parse(cv.version_number(value))
|
||||
ver_value = f"~3.{ver.major}{ver.minor:02d}{ver.patch:02d}.0"
|
||||
default_ver_hint = str(ver)
|
||||
value[CONF_VERSION] = ver_value
|
||||
|
||||
if CONF_VERSION_HINT not in value and default_ver_hint is None:
|
||||
raise cv.Invalid("Needs a version hint to understand the framework version")
|
||||
|
||||
ver_hint_s = value.get(CONF_VERSION_HINT, default_ver_hint)
|
||||
value[CONF_VERSION_HINT] = ver_hint_s
|
||||
if cv.Version.parse(ver_hint_s) < cv.Version(4, 0, 0):
|
||||
raise cv.Invalid("Only ESP-IDF 4.0+ is supported")
|
||||
if cv.Version.parse(ver_hint_s) != RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION:
|
||||
_LOGGER.warning(
|
||||
"The selected esp-idf framework version is not the recommended one"
|
||||
)
|
||||
_LOGGER.warning(
|
||||
"If there are connectivity or build issues please remove the manual version"
|
||||
)
|
||||
|
||||
plat_ver = value.get(CONF_PLATFORM_VERSION, ESP_IDF_PLATFORM_VERSION)
|
||||
value[CONF_PLATFORM_VERSION] = str(plat_ver)
|
||||
|
||||
return value
|
||||
|
||||
|
||||
CONF_VERSION_HINT = "version_hint"
|
||||
CONF_PLATFORM_VERSION = "platform_version"
|
||||
ARDUINO_FRAMEWORK_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_VERSION, default="recommended"): cv.string_strict,
|
||||
cv.Optional(CONF_VERSION_HINT): cv.version_number,
|
||||
cv.Optional(CONF_PLATFORM_VERSION): cv.string_strict,
|
||||
}
|
||||
),
|
||||
_arduino_check_versions,
|
||||
)
|
||||
CONF_SDKCONFIG_OPTIONS = "sdkconfig_options"
|
||||
ESP_IDF_FRAMEWORK_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_VERSION, default="recommended"): cv.string_strict,
|
||||
cv.Optional(CONF_VERSION_HINT): cv.version_number,
|
||||
cv.Optional(CONF_SDKCONFIG_OPTIONS, default={}): {
|
||||
cv.string_strict: cv.string_strict
|
||||
},
|
||||
cv.Optional(CONF_PLATFORM_VERSION): cv.string_strict,
|
||||
cv.Optional(CONF_ADVANCED, default={}): cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_IGNORE_EFUSE_MAC_CRC, default=False): cv.boolean,
|
||||
}
|
||||
),
|
||||
}
|
||||
),
|
||||
_esp_idf_check_versions,
|
||||
)
|
||||
|
||||
|
||||
FRAMEWORK_ESP_IDF = "esp-idf"
|
||||
FRAMEWORK_ARDUINO = "arduino"
|
||||
FRAMEWORK_SCHEMA = cv.typed_schema(
|
||||
{
|
||||
FRAMEWORK_ESP_IDF: ESP_IDF_FRAMEWORK_SCHEMA,
|
||||
FRAMEWORK_ARDUINO: ARDUINO_FRAMEWORK_SCHEMA,
|
||||
},
|
||||
lower=True,
|
||||
space="-",
|
||||
default_type=FRAMEWORK_ARDUINO,
|
||||
)
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_BOARD): cv.string_strict,
|
||||
cv.Optional(CONF_VARIANT, default="ESP32"): cv.one_of(
|
||||
*VARIANTS, upper=True
|
||||
),
|
||||
cv.Optional(CONF_FRAMEWORK, default={}): FRAMEWORK_SCHEMA,
|
||||
}
|
||||
),
|
||||
set_core_data,
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
cg.add_platformio_option("board", config[CONF_BOARD])
|
||||
cg.add_build_flag("-DUSE_ESP32")
|
||||
cg.add_define("ESPHOME_BOARD", config[CONF_BOARD])
|
||||
cg.add_build_flag(f"-DUSE_ESP32_VARIANT_{config[CONF_VARIANT]}")
|
||||
|
||||
cg.add_platformio_option("lib_ldf_mode", "off")
|
||||
|
||||
conf = config[CONF_FRAMEWORK]
|
||||
if conf[CONF_TYPE] == FRAMEWORK_ESP_IDF:
|
||||
cg.add_platformio_option(
|
||||
"platform", f"espressif32 @ {conf[CONF_PLATFORM_VERSION]}"
|
||||
)
|
||||
cg.add_platformio_option("framework", "espidf")
|
||||
cg.add_build_flag("-DUSE_ESP_IDF")
|
||||
cg.add_build_flag("-DUSE_ESP32_FRAMEWORK_ESP_IDF")
|
||||
cg.add_build_flag("-Wno-nonnull-compare")
|
||||
cg.add_platformio_option(
|
||||
"platform_packages",
|
||||
[f"platformio/framework-espidf @ {conf[CONF_VERSION]}"],
|
||||
)
|
||||
add_idf_sdkconfig_option("CONFIG_PARTITION_TABLE_SINGLE_APP", False)
|
||||
add_idf_sdkconfig_option("CONFIG_PARTITION_TABLE_CUSTOM", True)
|
||||
add_idf_sdkconfig_option(
|
||||
"CONFIG_PARTITION_TABLE_CUSTOM_FILENAME", "partitions.csv"
|
||||
)
|
||||
add_idf_sdkconfig_option("CONFIG_COMPILER_OPTIMIZATION_DEFAULT", False)
|
||||
add_idf_sdkconfig_option("CONFIG_COMPILER_OPTIMIZATION_SIZE", True)
|
||||
|
||||
cg.add_platformio_option("board_build.partitions", "partitions.csv")
|
||||
|
||||
for name, value in conf[CONF_SDKCONFIG_OPTIONS].items():
|
||||
add_idf_sdkconfig_option(name, RawSdkconfigValue(value))
|
||||
|
||||
if conf[CONF_ADVANCED][CONF_IGNORE_EFUSE_MAC_CRC]:
|
||||
cg.add_define("USE_ESP32_IGNORE_EFUSE_MAC_CRC")
|
||||
add_idf_sdkconfig_option(
|
||||
"CONFIG_ESP32_PHY_CALIBRATION_AND_DATA_STORAGE", False
|
||||
)
|
||||
|
||||
elif conf[CONF_TYPE] == FRAMEWORK_ARDUINO:
|
||||
cg.add_platformio_option(
|
||||
"platform", f"espressif32 @ {conf[CONF_PLATFORM_VERSION]}"
|
||||
)
|
||||
cg.add_platformio_option("framework", "arduino")
|
||||
cg.add_build_flag("-DUSE_ARDUINO")
|
||||
cg.add_build_flag("-DUSE_ESP32_FRAMEWORK_ARDUINO")
|
||||
cg.add_platformio_option(
|
||||
"platform_packages",
|
||||
[f"platformio/framework-arduinoespressif32 @ {conf[CONF_VERSION]}"],
|
||||
)
|
||||
|
||||
cg.add_platformio_option("board_build.partitions", "partitions.csv")
|
||||
|
||||
|
||||
ARDUINO_PARTITIONS_CSV = """\
|
||||
nvs, data, nvs, 0x009000, 0x005000,
|
||||
otadata, data, ota, 0x00e000, 0x002000,
|
||||
app0, app, ota_0, 0x010000, 0x1C0000,
|
||||
app1, app, ota_1, 0x1D0000, 0x1C0000,
|
||||
eeprom, data, 0x99, 0x390000, 0x001000,
|
||||
spiffs, data, spiffs, 0x391000, 0x00F000
|
||||
"""
|
||||
|
||||
|
||||
IDF_PARTITIONS_CSV = """\
|
||||
# Name, Type, SubType, Offset, Size, Flags
|
||||
nvs, data, nvs, , 0x4000,
|
||||
otadata, data, ota, , 0x2000,
|
||||
phy_init, data, phy, , 0x1000,
|
||||
app0, app, ota_0, , 0x1C0000,
|
||||
app1, app, ota_1, , 0x1C0000,
|
||||
"""
|
||||
|
||||
|
||||
def _format_sdkconfig_val(value: SdkconfigValueType) -> str:
|
||||
if isinstance(value, bool):
|
||||
return "y" if value else "n"
|
||||
if isinstance(value, int):
|
||||
return str(value)
|
||||
if isinstance(value, str):
|
||||
return f'"{value}"'
|
||||
if isinstance(value, RawSdkconfigValue):
|
||||
return value.value
|
||||
raise ValueError
|
||||
|
||||
|
||||
def _write_sdkconfig():
|
||||
# sdkconfig.{name} stores the real sdkconfig (modified by esp-idf with default)
|
||||
# sdkconfig.{name}.esphomeinternal stores what esphome last wrote
|
||||
# we use the internal one to detect if there were any changes, and if so write them to the
|
||||
# real sdkconfig
|
||||
sdk_path = Path(CORE.relative_build_path(f"sdkconfig.{CORE.name}"))
|
||||
internal_path = Path(
|
||||
CORE.relative_build_path(f"sdkconfig.{CORE.name}.esphomeinternal")
|
||||
)
|
||||
|
||||
want_opts = CORE.data[KEY_ESP32][KEY_SDKCONFIG_OPTIONS]
|
||||
contents = (
|
||||
"\n".join(
|
||||
f"{name}={_format_sdkconfig_val(value)}"
|
||||
for name, value in sorted(want_opts.items())
|
||||
)
|
||||
+ "\n"
|
||||
)
|
||||
if write_file_if_changed(internal_path, contents):
|
||||
# internal changed, update real one
|
||||
write_file_if_changed(sdk_path, contents)
|
||||
|
||||
|
||||
# Called by writer.py
|
||||
def copy_files():
|
||||
if CORE.using_arduino:
|
||||
write_file_if_changed(
|
||||
CORE.relative_build_path("partitions.csv"),
|
||||
ARDUINO_PARTITIONS_CSV,
|
||||
)
|
||||
if CORE.using_esp_idf:
|
||||
_write_sdkconfig()
|
||||
write_file_if_changed(
|
||||
CORE.relative_build_path("partitions.csv"),
|
||||
IDF_PARTITIONS_CSV,
|
||||
)
|
@ -1,212 +1,3 @@
|
||||
ESP8266_BASE_PINS = {
|
||||
"A0": 17,
|
||||
"SS": 15,
|
||||
"MOSI": 13,
|
||||
"MISO": 12,
|
||||
"SCK": 14,
|
||||
"SDA": 4,
|
||||
"SCL": 5,
|
||||
"RX": 3,
|
||||
"TX": 1,
|
||||
}
|
||||
|
||||
ESP8266_BOARD_PINS = {
|
||||
"d1": {
|
||||
"D0": 3,
|
||||
"D1": 1,
|
||||
"D2": 16,
|
||||
"D3": 5,
|
||||
"D4": 4,
|
||||
"D5": 14,
|
||||
"D6": 12,
|
||||
"D7": 13,
|
||||
"D8": 0,
|
||||
"D9": 2,
|
||||
"D10": 15,
|
||||
"D11": 13,
|
||||
"D12": 14,
|
||||
"D13": 14,
|
||||
"D14": 4,
|
||||
"D15": 5,
|
||||
"LED": 2,
|
||||
},
|
||||
"d1_mini": {
|
||||
"D0": 16,
|
||||
"D1": 5,
|
||||
"D2": 4,
|
||||
"D3": 0,
|
||||
"D4": 2,
|
||||
"D5": 14,
|
||||
"D6": 12,
|
||||
"D7": 13,
|
||||
"D8": 15,
|
||||
"LED": 2,
|
||||
},
|
||||
"d1_mini_lite": "d1_mini",
|
||||
"d1_mini_pro": "d1_mini",
|
||||
"esp01": {},
|
||||
"esp01_1m": {},
|
||||
"esp07": {},
|
||||
"esp12e": {},
|
||||
"esp210": {},
|
||||
"esp8285": {},
|
||||
"esp_wroom_02": {},
|
||||
"espduino": {"LED": 16},
|
||||
"espectro": {"LED": 15, "BUTTON": 2},
|
||||
"espino": {"LED": 2, "LED_RED": 2, "LED_GREEN": 4, "LED_BLUE": 5, "BUTTON": 0},
|
||||
"espinotee": {"LED": 16},
|
||||
"espmxdevkit": {},
|
||||
"espresso_lite_v1": {"LED": 16},
|
||||
"espresso_lite_v2": {"LED": 2},
|
||||
"gen4iod": {},
|
||||
"heltec_wifi_kit_8": "d1_mini",
|
||||
"huzzah": {
|
||||
"LED": 0,
|
||||
"LED_RED": 0,
|
||||
"LED_BLUE": 2,
|
||||
"D4": 4,
|
||||
"D5": 5,
|
||||
"D12": 12,
|
||||
"D13": 13,
|
||||
"D14": 14,
|
||||
"D15": 15,
|
||||
"D16": 16,
|
||||
},
|
||||
"inventone": {},
|
||||
"modwifi": {},
|
||||
"nodemcu": {
|
||||
"D0": 16,
|
||||
"D1": 5,
|
||||
"D2": 4,
|
||||
"D3": 0,
|
||||
"D4": 2,
|
||||
"D5": 14,
|
||||
"D6": 12,
|
||||
"D7": 13,
|
||||
"D8": 15,
|
||||
"D9": 3,
|
||||
"D10": 1,
|
||||
"LED": 16,
|
||||
},
|
||||
"nodemcuv2": "nodemcu",
|
||||
"oak": {
|
||||
"P0": 2,
|
||||
"P1": 5,
|
||||
"P2": 0,
|
||||
"P3": 3,
|
||||
"P4": 1,
|
||||
"P5": 4,
|
||||
"P6": 15,
|
||||
"P7": 13,
|
||||
"P8": 12,
|
||||
"P9": 14,
|
||||
"P10": 16,
|
||||
"P11": 17,
|
||||
"LED": 5,
|
||||
},
|
||||
"phoenix_v1": {"LED": 16},
|
||||
"phoenix_v2": {"LED": 2},
|
||||
"sonoff_basic": {},
|
||||
"sonoff_s20": {},
|
||||
"sonoff_sv": {},
|
||||
"sonoff_th": {},
|
||||
"sparkfunBlynk": "thing",
|
||||
"thing": {"LED": 5, "SDA": 2, "SCL": 14},
|
||||
"thingdev": "thing",
|
||||
"wifi_slot": {"LED": 2},
|
||||
"wifiduino": {
|
||||
"D0": 3,
|
||||
"D1": 1,
|
||||
"D2": 2,
|
||||
"D3": 0,
|
||||
"D4": 4,
|
||||
"D5": 5,
|
||||
"D6": 16,
|
||||
"D7": 14,
|
||||
"D8": 12,
|
||||
"D9": 13,
|
||||
"D10": 15,
|
||||
"D11": 13,
|
||||
"D12": 12,
|
||||
"D13": 14,
|
||||
},
|
||||
"wifinfo": {
|
||||
"LED": 12,
|
||||
"D0": 16,
|
||||
"D1": 5,
|
||||
"D2": 4,
|
||||
"D3": 0,
|
||||
"D4": 2,
|
||||
"D5": 14,
|
||||
"D6": 12,
|
||||
"D7": 13,
|
||||
"D8": 15,
|
||||
"D9": 3,
|
||||
"D10": 1,
|
||||
},
|
||||
"wio_link": {"LED": 2, "GROVE": 15, "D0": 14, "D1": 12, "D2": 13, "BUTTON": 0},
|
||||
"wio_node": {"LED": 2, "GROVE": 15, "D0": 3, "D1": 5, "BUTTON": 0},
|
||||
"xinabox_cw01": {"SDA": 2, "SCL": 14, "LED": 5, "LED_RED": 12, "LED_GREEN": 13},
|
||||
}
|
||||
|
||||
FLASH_SIZE_1_MB = 2 ** 20
|
||||
FLASH_SIZE_512_KB = FLASH_SIZE_1_MB // 2
|
||||
FLASH_SIZE_2_MB = 2 * FLASH_SIZE_1_MB
|
||||
FLASH_SIZE_4_MB = 4 * FLASH_SIZE_1_MB
|
||||
FLASH_SIZE_16_MB = 16 * FLASH_SIZE_1_MB
|
||||
|
||||
ESP8266_FLASH_SIZES = {
|
||||
"d1": FLASH_SIZE_4_MB,
|
||||
"d1_mini": FLASH_SIZE_4_MB,
|
||||
"d1_mini_lite": FLASH_SIZE_1_MB,
|
||||
"d1_mini_pro": FLASH_SIZE_16_MB,
|
||||
"esp01": FLASH_SIZE_512_KB,
|
||||
"esp01_1m": FLASH_SIZE_1_MB,
|
||||
"esp07": FLASH_SIZE_4_MB,
|
||||
"esp12e": FLASH_SIZE_4_MB,
|
||||
"esp210": FLASH_SIZE_4_MB,
|
||||
"esp8285": FLASH_SIZE_1_MB,
|
||||
"esp_wroom_02": FLASH_SIZE_2_MB,
|
||||
"espduino": FLASH_SIZE_4_MB,
|
||||
"espectro": FLASH_SIZE_4_MB,
|
||||
"espino": FLASH_SIZE_4_MB,
|
||||
"espinotee": FLASH_SIZE_4_MB,
|
||||
"espmxdevkit": FLASH_SIZE_1_MB,
|
||||
"espresso_lite_v1": FLASH_SIZE_4_MB,
|
||||
"espresso_lite_v2": FLASH_SIZE_4_MB,
|
||||
"gen4iod": FLASH_SIZE_512_KB,
|
||||
"heltec_wifi_kit_8": FLASH_SIZE_4_MB,
|
||||
"huzzah": FLASH_SIZE_4_MB,
|
||||
"inventone": FLASH_SIZE_4_MB,
|
||||
"modwifi": FLASH_SIZE_2_MB,
|
||||
"nodemcu": FLASH_SIZE_4_MB,
|
||||
"nodemcuv2": FLASH_SIZE_4_MB,
|
||||
"oak": FLASH_SIZE_4_MB,
|
||||
"phoenix_v1": FLASH_SIZE_4_MB,
|
||||
"phoenix_v2": FLASH_SIZE_4_MB,
|
||||
"sonoff_basic": FLASH_SIZE_1_MB,
|
||||
"sonoff_s20": FLASH_SIZE_1_MB,
|
||||
"sonoff_sv": FLASH_SIZE_1_MB,
|
||||
"sonoff_th": FLASH_SIZE_1_MB,
|
||||
"sparkfunBlynk": FLASH_SIZE_4_MB,
|
||||
"thing": FLASH_SIZE_512_KB,
|
||||
"thingdev": FLASH_SIZE_512_KB,
|
||||
"wifi_slot": FLASH_SIZE_1_MB,
|
||||
"wifiduino": FLASH_SIZE_4_MB,
|
||||
"wifinfo": FLASH_SIZE_1_MB,
|
||||
"wio_link": FLASH_SIZE_4_MB,
|
||||
"wio_node": FLASH_SIZE_4_MB,
|
||||
"xinabox_cw01": FLASH_SIZE_4_MB,
|
||||
}
|
||||
|
||||
ESP8266_LD_SCRIPTS = {
|
||||
FLASH_SIZE_512_KB: ("eagle.flash.512k0.ld", "eagle.flash.512k.ld"),
|
||||
FLASH_SIZE_1_MB: ("eagle.flash.1m0.ld", "eagle.flash.1m.ld"),
|
||||
FLASH_SIZE_2_MB: ("eagle.flash.2m.ld", "eagle.flash.2m.ld"),
|
||||
FLASH_SIZE_4_MB: ("eagle.flash.4m.ld", "eagle.flash.4m.ld"),
|
||||
FLASH_SIZE_16_MB: ("eagle.flash.16m.ld", "eagle.flash.16m14m.ld"),
|
||||
}
|
||||
|
||||
ESP32_BASE_PINS = {
|
||||
"TX": 1,
|
||||
"RX": 3,
|
||||
@ -1134,19 +925,3 @@ ESP32_BOARD_PINS = {
|
||||
},
|
||||
"xinabox_cw02": {"LED": 27},
|
||||
}
|
||||
|
||||
ESP32_C3_BASE_PINS = {
|
||||
"TX": 21,
|
||||
"RX": 20,
|
||||
"ADC1_0": 0,
|
||||
"ADC1_1": 1,
|
||||
"ADC1_2": 2,
|
||||
"ADC1_3": 3,
|
||||
"ADC1_4": 4,
|
||||
"ADC2_0": 5,
|
||||
}
|
||||
|
||||
ESP32_C3_BOARD_PINS = {
|
||||
"esp32-c3-devkitm-1": {"LED": 8},
|
||||
"esp32-c3-devkitc-02": "esp32-c3-devkitm-1",
|
||||
}
|
21
esphome/components/esp32/const.py
Normal file
21
esphome/components/esp32/const.py
Normal file
@ -0,0 +1,21 @@
|
||||
import esphome.codegen as cg
|
||||
|
||||
KEY_ESP32 = "esp32"
|
||||
KEY_BOARD = "board"
|
||||
KEY_VARIANT = "variant"
|
||||
KEY_SDKCONFIG_OPTIONS = "sdkconfig_options"
|
||||
|
||||
VARIANT_ESP32 = "ESP32"
|
||||
VARIANT_ESP32S2 = "ESP32S2"
|
||||
VARIANT_ESP32S3 = "ESP32S3"
|
||||
VARIANT_ESP32C3 = "ESP32C3"
|
||||
VARIANT_ESP32H2 = "ESP32H2"
|
||||
VARIANTS = [
|
||||
VARIANT_ESP32,
|
||||
VARIANT_ESP32S2,
|
||||
VARIANT_ESP32S3,
|
||||
VARIANT_ESP32C3,
|
||||
VARIANT_ESP32H2,
|
||||
]
|
||||
|
||||
esp32_ns = cg.esphome_ns.namespace("esp32")
|
89
esphome/components/esp32/core.cpp
Normal file
89
esphome/components/esp32/core.cpp
Normal file
@ -0,0 +1,89 @@
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "preferences.h"
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/task.h>
|
||||
#include <esp_idf_version.h>
|
||||
#include <soc/rtc.h>
|
||||
|
||||
#if ESP_IDF_VERSION_MAJOR >= 4
|
||||
#include <hal/cpu_hal.h>
|
||||
#endif
|
||||
|
||||
void setup();
|
||||
void loop();
|
||||
|
||||
namespace esphome {
|
||||
|
||||
void IRAM_ATTR HOT yield() { vPortYield(); }
|
||||
uint32_t IRAM_ATTR HOT millis() { return (uint32_t)(esp_timer_get_time() / 1000ULL); }
|
||||
void IRAM_ATTR HOT delay(uint32_t ms) { vTaskDelay(ms / portTICK_PERIOD_MS); }
|
||||
uint32_t IRAM_ATTR HOT micros() { return (uint32_t) esp_timer_get_time(); }
|
||||
void IRAM_ATTR HOT delayMicroseconds(uint32_t us) {
|
||||
auto start = (uint64_t) esp_timer_get_time();
|
||||
while (((uint64_t) esp_timer_get_time()) - start < us)
|
||||
;
|
||||
}
|
||||
void arch_restart() {
|
||||
esp_restart();
|
||||
// restart() doesn't always end execution
|
||||
while (true) { // NOLINT(clang-diagnostic-unreachable-code)
|
||||
yield();
|
||||
}
|
||||
}
|
||||
void IRAM_ATTR HOT arch_feed_wdt() {
|
||||
#ifdef USE_ARDUINO
|
||||
#if CONFIG_ARDUINO_RUNNING_CORE == 0
|
||||
#ifdef CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU0
|
||||
// ESP32 uses "Task Watchdog" which is hooked to the FreeRTOS idle task.
|
||||
// To cause the Watchdog to be triggered we need to put the current task
|
||||
// to sleep to get the idle task scheduled.
|
||||
delay(1);
|
||||
#endif
|
||||
#endif
|
||||
#endif // USE_ARDUINO
|
||||
|
||||
#ifdef USE_ESP_IDF
|
||||
#ifdef CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU0
|
||||
delay(1);
|
||||
#endif
|
||||
#endif // USE_ESP_IDF
|
||||
}
|
||||
|
||||
uint8_t progmem_read_byte(const uint8_t *addr) { return *addr; }
|
||||
uint32_t arch_get_cpu_cycle_count() {
|
||||
#if ESP_IDF_VERSION_MAJOR >= 4
|
||||
return cpu_hal_get_cycle_count();
|
||||
#else
|
||||
uint32_t ccount;
|
||||
__asm__ __volatile__("esync; rsr %0,ccount" : "=a"(ccount));
|
||||
return ccount;
|
||||
#endif
|
||||
}
|
||||
uint32_t arch_get_cpu_freq_hz() { return rtc_clk_apb_freq_get(); }
|
||||
|
||||
#ifdef USE_ESP_IDF
|
||||
TaskHandle_t loop_task_handle = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
||||
void loop_task(void *pv_params) {
|
||||
setup();
|
||||
while (true) {
|
||||
loop();
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" void app_main() {
|
||||
esp32::setup_preferences();
|
||||
xTaskCreate(loop_task, "loopTask", 8192, nullptr, 1, &loop_task_handle);
|
||||
}
|
||||
#endif // USE_ESP_IDF
|
||||
|
||||
#ifdef USE_ARDUINO
|
||||
extern "C" void init() { esp32::setup_preferences(); }
|
||||
#endif // USE_ARDUINO
|
||||
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ESP32
|
201
esphome/components/esp32/gpio.py
Normal file
201
esphome/components/esp32/gpio.py
Normal file
@ -0,0 +1,201 @@
|
||||
import logging
|
||||
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_INPUT,
|
||||
CONF_INVERTED,
|
||||
CONF_MODE,
|
||||
CONF_NUMBER,
|
||||
CONF_OPEN_DRAIN,
|
||||
CONF_OUTPUT,
|
||||
CONF_PULLDOWN,
|
||||
CONF_PULLUP,
|
||||
)
|
||||
from esphome import pins
|
||||
from esphome.core import CORE
|
||||
import esphome.config_validation as cv
|
||||
import esphome.codegen as cg
|
||||
|
||||
from . import boards
|
||||
from .const import KEY_BOARD, KEY_ESP32, esp32_ns
|
||||
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
IDFInternalGPIOPin = esp32_ns.class_("IDFInternalGPIOPin", cg.InternalGPIOPin)
|
||||
ArduinoInternalGPIOPin = esp32_ns.class_("ArduinoInternalGPIOPin", cg.InternalGPIOPin)
|
||||
|
||||
|
||||
def _lookup_pin(value):
|
||||
board = CORE.data[KEY_ESP32][KEY_BOARD]
|
||||
board_pins = boards.ESP32_BOARD_PINS.get(board, {})
|
||||
|
||||
# Resolved aliased board pins (shorthand when two boards have the same pin configuration)
|
||||
while isinstance(board_pins, str):
|
||||
board_pins = boards.ESP32_BOARD_PINS[board_pins]
|
||||
|
||||
if value in board_pins:
|
||||
return board_pins[value]
|
||||
if value in boards.ESP32_BASE_PINS:
|
||||
return boards.ESP32_BASE_PINS[value]
|
||||
raise cv.Invalid(f"Cannot resolve pin name '{value}' for board {board}.")
|
||||
|
||||
|
||||
def _translate_pin(value):
|
||||
if isinstance(value, dict) or value is None:
|
||||
raise cv.Invalid(
|
||||
"This variable only supports pin numbers, not full pin schemas "
|
||||
"(with inverted and mode)."
|
||||
)
|
||||
if isinstance(value, int):
|
||||
return value
|
||||
try:
|
||||
return int(value)
|
||||
except ValueError:
|
||||
pass
|
||||
if value.startswith("GPIO"):
|
||||
return cv.int_(value[len("GPIO") :].strip())
|
||||
return _lookup_pin(value)
|
||||
|
||||
|
||||
_ESP_SDIO_PINS = {
|
||||
6: "Flash Clock",
|
||||
7: "Flash Data 0",
|
||||
8: "Flash Data 1",
|
||||
11: "Flash Command",
|
||||
}
|
||||
|
||||
|
||||
def validate_gpio_pin(value):
|
||||
value = _translate_pin(value)
|
||||
if value < 0 or value > 39:
|
||||
raise cv.Invalid(f"Invalid pin number: {value} (must be 0-39)")
|
||||
if value in _ESP_SDIO_PINS:
|
||||
raise cv.Invalid(
|
||||
f"This pin cannot be used on ESP32s and is already used by the flash interface (function: {_ESP_SDIO_PINS[value]})"
|
||||
)
|
||||
if 9 <= value <= 10:
|
||||
_LOGGER.warning(
|
||||
"Pin %s (9-10) might already be used by the "
|
||||
"flash interface in QUAD IO flash mode.",
|
||||
value,
|
||||
)
|
||||
if value in (20, 24, 28, 29, 30, 31):
|
||||
# These pins are not exposed in GPIO mux (reason unknown)
|
||||
# but they're missing from IO_MUX list in datasheet
|
||||
raise cv.Invalid(f"The pin GPIO{value} is not usable on ESP32s.")
|
||||
return value
|
||||
|
||||
|
||||
def validate_supports(value):
|
||||
num = value[CONF_NUMBER]
|
||||
mode = value[CONF_MODE]
|
||||
is_input = mode[CONF_INPUT]
|
||||
is_output = mode[CONF_OUTPUT]
|
||||
is_open_drain = mode[CONF_OPEN_DRAIN]
|
||||
is_pullup = mode[CONF_PULLUP]
|
||||
is_pulldown = mode[CONF_PULLDOWN]
|
||||
|
||||
if is_input:
|
||||
# All ESP32 pins support input mode
|
||||
pass
|
||||
if is_output and 34 <= num <= 39:
|
||||
raise cv.Invalid(
|
||||
f"GPIO{num} (34-39) does not support output pin mode.",
|
||||
[CONF_MODE, CONF_OUTPUT],
|
||||
)
|
||||
if is_open_drain and not is_output:
|
||||
raise cv.Invalid(
|
||||
"Open-drain only works with output mode", [CONF_MODE, CONF_OPEN_DRAIN]
|
||||
)
|
||||
if is_pullup and 34 <= num <= 39:
|
||||
raise cv.Invalid(
|
||||
f"GPIO{num} (34-39) does not support pullups.", [CONF_MODE, CONF_PULLUP]
|
||||
)
|
||||
if is_pulldown and 34 <= num <= 39:
|
||||
raise cv.Invalid(
|
||||
f"GPIO{num} (34-39) does not support pulldowns.", [CONF_MODE, CONF_PULLDOWN]
|
||||
)
|
||||
|
||||
if CORE.using_arduino:
|
||||
# (input, output, open_drain, pullup, pulldown)
|
||||
supported_modes = {
|
||||
# INPUT
|
||||
(True, False, False, False, False),
|
||||
# OUTPUT
|
||||
(False, True, False, False, False),
|
||||
# INPUT_PULLUP
|
||||
(True, False, False, True, False),
|
||||
# INPUT_PULLDOWN
|
||||
(True, False, False, False, True),
|
||||
# OUTPUT_OPEN_DRAIN
|
||||
(False, True, True, False, False),
|
||||
}
|
||||
key = (is_input, is_output, is_open_drain, is_pullup, is_pulldown)
|
||||
if key not in supported_modes:
|
||||
raise cv.Invalid(
|
||||
"This pin mode is not supported on ESP32 for arduino frameworks",
|
||||
[CONF_MODE],
|
||||
)
|
||||
|
||||
return value
|
||||
|
||||
|
||||
# https://docs.espressif.com/projects/esp-idf/en/v3.3.5/api-reference/peripherals/gpio.html#_CPPv416gpio_drive_cap_t
|
||||
gpio_drive_cap_t = cg.global_ns.enum("gpio_drive_cap_t")
|
||||
DRIVE_STRENGTHS = {
|
||||
5.0: gpio_drive_cap_t.GPIO_DRIVE_CAP_0,
|
||||
10.0: gpio_drive_cap_t.GPIO_DRIVE_CAP_1,
|
||||
20.0: gpio_drive_cap_t.GPIO_DRIVE_CAP_2,
|
||||
40.0: gpio_drive_cap_t.GPIO_DRIVE_CAP_3,
|
||||
}
|
||||
gpio_num_t = cg.global_ns.enum("gpio_num_t")
|
||||
|
||||
|
||||
def _choose_pin_declaration(value):
|
||||
if CORE.using_esp_idf:
|
||||
return cv.declare_id(IDFInternalGPIOPin)(value)
|
||||
if CORE.using_arduino:
|
||||
return cv.declare_id(ArduinoInternalGPIOPin)(value)
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
CONF_DRIVE_STRENGTH = "drive_strength"
|
||||
ESP32_PIN_SCHEMA = cv.All(
|
||||
{
|
||||
cv.GenerateID(): _choose_pin_declaration,
|
||||
cv.Required(CONF_NUMBER): validate_gpio_pin,
|
||||
cv.Optional(CONF_MODE, default={}): cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_INPUT, default=False): cv.boolean,
|
||||
cv.Optional(CONF_OUTPUT, default=False): cv.boolean,
|
||||
cv.Optional(CONF_OPEN_DRAIN, default=False): cv.boolean,
|
||||
cv.Optional(CONF_PULLUP, default=False): cv.boolean,
|
||||
cv.Optional(CONF_PULLDOWN, default=False): cv.boolean,
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_INVERTED, default=False): cv.boolean,
|
||||
cv.SplitDefault(CONF_DRIVE_STRENGTH, esp32_idf="20mA"): cv.All(
|
||||
cv.only_with_esp_idf,
|
||||
cv.float_with_unit("current", "mA", optional_unit=True),
|
||||
cv.enum(DRIVE_STRENGTHS),
|
||||
),
|
||||
},
|
||||
validate_supports,
|
||||
)
|
||||
|
||||
|
||||
@pins.PIN_SCHEMA_REGISTRY.register("esp32", ESP32_PIN_SCHEMA)
|
||||
async def esp32_pin_to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
num = config[CONF_NUMBER]
|
||||
if CORE.using_esp_idf:
|
||||
cg.add(var.set_pin(getattr(gpio_num_t, f"GPIO_NUM_{num}")))
|
||||
else:
|
||||
cg.add(var.set_pin(num))
|
||||
cg.add(var.set_inverted(config[CONF_INVERTED]))
|
||||
if CONF_DRIVE_STRENGTH in config:
|
||||
cg.add(var.set_drive_strength(config[CONF_DRIVE_STRENGTH]))
|
||||
cg.add(var.set_flags(pins.gpio_flags_expr(config[CONF_MODE])))
|
||||
return var
|
107
esphome/components/esp32/gpio_arduino.cpp
Normal file
107
esphome/components/esp32/gpio_arduino.cpp
Normal file
@ -0,0 +1,107 @@
|
||||
#ifdef USE_ESP32_FRAMEWORK_ARDUINO
|
||||
|
||||
#include "gpio_arduino.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include <esp32-hal-gpio.h>
|
||||
|
||||
namespace esphome {
|
||||
namespace esp32 {
|
||||
|
||||
static const char *const TAG = "esp32";
|
||||
|
||||
struct ISRPinArg {
|
||||
uint8_t pin;
|
||||
bool inverted;
|
||||
};
|
||||
|
||||
ISRInternalGPIOPin ArduinoInternalGPIOPin::to_isr() const {
|
||||
auto *arg = new ISRPinArg{}; // NOLINT(cppcoreguidelines-owning-memory)
|
||||
arg->pin = pin_;
|
||||
arg->inverted = inverted_;
|
||||
return ISRInternalGPIOPin((void *) arg);
|
||||
}
|
||||
|
||||
void ArduinoInternalGPIOPin::attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const {
|
||||
uint8_t arduino_mode = DISABLED;
|
||||
switch (type) {
|
||||
case gpio::INTERRUPT_RISING_EDGE:
|
||||
arduino_mode = inverted_ ? FALLING : RISING;
|
||||
break;
|
||||
case gpio::INTERRUPT_FALLING_EDGE:
|
||||
arduino_mode = inverted_ ? RISING : FALLING;
|
||||
break;
|
||||
case gpio::INTERRUPT_ANY_EDGE:
|
||||
arduino_mode = CHANGE;
|
||||
break;
|
||||
case gpio::INTERRUPT_LOW_LEVEL:
|
||||
arduino_mode = inverted_ ? ONHIGH : ONLOW;
|
||||
break;
|
||||
case gpio::INTERRUPT_HIGH_LEVEL:
|
||||
arduino_mode = inverted_ ? ONLOW : ONHIGH;
|
||||
break;
|
||||
}
|
||||
|
||||
attachInterruptArg(pin_, func, arg, arduino_mode);
|
||||
}
|
||||
void ArduinoInternalGPIOPin::pin_mode(gpio::Flags flags) {
|
||||
uint8_t mode;
|
||||
if (flags == gpio::FLAG_INPUT) {
|
||||
mode = INPUT;
|
||||
} else if (flags == gpio::FLAG_OUTPUT) {
|
||||
mode = OUTPUT;
|
||||
} else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLUP)) {
|
||||
mode = INPUT_PULLUP;
|
||||
} else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLDOWN)) {
|
||||
mode = INPUT_PULLDOWN;
|
||||
} else if (flags == (gpio::FLAG_OUTPUT | gpio::FLAG_OPEN_DRAIN)) {
|
||||
mode = OUTPUT_OPEN_DRAIN;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
pinMode(pin_, mode); // NOLINT
|
||||
}
|
||||
|
||||
std::string ArduinoInternalGPIOPin::dump_summary() const {
|
||||
char buffer[32];
|
||||
snprintf(buffer, sizeof(buffer), "GPIO%u", pin_);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
bool ArduinoInternalGPIOPin::digital_read() {
|
||||
return bool(digitalRead(pin_)) != inverted_; // NOLINT
|
||||
}
|
||||
void ArduinoInternalGPIOPin::digital_write(bool value) {
|
||||
digitalWrite(pin_, value != inverted_ ? 1 : 0); // NOLINT
|
||||
}
|
||||
void ArduinoInternalGPIOPin::detach_interrupt() const {
|
||||
detachInterrupt(pin_); // NOLINT
|
||||
}
|
||||
|
||||
} // namespace esp32
|
||||
|
||||
using namespace esp32;
|
||||
|
||||
bool IRAM_ATTR ISRInternalGPIOPin::digital_read() {
|
||||
auto *arg = reinterpret_cast<ISRPinArg *>(arg_);
|
||||
return bool(digitalRead(arg->pin)) != arg->inverted; // NOLINT
|
||||
}
|
||||
void IRAM_ATTR ISRInternalGPIOPin::digital_write(bool value) {
|
||||
auto *arg = reinterpret_cast<ISRPinArg *>(arg_);
|
||||
digitalWrite(arg->pin, value != arg->inverted ? 1 : 0); // NOLINT
|
||||
}
|
||||
void IRAM_ATTR ISRInternalGPIOPin::clear_interrupt() {
|
||||
auto *arg = reinterpret_cast<ISRPinArg *>(arg_);
|
||||
#ifdef CONFIG_IDF_TARGET_ESP32C3
|
||||
GPIO.status_w1tc.val = 1UL << arg->pin;
|
||||
#else
|
||||
if (arg->pin < 32) {
|
||||
GPIO.status_w1tc = 1UL << arg->pin;
|
||||
} else {
|
||||
GPIO.status1_w1tc.intr_st = 1UL << (arg->pin - 32);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ESP32_FRAMEWORK_ARDUINO
|
36
esphome/components/esp32/gpio_arduino.h
Normal file
36
esphome/components/esp32/gpio_arduino.h
Normal file
@ -0,0 +1,36 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef USE_ESP32_FRAMEWORK_ARDUINO
|
||||
#include "esphome/core/hal.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace esp32 {
|
||||
|
||||
class ArduinoInternalGPIOPin : public InternalGPIOPin {
|
||||
public:
|
||||
void set_pin(uint8_t pin) { pin_ = pin; }
|
||||
void set_inverted(bool inverted) { inverted_ = inverted; }
|
||||
void set_flags(gpio::Flags flags) { flags_ = flags; }
|
||||
|
||||
void setup() override { pin_mode(flags_); }
|
||||
void pin_mode(gpio::Flags flags) override;
|
||||
bool digital_read() override;
|
||||
void digital_write(bool value) override;
|
||||
std::string dump_summary() const override;
|
||||
void detach_interrupt() const override;
|
||||
ISRInternalGPIOPin to_isr() const override;
|
||||
uint8_t get_pin() const override { return pin_; }
|
||||
bool is_inverted() const override { return inverted_; }
|
||||
|
||||
protected:
|
||||
void attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const override;
|
||||
|
||||
uint8_t pin_;
|
||||
bool inverted_;
|
||||
gpio::Flags flags_;
|
||||
};
|
||||
|
||||
} // namespace esp32
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ESP32_FRAMEWORK_ARDUINO
|
120
esphome/components/esp32/gpio_idf.cpp
Normal file
120
esphome/components/esp32/gpio_idf.cpp
Normal file
@ -0,0 +1,120 @@
|
||||
#ifdef USE_ESP32_FRAMEWORK_ESP_IDF
|
||||
|
||||
#include "gpio_idf.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace esp32 {
|
||||
|
||||
static const char *const TAG = "esp32";
|
||||
|
||||
bool IDFInternalGPIOPin::isr_service_installed = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
||||
struct ISRPinArg {
|
||||
gpio_num_t pin;
|
||||
bool inverted;
|
||||
};
|
||||
|
||||
ISRInternalGPIOPin IDFInternalGPIOPin::to_isr() const {
|
||||
auto *arg = new ISRPinArg{}; // NOLINT(cppcoreguidelines-owning-memory)
|
||||
arg->pin = pin_;
|
||||
arg->inverted = inverted_;
|
||||
return ISRInternalGPIOPin((void *) arg);
|
||||
}
|
||||
|
||||
void IDFInternalGPIOPin::setup() {
|
||||
pin_mode(flags_);
|
||||
gpio_set_drive_capability(pin_, drive_strength_);
|
||||
}
|
||||
|
||||
void IDFInternalGPIOPin::pin_mode(gpio::Flags flags) {
|
||||
gpio_config_t conf{};
|
||||
conf.pin_bit_mask = 1ULL << static_cast<uint32_t>(pin_);
|
||||
conf.mode = flags_to_mode(flags);
|
||||
conf.pull_up_en = flags & gpio::FLAG_PULLUP ? GPIO_PULLUP_ENABLE : GPIO_PULLUP_DISABLE;
|
||||
conf.pull_down_en = flags & gpio::FLAG_PULLDOWN ? GPIO_PULLDOWN_ENABLE : GPIO_PULLDOWN_DISABLE;
|
||||
conf.intr_type = GPIO_INTR_DISABLE;
|
||||
gpio_config(&conf);
|
||||
}
|
||||
|
||||
bool IDFInternalGPIOPin::digital_read() { return bool(gpio_get_level(pin_)) != inverted_; }
|
||||
|
||||
void IDFInternalGPIOPin::digital_write(bool value) { gpio_set_level(pin_, value != inverted_ ? 1 : 0); }
|
||||
|
||||
gpio_mode_t IDFInternalGPIOPin::flags_to_mode(gpio::Flags flags) {
|
||||
flags = (gpio::Flags)(flags & ~(gpio::FLAG_PULLUP | gpio::FLAG_PULLDOWN));
|
||||
if (flags == gpio::FLAG_NONE) {
|
||||
return GPIO_MODE_DISABLE;
|
||||
} else if (flags == gpio::FLAG_INPUT) {
|
||||
return GPIO_MODE_INPUT;
|
||||
} else if (flags == gpio::FLAG_OUTPUT) {
|
||||
return GPIO_MODE_OUTPUT;
|
||||
} else if (flags == (gpio::FLAG_OUTPUT | gpio::FLAG_OPEN_DRAIN)) {
|
||||
return GPIO_MODE_OUTPUT_OD;
|
||||
} else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_OUTPUT | gpio::FLAG_OPEN_DRAIN)) {
|
||||
return GPIO_MODE_INPUT_OUTPUT_OD;
|
||||
} else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_OUTPUT)) {
|
||||
return GPIO_MODE_INPUT_OUTPUT;
|
||||
} else {
|
||||
// unsupported
|
||||
return GPIO_MODE_DISABLE;
|
||||
}
|
||||
}
|
||||
|
||||
void IDFInternalGPIOPin::attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const {
|
||||
gpio_int_type_t idf_type = GPIO_INTR_ANYEDGE;
|
||||
switch (type) {
|
||||
case gpio::INTERRUPT_RISING_EDGE:
|
||||
idf_type = inverted_ ? GPIO_INTR_NEGEDGE : GPIO_INTR_POSEDGE;
|
||||
break;
|
||||
case gpio::INTERRUPT_FALLING_EDGE:
|
||||
idf_type = inverted_ ? GPIO_INTR_POSEDGE : GPIO_INTR_NEGEDGE;
|
||||
break;
|
||||
case gpio::INTERRUPT_ANY_EDGE:
|
||||
idf_type = GPIO_INTR_ANYEDGE;
|
||||
break;
|
||||
case gpio::INTERRUPT_LOW_LEVEL:
|
||||
idf_type = inverted_ ? GPIO_INTR_HIGH_LEVEL : GPIO_INTR_LOW_LEVEL;
|
||||
break;
|
||||
case gpio::INTERRUPT_HIGH_LEVEL:
|
||||
idf_type = inverted_ ? GPIO_INTR_LOW_LEVEL : GPIO_INTR_HIGH_LEVEL;
|
||||
break;
|
||||
}
|
||||
gpio_set_intr_type(pin_, idf_type);
|
||||
gpio_intr_enable(pin_);
|
||||
if (!isr_service_installed) {
|
||||
auto res = gpio_install_isr_service(ESP_INTR_FLAG_LEVEL3);
|
||||
if (res != ESP_OK) {
|
||||
ESP_LOGE(TAG, "attach_interrupt(): call to gpio_install_isr_service() failed, error code: %d", res);
|
||||
return;
|
||||
}
|
||||
isr_service_installed = true;
|
||||
}
|
||||
gpio_isr_handler_add(pin_, func, arg);
|
||||
}
|
||||
|
||||
std::string IDFInternalGPIOPin::dump_summary() const {
|
||||
char buffer[32];
|
||||
snprintf(buffer, sizeof(buffer), "GPIO%u", static_cast<uint32_t>(pin_));
|
||||
return buffer;
|
||||
}
|
||||
|
||||
} // namespace esp32
|
||||
|
||||
using namespace esp32;
|
||||
|
||||
bool IRAM_ATTR ISRInternalGPIOPin::digital_read() {
|
||||
auto *arg = reinterpret_cast<ISRPinArg *>(arg_);
|
||||
return bool(gpio_get_level(arg->pin)) != arg->inverted;
|
||||
}
|
||||
void IRAM_ATTR ISRInternalGPIOPin::digital_write(bool value) {
|
||||
auto *arg = reinterpret_cast<ISRPinArg *>(arg_);
|
||||
gpio_set_level(arg->pin, value != arg->inverted ? 1 : 0);
|
||||
}
|
||||
void IRAM_ATTR ISRInternalGPIOPin::clear_interrupt() {
|
||||
// not supported
|
||||
}
|
||||
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ESP32_FRAMEWORK_ESP_IDF
|
41
esphome/components/esp32/gpio_idf.h
Normal file
41
esphome/components/esp32/gpio_idf.h
Normal file
@ -0,0 +1,41 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef USE_ESP32_FRAMEWORK_ESP_IDF
|
||||
#include "esphome/core/hal.h"
|
||||
#include <driver/gpio.h>
|
||||
|
||||
namespace esphome {
|
||||
namespace esp32 {
|
||||
|
||||
class IDFInternalGPIOPin : public InternalGPIOPin {
|
||||
public:
|
||||
void set_pin(gpio_num_t pin) { pin_ = pin; }
|
||||
void set_inverted(bool inverted) { inverted_ = inverted; }
|
||||
void set_drive_strength(gpio_drive_cap_t drive_strength) { drive_strength_ = drive_strength; }
|
||||
void set_flags(gpio::Flags flags) { flags_ = flags; }
|
||||
void setup() override;
|
||||
void pin_mode(gpio::Flags flags) override;
|
||||
bool digital_read() override;
|
||||
void digital_write(bool value) override;
|
||||
std::string dump_summary() const override;
|
||||
void detach_interrupt() const override { gpio_intr_disable(pin_); }
|
||||
ISRInternalGPIOPin to_isr() const override;
|
||||
uint8_t get_pin() const override { return (uint8_t) pin_; }
|
||||
bool is_inverted() const override { return inverted_; }
|
||||
|
||||
protected:
|
||||
static gpio_mode_t flags_to_mode(gpio::Flags flags);
|
||||
void attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const override;
|
||||
|
||||
gpio_num_t pin_;
|
||||
bool inverted_;
|
||||
gpio_drive_cap_t drive_strength_;
|
||||
gpio::Flags flags_;
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
static bool isr_service_installed;
|
||||
};
|
||||
|
||||
} // namespace esp32
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ESP32_FRAMEWORK_ESP_IDF
|
153
esphome/components/esp32/preferences.cpp
Normal file
153
esphome/components/esp32/preferences.cpp
Normal file
@ -0,0 +1,153 @@
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include "esphome/core/preferences.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include <nvs_flash.h>
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
namespace esphome {
|
||||
namespace esp32 {
|
||||
|
||||
static const char *const TAG = "esp32.preferences";
|
||||
|
||||
struct NVSData {
|
||||
std::string key;
|
||||
std::vector<uint8_t> data;
|
||||
};
|
||||
|
||||
static std::vector<NVSData> s_pending_save; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
||||
class ESP32PreferenceBackend : public ESPPreferenceBackend {
|
||||
public:
|
||||
std::string key;
|
||||
uint32_t nvs_handle;
|
||||
bool save(const uint8_t *data, size_t len) override {
|
||||
// try find in pending saves and update that
|
||||
for (auto &obj : s_pending_save) {
|
||||
if (obj.key == key) {
|
||||
obj.data.assign(data, data + len);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
NVSData save{};
|
||||
save.key = key;
|
||||
save.data.assign(data, data + len);
|
||||
s_pending_save.emplace_back(save);
|
||||
return true;
|
||||
}
|
||||
bool load(uint8_t *data, size_t len) override {
|
||||
// try find in pending saves and load from that
|
||||
for (auto &obj : s_pending_save) {
|
||||
if (obj.key == key) {
|
||||
if (obj.data.size() != len) {
|
||||
// size mismatch
|
||||
return false;
|
||||
}
|
||||
memcpy(data, obj.data.data(), len);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
size_t actual_len;
|
||||
esp_err_t err = nvs_get_blob(nvs_handle, key.c_str(), nullptr, &actual_len);
|
||||
if (err != 0) {
|
||||
ESP_LOGV(TAG, "nvs_get_blob('%s'): %s - the key might not be set yet", key.c_str(), esp_err_to_name(err));
|
||||
return false;
|
||||
}
|
||||
if (actual_len != len) {
|
||||
ESP_LOGVV(TAG, "NVS length does not match (%u!=%u)", actual_len, len);
|
||||
return false;
|
||||
}
|
||||
err = nvs_get_blob(nvs_handle, key.c_str(), data, &len);
|
||||
if (err != 0) {
|
||||
ESP_LOGV(TAG, "nvs_get_blob('%s') failed: %s", key.c_str(), esp_err_to_name(err));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
class ESP32Preferences : public ESPPreferences {
|
||||
public:
|
||||
uint32_t nvs_handle;
|
||||
uint32_t current_offset = 0;
|
||||
|
||||
void open() {
|
||||
esp_err_t err = nvs_open("esphome", NVS_READWRITE, &nvs_handle);
|
||||
if (err == 0)
|
||||
return;
|
||||
|
||||
ESP_LOGW(TAG, "nvs_open failed: %s - erasing NVS...", esp_err_to_name(err));
|
||||
nvs_flash_deinit();
|
||||
nvs_flash_erase();
|
||||
nvs_flash_init();
|
||||
|
||||
err = nvs_open("esphome", NVS_READWRITE, &nvs_handle);
|
||||
if (err != 0) {
|
||||
nvs_handle = 0;
|
||||
}
|
||||
}
|
||||
ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash) override {
|
||||
return make_preference(length, type);
|
||||
}
|
||||
ESPPreferenceObject make_preference(size_t length, uint32_t type) override {
|
||||
auto *pref = new ESP32PreferenceBackend(); // NOLINT(cppcoreguidelines-owning-memory)
|
||||
pref->nvs_handle = nvs_handle;
|
||||
current_offset += length;
|
||||
|
||||
uint32_t keyval = current_offset ^ type;
|
||||
char keybuf[16];
|
||||
snprintf(keybuf, sizeof(keybuf), "%d", keyval);
|
||||
pref->key = keybuf; // copied to std::string
|
||||
|
||||
return ESPPreferenceObject(pref);
|
||||
}
|
||||
|
||||
bool sync() override {
|
||||
if (s_pending_save.empty())
|
||||
return true;
|
||||
|
||||
ESP_LOGD(TAG, "Saving preferences to flash...");
|
||||
// goal try write all pending saves even if one fails
|
||||
bool any_failed = false;
|
||||
|
||||
// go through vector from back to front (makes erase easier/more efficient)
|
||||
for (ssize_t i = s_pending_save.size() - 1; i >= 0; i--) {
|
||||
const auto &save = s_pending_save[i];
|
||||
esp_err_t err = nvs_set_blob(nvs_handle, save.key.c_str(), save.data.data(), save.data.size());
|
||||
if (err != 0) {
|
||||
ESP_LOGV(TAG, "nvs_set_blob('%s', len=%u) failed: %s", save.key.c_str(), save.data.size(),
|
||||
esp_err_to_name(err));
|
||||
any_failed = true;
|
||||
continue;
|
||||
}
|
||||
s_pending_save.erase(s_pending_save.begin() + i);
|
||||
}
|
||||
|
||||
// note: commit on esp-idf currently is a no-op, nvs_set_blob always writes
|
||||
esp_err_t err = nvs_commit(nvs_handle);
|
||||
if (err != 0) {
|
||||
ESP_LOGV(TAG, "nvs_commit() failed: %s", esp_err_to_name(err));
|
||||
return false;
|
||||
}
|
||||
|
||||
return !any_failed;
|
||||
}
|
||||
};
|
||||
|
||||
void setup_preferences() {
|
||||
auto *prefs = new ESP32Preferences(); // NOLINT(cppcoreguidelines-owning-memory)
|
||||
prefs->open();
|
||||
global_preferences = prefs;
|
||||
}
|
||||
|
||||
} // namespace esp32
|
||||
|
||||
ESPPreferences *global_preferences; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ESP32
|
12
esphome/components/esp32/preferences.h
Normal file
12
esphome/components/esp32/preferences.h
Normal file
@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
#ifdef USE_ESP32
|
||||
|
||||
namespace esphome {
|
||||
namespace esp32 {
|
||||
|
||||
void setup_preferences();
|
||||
|
||||
} // namespace esp32
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ESP32
|
@ -1,8 +1,10 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ID, ESP_PLATFORM_ESP32
|
||||
from esphome.const import CONF_ID
|
||||
from esphome.core import CORE
|
||||
from esphome.components.esp32 import add_idf_sdkconfig_option
|
||||
|
||||
ESP_PLATFORMS = [ESP_PLATFORM_ESP32]
|
||||
DEPENDENCIES = ["esp32"]
|
||||
CODEOWNERS = ["@jesserockz"]
|
||||
CONFLICTS_WITH = ["esp32_ble_tracker", "esp32_ble_beacon"]
|
||||
|
||||
@ -20,3 +22,6 @@ CONFIG_SCHEMA = cv.Schema(
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
|
||||
if CORE.using_esp_idf:
|
||||
add_idf_sdkconfig_option("CONFIG_BT_ENABLED", True)
|
||||
|
@ -1,17 +1,21 @@
|
||||
#include "ble.h"
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include "ble.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
|
||||
#include <nvs_flash.h>
|
||||
#include <freertos/FreeRTOSConfig.h>
|
||||
#include <esp_bt_main.h>
|
||||
#include <esp_bt.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/task.h>
|
||||
#include <esp_gap_ble_api.h>
|
||||
|
||||
#ifdef USE_ARDUINO
|
||||
#include <esp32-hal-bt.h>
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
namespace esp32_ble {
|
||||
|
||||
@ -27,7 +31,7 @@ void ESP32BLE::setup() {
|
||||
return;
|
||||
}
|
||||
|
||||
this->advertising_ = new BLEAdvertising();
|
||||
this->advertising_ = new BLEAdvertising(); // NOLINT(cppcoreguidelines-owning-memory)
|
||||
|
||||
this->advertising_->set_scan_response(true);
|
||||
this->advertising_->set_min_preferred_interval(0x06);
|
||||
@ -52,10 +56,37 @@ bool ESP32BLE::ble_setup_() {
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef USE_ARDUINO
|
||||
if (!btStart()) {
|
||||
ESP_LOGE(TAG, "btStart failed: %d", esp_bt_controller_get_status());
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
if (esp_bt_controller_get_status() != ESP_BT_CONTROLLER_STATUS_ENABLED) {
|
||||
// start bt controller
|
||||
if (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_IDLE) {
|
||||
esp_bt_controller_config_t cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
|
||||
err = esp_bt_controller_init(&cfg);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "esp_bt_controller_init failed: %s", esp_err_to_name(err));
|
||||
return false;
|
||||
}
|
||||
while (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_IDLE)
|
||||
;
|
||||
}
|
||||
if (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_INITED) {
|
||||
err = esp_bt_controller_enable(ESP_BT_MODE_BLE);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "esp_bt_controller_enable failed: %s", esp_err_to_name(err));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (esp_bt_controller_get_status() != ESP_BT_CONTROLLER_STATUS_ENABLED) {
|
||||
ESP_LOGE(TAG, "esp bt controller enable failed");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT);
|
||||
|
||||
@ -123,25 +154,25 @@ void ESP32BLE::loop() {
|
||||
BLEEvent *ble_event = this->ble_events_.pop();
|
||||
while (ble_event != nullptr) {
|
||||
switch (ble_event->type_) {
|
||||
case ble_event->GATTS:
|
||||
case BLEEvent::GATTS:
|
||||
this->real_gatts_event_handler_(ble_event->event_.gatts.gatts_event, ble_event->event_.gatts.gatts_if,
|
||||
&ble_event->event_.gatts.gatts_param);
|
||||
break;
|
||||
case ble_event->GAP:
|
||||
case BLEEvent::GAP:
|
||||
this->real_gap_event_handler_(ble_event->event_.gap.gap_event, &ble_event->event_.gap.gap_param);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
delete ble_event;
|
||||
delete ble_event; // NOLINT(cppcoreguidelines-owning-memory)
|
||||
ble_event = this->ble_events_.pop();
|
||||
}
|
||||
}
|
||||
|
||||
void ESP32BLE::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) {
|
||||
BLEEvent *new_event = new BLEEvent(event, param);
|
||||
BLEEvent *new_event = new BLEEvent(event, param); // NOLINT(cppcoreguidelines-owning-memory)
|
||||
global_ble->ble_events_.push(new_event);
|
||||
}
|
||||
} // NOLINT(clang-analyzer-cplusplus.NewDeleteLeaks)
|
||||
|
||||
void ESP32BLE::real_gap_event_handler_(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) {
|
||||
ESP_LOGV(TAG, "(BLE) gap_event_handler - %d", event);
|
||||
@ -153,9 +184,9 @@ void ESP32BLE::real_gap_event_handler_(esp_gap_ble_cb_event_t event, esp_ble_gap
|
||||
|
||||
void ESP32BLE::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if,
|
||||
esp_ble_gatts_cb_param_t *param) {
|
||||
BLEEvent *new_event = new BLEEvent(event, gatts_if, param);
|
||||
BLEEvent *new_event = new BLEEvent(event, gatts_if, param); // NOLINT(cppcoreguidelines-owning-memory)
|
||||
global_ble->ble_events_.push(new_event);
|
||||
}
|
||||
} // NOLINT(clang-analyzer-cplusplus.NewDeleteLeaks)
|
||||
|
||||
void ESP32BLE::real_gatts_event_handler_(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if,
|
||||
esp_ble_gatts_cb_param_t *param) {
|
||||
@ -174,7 +205,7 @@ float ESP32BLE::get_setup_priority() const { return setup_priority::BLUETOOTH; }
|
||||
|
||||
void ESP32BLE::dump_config() { ESP_LOGCONFIG(TAG, "ESP32 BLE:"); }
|
||||
|
||||
ESP32BLE *global_ble = nullptr;
|
||||
ESP32BLE *global_ble = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
||||
} // namespace esp32_ble
|
||||
} // namespace esphome
|
||||
|
@ -11,7 +11,7 @@
|
||||
#include "esphome/components/esp32_ble_server/ble_server.h"
|
||||
#endif
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include <esp_gap_ble_api.h>
|
||||
#include <esp_gatts_api.h>
|
||||
@ -19,6 +19,7 @@
|
||||
namespace esphome {
|
||||
namespace esp32_ble {
|
||||
|
||||
// NOLINTNEXTLINE(modernize-use-using)
|
||||
typedef struct {
|
||||
void *peer_device;
|
||||
bool connected;
|
||||
@ -65,6 +66,7 @@ class ESP32BLE : public Component {
|
||||
BLEAdvertising *advertising_;
|
||||
};
|
||||
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
extern ESP32BLE *global_ble;
|
||||
|
||||
} // namespace esp32_ble
|
||||
|
@ -1,8 +1,11 @@
|
||||
#include "ble_advertising.h"
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include "ble_uuid.h"
|
||||
#include <cstring>
|
||||
#include <cstdio>
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace esp32_ble {
|
||||
@ -45,6 +48,7 @@ void BLEAdvertising::start() {
|
||||
this->advertising_data_.service_uuid_len = 0;
|
||||
} else {
|
||||
this->advertising_data_.service_uuid_len = 16 * num_services;
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-owning-memory)
|
||||
this->advertising_data_.p_service_uuid = new uint8_t[this->advertising_data_.service_uuid_len];
|
||||
uint8_t *p = this->advertising_data_.p_service_uuid;
|
||||
for (int i = 0; i < num_services; i++) {
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
#include <vector>
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include <esp_gap_ble_api.h>
|
||||
#include <esp_gatts_api.h>
|
||||
|
@ -1,6 +1,10 @@
|
||||
#include "ble_uuid.h"
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include <cstring>
|
||||
#include <cstdio>
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace esp32_ble {
|
||||
@ -33,28 +37,28 @@ ESPBTUUID ESPBTUUID::from_raw(const std::string &data) {
|
||||
ret.uuid_.len = ESP_UUID_LEN_16;
|
||||
ret.uuid_.uuid.uuid16 = 0;
|
||||
for (int i = 0; i < data.length();) {
|
||||
uint8_t MSB = data.c_str()[i];
|
||||
uint8_t LSB = data.c_str()[i + 1];
|
||||
uint8_t msb = data.c_str()[i];
|
||||
uint8_t lsb = data.c_str()[i + 1];
|
||||
|
||||
if (MSB > '9')
|
||||
MSB -= 7;
|
||||
if (LSB > '9')
|
||||
LSB -= 7;
|
||||
ret.uuid_.uuid.uuid16 += (((MSB & 0x0F) << 4) | (LSB & 0x0F)) << (2 - i) * 4;
|
||||
if (msb > '9')
|
||||
msb -= 7;
|
||||
if (lsb > '9')
|
||||
lsb -= 7;
|
||||
ret.uuid_.uuid.uuid16 += (((msb & 0x0F) << 4) | (lsb & 0x0F)) << (2 - i) * 4;
|
||||
i += 2;
|
||||
}
|
||||
} else if (data.length() == 8) {
|
||||
ret.uuid_.len = ESP_UUID_LEN_32;
|
||||
ret.uuid_.uuid.uuid32 = 0;
|
||||
for (int i = 0; i < data.length();) {
|
||||
uint8_t MSB = data.c_str()[i];
|
||||
uint8_t LSB = data.c_str()[i + 1];
|
||||
uint8_t msb = data.c_str()[i];
|
||||
uint8_t lsb = data.c_str()[i + 1];
|
||||
|
||||
if (MSB > '9')
|
||||
MSB -= 7;
|
||||
if (LSB > '9')
|
||||
LSB -= 7;
|
||||
ret.uuid_.uuid.uuid32 += (((MSB & 0x0F) << 4) | (LSB & 0x0F)) << (6 - i) * 4;
|
||||
if (msb > '9')
|
||||
msb -= 7;
|
||||
if (lsb > '9')
|
||||
lsb -= 7;
|
||||
ret.uuid_.uuid.uuid32 += (((msb & 0x0F) << 4) | (lsb & 0x0F)) << (6 - i) * 4;
|
||||
i += 2;
|
||||
}
|
||||
} else if (data.length() == 16) { // how we can have 16 byte length string reprezenting 128 bit uuid??? needs to be
|
||||
@ -69,14 +73,14 @@ ESPBTUUID ESPBTUUID::from_raw(const std::string &data) {
|
||||
for (int i = 0; i < data.length();) {
|
||||
if (data.c_str()[i] == '-')
|
||||
i++;
|
||||
uint8_t MSB = data.c_str()[i];
|
||||
uint8_t LSB = data.c_str()[i + 1];
|
||||
uint8_t msb = data.c_str()[i];
|
||||
uint8_t lsb = data.c_str()[i + 1];
|
||||
|
||||
if (MSB > '9')
|
||||
MSB -= 7;
|
||||
if (LSB > '9')
|
||||
LSB -= 7;
|
||||
ret.uuid_.uuid.uuid128[15 - n++] = ((MSB & 0x0F) << 4) | (LSB & 0x0F);
|
||||
if (msb > '9')
|
||||
msb -= 7;
|
||||
if (lsb > '9')
|
||||
lsb -= 7;
|
||||
ret.uuid_.uuid.uuid128[15 - n++] = ((msb & 0x0F) << 4) | (lsb & 0x0F);
|
||||
i += 2;
|
||||
}
|
||||
} else {
|
||||
|
@ -1,8 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/hal.h"
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include <string>
|
||||
#include <esp_bt_defs.h>
|
||||
|
@ -1,15 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
#include <queue>
|
||||
#include <mutex>
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#include <cstring>
|
||||
|
||||
#include <esp_gap_ble_api.h>
|
||||
#include <esp_gatts_api.h>
|
||||
#include <esp_gattc_api.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/semphr.h>
|
||||
|
||||
/*
|
||||
* BLE events come in from a separate Task (thread) in the ESP32 stack. Rather
|
||||
@ -24,33 +28,33 @@ namespace esp32_ble {
|
||||
|
||||
template<class T> class Queue {
|
||||
public:
|
||||
Queue() { m = xSemaphoreCreateMutex(); }
|
||||
Queue() { m_ = xSemaphoreCreateMutex(); }
|
||||
|
||||
void push(T *element) {
|
||||
if (element == nullptr)
|
||||
return;
|
||||
if (xSemaphoreTake(m, 5L / portTICK_PERIOD_MS)) {
|
||||
q.push(element);
|
||||
xSemaphoreGive(m);
|
||||
if (xSemaphoreTake(m_, 5L / portTICK_PERIOD_MS)) {
|
||||
q_.push(element);
|
||||
xSemaphoreGive(m_);
|
||||
}
|
||||
}
|
||||
|
||||
T *pop() {
|
||||
T *element = nullptr;
|
||||
|
||||
if (xSemaphoreTake(m, 5L / portTICK_PERIOD_MS)) {
|
||||
if (!q.empty()) {
|
||||
element = q.front();
|
||||
q.pop();
|
||||
if (xSemaphoreTake(m_, 5L / portTICK_PERIOD_MS)) {
|
||||
if (!q_.empty()) {
|
||||
element = q_.front();
|
||||
q_.pop();
|
||||
}
|
||||
xSemaphoreGive(m);
|
||||
xSemaphoreGive(m_);
|
||||
}
|
||||
return element;
|
||||
}
|
||||
|
||||
protected:
|
||||
std::queue<T *> q;
|
||||
SemaphoreHandle_t m;
|
||||
std::queue<T *> q_;
|
||||
SemaphoreHandle_t m_;
|
||||
};
|
||||
|
||||
// Received GAP, GATTC and GATTS events are only queued, and get processed in the main loop().
|
||||
@ -101,11 +105,13 @@ class BLEEvent {
|
||||
};
|
||||
|
||||
union {
|
||||
// NOLINTNEXTLINE(readability-identifier-naming)
|
||||
struct gap_event {
|
||||
esp_gap_ble_cb_event_t gap_event;
|
||||
esp_ble_gap_cb_param_t gap_param;
|
||||
} gap;
|
||||
|
||||
// NOLINTNEXTLINE(readability-identifier-naming)
|
||||
struct gattc_event {
|
||||
esp_gattc_cb_event_t gattc_event;
|
||||
esp_gatt_if_t gattc_if;
|
||||
@ -113,6 +119,7 @@ class BLEEvent {
|
||||
uint8_t data[64];
|
||||
} gattc;
|
||||
|
||||
// NOLINTNEXTLINE(readability-identifier-naming)
|
||||
struct gatts_event {
|
||||
esp_gatts_cb_event_t gatts_event;
|
||||
esp_gatt_if_t gatts_if;
|
||||
@ -120,6 +127,7 @@ class BLEEvent {
|
||||
uint8_t data[64];
|
||||
} gatts;
|
||||
} event_;
|
||||
// NOLINTNEXTLINE(readability-identifier-naming)
|
||||
enum ble_event_t : uint8_t {
|
||||
GAP,
|
||||
GATTC,
|
||||
|
@ -1,8 +1,10 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ID, CONF_TYPE, CONF_UUID, ESP_PLATFORM_ESP32
|
||||
from esphome.const import CONF_ID, CONF_TYPE, CONF_UUID
|
||||
from esphome.core import CORE
|
||||
from esphome.components.esp32 import add_idf_sdkconfig_option
|
||||
|
||||
ESP_PLATFORMS = [ESP_PLATFORM_ESP32]
|
||||
DEPENDENCIES = ["esp32"]
|
||||
CONFLICTS_WITH = ["esp32_ble_tracker"]
|
||||
|
||||
esp32_ble_beacon_ns = cg.esphome_ns.namespace("esp32_ble_beacon")
|
||||
@ -24,10 +26,11 @@ CONFIG_SCHEMA = cv.Schema(
|
||||
|
||||
async def to_code(config):
|
||||
uuid = config[CONF_UUID].hex
|
||||
uuid_arr = [
|
||||
cg.RawExpression("0x{}".format(uuid[i : i + 2])) for i in range(0, len(uuid), 2)
|
||||
]
|
||||
uuid_arr = [cg.RawExpression(f"0x{uuid[i:i + 2]}") for i in range(0, len(uuid), 2)]
|
||||
var = cg.new_Pvariable(config[CONF_ID], uuid_arr)
|
||||
await cg.register_component(var, config)
|
||||
cg.add(var.set_major(config[CONF_MAJOR]))
|
||||
cg.add(var.set_minor(config[CONF_MINOR]))
|
||||
|
||||
if CORE.using_esp_idf:
|
||||
add_idf_sdkconfig_option("CONFIG_BT_ENABLED", True)
|
||||
|
@ -1,20 +1,27 @@
|
||||
#include "esp32_ble_beacon.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include <nvs_flash.h>
|
||||
#include <freertos/FreeRTOSConfig.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <esp_bt_main.h>
|
||||
#include <esp_bt.h>
|
||||
#include <freertos/task.h>
|
||||
#include <esp_gap_ble_api.h>
|
||||
#include <cstring>
|
||||
#include "esphome/core/hal.h"
|
||||
|
||||
#ifdef USE_ARDUINO
|
||||
#include <esp32-hal-bt.h>
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
namespace esp32_ble_beacon {
|
||||
|
||||
static const char *const TAG = "esp32_ble_beacon";
|
||||
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
static esp_ble_adv_params_t ble_adv_params = {
|
||||
.adv_int_min = 0x20,
|
||||
.adv_int_max = 0x40,
|
||||
@ -28,7 +35,7 @@ static esp_ble_adv_params_t ble_adv_params = {
|
||||
|
||||
#define ENDIAN_CHANGE_U16(x) ((((x) &0xFF00) >> 8) + (((x) &0xFF) << 8))
|
||||
|
||||
static esp_ble_ibeacon_head_t ibeacon_common_head = {
|
||||
static const esp_ble_ibeacon_head_t IBEACON_COMMON_HEAD = {
|
||||
.flags = {0x02, 0x01, 0x06}, .length = 0x1A, .type = 0xFF, .company_id = 0x004C, .beacon_type = 0x1502};
|
||||
|
||||
void ESP32BLEBeacon::dump_config() {
|
||||
@ -58,6 +65,7 @@ void ESP32BLEBeacon::ble_core_task(void *params) {
|
||||
delay(1000); // NOLINT
|
||||
}
|
||||
}
|
||||
|
||||
void ESP32BLEBeacon::ble_setup() {
|
||||
// Initialize non-volatile storage for the bluetooth controller
|
||||
esp_err_t err = nvs_flash_init();
|
||||
@ -66,10 +74,37 @@ void ESP32BLEBeacon::ble_setup() {
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef USE_ARDUINO
|
||||
if (!btStart()) {
|
||||
ESP_LOGE(TAG, "btStart failed: %d", esp_bt_controller_get_status());
|
||||
return;
|
||||
}
|
||||
#else
|
||||
if (esp_bt_controller_get_status() != ESP_BT_CONTROLLER_STATUS_ENABLED) {
|
||||
// start bt controller
|
||||
if (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_IDLE) {
|
||||
esp_bt_controller_config_t cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
|
||||
err = esp_bt_controller_init(&cfg);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "esp_bt_controller_init failed: %s", esp_err_to_name(err));
|
||||
return;
|
||||
}
|
||||
while (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_IDLE)
|
||||
;
|
||||
}
|
||||
if (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_INITED) {
|
||||
err = esp_bt_controller_enable(ESP_BT_MODE_BLE);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "esp_bt_controller_enable failed: %s", esp_err_to_name(err));
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (esp_bt_controller_get_status() != ESP_BT_CONTROLLER_STATUS_ENABLED) {
|
||||
ESP_LOGE(TAG, "esp bt controller enable failed");
|
||||
return;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT);
|
||||
|
||||
@ -90,7 +125,7 @@ void ESP32BLEBeacon::ble_setup() {
|
||||
}
|
||||
|
||||
esp_ble_ibeacon_t ibeacon_adv_data;
|
||||
memcpy(&ibeacon_adv_data.ibeacon_head, &ibeacon_common_head, sizeof(esp_ble_ibeacon_head_t));
|
||||
memcpy(&ibeacon_adv_data.ibeacon_head, &IBEACON_COMMON_HEAD, sizeof(esp_ble_ibeacon_head_t));
|
||||
memcpy(&ibeacon_adv_data.ibeacon_vendor.proximity_uuid, global_esp32_ble_beacon->uuid_.data(),
|
||||
sizeof(ibeacon_adv_data.ibeacon_vendor.proximity_uuid));
|
||||
ibeacon_adv_data.ibeacon_vendor.minor = ENDIAN_CHANGE_U16(global_esp32_ble_beacon->minor_);
|
||||
@ -131,7 +166,7 @@ void ESP32BLEBeacon::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap
|
||||
}
|
||||
}
|
||||
|
||||
ESP32BLEBeacon *global_esp32_ble_beacon = nullptr;
|
||||
ESP32BLEBeacon *global_esp32_ble_beacon = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
||||
} // namespace esp32_ble_beacon
|
||||
} // namespace esphome
|
||||
|
@ -2,13 +2,14 @@
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include <esp_gap_ble_api.h>
|
||||
|
||||
namespace esphome {
|
||||
namespace esp32_ble_beacon {
|
||||
|
||||
// NOLINTNEXTLINE(modernize-use-using)
|
||||
typedef struct {
|
||||
uint8_t flags[3];
|
||||
uint8_t length;
|
||||
@ -17,6 +18,7 @@ typedef struct {
|
||||
uint16_t beacon_type;
|
||||
} __attribute__((packed)) esp_ble_ibeacon_head_t;
|
||||
|
||||
// NOLINTNEXTLINE(modernize-use-using)
|
||||
typedef struct {
|
||||
uint8_t proximity_uuid[16];
|
||||
uint16_t major;
|
||||
@ -24,6 +26,7 @@ typedef struct {
|
||||
uint8_t measured_power;
|
||||
} __attribute__((packed)) esp_ble_ibeacon_vendor_t;
|
||||
|
||||
// NOLINTNEXTLINE(modernize-use-using)
|
||||
typedef struct {
|
||||
esp_ble_ibeacon_head_t ibeacon_head;
|
||||
esp_ble_ibeacon_vendor_t ibeacon_vendor;
|
||||
@ -50,6 +53,7 @@ class ESP32BLEBeacon : public Component {
|
||||
uint16_t minor_{};
|
||||
};
|
||||
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
extern ESP32BLEBeacon *global_esp32_ble_beacon;
|
||||
|
||||
} // namespace esp32_ble_beacon
|
||||
|
@ -1,12 +1,14 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ID, CONF_MODEL, ESP_PLATFORM_ESP32
|
||||
from esphome.const import CONF_ID, CONF_MODEL
|
||||
from esphome.components import esp32_ble
|
||||
from esphome.core import CORE
|
||||
from esphome.components.esp32 import add_idf_sdkconfig_option
|
||||
|
||||
AUTO_LOAD = ["esp32_ble"]
|
||||
CODEOWNERS = ["@jesserockz"]
|
||||
CONFLICTS_WITH = ["esp32_ble_tracker", "esp32_ble_beacon"]
|
||||
ESP_PLATFORMS = [ESP_PLATFORM_ESP32]
|
||||
DEPENDENCIES = ["esp32"]
|
||||
|
||||
CONF_MANUFACTURER = "manufacturer"
|
||||
CONF_BLE_ID = "ble_id"
|
||||
@ -37,3 +39,6 @@ async def to_code(config):
|
||||
cg.add_define("USE_ESP32_BLE_SERVER")
|
||||
|
||||
cg.add(parent.set_server(var))
|
||||
|
||||
if CORE.using_esp_idf:
|
||||
add_idf_sdkconfig_option("CONFIG_BT_ENABLED", True)
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user