mirror of
https://github.com/esphome/esphome.git
synced 2025-11-01 15:41:52 +00:00
Compare commits
185 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e006045f59 | ||
|
|
fff3645901 | ||
|
|
a5383fd208 | ||
|
|
9ce3a2059f | ||
|
|
69e6cf2c0c | ||
|
|
28635124f9 | ||
|
|
035be87a83 | ||
|
|
e8bdbc45a9 | ||
|
|
429caccefa | ||
|
|
744ca1af7c | ||
|
|
106f0d611f | ||
|
|
d826416684 | ||
|
|
81959804df | ||
|
|
75b524ddc4 | ||
|
|
f599c36272 | ||
|
|
9bb64315f3 | ||
|
|
575badc690 | ||
|
|
4b91cfb7f9 | ||
|
|
a57a842f7b | ||
|
|
a8c253a2a5 | ||
|
|
8b737aabd9 | ||
|
|
0db4815f3d | ||
|
|
139db58a66 | ||
|
|
c32fec7432 | ||
|
|
8bd23dd457 | ||
|
|
97a12c0169 | ||
|
|
635916737b | ||
|
|
65c50e4f01 | ||
|
|
5cf18235e3 | ||
|
|
b80f3fdec9 | ||
|
|
0426be9280 | ||
|
|
7c678659d4 | ||
|
|
13fe9e83fa | ||
|
|
4711f36a1f | ||
|
|
01e2a51132 | ||
|
|
a70a205ace | ||
|
|
33625e2dd3 | ||
|
|
0277218319 | ||
|
|
18a8c727fa | ||
|
|
80ad784a4e | ||
|
|
ebadaa9660 | ||
|
|
7bc51582f0 | ||
|
|
11fb54c74e | ||
|
|
913ac8b7e8 | ||
|
|
2b9350ce76 | ||
|
|
3b18f1b87f | ||
|
|
c5c24c1989 | ||
|
|
c3938d04f3 | ||
|
|
f968713be8 | ||
|
|
7b11284008 | ||
|
|
f39c0d52ee | ||
|
|
a3756a9600 | ||
|
|
afa436fe8f | ||
|
|
48b5ea9e59 | ||
|
|
56974153f1 | ||
|
|
9a2cd71571 | ||
|
|
d1c6368283 | ||
|
|
5c3268b8d4 | ||
|
|
25af5ab7c6 | ||
|
|
4d586b1446 | ||
|
|
bb759d52c8 | ||
|
|
9a2cf05c5f | ||
|
|
c79d700d03 | ||
|
|
482a3aebc9 | ||
|
|
387f249363 | ||
|
|
3d917d0b7e | ||
|
|
824f3187ac | ||
|
|
e3c27a483c | ||
|
|
a33bb32874 | ||
|
|
93d9d4b50a | ||
|
|
2376a2c941 | ||
|
|
b92702a312 | ||
|
|
b11d5f6799 | ||
|
|
072dce340e | ||
|
|
cccb1a2c9e | ||
|
|
063d9c47a4 | ||
|
|
8d8d421286 | ||
|
|
0ce57e5a39 | ||
|
|
aebad04c0b | ||
|
|
514d11d46f | ||
|
|
96e46db272 | ||
|
|
76f78877f6 | ||
|
|
4ffa68b773 | ||
|
|
8eaffee160 | ||
|
|
557a622f71 | ||
|
|
e9c6556296 | ||
|
|
dce9d59dfe | ||
|
|
d3e291b442 | ||
|
|
d4686c0fb1 | ||
|
|
ae9b247f47 | ||
|
|
a59761d292 | ||
|
|
030c87d142 | ||
|
|
95ed3e9d46 | ||
|
|
d0eaebe19f | ||
|
|
9ecead2645 | ||
|
|
98166dfa66 | ||
|
|
5645be4e0f | ||
|
|
9a7a205510 | ||
|
|
7e3b8fd346 | ||
|
|
3d6dcc9eee | ||
|
|
4f6982fbc5 | ||
|
|
00c144daeb | ||
|
|
fddb05c845 | ||
|
|
3b13e62d3f | ||
|
|
9e7acd6f75 | ||
|
|
7d9c043f1d | ||
|
|
bafbeefb37 | ||
|
|
54660300e9 | ||
|
|
5dc40049be | ||
|
|
1e46b4073f | ||
|
|
29fc4af0fc | ||
|
|
4030a2e253 | ||
|
|
ea80cb751b | ||
|
|
6aa61dbce7 | ||
|
|
cdc9c99d40 | ||
|
|
07f2931841 | ||
|
|
616ad04131 | ||
|
|
b966e58f9e | ||
|
|
a546677b08 | ||
|
|
5c3af1d3f6 | ||
|
|
66b0b6feeb | ||
|
|
7665a220a0 | ||
|
|
4250af4dd9 | ||
|
|
73252ccd25 | ||
|
|
33bf78c369 | ||
|
|
f33c2a48eb | ||
|
|
96ded4e402 | ||
|
|
44d5be6e7b | ||
|
|
076124eb71 | ||
|
|
44562dbef1 | ||
|
|
cafdcaec29 | ||
|
|
b660e5a7fa | ||
|
|
3b4ea0ed0a | ||
|
|
403b6e32e3 | ||
|
|
229bf719a2 | ||
|
|
2225594ee8 | ||
|
|
f7232b199a | ||
|
|
ffc6fe9ca0 | ||
|
|
06b8e4df27 | ||
|
|
b103be99e8 | ||
|
|
28ed42d879 | ||
|
|
98f0d75180 | ||
|
|
dd4fb85170 | ||
|
|
07b3327102 | ||
|
|
02aa75f68c | ||
|
|
07db9319ad | ||
|
|
4ae4a4ee88 | ||
|
|
a7c648b60b | ||
|
|
4d7c1ae143 | ||
|
|
cc6d1e85cc | ||
|
|
7fb116d87d | ||
|
|
cc43e01e34 | ||
|
|
6d3ccf4df5 | ||
|
|
bb3d0706d3 | ||
|
|
820dedbcd2 | ||
|
|
aed6f2b1ea | ||
|
|
bf1885af3f | ||
|
|
d8e4f5d56b | ||
|
|
af616473aa | ||
|
|
2033ac34e5 | ||
|
|
5e239d3d88 | ||
|
|
30893afd67 | ||
|
|
a39bb7b92f | ||
|
|
b53f9f2a81 | ||
|
|
586e36906d | ||
|
|
e6f8e73705 | ||
|
|
eaf9735eda | ||
|
|
2e50e1f506 | ||
|
|
76a6c39f25 | ||
|
|
bf01c22e1f | ||
|
|
99f14e03d4 | ||
|
|
d92c8ccadf | ||
|
|
7964b724ed | ||
|
|
e0c5b45694 | ||
|
|
26407e001b | ||
|
|
3ecae3f16f | ||
|
|
5c359856ff | ||
|
|
2d618768d5 | ||
|
|
808e3be324 | ||
|
|
9e23987db8 | ||
|
|
ad76312f66 | ||
|
|
2028362fd5 | ||
|
|
13e0d6b9a1 | ||
|
|
a6255c31fe | ||
|
|
46356cbc4a |
38
.clang-tidy
38
.clang-tidy
@@ -4,14 +4,35 @@ Checks: >-
|
||||
-abseil-*,
|
||||
-android-*,
|
||||
-boost-*,
|
||||
-bugprone-branch-clone,
|
||||
-bugprone-macro-parentheses,
|
||||
-bugprone-narrowing-conversions,
|
||||
-bugprone-reserved-identifier,
|
||||
-bugprone-signed-char-misuse,
|
||||
-bugprone-suspicious-include,
|
||||
-bugprone-too-small-loop-variable,
|
||||
-bugprone-unhandled-self-assignment,
|
||||
-cert-dcl37-c,
|
||||
-cert-dcl50-cpp,
|
||||
-cert-dcl51-cpp,
|
||||
-cert-err58-cpp,
|
||||
-cert-oop54-cpp,
|
||||
-cert-oop57-cpp,
|
||||
-cert-str34-c,
|
||||
-clang-analyzer-core.CallAndMessage,
|
||||
-clang-analyzer-optin.*,
|
||||
-clang-analyzer-osx.*,
|
||||
-clang-analyzer-security.*,
|
||||
-clang-diagnostic-shadow-field,
|
||||
-cppcoreguidelines-avoid-c-arrays,
|
||||
-cppcoreguidelines-avoid-goto,
|
||||
-cppcoreguidelines-avoid-magic-numbers,
|
||||
-cppcoreguidelines-avoid-non-const-global-variables,
|
||||
-cppcoreguidelines-c-copy-assignment-signature,
|
||||
-cppcoreguidelines-init-variables,
|
||||
-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,
|
||||
@@ -37,10 +58,16 @@ Checks: >-
|
||||
-google-runtime-int,
|
||||
-google-runtime-references,
|
||||
-hicpp-*,
|
||||
-llvm-else-after-return,
|
||||
-llvm-header-guard,
|
||||
-llvm-include-order,
|
||||
-llvm-qualified-auto,
|
||||
-llvmlibc-*,
|
||||
-misc-non-private-member-variables-in-classes,
|
||||
-misc-no-recursion,
|
||||
-misc-unconventional-assign-operator,
|
||||
-misc-unused-parameters,
|
||||
-modernize-avoid-c-arrays,
|
||||
-modernize-deprecated-headers,
|
||||
-modernize-pass-by-value,
|
||||
-modernize-pass-by-value,
|
||||
@@ -48,14 +75,25 @@ Checks: >-
|
||||
-modernize-use-auto,
|
||||
-modernize-use-default-member-init,
|
||||
-modernize-use-equals-default,
|
||||
-modernize-use-trailing-return-type,
|
||||
-mpi-*,
|
||||
-objc-*,
|
||||
-performance-unnecessary-value-param,
|
||||
-readability-braces-around-statements,
|
||||
-readability-const-return-type,
|
||||
-readability-convert-member-functions-to-static,
|
||||
-readability-else-after-return,
|
||||
-readability-implicit-bool-conversion,
|
||||
-readability-isolate-declaration,
|
||||
-readability-magic-numbers,
|
||||
-readability-make-member-function-const,
|
||||
-readability-named-parameter,
|
||||
-readability-qualified-auto,
|
||||
-readability-redundant-access-specifiers,
|
||||
-readability-redundant-member-init,
|
||||
-readability-redundant-string-init,
|
||||
-readability-uppercase-literal-suffix,
|
||||
-readability-use-anyofallof,
|
||||
-warnings-as-errors,
|
||||
-zircon-*
|
||||
WarningsAsErrors: '*'
|
||||
|
||||
7
.github/FUNDING.yml
vendored
7
.github/FUNDING.yml
vendored
@@ -1,8 +1,3 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github:
|
||||
patreon: ottowinter
|
||||
open_collective:
|
||||
ko_fi:
|
||||
tidelift:
|
||||
custom: https://esphome.io/guides/supporters.html
|
||||
custom: https://www.nabucasa.com
|
||||
|
||||
16
.github/PULL_REQUEST_TEMPLATE.md
vendored
16
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -1,25 +1,22 @@
|
||||
# What does this implement/fix?
|
||||
|
||||
Quick description
|
||||
Quick description and explanation of changes
|
||||
|
||||
## Types of changes
|
||||
|
||||
- [ ] Bugfix (non-breaking change which fixes an issue)
|
||||
- [ ] New feature (non-breaking change which adds functionality)
|
||||
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
|
||||
- [ ] Configuration change (this will require users to update their yaml configuration files to keep working)
|
||||
- [ ] Other
|
||||
|
||||
**Related issue or feature (if applicable):** fixes <link to issue>
|
||||
|
||||
**Pull request in [esphome-docs](https://github.com/esphome/esphome-docs) with documentation (if applicable):** esphome/esphome-docs#<esphome-docs PR number goes here>
|
||||
|
||||
# Test Environment
|
||||
|
||||
## Test Environment
|
||||
|
||||
- [ ] ESP32
|
||||
- [ ] ESP8266
|
||||
- [ ] Windows
|
||||
- [ ] Mac OS
|
||||
- [ ] Linux
|
||||
|
||||
## Example entry for `config.yaml`:
|
||||
<!--
|
||||
@@ -34,11 +31,6 @@ Quick description
|
||||
|
||||
```
|
||||
|
||||
# Explain your changes
|
||||
|
||||
Describe your changes here to communicate to the maintainers **why we should accept this pull request**.
|
||||
Very important to fill if no issue linked
|
||||
|
||||
## Checklist:
|
||||
- [ ] The code change is tested and works locally.
|
||||
- [ ] Tests have been added to verify that the new code works (under `tests/` folder).
|
||||
|
||||
2
.github/workflows/ci-docker.yml
vendored
2
.github/workflows/ci-docker.yml
vendored
@@ -26,7 +26,7 @@ jobs:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up env variables
|
||||
run: |
|
||||
base_version="3.0.0"
|
||||
base_version="3.4.0"
|
||||
|
||||
if [[ "${{ matrix.build_type }}" == "hassio" ]]; then
|
||||
build_from="esphome/esphome-hassio-base-${{ matrix.arch }}:${base_version}"
|
||||
|
||||
9
.github/workflows/ci.yml
vendored
9
.github/workflows/ci.yml
vendored
@@ -15,7 +15,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
# cpp lint job runs with esphome-lint docker image so that clang-format-*
|
||||
# doesn't have to be installed
|
||||
container: esphome/esphome-lint:latest
|
||||
container: esphome/esphome-lint:1.1
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
# Set up the pio project so that the cpp checks know how files are compiled
|
||||
@@ -32,7 +32,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
# cpp lint job runs with esphome-lint docker image so that clang-format-*
|
||||
# doesn't have to be installed
|
||||
container: esphome/esphome-lint:latest
|
||||
container: esphome/esphome-lint:1.1
|
||||
# Split clang-tidy check into 4 jobs. Each one will check 1/4th of the .cpp files
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@@ -97,6 +97,7 @@ jobs:
|
||||
- test2
|
||||
- test3
|
||||
- test4
|
||||
- test5
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up Python
|
||||
@@ -115,7 +116,7 @@ jobs:
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ~/.platformio
|
||||
key: test-home-platformio-${{ matrix.test }}-${{ hashFiles('esphome/core_config.py') }}
|
||||
key: test-home-platformio-${{ matrix.test }}-${{ hashFiles('esphome/core/config.py') }}
|
||||
restore-keys: |
|
||||
test-home-platformio-${{ matrix.test }}-
|
||||
- name: Set up environment
|
||||
@@ -126,7 +127,7 @@ jobs:
|
||||
run: |
|
||||
echo "::add-matcher::.github/workflows/matchers/gcc.json"
|
||||
echo "::add-matcher::.github/workflows/matchers/python.json"
|
||||
- run: esphome tests/${{ matrix.test }}.yaml compile
|
||||
- run: esphome compile tests/${{ matrix.test }}.yaml
|
||||
|
||||
pytest:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
42
.github/workflows/docker-lint-build.yml
vendored
Normal file
42
.github/workflows/docker-lint-build.yml
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
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:
|
||||
publish-docker-lint-iage:
|
||||
name: Build docker containers
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set TAG
|
||||
run: |
|
||||
echo "TAG=1.1" >> $GITHUB_ENV
|
||||
- name: Pull for cache
|
||||
run: |
|
||||
docker pull "esphome/esphome-lint:latest" || true
|
||||
- name: Build
|
||||
run: |
|
||||
docker build \
|
||||
--cache-from "esphome/esphome-lint:latest" \
|
||||
--file "docker/Dockerfile.lint" \
|
||||
--tag "esphome/esphome-lint:latest" \
|
||||
--tag "esphome/esphome-lint:${TAG}" \
|
||||
.
|
||||
- name: Log in to docker hub
|
||||
env:
|
||||
DOCKER_USER: ${{ secrets.DOCKER_USER }}
|
||||
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
|
||||
run: docker login -u "${DOCKER_USER}" -p "${DOCKER_PASSWORD}"
|
||||
- run: |
|
||||
docker push "esphome/esphome-lint:${TAG}"
|
||||
docker push "esphome/esphome-lint:latest"
|
||||
11
.github/workflows/release-dev.yml
vendored
11
.github/workflows/release-dev.yml
vendored
@@ -12,7 +12,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
# cpp lint job runs with esphome-lint docker image so that clang-format-*
|
||||
# doesn't have to be installed
|
||||
container: esphome/esphome-lint:latest
|
||||
container: esphome/esphome-lint:1.1
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
# Set up the pio project so that the cpp checks know how files are compiled
|
||||
@@ -29,7 +29,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
# cpp lint job runs with esphome-lint docker image so that clang-format-*
|
||||
# doesn't have to be installed
|
||||
container: esphome/esphome-lint:latest
|
||||
container: esphome/esphome-lint:1.1
|
||||
# Split clang-tidy check into 4 jobs. Each one will check 1/4th of the .cpp files
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@@ -94,6 +94,7 @@ jobs:
|
||||
- test2
|
||||
- test3
|
||||
- test4
|
||||
- test5
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up Python
|
||||
@@ -112,7 +113,7 @@ jobs:
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ~/.platformio
|
||||
key: test-home-platformio-${{ matrix.test }}-${{ hashFiles('esphome/core_config.py') }}
|
||||
key: test-home-platformio-${{ matrix.test }}-${{ hashFiles('esphome/core/config.py') }}
|
||||
restore-keys: |
|
||||
test-home-platformio-${{ matrix.test }}-
|
||||
- name: Set up environment
|
||||
@@ -123,7 +124,7 @@ jobs:
|
||||
run: |
|
||||
echo "::add-matcher::.github/workflows/matchers/gcc.json"
|
||||
echo "::add-matcher::.github/workflows/matchers/python.json"
|
||||
- run: esphome tests/${{ matrix.test }}.yaml compile
|
||||
- run: esphome compile tests/${{ matrix.test }}.yaml
|
||||
|
||||
pytest:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -174,7 +175,7 @@ jobs:
|
||||
echo "TAG=${TAG}" >> $GITHUB_ENV
|
||||
- name: Set up env variables
|
||||
run: |
|
||||
base_version="3.0.0"
|
||||
base_version="3.4.0"
|
||||
|
||||
if [[ "${{ matrix.build_type }}" == "hassio" ]]; then
|
||||
build_from="esphome/esphome-hassio-base-${{ matrix.arch }}:${base_version}"
|
||||
|
||||
11
.github/workflows/release.yml
vendored
11
.github/workflows/release.yml
vendored
@@ -11,7 +11,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
# cpp lint job runs with esphome-lint docker image so that clang-format-*
|
||||
# doesn't have to be installed
|
||||
container: esphome/esphome-lint:latest
|
||||
container: esphome/esphome-lint:1.1
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
# Set up the pio project so that the cpp checks know how files are compiled
|
||||
@@ -28,7 +28,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
# cpp lint job runs with esphome-lint docker image so that clang-format-*
|
||||
# doesn't have to be installed
|
||||
container: esphome/esphome-lint:latest
|
||||
container: esphome/esphome-lint:1.1
|
||||
# Split clang-tidy check into 4 jobs. Each one will check 1/4th of the .cpp files
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@@ -93,6 +93,7 @@ jobs:
|
||||
- test2
|
||||
- test3
|
||||
- test4
|
||||
- test5
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up Python
|
||||
@@ -111,7 +112,7 @@ jobs:
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ~/.platformio
|
||||
key: test-home-platformio-${{ matrix.test }}-${{ hashFiles('esphome/core_config.py') }}
|
||||
key: test-home-platformio-${{ matrix.test }}-${{ hashFiles('esphome/core/config.py') }}
|
||||
restore-keys: |
|
||||
test-home-platformio-${{ matrix.test }}-
|
||||
- name: Set up environment
|
||||
@@ -121,7 +122,7 @@ jobs:
|
||||
run: |
|
||||
echo "::add-matcher::.github/workflows/matchers/gcc.json"
|
||||
echo "::add-matcher::.github/workflows/matchers/python.json"
|
||||
- run: esphome tests/${{ matrix.test }}.yaml compile
|
||||
- run: esphome compile tests/${{ matrix.test }}.yaml
|
||||
|
||||
pytest:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -194,7 +195,7 @@ jobs:
|
||||
echo "TAG=${TAG}" >> $GITHUB_ENV
|
||||
- name: Set up env variables
|
||||
run: |
|
||||
base_version="3.0.0"
|
||||
base_version="3.4.0"
|
||||
|
||||
if [[ "${{ matrix.build_type }}" == "hassio" ]]; then
|
||||
build_from="esphome/esphome-hassio-base-${{ matrix.arch }}:${base_version}"
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -100,6 +100,8 @@ CMakeLists.txt
|
||||
|
||||
# CMake
|
||||
cmake-build-debug/
|
||||
cmake-build-livingroom8266/
|
||||
cmake-build-livingroom32/
|
||||
cmake-build-release/
|
||||
|
||||
CMakeCache.txt
|
||||
|
||||
2
.vscode/tasks.json
vendored
2
.vscode/tasks.json
vendored
@@ -4,7 +4,7 @@
|
||||
{
|
||||
"label": "run",
|
||||
"type": "shell",
|
||||
"command": "python3 -m esphome config dashboard",
|
||||
"command": "python3 -m esphome dashboard config",
|
||||
"problemMatcher": []
|
||||
}
|
||||
]
|
||||
|
||||
18
CODEOWNERS
18
CODEOWNERS
@@ -18,26 +18,36 @@ esphome/components/animation/* @syndlex
|
||||
esphome/components/api/* @OttoWinter
|
||||
esphome/components/async_tcp/* @OttoWinter
|
||||
esphome/components/atc_mithermometer/* @ahpohl
|
||||
esphome/components/b_parasite/* @rbaron
|
||||
esphome/components/bang_bang/* @OttoWinter
|
||||
esphome/components/binary_sensor/* @esphome/core
|
||||
esphome/components/ble_client/* @buxtronix
|
||||
esphome/components/bme680_bsec/* @trvrnrth
|
||||
esphome/components/canbus/* @danielschramm @mvturnho
|
||||
esphome/components/captive_portal/* @OttoWinter
|
||||
esphome/components/climate/* @esphome/core
|
||||
esphome/components/climate_ir/* @glmnet
|
||||
esphome/components/coolix/* @glmnet
|
||||
esphome/components/cover/* @esphome/core
|
||||
esphome/components/cs5460a/* @balrog-kun
|
||||
esphome/components/ct_clamp/* @jesserockz
|
||||
esphome/components/debug/* @OttoWinter
|
||||
esphome/components/dfplayer/* @glmnet
|
||||
esphome/components/dht/* @OttoWinter
|
||||
esphome/components/ds1307/* @badbadc0ffee
|
||||
esphome/components/esp32_ble/* @jesserockz
|
||||
esphome/components/esp32_ble_server/* @jesserockz
|
||||
esphome/components/esp32_improv/* @jesserockz
|
||||
esphome/components/exposure_notifications/* @OttoWinter
|
||||
esphome/components/ezo/* @ssieb
|
||||
esphome/components/fastled_base/* @OttoWinter
|
||||
esphome/components/fingerprint_grow/* @OnFreund @loongyh
|
||||
esphome/components/globals/* @esphome/core
|
||||
esphome/components/gpio/* @esphome/core
|
||||
esphome/components/gps/* @coogle
|
||||
esphome/components/homeassistant/* @OttoWinter
|
||||
esphome/components/i2c/* @esphome/core
|
||||
esphome/components/improv/* @jesserockz
|
||||
esphome/components/inkbird_ibsth1_mini/* @fkirill
|
||||
esphome/components/inkplate6/* @jesserockz
|
||||
esphome/components/integration/* @OttoWinter
|
||||
@@ -58,6 +68,7 @@ esphome/components/mcp2515/* @danielschramm @mvturnho
|
||||
esphome/components/mcp9808/* @k7hpn
|
||||
esphome/components/midea_ac/* @dudanov
|
||||
esphome/components/midea_dongle/* @dudanov
|
||||
esphome/components/mitsubishi/* @RubyBailey
|
||||
esphome/components/network/* @esphome/core
|
||||
esphome/components/nfc/* @jesserockz
|
||||
esphome/components/ota/* @esphome/core
|
||||
@@ -75,9 +86,13 @@ esphome/components/restart/* @esphome/core
|
||||
esphome/components/rf_bridge/* @jesserockz
|
||||
esphome/components/rtttl/* @glmnet
|
||||
esphome/components/script/* @esphome/core
|
||||
esphome/components/sdm_meter/* @jesserockz @polyfaces
|
||||
esphome/components/sensor/* @esphome/core
|
||||
esphome/components/sgp40/* @SenexCrenshaw
|
||||
esphome/components/sht4x/* @sjtrny
|
||||
esphome/components/shutdown/* @esphome/core
|
||||
esphome/components/sim800l/* @glmnet
|
||||
esphome/components/sm2135/* @BoukeHaarsma23
|
||||
esphome/components/spi/* @esphome/core
|
||||
esphome/components/ssd1322_base/* @kbx81
|
||||
esphome/components/ssd1322_spi/* @kbx81
|
||||
@@ -95,12 +110,14 @@ esphome/components/st7789v/* @kbx81
|
||||
esphome/components/substitutions/* @esphome/core
|
||||
esphome/components/sun/* @OttoWinter
|
||||
esphome/components/switch/* @esphome/core
|
||||
esphome/components/tca9548a/* @andreashergert1984
|
||||
esphome/components/tcl112/* @glmnet
|
||||
esphome/components/teleinfo/* @0hax
|
||||
esphome/components/thermostat/* @kbx81
|
||||
esphome/components/time/* @OttoWinter
|
||||
esphome/components/tm1637/* @glmnet
|
||||
esphome/components/tmp102/* @timsavage
|
||||
esphome/components/tof10120/* @wstrzalka
|
||||
esphome/components/tuya/binary_sensor/* @jesserockz
|
||||
esphome/components/tuya/climate/* @jesserockz
|
||||
esphome/components/tuya/sensor/* @jesserockz
|
||||
@@ -112,3 +129,4 @@ esphome/components/web_server_base/* @OttoWinter
|
||||
esphome/components/whirlpool/* @glmnet
|
||||
esphome/components/xiaomi_lywsd03mmc/* @ahpohl
|
||||
esphome/components/xiaomi_mhoc401/* @vevsvevs
|
||||
esphome/components/xpt2046/* @numo68
|
||||
|
||||
@@ -8,19 +8,19 @@ In the interest of fostering an open and welcoming environment, we as contributo
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
- Using welcoming and inclusive language
|
||||
- Being respectful of differing viewpoints and experiences
|
||||
- Gracefully accepting constructive criticism
|
||||
- Focusing on what is best for the community
|
||||
- Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a professional setting
|
||||
- The use of sexualized language or imagery and unwelcome sexual attention or advances
|
||||
- Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
- Public or private harassment
|
||||
- Publishing others' private information, such as a physical or electronic address, without explicit permission
|
||||
- Other conduct which could reasonably be considered inappropriate in a professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
@@ -34,7 +34,7 @@ This Code of Conduct applies both within project spaces and in public spaces whe
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at contact@otto-winter.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at esphome@nabucasa.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
ARG BUILD_FROM=esphome/esphome-base-amd64:3.0.0
|
||||
ARG BUILD_FROM=esphome/esphome-base-amd64:3.4.0
|
||||
FROM ${BUILD_FROM}
|
||||
|
||||
# First install requirements to leverage caching when requirements don't change
|
||||
COPY requirements.txt docker/platformio_install_deps.py platformio.ini /
|
||||
COPY requirements.txt requirements_optional.txt docker/platformio_install_deps.py platformio.ini /
|
||||
RUN \
|
||||
pip3 install --no-cache-dir -r /requirements.txt \
|
||||
pip3 install --no-cache-dir -r /requirements.txt -r /requirements_optional.txt \
|
||||
&& /platformio_install_deps.py /platformio.ini
|
||||
|
||||
# Then copy esphome and install
|
||||
@@ -27,4 +27,4 @@ WORKDIR /config
|
||||
# in every docker command twice
|
||||
ENTRYPOINT ["esphome"]
|
||||
# When no arguments given, start the dashboard in the workdir
|
||||
CMD ["/config", "dashboard"]
|
||||
CMD ["dashboard", "/config"]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM esphome/esphome-base-amd64:3.0.0
|
||||
FROM esphome/esphome-base-amd64:3.4.0
|
||||
|
||||
COPY . .
|
||||
|
||||
|
||||
@@ -2,9 +2,9 @@ ARG BUILD_FROM
|
||||
FROM ${BUILD_FROM}
|
||||
|
||||
# First install requirements to leverage caching when requirements don't change
|
||||
COPY requirements.txt docker/platformio_install_deps.py platformio.ini /
|
||||
COPY requirements.txt requirements_optional.txt docker/platformio_install_deps.py platformio.ini /
|
||||
RUN \
|
||||
pip3 install --no-cache-dir -r /requirements.txt \
|
||||
pip3 install --no-cache-dir -r /requirements.txt -r /requirements_optional.txt \
|
||||
&& /platformio_install_deps.py /platformio.ini
|
||||
|
||||
# Copy root filesystem
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
FROM esphome/esphome-lint-base:3.0.0
|
||||
FROM esphome/esphome-lint-base:3.4.0
|
||||
|
||||
COPY requirements.txt requirements_test.txt docker/platformio_install_deps.py platformio.ini /
|
||||
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_test.txt \
|
||||
pip3 install --no-cache-dir -r /requirements.txt -r /requirements_optional.txt -r /requirements_test.txt \
|
||||
&& /platformio_install_deps.py /platformio.ini
|
||||
|
||||
VOLUME ["/esphome"]
|
||||
|
||||
@@ -23,4 +23,4 @@ if bashio::config.has_value 'relative_url'; then
|
||||
fi
|
||||
|
||||
bashio::log.info "Starting ESPHome dashboard..."
|
||||
exec esphome /config/esphome dashboard --socket /var/run/esphome.sock --hassio
|
||||
exec esphome dashboard /config/esphome --socket /var/run/esphome.sock --hassio
|
||||
|
||||
@@ -18,7 +18,7 @@ from esphome.const import (
|
||||
CONF_ESPHOME,
|
||||
CONF_PLATFORMIO_OPTIONS,
|
||||
)
|
||||
from esphome.core import CORE, EsphomeError, coroutine, coroutine_with_priority
|
||||
from esphome.core import CORE, EsphomeError, coroutine
|
||||
from esphome.helpers import indent
|
||||
from esphome.util import (
|
||||
run_external_command,
|
||||
@@ -127,15 +127,16 @@ def wrap_to_code(name, comp):
|
||||
coro = coroutine(comp.to_code)
|
||||
|
||||
@functools.wraps(comp.to_code)
|
||||
@coroutine_with_priority(coro.priority)
|
||||
def wrapped(conf):
|
||||
async def wrapped(conf):
|
||||
cg.add(cg.LineComment(f"{name}:"))
|
||||
if comp.config_schema is not None:
|
||||
conf_str = yaml_util.dump(conf)
|
||||
conf_str = conf_str.replace("//", "")
|
||||
cg.add(cg.LineComment(indent(conf_str)))
|
||||
yield coro(conf)
|
||||
await coro(conf)
|
||||
|
||||
if hasattr(coro, "priority"):
|
||||
wrapped.priority = coro.priority
|
||||
return wrapped
|
||||
|
||||
|
||||
@@ -267,7 +268,7 @@ def clean_mqtt(config, args):
|
||||
def command_wizard(args):
|
||||
from esphome import wizard
|
||||
|
||||
return wizard.wizard(args.configuration[0])
|
||||
return wizard.wizard(args.configuration)
|
||||
|
||||
|
||||
def command_config(args, config):
|
||||
@@ -283,7 +284,7 @@ def command_vscode(args):
|
||||
|
||||
logging.disable(logging.INFO)
|
||||
logging.disable(logging.WARNING)
|
||||
CORE.config_path = args.configuration[0]
|
||||
CORE.config_path = args.configuration
|
||||
vscode.read_config(args)
|
||||
|
||||
|
||||
@@ -303,7 +304,7 @@ def command_compile(args, config):
|
||||
|
||||
def command_upload(args, config):
|
||||
port = choose_upload_log_host(
|
||||
default=args.upload_port,
|
||||
default=args.device,
|
||||
check_default=None,
|
||||
show_ota=True,
|
||||
show_mqtt=False,
|
||||
@@ -318,7 +319,7 @@ def command_upload(args, config):
|
||||
|
||||
def command_logs(args, config):
|
||||
port = choose_upload_log_host(
|
||||
default=args.serial_port,
|
||||
default=args.device,
|
||||
check_default=None,
|
||||
show_ota=False,
|
||||
show_mqtt=True,
|
||||
@@ -336,7 +337,7 @@ def command_run(args, config):
|
||||
return exit_code
|
||||
_LOGGER.info("Successfully compiled program.")
|
||||
port = choose_upload_log_host(
|
||||
default=args.upload_port,
|
||||
default=args.device,
|
||||
check_default=None,
|
||||
show_ota=True,
|
||||
show_mqtt=False,
|
||||
@@ -349,7 +350,7 @@ def command_run(args, config):
|
||||
if args.no_logs:
|
||||
return 0
|
||||
port = choose_upload_log_host(
|
||||
default=args.upload_port,
|
||||
default=args.device,
|
||||
check_default=port,
|
||||
show_ota=False,
|
||||
show_mqtt=True,
|
||||
@@ -393,7 +394,7 @@ def command_update_all(args):
|
||||
import click
|
||||
|
||||
success = {}
|
||||
files = list_yaml_files(args.configuration[0])
|
||||
files = list_yaml_files(args.configuration)
|
||||
twidth = 60
|
||||
|
||||
def print_bar(middle_text):
|
||||
@@ -407,7 +408,7 @@ def command_update_all(args):
|
||||
print("-" * twidth)
|
||||
print()
|
||||
rc = run_external_process(
|
||||
"esphome", "--dashboard", f, "run", "--no-logs", "--upload-port", "OTA"
|
||||
"esphome", "--dashboard", "run", f, "--no-logs", "--device", "OTA"
|
||||
)
|
||||
if rc == 0:
|
||||
print_bar("[{}] {}".format(color(Fore.BOLD_GREEN, "SUCCESS"), f))
|
||||
@@ -452,15 +453,17 @@ POST_CONFIG_ACTIONS = {
|
||||
|
||||
|
||||
def parse_args(argv):
|
||||
parser = argparse.ArgumentParser(description=f"ESPHome v{const.__version__}")
|
||||
parser.add_argument(
|
||||
"-v", "--verbose", help="Enable verbose esphome logs.", action="store_true"
|
||||
options_parser = argparse.ArgumentParser(add_help=False)
|
||||
options_parser.add_argument(
|
||||
"-v", "--verbose", help="Enable verbose ESPHome logs.", action="store_true"
|
||||
)
|
||||
parser.add_argument(
|
||||
"-q", "--quiet", help="Disable all esphome logs.", action="store_true"
|
||||
options_parser.add_argument(
|
||||
"-q", "--quiet", help="Disable all ESPHome logs.", action="store_true"
|
||||
)
|
||||
parser.add_argument("--dashboard", help=argparse.SUPPRESS, action="store_true")
|
||||
parser.add_argument(
|
||||
options_parser.add_argument(
|
||||
"--dashboard", help=argparse.SUPPRESS, action="store_true"
|
||||
)
|
||||
options_parser.add_argument(
|
||||
"-s",
|
||||
"--substitution",
|
||||
nargs=2,
|
||||
@@ -468,17 +471,88 @@ def parse_args(argv):
|
||||
help="Add a substitution",
|
||||
metavar=("key", "value"),
|
||||
)
|
||||
parser.add_argument(
|
||||
"configuration", help="Your YAML configuration file.", nargs="*"
|
||||
|
||||
# Keep backward compatibility with the old command line format of
|
||||
# esphome <config> <command>.
|
||||
#
|
||||
# Unfortunately this can't be done by adding another configuration argument to the
|
||||
# main config parser, as argparse is greedy when parsing arguments, so in regular
|
||||
# usage it'll eat the command as the configuration argument and error out out
|
||||
# because it can't parse the configuration as a command.
|
||||
#
|
||||
# Instead, construct an ad-hoc parser for the old format that doesn't actually
|
||||
# process the arguments, but parses them enough to let us figure out if the old
|
||||
# format is used. In that case, swap the command and configuration in the arguments
|
||||
# and continue on with the normal parser (after raising a deprecation warning).
|
||||
#
|
||||
# Disable argparse's built-in help option and add it manually to prevent this
|
||||
# parser from printing the help messagefor the old format when invoked with -h.
|
||||
compat_parser = argparse.ArgumentParser(parents=[options_parser], add_help=False)
|
||||
compat_parser.add_argument("-h", "--help")
|
||||
compat_parser.add_argument("configuration", nargs="*")
|
||||
compat_parser.add_argument(
|
||||
"command",
|
||||
choices=[
|
||||
"config",
|
||||
"compile",
|
||||
"upload",
|
||||
"logs",
|
||||
"run",
|
||||
"clean-mqtt",
|
||||
"wizard",
|
||||
"mqtt-fingerprint",
|
||||
"version",
|
||||
"clean",
|
||||
"dashboard",
|
||||
"vscode",
|
||||
],
|
||||
)
|
||||
|
||||
subparsers = parser.add_subparsers(help="Commands", dest="command")
|
||||
# on Python 3.9+ we can simply set exit_on_error=False in the constructor
|
||||
def _raise(x):
|
||||
raise argparse.ArgumentError(None, x)
|
||||
|
||||
compat_parser.error = _raise
|
||||
|
||||
try:
|
||||
result, unparsed = compat_parser.parse_known_args(argv[1:])
|
||||
last_option = len(argv) - len(unparsed) - 1 - len(result.configuration)
|
||||
argv = argv[0:last_option] + [result.command] + result.configuration + unparsed
|
||||
deprecated_argv_suggestion = argv
|
||||
except argparse.ArgumentError:
|
||||
# This is not an old-style command line, so we don't have to do anything.
|
||||
deprecated_argv_suggestion = None
|
||||
|
||||
# And continue on with regular parsing
|
||||
parser = argparse.ArgumentParser(
|
||||
description=f"ESPHome v{const.__version__}", parents=[options_parser]
|
||||
)
|
||||
parser.set_defaults(deprecated_argv_suggestion=deprecated_argv_suggestion)
|
||||
|
||||
mqtt_options = argparse.ArgumentParser(add_help=False)
|
||||
mqtt_options.add_argument("--topic", help="Manually set the MQTT topic.")
|
||||
mqtt_options.add_argument("--username", help="Manually set the MQTT username.")
|
||||
mqtt_options.add_argument("--password", help="Manually set the MQTT password.")
|
||||
mqtt_options.add_argument("--client-id", help="Manually set the MQTT client id.")
|
||||
|
||||
subparsers = parser.add_subparsers(
|
||||
help="Command to run:", dest="command", metavar="command"
|
||||
)
|
||||
subparsers.required = True
|
||||
subparsers.add_parser("config", help="Validate the configuration and spit it out.")
|
||||
|
||||
parser_config = subparsers.add_parser(
|
||||
"config", help="Validate the configuration and spit it out."
|
||||
)
|
||||
parser_config.add_argument(
|
||||
"configuration", help="Your YAML configuration file(s).", nargs="+"
|
||||
)
|
||||
|
||||
parser_compile = subparsers.add_parser(
|
||||
"compile", help="Read the configuration and compile a program."
|
||||
)
|
||||
parser_compile.add_argument(
|
||||
"configuration", help="Your YAML configuration file(s).", nargs="+"
|
||||
)
|
||||
parser_compile.add_argument(
|
||||
"--only-generate",
|
||||
help="Only generate source code, do not compile.",
|
||||
@@ -486,106 +560,124 @@ def parse_args(argv):
|
||||
)
|
||||
|
||||
parser_upload = subparsers.add_parser(
|
||||
"upload", help="Validate the configuration " "and upload the latest binary."
|
||||
"upload", help="Validate the configuration and upload the latest binary."
|
||||
)
|
||||
parser_upload.add_argument(
|
||||
"--upload-port",
|
||||
help="Manually specify the upload port to use. "
|
||||
"For example /dev/cu.SLAB_USBtoUART.",
|
||||
"configuration", help="Your YAML configuration file(s).", nargs="+"
|
||||
)
|
||||
parser_upload.add_argument(
|
||||
"--device",
|
||||
help="Manually specify the serial port/address to use, for example /dev/ttyUSB0.",
|
||||
)
|
||||
|
||||
parser_logs = subparsers.add_parser(
|
||||
"logs", help="Validate the configuration " "and show all MQTT logs."
|
||||
"logs",
|
||||
help="Validate the configuration and show all logs.",
|
||||
parents=[mqtt_options],
|
||||
)
|
||||
parser_logs.add_argument("--topic", help="Manually set the topic to subscribe to.")
|
||||
parser_logs.add_argument("--username", help="Manually set the username.")
|
||||
parser_logs.add_argument("--password", help="Manually set the password.")
|
||||
parser_logs.add_argument("--client-id", help="Manually set the client id.")
|
||||
parser_logs.add_argument(
|
||||
"--serial-port",
|
||||
help="Manually specify a serial port to use"
|
||||
"For example /dev/cu.SLAB_USBtoUART.",
|
||||
"configuration", help="Your YAML configuration file.", nargs=1
|
||||
)
|
||||
parser_logs.add_argument(
|
||||
"--device",
|
||||
help="Manually specify the serial port/address to use, for example /dev/ttyUSB0.",
|
||||
)
|
||||
|
||||
parser_run = subparsers.add_parser(
|
||||
"run",
|
||||
help="Validate the configuration, create a binary, "
|
||||
"upload it, and start MQTT logs.",
|
||||
help="Validate the configuration, create a binary, upload it, and start logs.",
|
||||
parents=[mqtt_options],
|
||||
)
|
||||
parser_run.add_argument(
|
||||
"--upload-port",
|
||||
help="Manually specify the upload port/ip to use. "
|
||||
"For example /dev/cu.SLAB_USBtoUART.",
|
||||
"configuration", help="Your YAML configuration file(s).", nargs="+"
|
||||
)
|
||||
parser_run.add_argument(
|
||||
"--no-logs", help="Disable starting MQTT logs.", action="store_true"
|
||||
"--device",
|
||||
help="Manually specify the serial port/address to use, for example /dev/ttyUSB0.",
|
||||
)
|
||||
parser_run.add_argument(
|
||||
"--topic", help="Manually set the topic to subscribe to for logs."
|
||||
"--no-logs", help="Disable starting logs.", action="store_true"
|
||||
)
|
||||
parser_run.add_argument(
|
||||
"--username", help="Manually set the MQTT username for logs."
|
||||
)
|
||||
parser_run.add_argument(
|
||||
"--password", help="Manually set the MQTT password for logs."
|
||||
)
|
||||
parser_run.add_argument("--client-id", help="Manually set the client id for logs.")
|
||||
|
||||
parser_clean = subparsers.add_parser(
|
||||
"clean-mqtt", help="Helper to clear an MQTT topic from " "retain messages."
|
||||
"clean-mqtt",
|
||||
help="Helper to clear retained messages from an MQTT topic.",
|
||||
parents=[mqtt_options],
|
||||
)
|
||||
parser_clean.add_argument(
|
||||
"configuration", help="Your YAML configuration file(s).", nargs="+"
|
||||
)
|
||||
parser_clean.add_argument("--topic", help="Manually set the topic to subscribe to.")
|
||||
parser_clean.add_argument("--username", help="Manually set the username.")
|
||||
parser_clean.add_argument("--password", help="Manually set the password.")
|
||||
parser_clean.add_argument("--client-id", help="Manually set the client id.")
|
||||
|
||||
subparsers.add_parser(
|
||||
parser_wizard = subparsers.add_parser(
|
||||
"wizard",
|
||||
help="A helpful setup wizard that will guide "
|
||||
"you through setting up esphome.",
|
||||
help="A helpful setup wizard that will guide you through setting up ESPHome.",
|
||||
)
|
||||
parser_wizard.add_argument(
|
||||
"configuration",
|
||||
help="Your YAML configuration file.",
|
||||
)
|
||||
|
||||
subparsers.add_parser(
|
||||
parser_fingerprint = subparsers.add_parser(
|
||||
"mqtt-fingerprint", help="Get the SSL fingerprint from a MQTT broker."
|
||||
)
|
||||
parser_fingerprint.add_argument(
|
||||
"configuration", help="Your YAML configuration file(s).", nargs="+"
|
||||
)
|
||||
|
||||
subparsers.add_parser("version", help="Print the esphome version and exit.")
|
||||
subparsers.add_parser("version", help="Print the ESPHome version and exit.")
|
||||
|
||||
subparsers.add_parser("clean", help="Delete all temporary build files.")
|
||||
parser_clean = subparsers.add_parser(
|
||||
"clean", help="Delete all temporary build files."
|
||||
)
|
||||
parser_clean.add_argument(
|
||||
"configuration", help="Your YAML configuration file(s).", nargs="+"
|
||||
)
|
||||
|
||||
dashboard = subparsers.add_parser(
|
||||
parser_dashboard = subparsers.add_parser(
|
||||
"dashboard", help="Create a simple web server for a dashboard."
|
||||
)
|
||||
dashboard.add_argument(
|
||||
parser_dashboard.add_argument(
|
||||
"configuration",
|
||||
help="Your YAML configuration file directory.",
|
||||
)
|
||||
parser_dashboard.add_argument(
|
||||
"--port",
|
||||
help="The HTTP port to open connections on. Defaults to 6052.",
|
||||
type=int,
|
||||
default=6052,
|
||||
)
|
||||
dashboard.add_argument(
|
||||
parser_dashboard.add_argument(
|
||||
"--username",
|
||||
help="The optional username to require " "for authentication.",
|
||||
help="The optional username to require for authentication.",
|
||||
type=str,
|
||||
default="",
|
||||
)
|
||||
dashboard.add_argument(
|
||||
parser_dashboard.add_argument(
|
||||
"--password",
|
||||
help="The optional password to require " "for authentication.",
|
||||
help="The optional password to require for authentication.",
|
||||
type=str,
|
||||
default="",
|
||||
)
|
||||
dashboard.add_argument(
|
||||
parser_dashboard.add_argument(
|
||||
"--open-ui", help="Open the dashboard UI in a browser.", action="store_true"
|
||||
)
|
||||
dashboard.add_argument("--hassio", help=argparse.SUPPRESS, action="store_true")
|
||||
dashboard.add_argument(
|
||||
parser_dashboard.add_argument(
|
||||
"--hassio", help=argparse.SUPPRESS, action="store_true"
|
||||
)
|
||||
parser_dashboard.add_argument(
|
||||
"--socket", help="Make the dashboard serve under a unix socket", type=str
|
||||
)
|
||||
|
||||
vscode = subparsers.add_parser("vscode", help=argparse.SUPPRESS)
|
||||
vscode.add_argument("--ace", action="store_true")
|
||||
parser_vscode = subparsers.add_parser("vscode")
|
||||
parser_vscode.add_argument(
|
||||
"configuration", help="Your YAML configuration file.", nargs=1
|
||||
)
|
||||
parser_vscode.add_argument("--ace", action="store_true")
|
||||
|
||||
subparsers.add_parser("update-all", help=argparse.SUPPRESS)
|
||||
parser_update = subparsers.add_parser("update-all")
|
||||
parser_update.add_argument(
|
||||
"configuration", help="Your YAML configuration file directory.", nargs=1
|
||||
)
|
||||
|
||||
return parser.parse_args(argv[1:])
|
||||
|
||||
@@ -595,14 +687,18 @@ def run_esphome(argv):
|
||||
CORE.dashboard = args.dashboard
|
||||
|
||||
setup_log(args.verbose, args.quiet)
|
||||
if args.command != "version" and not args.configuration:
|
||||
_LOGGER.error("Missing configuration parameter, see esphome --help.")
|
||||
return 1
|
||||
if args.deprecated_argv_suggestion is not None and args.command != "vscode":
|
||||
_LOGGER.warning(
|
||||
"Calling ESPHome with the configuration before the command is deprecated "
|
||||
"and will be removed in the future. "
|
||||
)
|
||||
_LOGGER.warning("Please instead use:")
|
||||
_LOGGER.warning(" esphome %s", " ".join(args.deprecated_argv_suggestion[1:]))
|
||||
|
||||
if sys.version_info < (3, 6, 0):
|
||||
if sys.version_info < (3, 7, 0):
|
||||
_LOGGER.error(
|
||||
"You're running ESPHome with Python <3.6. ESPHome is no longer compatible "
|
||||
"with this Python version. Please reinstall ESPHome with Python 3.6+"
|
||||
"You're running ESPHome with Python <3.7. ESPHome is no longer compatible "
|
||||
"with this Python version. Please reinstall ESPHome with Python 3.7+"
|
||||
)
|
||||
return 1
|
||||
|
||||
@@ -610,7 +706,7 @@ def run_esphome(argv):
|
||||
try:
|
||||
return PRE_CONFIG_ACTIONS[args.command](args)
|
||||
except EsphomeError as e:
|
||||
_LOGGER.error(e)
|
||||
_LOGGER.error(e, exc_info=args.verbose)
|
||||
return 1
|
||||
|
||||
for conf_path in args.configuration:
|
||||
@@ -628,7 +724,7 @@ def run_esphome(argv):
|
||||
try:
|
||||
rc = POST_CONFIG_ACTIONS[args.command](args, config)
|
||||
except EsphomeError as e:
|
||||
_LOGGER.error(e)
|
||||
_LOGGER.error(e, exc_info=args.verbose)
|
||||
return 1
|
||||
if rc != 0:
|
||||
return rc
|
||||
|
||||
@@ -10,7 +10,6 @@ from esphome.const import (
|
||||
CONF_TYPE_ID,
|
||||
CONF_TIME,
|
||||
)
|
||||
from esphome.core import coroutine
|
||||
from esphome.jsonschema import jschema_extractor
|
||||
from esphome.util import Registry
|
||||
|
||||
@@ -142,27 +141,27 @@ NotCondition = cg.esphome_ns.class_("NotCondition", Condition)
|
||||
|
||||
|
||||
@register_condition("and", AndCondition, validate_condition_list)
|
||||
def and_condition_to_code(config, condition_id, template_arg, args):
|
||||
conditions = yield build_condition_list(config, template_arg, args)
|
||||
yield cg.new_Pvariable(condition_id, template_arg, conditions)
|
||||
async def and_condition_to_code(config, condition_id, template_arg, args):
|
||||
conditions = await build_condition_list(config, template_arg, args)
|
||||
return cg.new_Pvariable(condition_id, template_arg, conditions)
|
||||
|
||||
|
||||
@register_condition("or", OrCondition, validate_condition_list)
|
||||
def or_condition_to_code(config, condition_id, template_arg, args):
|
||||
conditions = yield build_condition_list(config, template_arg, args)
|
||||
yield cg.new_Pvariable(condition_id, template_arg, conditions)
|
||||
async def or_condition_to_code(config, condition_id, template_arg, args):
|
||||
conditions = await build_condition_list(config, template_arg, args)
|
||||
return cg.new_Pvariable(condition_id, template_arg, conditions)
|
||||
|
||||
|
||||
@register_condition("not", NotCondition, validate_potentially_and_condition)
|
||||
def not_condition_to_code(config, condition_id, template_arg, args):
|
||||
condition = yield build_condition(config, template_arg, args)
|
||||
yield cg.new_Pvariable(condition_id, template_arg, condition)
|
||||
async def not_condition_to_code(config, condition_id, template_arg, args):
|
||||
condition = await build_condition(config, template_arg, args)
|
||||
return cg.new_Pvariable(condition_id, template_arg, condition)
|
||||
|
||||
|
||||
@register_condition("lambda", LambdaCondition, cv.lambda_)
|
||||
def lambda_condition_to_code(config, condition_id, template_arg, args):
|
||||
lambda_ = yield cg.process_lambda(config, args, return_type=bool)
|
||||
yield cg.new_Pvariable(condition_id, template_arg, lambda_)
|
||||
@register_condition("lambda", LambdaCondition, cv.returning_lambda)
|
||||
async def lambda_condition_to_code(config, condition_id, template_arg, args):
|
||||
lambda_ = await cg.process_lambda(config, args, return_type=bool)
|
||||
return cg.new_Pvariable(condition_id, template_arg, lambda_)
|
||||
|
||||
|
||||
@register_condition(
|
||||
@@ -177,26 +176,26 @@ def lambda_condition_to_code(config, condition_id, template_arg, args):
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA),
|
||||
)
|
||||
def for_condition_to_code(config, condition_id, template_arg, args):
|
||||
condition = yield build_condition(
|
||||
async def for_condition_to_code(config, condition_id, template_arg, args):
|
||||
condition = await build_condition(
|
||||
config[CONF_CONDITION], cg.TemplateArguments(), []
|
||||
)
|
||||
var = cg.new_Pvariable(condition_id, template_arg, condition)
|
||||
yield cg.register_component(var, config)
|
||||
templ = yield cg.templatable(config[CONF_TIME], args, cg.uint32)
|
||||
await cg.register_component(var, config)
|
||||
templ = await cg.templatable(config[CONF_TIME], args, cg.uint32)
|
||||
cg.add(var.set_time(templ))
|
||||
yield var
|
||||
return var
|
||||
|
||||
|
||||
@register_action(
|
||||
"delay", DelayAction, cv.templatable(cv.positive_time_period_milliseconds)
|
||||
)
|
||||
def delay_action_to_code(config, action_id, template_arg, args):
|
||||
async def delay_action_to_code(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
yield cg.register_component(var, {})
|
||||
template_ = yield cg.templatable(config, args, cg.uint32)
|
||||
await cg.register_component(var, {})
|
||||
template_ = await cg.templatable(config, args, cg.uint32)
|
||||
cg.add(var.set_delay(template_))
|
||||
yield var
|
||||
return var
|
||||
|
||||
|
||||
@register_action(
|
||||
@@ -211,16 +210,16 @@ def delay_action_to_code(config, action_id, template_arg, args):
|
||||
cv.has_at_least_one_key(CONF_THEN, CONF_ELSE),
|
||||
),
|
||||
)
|
||||
def if_action_to_code(config, action_id, template_arg, args):
|
||||
conditions = yield build_condition(config[CONF_CONDITION], template_arg, args)
|
||||
async def if_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_THEN in config:
|
||||
actions = yield build_action_list(config[CONF_THEN], template_arg, args)
|
||||
actions = await build_action_list(config[CONF_THEN], template_arg, args)
|
||||
cg.add(var.add_then(actions))
|
||||
if CONF_ELSE in config:
|
||||
actions = yield build_action_list(config[CONF_ELSE], template_arg, args)
|
||||
actions = await build_action_list(config[CONF_ELSE], template_arg, args)
|
||||
cg.add(var.add_else(actions))
|
||||
yield var
|
||||
return var
|
||||
|
||||
|
||||
@register_action(
|
||||
@@ -233,12 +232,12 @@ def if_action_to_code(config, action_id, template_arg, args):
|
||||
}
|
||||
),
|
||||
)
|
||||
def while_action_to_code(config, action_id, template_arg, args):
|
||||
conditions = yield build_condition(config[CONF_CONDITION], template_arg, args)
|
||||
async def while_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)
|
||||
actions = yield build_action_list(config[CONF_THEN], template_arg, args)
|
||||
actions = await build_action_list(config[CONF_THEN], template_arg, args)
|
||||
cg.add(var.add_then(actions))
|
||||
yield var
|
||||
return var
|
||||
|
||||
|
||||
def validate_wait_until(value):
|
||||
@@ -253,17 +252,17 @@ def validate_wait_until(value):
|
||||
|
||||
|
||||
@register_action("wait_until", WaitUntilAction, validate_wait_until)
|
||||
def wait_until_action_to_code(config, action_id, template_arg, args):
|
||||
conditions = yield build_condition(config[CONF_CONDITION], template_arg, args)
|
||||
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)
|
||||
yield cg.register_component(var, {})
|
||||
yield var
|
||||
await cg.register_component(var, {})
|
||||
return var
|
||||
|
||||
|
||||
@register_action("lambda", LambdaAction, cv.lambda_)
|
||||
def lambda_action_to_code(config, action_id, template_arg, args):
|
||||
lambda_ = yield cg.process_lambda(config, args, return_type=cg.void)
|
||||
yield cg.new_Pvariable(action_id, template_arg, lambda_)
|
||||
async def lambda_action_to_code(config, action_id, template_arg, args):
|
||||
lambda_ = await cg.process_lambda(config, args, return_type=cg.void)
|
||||
return cg.new_Pvariable(action_id, template_arg, lambda_)
|
||||
|
||||
|
||||
@register_action(
|
||||
@@ -275,54 +274,51 @@ def lambda_action_to_code(config, action_id, template_arg, args):
|
||||
}
|
||||
),
|
||||
)
|
||||
def component_update_action_to_code(config, action_id, template_arg, args):
|
||||
comp = yield cg.get_variable(config[CONF_ID])
|
||||
yield cg.new_Pvariable(action_id, template_arg, comp)
|
||||
async def component_update_action_to_code(config, action_id, template_arg, args):
|
||||
comp = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(action_id, template_arg, comp)
|
||||
|
||||
|
||||
@coroutine
|
||||
def build_action(full_config, template_arg, args):
|
||||
async def build_action(full_config, template_arg, args):
|
||||
registry_entry, config = cg.extract_registry_entry_config(
|
||||
ACTION_REGISTRY, full_config
|
||||
)
|
||||
action_id = full_config[CONF_TYPE_ID]
|
||||
builder = registry_entry.coroutine_fun
|
||||
yield builder(config, action_id, template_arg, args)
|
||||
ret = await builder(config, action_id, template_arg, args)
|
||||
return ret
|
||||
|
||||
|
||||
@coroutine
|
||||
def build_action_list(config, templ, arg_type):
|
||||
async def build_action_list(config, templ, arg_type):
|
||||
actions = []
|
||||
for conf in config:
|
||||
action = yield build_action(conf, templ, arg_type)
|
||||
action = await build_action(conf, templ, arg_type)
|
||||
actions.append(action)
|
||||
yield actions
|
||||
return actions
|
||||
|
||||
|
||||
@coroutine
|
||||
def build_condition(full_config, template_arg, args):
|
||||
async def build_condition(full_config, template_arg, args):
|
||||
registry_entry, config = cg.extract_registry_entry_config(
|
||||
CONDITION_REGISTRY, full_config
|
||||
)
|
||||
action_id = full_config[CONF_TYPE_ID]
|
||||
builder = registry_entry.coroutine_fun
|
||||
yield builder(config, action_id, template_arg, args)
|
||||
ret = await builder(config, action_id, template_arg, args)
|
||||
return ret
|
||||
|
||||
|
||||
@coroutine
|
||||
def build_condition_list(config, templ, args):
|
||||
async def build_condition_list(config, templ, args):
|
||||
conditions = []
|
||||
for conf in config:
|
||||
condition = yield build_condition(conf, templ, args)
|
||||
condition = await build_condition(conf, templ, args)
|
||||
conditions.append(condition)
|
||||
yield conditions
|
||||
return conditions
|
||||
|
||||
|
||||
@coroutine
|
||||
def build_automation(trigger, args, config):
|
||||
async def build_automation(trigger, args, config):
|
||||
arg_types = [arg[0] for arg in args]
|
||||
templ = cg.TemplateArguments(*arg_types)
|
||||
obj = cg.new_Pvariable(config[CONF_AUTOMATION_ID], templ, trigger)
|
||||
actions = yield build_action_list(config[CONF_THEN], templ, args)
|
||||
actions = await build_action_list(config[CONF_THEN], templ, args)
|
||||
cg.add(obj.add_actions(actions))
|
||||
yield obj
|
||||
return obj
|
||||
|
||||
@@ -19,6 +19,7 @@ from esphome.cpp_generator import ( # noqa
|
||||
Statement,
|
||||
LineComment,
|
||||
progmem_array,
|
||||
static_const_array,
|
||||
statement,
|
||||
variable,
|
||||
new_variable,
|
||||
|
||||
@@ -18,16 +18,16 @@ CONFIG_SCHEMA = stepper.STEPPER_SCHEMA.extend(
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
yield cg.register_component(var, config)
|
||||
yield stepper.register_stepper(var, config)
|
||||
await cg.register_component(var, config)
|
||||
await stepper.register_stepper(var, config)
|
||||
|
||||
step_pin = yield cg.gpio_pin_expression(config[CONF_STEP_PIN])
|
||||
step_pin = await cg.gpio_pin_expression(config[CONF_STEP_PIN])
|
||||
cg.add(var.set_step_pin(step_pin))
|
||||
dir_pin = yield cg.gpio_pin_expression(config[CONF_DIR_PIN])
|
||||
dir_pin = await cg.gpio_pin_expression(config[CONF_DIR_PIN])
|
||||
cg.add(var.set_dir_pin(dir_pin))
|
||||
|
||||
if CONF_SLEEP_PIN in config:
|
||||
sleep_pin = yield cg.gpio_pin_expression(config[CONF_SLEEP_PIN])
|
||||
sleep_pin = await cg.gpio_pin_expression(config[CONF_SLEEP_PIN])
|
||||
cg.add(var.set_sleep_pin(sleep_pin))
|
||||
|
||||
@@ -32,18 +32,18 @@ CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend(
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
yield cg.register_component(var, config)
|
||||
await cg.register_component(var, config)
|
||||
|
||||
# override default min power to 10%
|
||||
if CONF_MIN_POWER not in config:
|
||||
config[CONF_MIN_POWER] = 0.1
|
||||
yield output.register_output(var, config)
|
||||
await output.register_output(var, config)
|
||||
|
||||
pin = yield cg.gpio_pin_expression(config[CONF_GATE_PIN])
|
||||
pin = await cg.gpio_pin_expression(config[CONF_GATE_PIN])
|
||||
cg.add(var.set_gate_pin(pin))
|
||||
pin = yield cg.gpio_pin_expression(config[CONF_ZERO_CROSS_PIN])
|
||||
pin = await cg.gpio_pin_expression(config[CONF_ZERO_CROSS_PIN])
|
||||
cg.add(var.set_zero_cross_pin(pin))
|
||||
cg.add(var.set_init_with_half_cycle(config[CONF_INIT_WITH_HALF_CYCLE]))
|
||||
cg.add(var.set_method(config[CONF_METHOD]))
|
||||
|
||||
@@ -21,8 +21,7 @@ CONFIG_SCHEMA = cv.Schema({})
|
||||
"Adalight",
|
||||
{cv.GenerateID(CONF_UART_ID): cv.use_id(uart.UARTComponent)},
|
||||
)
|
||||
def adalight_light_effect_to_code(config, effect_id):
|
||||
async def adalight_light_effect_to_code(config, effect_id):
|
||||
effect = cg.new_Pvariable(effect_id, config[CONF_NAME])
|
||||
yield uart.register_uart_device(effect, config)
|
||||
|
||||
yield effect
|
||||
await uart.register_uart_device(effect, config)
|
||||
return effect
|
||||
|
||||
@@ -8,6 +8,7 @@ from esphome.const import (
|
||||
CONF_PIN,
|
||||
DEVICE_CLASS_VOLTAGE,
|
||||
ICON_EMPTY,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_VOLT,
|
||||
)
|
||||
|
||||
@@ -35,7 +36,9 @@ ADCSensor = adc_ns.class_(
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
sensor.sensor_schema(UNIT_VOLT, ICON_EMPTY, 2, DEVICE_CLASS_VOLTAGE)
|
||||
sensor.sensor_schema(
|
||||
UNIT_VOLT, ICON_EMPTY, 2, DEVICE_CLASS_VOLTAGE, STATE_CLASS_MEASUREMENT
|
||||
)
|
||||
.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(ADCSensor),
|
||||
@@ -49,10 +52,10 @@ CONFIG_SCHEMA = (
|
||||
)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
yield cg.register_component(var, config)
|
||||
yield sensor.register_sensor(var, config)
|
||||
await cg.register_component(var, config)
|
||||
await sensor.register_sensor(var, config)
|
||||
|
||||
if config[CONF_PIN] == "VCC":
|
||||
cg.add_define("USE_ADC_SENSOR_VCC")
|
||||
|
||||
@@ -38,18 +38,18 @@ CONFIG_SCHEMA = cv.All(
|
||||
)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
wrapped_light = yield cg.get_variable(config[CONF_ADDRESSABLE_LIGHT_ID])
|
||||
wrapped_light = await cg.get_variable(config[CONF_ADDRESSABLE_LIGHT_ID])
|
||||
cg.add(var.set_width(config[CONF_WIDTH]))
|
||||
cg.add(var.set_height(config[CONF_HEIGHT]))
|
||||
cg.add(var.set_light(wrapped_light))
|
||||
|
||||
yield cg.register_component(var, config)
|
||||
yield display.register_display(var, config)
|
||||
await cg.register_component(var, config)
|
||||
await display.register_display(var, config)
|
||||
|
||||
if CONF_PIXEL_MAPPER in config:
|
||||
pixel_mapper_template_ = yield cg.process_lambda(
|
||||
pixel_mapper_template_ = await cg.process_lambda(
|
||||
config[CONF_PIXEL_MAPPER],
|
||||
[(int, "x"), (int, "y")],
|
||||
return_type=cg.int_,
|
||||
@@ -57,7 +57,7 @@ def to_code(config):
|
||||
cg.add(var.set_pixel_mapper(pixel_mapper_template_))
|
||||
|
||||
if CONF_LAMBDA in config:
|
||||
lambda_ = yield cg.process_lambda(
|
||||
lambda_ = await cg.process_lambda(
|
||||
config[CONF_LAMBDA], [(display.DisplayBufferRef, "it")], return_type=cg.void
|
||||
)
|
||||
cg.add(var.set_writer(lambda_))
|
||||
|
||||
@@ -9,6 +9,7 @@ from esphome.const import (
|
||||
DEVICE_CLASS_POWER,
|
||||
DEVICE_CLASS_VOLTAGE,
|
||||
ICON_EMPTY,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_VOLT,
|
||||
UNIT_AMPERE,
|
||||
UNIT_WATT,
|
||||
@@ -31,19 +32,27 @@ CONFIG_SCHEMA = (
|
||||
cv.GenerateID(): cv.declare_id(ADE7953),
|
||||
cv.Optional(CONF_IRQ_PIN): pins.input_pin,
|
||||
cv.Optional(CONF_VOLTAGE): sensor.sensor_schema(
|
||||
UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE
|
||||
UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE, STATE_CLASS_MEASUREMENT
|
||||
),
|
||||
cv.Optional(CONF_CURRENT_A): sensor.sensor_schema(
|
||||
UNIT_AMPERE, ICON_EMPTY, 2, DEVICE_CLASS_CURRENT
|
||||
UNIT_AMPERE,
|
||||
ICON_EMPTY,
|
||||
2,
|
||||
DEVICE_CLASS_CURRENT,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_CURRENT_B): sensor.sensor_schema(
|
||||
UNIT_AMPERE, ICON_EMPTY, 2, DEVICE_CLASS_CURRENT
|
||||
UNIT_AMPERE,
|
||||
ICON_EMPTY,
|
||||
2,
|
||||
DEVICE_CLASS_CURRENT,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_ACTIVE_POWER_A): sensor.sensor_schema(
|
||||
UNIT_WATT, ICON_EMPTY, 1, DEVICE_CLASS_POWER
|
||||
UNIT_WATT, ICON_EMPTY, 1, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT
|
||||
),
|
||||
cv.Optional(CONF_ACTIVE_POWER_B): sensor.sensor_schema(
|
||||
UNIT_WATT, ICON_EMPTY, 1, DEVICE_CLASS_POWER
|
||||
UNIT_WATT, ICON_EMPTY, 1, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT
|
||||
),
|
||||
}
|
||||
)
|
||||
@@ -52,10 +61,10 @@ CONFIG_SCHEMA = (
|
||||
)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
yield cg.register_component(var, config)
|
||||
yield i2c.register_i2c_device(var, config)
|
||||
await cg.register_component(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
|
||||
if CONF_IRQ_PIN in config:
|
||||
cg.add(var.set_irq_pin(config[CONF_IRQ_PIN]))
|
||||
@@ -70,5 +79,5 @@ def to_code(config):
|
||||
if key not in config:
|
||||
continue
|
||||
conf = config[key]
|
||||
sens = yield sensor.new_sensor(conf)
|
||||
sens = await sensor.new_sensor(conf)
|
||||
cg.add(getattr(var, f"set_{key}_sensor")(sens))
|
||||
|
||||
@@ -23,9 +23,9 @@ CONFIG_SCHEMA = (
|
||||
)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
yield cg.register_component(var, config)
|
||||
yield i2c.register_i2c_device(var, config)
|
||||
await cg.register_component(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
|
||||
cg.add(var.set_continuous_mode(config[CONF_CONTINUOUS_MODE]))
|
||||
|
||||
@@ -6,6 +6,7 @@ from esphome.const import (
|
||||
CONF_MULTIPLEXER,
|
||||
DEVICE_CLASS_VOLTAGE,
|
||||
ICON_EMPTY,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_VOLT,
|
||||
CONF_ID,
|
||||
)
|
||||
@@ -51,7 +52,9 @@ ADS1115Sensor = ads1115_ns.class_(
|
||||
|
||||
CONF_ADS1115_ID = "ads1115_id"
|
||||
CONFIG_SCHEMA = (
|
||||
sensor.sensor_schema(UNIT_VOLT, ICON_EMPTY, 3, DEVICE_CLASS_VOLTAGE)
|
||||
sensor.sensor_schema(
|
||||
UNIT_VOLT, ICON_EMPTY, 3, DEVICE_CLASS_VOLTAGE, STATE_CLASS_MEASUREMENT
|
||||
)
|
||||
.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(ADS1115Sensor),
|
||||
@@ -64,11 +67,11 @@ CONFIG_SCHEMA = (
|
||||
)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
paren = yield cg.get_variable(config[CONF_ADS1115_ID])
|
||||
async def to_code(config):
|
||||
paren = await cg.get_variable(config[CONF_ADS1115_ID])
|
||||
var = cg.new_Pvariable(config[CONF_ID], paren)
|
||||
yield sensor.register_sensor(var, config)
|
||||
yield cg.register_component(var, config)
|
||||
await sensor.register_sensor(var, config)
|
||||
await cg.register_component(var, config)
|
||||
|
||||
cg.add(var.set_multiplexer(config[CONF_MULTIPLEXER]))
|
||||
cg.add(var.set_gain(config[CONF_GAIN]))
|
||||
|
||||
@@ -60,6 +60,7 @@ void AHT10Component::update() {
|
||||
delay = AHT10_HUMIDITY_DELAY;
|
||||
for (int i = 0; i < AHT10_ATTEMPS; ++i) {
|
||||
ESP_LOGVV(TAG, "Attemps %u at %6ld", i, millis());
|
||||
delay_microseconds_accurate(4);
|
||||
if (!this->read_bytes(0, data, 6, delay)) {
|
||||
ESP_LOGD(TAG, "Communication with AHT10 failed, waiting...");
|
||||
} else if ((data[0] & 0x80) == 0x80) { // Bit[7] = 0b1, device is busy
|
||||
|
||||
@@ -8,6 +8,7 @@ from esphome.const import (
|
||||
DEVICE_CLASS_HUMIDITY,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
ICON_EMPTY,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_CELSIUS,
|
||||
UNIT_PERCENT,
|
||||
)
|
||||
@@ -22,10 +23,18 @@ CONFIG_SCHEMA = (
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(AHT10Component),
|
||||
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
|
||||
UNIT_CELSIUS, ICON_EMPTY, 2, DEVICE_CLASS_TEMPERATURE
|
||||
UNIT_CELSIUS,
|
||||
ICON_EMPTY,
|
||||
2,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
|
||||
UNIT_PERCENT, ICON_EMPTY, 2, DEVICE_CLASS_HUMIDITY
|
||||
UNIT_PERCENT,
|
||||
ICON_EMPTY,
|
||||
2,
|
||||
DEVICE_CLASS_HUMIDITY,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
}
|
||||
)
|
||||
@@ -34,15 +43,15 @@ CONFIG_SCHEMA = (
|
||||
)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
yield cg.register_component(var, config)
|
||||
yield i2c.register_i2c_device(var, config)
|
||||
await cg.register_component(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
|
||||
if CONF_TEMPERATURE in config:
|
||||
sens = yield sensor.new_sensor(config[CONF_TEMPERATURE])
|
||||
sens = await sensor.new_sensor(config[CONF_TEMPERATURE])
|
||||
cg.add(var.set_temperature_sensor(sens))
|
||||
|
||||
if CONF_HUMIDITY in config:
|
||||
sens = yield sensor.new_sensor(config[CONF_HUMIDITY])
|
||||
sens = await sensor.new_sensor(config[CONF_HUMIDITY])
|
||||
cg.add(var.set_humidity_sensor(sens))
|
||||
|
||||
@@ -7,6 +7,7 @@ from esphome.const import (
|
||||
CONF_TEMPERATURE,
|
||||
DEVICE_CLASS_HUMIDITY,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_CELSIUS,
|
||||
ICON_EMPTY,
|
||||
UNIT_PERCENT,
|
||||
@@ -24,10 +25,18 @@ CONFIG_SCHEMA = (
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(AM2320Component),
|
||||
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
|
||||
UNIT_CELSIUS, ICON_EMPTY, 1, DEVICE_CLASS_TEMPERATURE
|
||||
UNIT_CELSIUS,
|
||||
ICON_EMPTY,
|
||||
1,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
|
||||
UNIT_PERCENT, ICON_EMPTY, 1, DEVICE_CLASS_HUMIDITY
|
||||
UNIT_PERCENT,
|
||||
ICON_EMPTY,
|
||||
1,
|
||||
DEVICE_CLASS_HUMIDITY,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
}
|
||||
)
|
||||
@@ -36,15 +45,15 @@ CONFIG_SCHEMA = (
|
||||
)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
yield cg.register_component(var, config)
|
||||
yield i2c.register_i2c_device(var, config)
|
||||
await cg.register_component(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
|
||||
if CONF_TEMPERATURE in config:
|
||||
sens = yield sensor.new_sensor(config[CONF_TEMPERATURE])
|
||||
sens = await sensor.new_sensor(config[CONF_TEMPERATURE])
|
||||
cg.add(var.set_temperature_sensor(sens))
|
||||
|
||||
if CONF_HUMIDITY in config:
|
||||
sens = yield sensor.new_sensor(config[CONF_HUMIDITY])
|
||||
sens = await sensor.new_sensor(config[CONF_HUMIDITY])
|
||||
cg.add(var.set_humidity_sensor(sens))
|
||||
|
||||
@@ -34,7 +34,7 @@ CONFIG_SCHEMA = cv.All(font.validate_pillow_installed, ANIMATION_SCHEMA)
|
||||
CODEOWNERS = ["@syndlex"]
|
||||
|
||||
|
||||
def to_code(config):
|
||||
async def to_code(config):
|
||||
from PIL import Image
|
||||
|
||||
path = CORE.relative_config_path(config[CONF_FILE])
|
||||
|
||||
@@ -23,7 +23,7 @@ CONFIG_SCHEMA = (
|
||||
)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
yield cg.register_component(var, config)
|
||||
yield i2c.register_i2c_device(var, config)
|
||||
await cg.register_component(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
|
||||
@@ -24,8 +24,8 @@ CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend(
|
||||
)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
hub = yield cg.get_variable(config[CONF_APDS9960_ID])
|
||||
var = yield binary_sensor.new_binary_sensor(config)
|
||||
async def to_code(config):
|
||||
hub = await cg.get_variable(config[CONF_APDS9960_ID])
|
||||
var = await binary_sensor.new_binary_sensor(config)
|
||||
func = getattr(hub, DIRECTIONS[config[CONF_DIRECTION]])
|
||||
cg.add(func(var))
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import sensor
|
||||
from esphome.const import CONF_TYPE, DEVICE_CLASS_EMPTY, UNIT_PERCENT, ICON_LIGHTBULB
|
||||
from esphome.const import (
|
||||
CONF_TYPE,
|
||||
DEVICE_CLASS_EMPTY,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_PERCENT,
|
||||
ICON_LIGHTBULB,
|
||||
)
|
||||
from . import APDS9960, CONF_APDS9960_ID
|
||||
|
||||
DEPENDENCIES = ["apds9960"]
|
||||
@@ -15,7 +21,7 @@ TYPES = {
|
||||
}
|
||||
|
||||
CONFIG_SCHEMA = sensor.sensor_schema(
|
||||
UNIT_PERCENT, ICON_LIGHTBULB, 1, DEVICE_CLASS_EMPTY
|
||||
UNIT_PERCENT, ICON_LIGHTBULB, 1, DEVICE_CLASS_EMPTY, STATE_CLASS_MEASUREMENT
|
||||
).extend(
|
||||
{
|
||||
cv.Required(CONF_TYPE): cv.one_of(*TYPES, upper=True),
|
||||
@@ -24,8 +30,8 @@ CONFIG_SCHEMA = sensor.sensor_schema(
|
||||
)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
hub = yield cg.get_variable(config[CONF_APDS9960_ID])
|
||||
var = yield sensor.new_sensor(config)
|
||||
async def to_code(config):
|
||||
hub = await cg.get_variable(config[CONF_APDS9960_ID])
|
||||
var = await sensor.new_sensor(config)
|
||||
func = getattr(hub, TYPES[config[CONF_TYPE]])
|
||||
cg.add(func(var))
|
||||
|
||||
@@ -68,9 +68,9 @@ CONFIG_SCHEMA = cv.Schema(
|
||||
|
||||
|
||||
@coroutine_with_priority(40.0)
|
||||
def to_code(config):
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
yield cg.register_component(var, config)
|
||||
await cg.register_component(var, config)
|
||||
|
||||
cg.add(var.set_port(config[CONF_PORT]))
|
||||
cg.add(var.set_password(config[CONF_PASSWORD]))
|
||||
@@ -90,7 +90,7 @@ def to_code(config):
|
||||
conf[CONF_TRIGGER_ID], templ, conf[CONF_SERVICE], service_arg_names
|
||||
)
|
||||
cg.add(var.register_user_service(trigger))
|
||||
yield automation.build_automation(trigger, func_args, conf)
|
||||
await automation.build_automation(trigger, func_args, conf)
|
||||
|
||||
cg.add_define("USE_API")
|
||||
cg.add_global(api_ns.using)
|
||||
@@ -116,21 +116,21 @@ HOMEASSISTANT_SERVICE_ACTION_SCHEMA = cv.Schema(
|
||||
HomeAssistantServiceCallAction,
|
||||
HOMEASSISTANT_SERVICE_ACTION_SCHEMA,
|
||||
)
|
||||
def homeassistant_service_to_code(config, action_id, template_arg, args):
|
||||
serv = yield cg.get_variable(config[CONF_ID])
|
||||
async def homeassistant_service_to_code(config, action_id, template_arg, args):
|
||||
serv = await cg.get_variable(config[CONF_ID])
|
||||
var = cg.new_Pvariable(action_id, template_arg, serv, False)
|
||||
templ = yield cg.templatable(config[CONF_SERVICE], args, None)
|
||||
templ = await cg.templatable(config[CONF_SERVICE], args, None)
|
||||
cg.add(var.set_service(templ))
|
||||
for key, value in config[CONF_DATA].items():
|
||||
templ = yield cg.templatable(value, args, None)
|
||||
templ = await cg.templatable(value, args, None)
|
||||
cg.add(var.add_data(key, templ))
|
||||
for key, value in config[CONF_DATA_TEMPLATE].items():
|
||||
templ = yield cg.templatable(value, args, None)
|
||||
templ = await cg.templatable(value, args, None)
|
||||
cg.add(var.add_data_template(key, templ))
|
||||
for key, value in config[CONF_VARIABLES].items():
|
||||
templ = yield cg.templatable(value, args, None)
|
||||
templ = await cg.templatable(value, args, None)
|
||||
cg.add(var.add_variable(key, templ))
|
||||
yield var
|
||||
return var
|
||||
|
||||
|
||||
def validate_homeassistant_event(value):
|
||||
@@ -159,21 +159,21 @@ HOMEASSISTANT_EVENT_ACTION_SCHEMA = cv.Schema(
|
||||
HomeAssistantServiceCallAction,
|
||||
HOMEASSISTANT_EVENT_ACTION_SCHEMA,
|
||||
)
|
||||
def homeassistant_event_to_code(config, action_id, template_arg, args):
|
||||
serv = yield cg.get_variable(config[CONF_ID])
|
||||
async def homeassistant_event_to_code(config, action_id, template_arg, args):
|
||||
serv = await cg.get_variable(config[CONF_ID])
|
||||
var = cg.new_Pvariable(action_id, template_arg, serv, True)
|
||||
templ = yield cg.templatable(config[CONF_EVENT], args, None)
|
||||
templ = await cg.templatable(config[CONF_EVENT], args, None)
|
||||
cg.add(var.set_service(templ))
|
||||
for key, value in config[CONF_DATA].items():
|
||||
templ = yield cg.templatable(value, args, None)
|
||||
templ = await cg.templatable(value, args, None)
|
||||
cg.add(var.add_data(key, templ))
|
||||
for key, value in config[CONF_DATA_TEMPLATE].items():
|
||||
templ = yield cg.templatable(value, args, None)
|
||||
templ = await cg.templatable(value, args, None)
|
||||
cg.add(var.add_data_template(key, templ))
|
||||
for key, value in config[CONF_VARIABLES].items():
|
||||
templ = yield cg.templatable(value, args, None)
|
||||
templ = await cg.templatable(value, args, None)
|
||||
cg.add(var.add_variable(key, templ))
|
||||
yield var
|
||||
return var
|
||||
|
||||
|
||||
HOMEASSISTANT_TAG_SCANNED_ACTION_SCHEMA = cv.maybe_simple_value(
|
||||
@@ -190,15 +190,15 @@ HOMEASSISTANT_TAG_SCANNED_ACTION_SCHEMA = cv.maybe_simple_value(
|
||||
HomeAssistantServiceCallAction,
|
||||
HOMEASSISTANT_TAG_SCANNED_ACTION_SCHEMA,
|
||||
)
|
||||
def homeassistant_tag_scanned_to_code(config, action_id, template_arg, args):
|
||||
serv = yield cg.get_variable(config[CONF_ID])
|
||||
async def homeassistant_tag_scanned_to_code(config, action_id, template_arg, args):
|
||||
serv = await cg.get_variable(config[CONF_ID])
|
||||
var = cg.new_Pvariable(action_id, template_arg, serv, True)
|
||||
cg.add(var.set_service("esphome.tag_scanned"))
|
||||
templ = yield cg.templatable(config[CONF_TAG], args, cg.std_string)
|
||||
templ = await cg.templatable(config[CONF_TAG], args, cg.std_string)
|
||||
cg.add(var.add_data("tag_id", templ))
|
||||
yield var
|
||||
return var
|
||||
|
||||
|
||||
@automation.register_condition("api.connected", APIConnectedCondition, {})
|
||||
def api_connected_to_code(config, condition_id, template_arg, args):
|
||||
yield cg.new_Pvariable(condition_id, template_arg)
|
||||
async def api_connected_to_code(config, condition_id, template_arg, args):
|
||||
return cg.new_Pvariable(condition_id, template_arg)
|
||||
|
||||
@@ -176,6 +176,10 @@ message DeviceInfoResponse {
|
||||
string model = 6;
|
||||
|
||||
bool has_deep_sleep = 7;
|
||||
|
||||
// The esphome project details if set
|
||||
string project_name = 8;
|
||||
string project_version = 9;
|
||||
}
|
||||
|
||||
message ListEntitiesRequest {
|
||||
@@ -409,6 +413,11 @@ message LightCommandRequest {
|
||||
}
|
||||
|
||||
// ==================== SENSOR ====================
|
||||
enum SensorStateClass {
|
||||
STATE_CLASS_NONE = 0;
|
||||
STATE_CLASS_MEASUREMENT = 1;
|
||||
}
|
||||
|
||||
message ListEntitiesSensorResponse {
|
||||
option (id) = 16;
|
||||
option (source) = SOURCE_SERVER;
|
||||
@@ -424,6 +433,7 @@ message ListEntitiesSensorResponse {
|
||||
int32 accuracy_decimals = 7;
|
||||
bool force_update = 8;
|
||||
string device_class = 9;
|
||||
SensorStateClass state_class = 10;
|
||||
}
|
||||
message SensorStateResponse {
|
||||
option (id) = 25;
|
||||
@@ -561,6 +571,7 @@ message SubscribeHomeAssistantStateResponse {
|
||||
option (id) = 39;
|
||||
option (source) = SOURCE_SERVER;
|
||||
string entity_id = 1;
|
||||
string attribute = 2;
|
||||
}
|
||||
|
||||
message HomeAssistantStateResponse {
|
||||
@@ -570,6 +581,7 @@ message HomeAssistantStateResponse {
|
||||
|
||||
string entity_id = 1;
|
||||
string state = 2;
|
||||
string attribute = 3;
|
||||
}
|
||||
|
||||
// ==================== IMPORT TIME ====================
|
||||
@@ -664,11 +676,12 @@ message CameraImageRequest {
|
||||
// ==================== CLIMATE ====================
|
||||
enum ClimateMode {
|
||||
CLIMATE_MODE_OFF = 0;
|
||||
CLIMATE_MODE_AUTO = 1;
|
||||
CLIMATE_MODE_HEAT_COOL = 1;
|
||||
CLIMATE_MODE_COOL = 2;
|
||||
CLIMATE_MODE_HEAT = 3;
|
||||
CLIMATE_MODE_FAN_ONLY = 4;
|
||||
CLIMATE_MODE_DRY = 5;
|
||||
CLIMATE_MODE_AUTO = 6;
|
||||
}
|
||||
enum ClimateFanMode {
|
||||
CLIMATE_FAN_ON = 0;
|
||||
@@ -696,6 +709,15 @@ enum ClimateAction {
|
||||
CLIMATE_ACTION_DRYING = 5;
|
||||
CLIMATE_ACTION_FAN = 6;
|
||||
}
|
||||
enum ClimatePreset {
|
||||
CLIMATE_PRESET_ECO = 0;
|
||||
CLIMATE_PRESET_AWAY = 1;
|
||||
CLIMATE_PRESET_BOOST = 2;
|
||||
CLIMATE_PRESET_COMFORT = 3;
|
||||
CLIMATE_PRESET_HOME = 4;
|
||||
CLIMATE_PRESET_SLEEP = 5;
|
||||
CLIMATE_PRESET_ACTIVITY = 6;
|
||||
}
|
||||
message ListEntitiesClimateResponse {
|
||||
option (id) = 46;
|
||||
option (source) = SOURCE_SERVER;
|
||||
@@ -716,6 +738,9 @@ message ListEntitiesClimateResponse {
|
||||
bool supports_action = 12;
|
||||
repeated ClimateFanMode supported_fan_modes = 13;
|
||||
repeated ClimateSwingMode supported_swing_modes = 14;
|
||||
repeated string supported_custom_fan_modes = 15;
|
||||
repeated ClimatePreset supported_presets = 16;
|
||||
repeated string supported_custom_presets = 17;
|
||||
}
|
||||
message ClimateStateResponse {
|
||||
option (id) = 47;
|
||||
@@ -733,6 +758,9 @@ message ClimateStateResponse {
|
||||
ClimateAction action = 8;
|
||||
ClimateFanMode fan_mode = 9;
|
||||
ClimateSwingMode swing_mode = 10;
|
||||
string custom_fan_mode = 11;
|
||||
ClimatePreset preset = 12;
|
||||
string custom_preset = 13;
|
||||
}
|
||||
message ClimateCommandRequest {
|
||||
option (id) = 48;
|
||||
@@ -755,4 +783,10 @@ message ClimateCommandRequest {
|
||||
ClimateFanMode fan_mode = 13;
|
||||
bool has_swing_mode = 14;
|
||||
ClimateSwingMode swing_mode = 15;
|
||||
bool has_custom_fan_mode = 16;
|
||||
string custom_fan_mode = 17;
|
||||
bool has_preset = 18;
|
||||
ClimatePreset preset = 19;
|
||||
bool has_custom_preset = 20;
|
||||
string custom_preset = 21;
|
||||
}
|
||||
|
||||
@@ -395,6 +395,8 @@ bool APIConnection::send_sensor_info(sensor::Sensor *sensor) {
|
||||
msg.accuracy_decimals = sensor->get_accuracy_decimals();
|
||||
msg.force_update = sensor->get_force_update();
|
||||
msg.device_class = sensor->get_device_class();
|
||||
msg.state_class = static_cast<enums::SensorStateClass>(sensor->state_class);
|
||||
|
||||
return this->send_list_entities_sensor_response(msg);
|
||||
}
|
||||
#endif
|
||||
@@ -475,8 +477,14 @@ bool APIConnection::send_climate_state(climate::Climate *climate) {
|
||||
}
|
||||
if (traits.get_supports_away())
|
||||
resp.away = climate->away;
|
||||
if (traits.get_supports_fan_modes())
|
||||
resp.fan_mode = static_cast<enums::ClimateFanMode>(climate->fan_mode);
|
||||
if (traits.get_supports_fan_modes() && climate->fan_mode.has_value())
|
||||
resp.fan_mode = static_cast<enums::ClimateFanMode>(climate->fan_mode.value());
|
||||
if (!traits.get_supported_custom_fan_modes().empty() && climate->custom_fan_mode.has_value())
|
||||
resp.custom_fan_mode = climate->custom_fan_mode.value();
|
||||
if (traits.get_supports_presets() && climate->preset.has_value())
|
||||
resp.preset = static_cast<enums::ClimatePreset>(climate->preset.value());
|
||||
if (!traits.get_supported_custom_presets().empty() && climate->custom_preset.has_value())
|
||||
resp.custom_preset = climate->custom_preset.value();
|
||||
if (traits.get_supports_swing_modes())
|
||||
resp.swing_mode = static_cast<enums::ClimateSwingMode>(climate->swing_mode);
|
||||
return this->send_climate_state_response(resp);
|
||||
@@ -490,8 +498,9 @@ bool APIConnection::send_climate_info(climate::Climate *climate) {
|
||||
msg.unique_id = get_default_unique_id("climate", climate);
|
||||
msg.supports_current_temperature = traits.get_supports_current_temperature();
|
||||
msg.supports_two_point_target_temperature = traits.get_supports_two_point_target_temperature();
|
||||
for (auto mode : {climate::CLIMATE_MODE_AUTO, climate::CLIMATE_MODE_OFF, climate::CLIMATE_MODE_COOL,
|
||||
climate::CLIMATE_MODE_HEAT, climate::CLIMATE_MODE_DRY, climate::CLIMATE_MODE_FAN_ONLY}) {
|
||||
for (auto mode :
|
||||
{climate::CLIMATE_MODE_AUTO, climate::CLIMATE_MODE_OFF, climate::CLIMATE_MODE_COOL, climate::CLIMATE_MODE_HEAT,
|
||||
climate::CLIMATE_MODE_DRY, climate::CLIMATE_MODE_FAN_ONLY, climate::CLIMATE_MODE_HEAT_COOL}) {
|
||||
if (traits.supports_mode(mode))
|
||||
msg.supported_modes.push_back(static_cast<enums::ClimateMode>(mode));
|
||||
}
|
||||
@@ -506,6 +515,18 @@ bool APIConnection::send_climate_info(climate::Climate *climate) {
|
||||
if (traits.supports_fan_mode(fan_mode))
|
||||
msg.supported_fan_modes.push_back(static_cast<enums::ClimateFanMode>(fan_mode));
|
||||
}
|
||||
for (auto const &custom_fan_mode : traits.get_supported_custom_fan_modes()) {
|
||||
msg.supported_custom_fan_modes.push_back(custom_fan_mode);
|
||||
}
|
||||
for (auto preset : {climate::CLIMATE_PRESET_ECO, climate::CLIMATE_PRESET_AWAY, climate::CLIMATE_PRESET_BOOST,
|
||||
climate::CLIMATE_PRESET_COMFORT, climate::CLIMATE_PRESET_HOME, climate::CLIMATE_PRESET_SLEEP,
|
||||
climate::CLIMATE_PRESET_ACTIVITY}) {
|
||||
if (traits.supports_preset(preset))
|
||||
msg.supported_presets.push_back(static_cast<enums::ClimatePreset>(preset));
|
||||
}
|
||||
for (auto const &custom_preset : traits.get_supported_custom_presets()) {
|
||||
msg.supported_custom_presets.push_back(custom_preset);
|
||||
}
|
||||
for (auto swing_mode : {climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_BOTH, climate::CLIMATE_SWING_VERTICAL,
|
||||
climate::CLIMATE_SWING_HORIZONTAL}) {
|
||||
if (traits.supports_swing_mode(swing_mode))
|
||||
@@ -531,6 +552,12 @@ void APIConnection::climate_command(const ClimateCommandRequest &msg) {
|
||||
call.set_away(msg.away);
|
||||
if (msg.has_fan_mode)
|
||||
call.set_fan_mode(static_cast<climate::ClimateFanMode>(msg.fan_mode));
|
||||
if (msg.has_custom_fan_mode)
|
||||
call.set_fan_mode(msg.custom_fan_mode);
|
||||
if (msg.has_preset)
|
||||
call.set_preset(static_cast<climate::ClimatePreset>(msg.preset));
|
||||
if (msg.has_custom_preset)
|
||||
call.set_preset(msg.custom_preset);
|
||||
if (msg.has_swing_mode)
|
||||
call.set_swing_mode(static_cast<climate::ClimateSwingMode>(msg.swing_mode));
|
||||
call.perform();
|
||||
@@ -637,13 +664,18 @@ DeviceInfoResponse APIConnection::device_info(const DeviceInfoRequest &msg) {
|
||||
#endif
|
||||
#ifdef USE_DEEP_SLEEP
|
||||
resp.has_deep_sleep = deep_sleep::global_has_deep_sleep;
|
||||
#endif
|
||||
#ifdef ESPHOME_PROJECT_NAME
|
||||
resp.project_name = ESPHOME_PROJECT_NAME;
|
||||
resp.project_version = ESPHOME_PROJECT_VERSION;
|
||||
#endif
|
||||
return resp;
|
||||
}
|
||||
void APIConnection::on_home_assistant_state_response(const HomeAssistantStateResponse &msg) {
|
||||
for (auto &it : this->parent_->get_state_subs())
|
||||
if (it.entity_id == msg.entity_id)
|
||||
if (it.entity_id == msg.entity_id && it.attribute.value() == msg.attribute) {
|
||||
it.callback(msg.state);
|
||||
}
|
||||
}
|
||||
void APIConnection::execute_service(const ExecuteServiceRequest &msg) {
|
||||
bool found = false;
|
||||
@@ -660,6 +692,7 @@ void APIConnection::subscribe_home_assistant_states(const SubscribeHomeAssistant
|
||||
for (auto &it : this->parent_->get_state_subs()) {
|
||||
SubscribeHomeAssistantStateResponse resp;
|
||||
resp.entity_id = it.entity_id;
|
||||
resp.attribute = it.attribute.value();
|
||||
if (!this->send_subscribe_home_assistant_state_response(resp)) {
|
||||
this->on_fatal_error();
|
||||
return;
|
||||
|
||||
@@ -62,6 +62,16 @@ template<> const char *proto_enum_to_string<enums::FanDirection>(enums::FanDirec
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
template<> const char *proto_enum_to_string<enums::SensorStateClass>(enums::SensorStateClass value) {
|
||||
switch (value) {
|
||||
case enums::STATE_CLASS_NONE:
|
||||
return "STATE_CLASS_NONE";
|
||||
case enums::STATE_CLASS_MEASUREMENT:
|
||||
return "STATE_CLASS_MEASUREMENT";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
template<> const char *proto_enum_to_string<enums::LogLevel>(enums::LogLevel value) {
|
||||
switch (value) {
|
||||
case enums::LOG_LEVEL_NONE:
|
||||
@@ -108,8 +118,8 @@ template<> const char *proto_enum_to_string<enums::ClimateMode>(enums::ClimateMo
|
||||
switch (value) {
|
||||
case enums::CLIMATE_MODE_OFF:
|
||||
return "CLIMATE_MODE_OFF";
|
||||
case enums::CLIMATE_MODE_AUTO:
|
||||
return "CLIMATE_MODE_AUTO";
|
||||
case enums::CLIMATE_MODE_HEAT_COOL:
|
||||
return "CLIMATE_MODE_HEAT_COOL";
|
||||
case enums::CLIMATE_MODE_COOL:
|
||||
return "CLIMATE_MODE_COOL";
|
||||
case enums::CLIMATE_MODE_HEAT:
|
||||
@@ -118,6 +128,8 @@ template<> const char *proto_enum_to_string<enums::ClimateMode>(enums::ClimateMo
|
||||
return "CLIMATE_MODE_FAN_ONLY";
|
||||
case enums::CLIMATE_MODE_DRY:
|
||||
return "CLIMATE_MODE_DRY";
|
||||
case enums::CLIMATE_MODE_AUTO:
|
||||
return "CLIMATE_MODE_AUTO";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
@@ -178,6 +190,26 @@ template<> const char *proto_enum_to_string<enums::ClimateAction>(enums::Climate
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
template<> const char *proto_enum_to_string<enums::ClimatePreset>(enums::ClimatePreset value) {
|
||||
switch (value) {
|
||||
case enums::CLIMATE_PRESET_ECO:
|
||||
return "CLIMATE_PRESET_ECO";
|
||||
case enums::CLIMATE_PRESET_AWAY:
|
||||
return "CLIMATE_PRESET_AWAY";
|
||||
case enums::CLIMATE_PRESET_BOOST:
|
||||
return "CLIMATE_PRESET_BOOST";
|
||||
case enums::CLIMATE_PRESET_COMFORT:
|
||||
return "CLIMATE_PRESET_COMFORT";
|
||||
case enums::CLIMATE_PRESET_HOME:
|
||||
return "CLIMATE_PRESET_HOME";
|
||||
case enums::CLIMATE_PRESET_SLEEP:
|
||||
return "CLIMATE_PRESET_SLEEP";
|
||||
case enums::CLIMATE_PRESET_ACTIVITY:
|
||||
return "CLIMATE_PRESET_ACTIVITY";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
bool HelloRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
|
||||
switch (field_id) {
|
||||
case 1: {
|
||||
@@ -328,6 +360,14 @@ bool DeviceInfoResponse::decode_length(uint32_t field_id, ProtoLengthDelimited v
|
||||
this->model = value.as_string();
|
||||
return true;
|
||||
}
|
||||
case 8: {
|
||||
this->project_name = value.as_string();
|
||||
return true;
|
||||
}
|
||||
case 9: {
|
||||
this->project_version = value.as_string();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -340,6 +380,8 @@ void DeviceInfoResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_string(5, this->compilation_time);
|
||||
buffer.encode_string(6, this->model);
|
||||
buffer.encode_bool(7, this->has_deep_sleep);
|
||||
buffer.encode_string(8, this->project_name);
|
||||
buffer.encode_string(9, this->project_version);
|
||||
}
|
||||
void DeviceInfoResponse::dump_to(std::string &out) const {
|
||||
char buffer[64];
|
||||
@@ -371,6 +413,14 @@ void DeviceInfoResponse::dump_to(std::string &out) const {
|
||||
out.append(" has_deep_sleep: ");
|
||||
out.append(YESNO(this->has_deep_sleep));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" project_name: ");
|
||||
out.append("'").append(this->project_name).append("'");
|
||||
out.append("\n");
|
||||
|
||||
out.append(" project_version: ");
|
||||
out.append("'").append(this->project_version).append("'");
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
void ListEntitiesRequest::encode(ProtoWriteBuffer buffer) const {}
|
||||
@@ -1507,6 +1557,10 @@ bool ListEntitiesSensorResponse::decode_varint(uint32_t field_id, ProtoVarInt va
|
||||
this->force_update = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
case 10: {
|
||||
this->state_class = value.as_enum<enums::SensorStateClass>();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -1561,6 +1615,7 @@ void ListEntitiesSensorResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_int32(7, this->accuracy_decimals);
|
||||
buffer.encode_bool(8, this->force_update);
|
||||
buffer.encode_string(9, this->device_class);
|
||||
buffer.encode_enum<enums::SensorStateClass>(10, this->state_class);
|
||||
}
|
||||
void ListEntitiesSensorResponse::dump_to(std::string &out) const {
|
||||
char buffer[64];
|
||||
@@ -1602,6 +1657,10 @@ void ListEntitiesSensorResponse::dump_to(std::string &out) const {
|
||||
out.append(" device_class: ");
|
||||
out.append("'").append(this->device_class).append("'");
|
||||
out.append("\n");
|
||||
|
||||
out.append(" state_class: ");
|
||||
out.append(proto_enum_to_string<enums::SensorStateClass>(this->state_class));
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
bool SensorStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
@@ -2123,12 +2182,17 @@ bool SubscribeHomeAssistantStateResponse::decode_length(uint32_t field_id, Proto
|
||||
this->entity_id = value.as_string();
|
||||
return true;
|
||||
}
|
||||
case 2: {
|
||||
this->attribute = value.as_string();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
void SubscribeHomeAssistantStateResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_string(1, this->entity_id);
|
||||
buffer.encode_string(2, this->attribute);
|
||||
}
|
||||
void SubscribeHomeAssistantStateResponse::dump_to(std::string &out) const {
|
||||
char buffer[64];
|
||||
@@ -2136,6 +2200,10 @@ void SubscribeHomeAssistantStateResponse::dump_to(std::string &out) const {
|
||||
out.append(" entity_id: ");
|
||||
out.append("'").append(this->entity_id).append("'");
|
||||
out.append("\n");
|
||||
|
||||
out.append(" attribute: ");
|
||||
out.append("'").append(this->attribute).append("'");
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
bool HomeAssistantStateResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
|
||||
@@ -2148,6 +2216,10 @@ bool HomeAssistantStateResponse::decode_length(uint32_t field_id, ProtoLengthDel
|
||||
this->state = value.as_string();
|
||||
return true;
|
||||
}
|
||||
case 3: {
|
||||
this->attribute = value.as_string();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -2155,6 +2227,7 @@ bool HomeAssistantStateResponse::decode_length(uint32_t field_id, ProtoLengthDel
|
||||
void HomeAssistantStateResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_string(1, this->entity_id);
|
||||
buffer.encode_string(2, this->state);
|
||||
buffer.encode_string(3, this->attribute);
|
||||
}
|
||||
void HomeAssistantStateResponse::dump_to(std::string &out) const {
|
||||
char buffer[64];
|
||||
@@ -2166,6 +2239,10 @@ void HomeAssistantStateResponse::dump_to(std::string &out) const {
|
||||
out.append(" state: ");
|
||||
out.append("'").append(this->state).append("'");
|
||||
out.append("\n");
|
||||
|
||||
out.append(" attribute: ");
|
||||
out.append("'").append(this->attribute).append("'");
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
void GetTimeRequest::encode(ProtoWriteBuffer buffer) const {}
|
||||
@@ -2610,6 +2687,10 @@ bool ListEntitiesClimateResponse::decode_varint(uint32_t field_id, ProtoVarInt v
|
||||
this->supported_swing_modes.push_back(value.as_enum<enums::ClimateSwingMode>());
|
||||
return true;
|
||||
}
|
||||
case 16: {
|
||||
this->supported_presets.push_back(value.as_enum<enums::ClimatePreset>());
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -2628,6 +2709,14 @@ bool ListEntitiesClimateResponse::decode_length(uint32_t field_id, ProtoLengthDe
|
||||
this->unique_id = value.as_string();
|
||||
return true;
|
||||
}
|
||||
case 15: {
|
||||
this->supported_custom_fan_modes.push_back(value.as_string());
|
||||
return true;
|
||||
}
|
||||
case 17: {
|
||||
this->supported_custom_presets.push_back(value.as_string());
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -2675,6 +2764,15 @@ void ListEntitiesClimateResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
for (auto &it : this->supported_swing_modes) {
|
||||
buffer.encode_enum<enums::ClimateSwingMode>(14, it, true);
|
||||
}
|
||||
for (auto &it : this->supported_custom_fan_modes) {
|
||||
buffer.encode_string(15, it, true);
|
||||
}
|
||||
for (auto &it : this->supported_presets) {
|
||||
buffer.encode_enum<enums::ClimatePreset>(16, it, true);
|
||||
}
|
||||
for (auto &it : this->supported_custom_presets) {
|
||||
buffer.encode_string(17, it, true);
|
||||
}
|
||||
}
|
||||
void ListEntitiesClimateResponse::dump_to(std::string &out) const {
|
||||
char buffer[64];
|
||||
@@ -2744,6 +2842,24 @@ void ListEntitiesClimateResponse::dump_to(std::string &out) const {
|
||||
out.append(proto_enum_to_string<enums::ClimateSwingMode>(it));
|
||||
out.append("\n");
|
||||
}
|
||||
|
||||
for (const auto &it : this->supported_custom_fan_modes) {
|
||||
out.append(" supported_custom_fan_modes: ");
|
||||
out.append("'").append(it).append("'");
|
||||
out.append("\n");
|
||||
}
|
||||
|
||||
for (const auto &it : this->supported_presets) {
|
||||
out.append(" supported_presets: ");
|
||||
out.append(proto_enum_to_string<enums::ClimatePreset>(it));
|
||||
out.append("\n");
|
||||
}
|
||||
|
||||
for (const auto &it : this->supported_custom_presets) {
|
||||
out.append(" supported_custom_presets: ");
|
||||
out.append("'").append(it).append("'");
|
||||
out.append("\n");
|
||||
}
|
||||
out.append("}");
|
||||
}
|
||||
bool ClimateStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
@@ -2768,6 +2884,24 @@ bool ClimateStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
this->swing_mode = value.as_enum<enums::ClimateSwingMode>();
|
||||
return true;
|
||||
}
|
||||
case 12: {
|
||||
this->preset = value.as_enum<enums::ClimatePreset>();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bool ClimateStateResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
|
||||
switch (field_id) {
|
||||
case 11: {
|
||||
this->custom_fan_mode = value.as_string();
|
||||
return true;
|
||||
}
|
||||
case 13: {
|
||||
this->custom_preset = value.as_string();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -2809,6 +2943,9 @@ void ClimateStateResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_enum<enums::ClimateAction>(8, this->action);
|
||||
buffer.encode_enum<enums::ClimateFanMode>(9, this->fan_mode);
|
||||
buffer.encode_enum<enums::ClimateSwingMode>(10, this->swing_mode);
|
||||
buffer.encode_string(11, this->custom_fan_mode);
|
||||
buffer.encode_enum<enums::ClimatePreset>(12, this->preset);
|
||||
buffer.encode_string(13, this->custom_preset);
|
||||
}
|
||||
void ClimateStateResponse::dump_to(std::string &out) const {
|
||||
char buffer[64];
|
||||
@@ -2857,6 +2994,18 @@ void ClimateStateResponse::dump_to(std::string &out) const {
|
||||
out.append(" swing_mode: ");
|
||||
out.append(proto_enum_to_string<enums::ClimateSwingMode>(this->swing_mode));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" custom_fan_mode: ");
|
||||
out.append("'").append(this->custom_fan_mode).append("'");
|
||||
out.append("\n");
|
||||
|
||||
out.append(" preset: ");
|
||||
out.append(proto_enum_to_string<enums::ClimatePreset>(this->preset));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" custom_preset: ");
|
||||
out.append("'").append(this->custom_preset).append("'");
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
bool ClimateCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
@@ -2905,6 +3054,36 @@ bool ClimateCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value)
|
||||
this->swing_mode = value.as_enum<enums::ClimateSwingMode>();
|
||||
return true;
|
||||
}
|
||||
case 16: {
|
||||
this->has_custom_fan_mode = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
case 18: {
|
||||
this->has_preset = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
case 19: {
|
||||
this->preset = value.as_enum<enums::ClimatePreset>();
|
||||
return true;
|
||||
}
|
||||
case 20: {
|
||||
this->has_custom_preset = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bool ClimateCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
|
||||
switch (field_id) {
|
||||
case 17: {
|
||||
this->custom_fan_mode = value.as_string();
|
||||
return true;
|
||||
}
|
||||
case 21: {
|
||||
this->custom_preset = value.as_string();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -2947,6 +3126,12 @@ void ClimateCommandRequest::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_enum<enums::ClimateFanMode>(13, this->fan_mode);
|
||||
buffer.encode_bool(14, this->has_swing_mode);
|
||||
buffer.encode_enum<enums::ClimateSwingMode>(15, this->swing_mode);
|
||||
buffer.encode_bool(16, this->has_custom_fan_mode);
|
||||
buffer.encode_string(17, this->custom_fan_mode);
|
||||
buffer.encode_bool(18, this->has_preset);
|
||||
buffer.encode_enum<enums::ClimatePreset>(19, this->preset);
|
||||
buffer.encode_bool(20, this->has_custom_preset);
|
||||
buffer.encode_string(21, this->custom_preset);
|
||||
}
|
||||
void ClimateCommandRequest::dump_to(std::string &out) const {
|
||||
char buffer[64];
|
||||
@@ -3014,6 +3199,30 @@ void ClimateCommandRequest::dump_to(std::string &out) const {
|
||||
out.append(" swing_mode: ");
|
||||
out.append(proto_enum_to_string<enums::ClimateSwingMode>(this->swing_mode));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" has_custom_fan_mode: ");
|
||||
out.append(YESNO(this->has_custom_fan_mode));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" custom_fan_mode: ");
|
||||
out.append("'").append(this->custom_fan_mode).append("'");
|
||||
out.append("\n");
|
||||
|
||||
out.append(" has_preset: ");
|
||||
out.append(YESNO(this->has_preset));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" preset: ");
|
||||
out.append(proto_enum_to_string<enums::ClimatePreset>(this->preset));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" has_custom_preset: ");
|
||||
out.append(YESNO(this->has_custom_preset));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" custom_preset: ");
|
||||
out.append("'").append(this->custom_preset).append("'");
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
|
||||
|
||||
@@ -32,6 +32,10 @@ enum FanDirection : uint32_t {
|
||||
FAN_DIRECTION_FORWARD = 0,
|
||||
FAN_DIRECTION_REVERSE = 1,
|
||||
};
|
||||
enum SensorStateClass : uint32_t {
|
||||
STATE_CLASS_NONE = 0,
|
||||
STATE_CLASS_MEASUREMENT = 1,
|
||||
};
|
||||
enum LogLevel : uint32_t {
|
||||
LOG_LEVEL_NONE = 0,
|
||||
LOG_LEVEL_ERROR = 1,
|
||||
@@ -53,11 +57,12 @@ enum ServiceArgType : uint32_t {
|
||||
};
|
||||
enum ClimateMode : uint32_t {
|
||||
CLIMATE_MODE_OFF = 0,
|
||||
CLIMATE_MODE_AUTO = 1,
|
||||
CLIMATE_MODE_HEAT_COOL = 1,
|
||||
CLIMATE_MODE_COOL = 2,
|
||||
CLIMATE_MODE_HEAT = 3,
|
||||
CLIMATE_MODE_FAN_ONLY = 4,
|
||||
CLIMATE_MODE_DRY = 5,
|
||||
CLIMATE_MODE_AUTO = 6,
|
||||
};
|
||||
enum ClimateFanMode : uint32_t {
|
||||
CLIMATE_FAN_ON = 0,
|
||||
@@ -84,12 +89,21 @@ enum ClimateAction : uint32_t {
|
||||
CLIMATE_ACTION_DRYING = 5,
|
||||
CLIMATE_ACTION_FAN = 6,
|
||||
};
|
||||
enum ClimatePreset : uint32_t {
|
||||
CLIMATE_PRESET_ECO = 0,
|
||||
CLIMATE_PRESET_AWAY = 1,
|
||||
CLIMATE_PRESET_BOOST = 2,
|
||||
CLIMATE_PRESET_COMFORT = 3,
|
||||
CLIMATE_PRESET_HOME = 4,
|
||||
CLIMATE_PRESET_SLEEP = 5,
|
||||
CLIMATE_PRESET_ACTIVITY = 6,
|
||||
};
|
||||
|
||||
} // namespace enums
|
||||
|
||||
class HelloRequest : public ProtoMessage {
|
||||
public:
|
||||
std::string client_info{}; // NOLINT
|
||||
std::string client_info{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
@@ -98,9 +112,9 @@ class HelloRequest : public ProtoMessage {
|
||||
};
|
||||
class HelloResponse : public ProtoMessage {
|
||||
public:
|
||||
uint32_t api_version_major{0}; // NOLINT
|
||||
uint32_t api_version_minor{0}; // NOLINT
|
||||
std::string server_info{}; // NOLINT
|
||||
uint32_t api_version_major{0};
|
||||
uint32_t api_version_minor{0};
|
||||
std::string server_info{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
@@ -110,7 +124,7 @@ class HelloResponse : public ProtoMessage {
|
||||
};
|
||||
class ConnectRequest : public ProtoMessage {
|
||||
public:
|
||||
std::string password{}; // NOLINT
|
||||
std::string password{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
@@ -119,7 +133,7 @@ class ConnectRequest : public ProtoMessage {
|
||||
};
|
||||
class ConnectResponse : public ProtoMessage {
|
||||
public:
|
||||
bool invalid_password{false}; // NOLINT
|
||||
bool invalid_password{false};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
@@ -163,13 +177,15 @@ class DeviceInfoRequest : public ProtoMessage {
|
||||
};
|
||||
class DeviceInfoResponse : public ProtoMessage {
|
||||
public:
|
||||
bool uses_password{false}; // NOLINT
|
||||
std::string name{}; // NOLINT
|
||||
std::string mac_address{}; // NOLINT
|
||||
std::string esphome_version{}; // NOLINT
|
||||
std::string compilation_time{}; // NOLINT
|
||||
std::string model{}; // NOLINT
|
||||
bool has_deep_sleep{false}; // NOLINT
|
||||
bool uses_password{false};
|
||||
std::string name{};
|
||||
std::string mac_address{};
|
||||
std::string esphome_version{};
|
||||
std::string compilation_time{};
|
||||
std::string model{};
|
||||
bool has_deep_sleep{false};
|
||||
std::string project_name{};
|
||||
std::string project_version{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
@@ -200,12 +216,12 @@ class SubscribeStatesRequest : public ProtoMessage {
|
||||
};
|
||||
class ListEntitiesBinarySensorResponse : public ProtoMessage {
|
||||
public:
|
||||
std::string object_id{}; // NOLINT
|
||||
uint32_t key{0}; // NOLINT
|
||||
std::string name{}; // NOLINT
|
||||
std::string unique_id{}; // NOLINT
|
||||
std::string device_class{}; // NOLINT
|
||||
bool is_status_binary_sensor{false}; // NOLINT
|
||||
std::string object_id{};
|
||||
uint32_t key{0};
|
||||
std::string name{};
|
||||
std::string unique_id{};
|
||||
std::string device_class{};
|
||||
bool is_status_binary_sensor{false};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
@@ -216,9 +232,9 @@ class ListEntitiesBinarySensorResponse : public ProtoMessage {
|
||||
};
|
||||
class BinarySensorStateResponse : public ProtoMessage {
|
||||
public:
|
||||
uint32_t key{0}; // NOLINT
|
||||
bool state{false}; // NOLINT
|
||||
bool missing_state{false}; // NOLINT
|
||||
uint32_t key{0};
|
||||
bool state{false};
|
||||
bool missing_state{false};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
@@ -228,14 +244,14 @@ class BinarySensorStateResponse : public ProtoMessage {
|
||||
};
|
||||
class ListEntitiesCoverResponse : public ProtoMessage {
|
||||
public:
|
||||
std::string object_id{}; // NOLINT
|
||||
uint32_t key{0}; // NOLINT
|
||||
std::string name{}; // NOLINT
|
||||
std::string unique_id{}; // NOLINT
|
||||
bool assumed_state{false}; // NOLINT
|
||||
bool supports_position{false}; // NOLINT
|
||||
bool supports_tilt{false}; // NOLINT
|
||||
std::string device_class{}; // NOLINT
|
||||
std::string object_id{};
|
||||
uint32_t key{0};
|
||||
std::string name{};
|
||||
std::string unique_id{};
|
||||
bool assumed_state{false};
|
||||
bool supports_position{false};
|
||||
bool supports_tilt{false};
|
||||
std::string device_class{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
@@ -246,11 +262,11 @@ class ListEntitiesCoverResponse : public ProtoMessage {
|
||||
};
|
||||
class CoverStateResponse : public ProtoMessage {
|
||||
public:
|
||||
uint32_t key{0}; // NOLINT
|
||||
enums::LegacyCoverState legacy_state{}; // NOLINT
|
||||
float position{0.0f}; // NOLINT
|
||||
float tilt{0.0f}; // NOLINT
|
||||
enums::CoverOperation current_operation{}; // NOLINT
|
||||
uint32_t key{0};
|
||||
enums::LegacyCoverState legacy_state{};
|
||||
float position{0.0f};
|
||||
float tilt{0.0f};
|
||||
enums::CoverOperation current_operation{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
@@ -260,14 +276,14 @@ class CoverStateResponse : public ProtoMessage {
|
||||
};
|
||||
class CoverCommandRequest : public ProtoMessage {
|
||||
public:
|
||||
uint32_t key{0}; // NOLINT
|
||||
bool has_legacy_command{false}; // NOLINT
|
||||
enums::LegacyCoverCommand legacy_command{}; // NOLINT
|
||||
bool has_position{false}; // NOLINT
|
||||
float position{0.0f}; // NOLINT
|
||||
bool has_tilt{false}; // NOLINT
|
||||
float tilt{0.0f}; // NOLINT
|
||||
bool stop{false}; // NOLINT
|
||||
uint32_t key{0};
|
||||
bool has_legacy_command{false};
|
||||
enums::LegacyCoverCommand legacy_command{};
|
||||
bool has_position{false};
|
||||
float position{0.0f};
|
||||
bool has_tilt{false};
|
||||
float tilt{0.0f};
|
||||
bool stop{false};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
@@ -277,14 +293,14 @@ class CoverCommandRequest : public ProtoMessage {
|
||||
};
|
||||
class ListEntitiesFanResponse : public ProtoMessage {
|
||||
public:
|
||||
std::string object_id{}; // NOLINT
|
||||
uint32_t key{0}; // NOLINT
|
||||
std::string name{}; // NOLINT
|
||||
std::string unique_id{}; // NOLINT
|
||||
bool supports_oscillation{false}; // NOLINT
|
||||
bool supports_speed{false}; // NOLINT
|
||||
bool supports_direction{false}; // NOLINT
|
||||
int32_t supported_speed_count{0}; // NOLINT
|
||||
std::string object_id{};
|
||||
uint32_t key{0};
|
||||
std::string name{};
|
||||
std::string unique_id{};
|
||||
bool supports_oscillation{false};
|
||||
bool supports_speed{false};
|
||||
bool supports_direction{false};
|
||||
int32_t supported_speed_count{0};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
@@ -295,12 +311,12 @@ class ListEntitiesFanResponse : public ProtoMessage {
|
||||
};
|
||||
class FanStateResponse : public ProtoMessage {
|
||||
public:
|
||||
uint32_t key{0}; // NOLINT
|
||||
bool state{false}; // NOLINT
|
||||
bool oscillating{false}; // NOLINT
|
||||
enums::FanSpeed speed{}; // NOLINT
|
||||
enums::FanDirection direction{}; // NOLINT
|
||||
int32_t speed_level{0}; // NOLINT
|
||||
uint32_t key{0};
|
||||
bool state{false};
|
||||
bool oscillating{false};
|
||||
enums::FanSpeed speed{};
|
||||
enums::FanDirection direction{};
|
||||
int32_t speed_level{0};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
@@ -310,17 +326,17 @@ class FanStateResponse : public ProtoMessage {
|
||||
};
|
||||
class FanCommandRequest : public ProtoMessage {
|
||||
public:
|
||||
uint32_t key{0}; // NOLINT
|
||||
bool has_state{false}; // NOLINT
|
||||
bool state{false}; // NOLINT
|
||||
bool has_speed{false}; // NOLINT
|
||||
enums::FanSpeed speed{}; // NOLINT
|
||||
bool has_oscillating{false}; // NOLINT
|
||||
bool oscillating{false}; // NOLINT
|
||||
bool has_direction{false}; // NOLINT
|
||||
enums::FanDirection direction{}; // NOLINT
|
||||
bool has_speed_level{false}; // NOLINT
|
||||
int32_t speed_level{0}; // NOLINT
|
||||
uint32_t key{0};
|
||||
bool has_state{false};
|
||||
bool state{false};
|
||||
bool has_speed{false};
|
||||
enums::FanSpeed speed{};
|
||||
bool has_oscillating{false};
|
||||
bool oscillating{false};
|
||||
bool has_direction{false};
|
||||
enums::FanDirection direction{};
|
||||
bool has_speed_level{false};
|
||||
int32_t speed_level{0};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
@@ -330,17 +346,17 @@ class FanCommandRequest : public ProtoMessage {
|
||||
};
|
||||
class ListEntitiesLightResponse : public ProtoMessage {
|
||||
public:
|
||||
std::string object_id{}; // NOLINT
|
||||
uint32_t key{0}; // NOLINT
|
||||
std::string name{}; // NOLINT
|
||||
std::string unique_id{}; // NOLINT
|
||||
bool supports_brightness{false}; // NOLINT
|
||||
bool supports_rgb{false}; // NOLINT
|
||||
bool supports_white_value{false}; // NOLINT
|
||||
bool supports_color_temperature{false}; // NOLINT
|
||||
float min_mireds{0.0f}; // NOLINT
|
||||
float max_mireds{0.0f}; // NOLINT
|
||||
std::vector<std::string> effects{}; // NOLINT
|
||||
std::string object_id{};
|
||||
uint32_t key{0};
|
||||
std::string name{};
|
||||
std::string unique_id{};
|
||||
bool supports_brightness{false};
|
||||
bool supports_rgb{false};
|
||||
bool supports_white_value{false};
|
||||
bool supports_color_temperature{false};
|
||||
float min_mireds{0.0f};
|
||||
float max_mireds{0.0f};
|
||||
std::vector<std::string> effects{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
@@ -351,15 +367,15 @@ class ListEntitiesLightResponse : public ProtoMessage {
|
||||
};
|
||||
class LightStateResponse : public ProtoMessage {
|
||||
public:
|
||||
uint32_t key{0}; // NOLINT
|
||||
bool state{false}; // NOLINT
|
||||
float brightness{0.0f}; // NOLINT
|
||||
float red{0.0f}; // NOLINT
|
||||
float green{0.0f}; // NOLINT
|
||||
float blue{0.0f}; // NOLINT
|
||||
float white{0.0f}; // NOLINT
|
||||
float color_temperature{0.0f}; // NOLINT
|
||||
std::string effect{}; // NOLINT
|
||||
uint32_t key{0};
|
||||
bool state{false};
|
||||
float brightness{0.0f};
|
||||
float red{0.0f};
|
||||
float green{0.0f};
|
||||
float blue{0.0f};
|
||||
float white{0.0f};
|
||||
float color_temperature{0.0f};
|
||||
std::string effect{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
@@ -370,25 +386,25 @@ class LightStateResponse : public ProtoMessage {
|
||||
};
|
||||
class LightCommandRequest : public ProtoMessage {
|
||||
public:
|
||||
uint32_t key{0}; // NOLINT
|
||||
bool has_state{false}; // NOLINT
|
||||
bool state{false}; // NOLINT
|
||||
bool has_brightness{false}; // NOLINT
|
||||
float brightness{0.0f}; // NOLINT
|
||||
bool has_rgb{false}; // NOLINT
|
||||
float red{0.0f}; // NOLINT
|
||||
float green{0.0f}; // NOLINT
|
||||
float blue{0.0f}; // NOLINT
|
||||
bool has_white{false}; // NOLINT
|
||||
float white{0.0f}; // NOLINT
|
||||
bool has_color_temperature{false}; // NOLINT
|
||||
float color_temperature{0.0f}; // NOLINT
|
||||
bool has_transition_length{false}; // NOLINT
|
||||
uint32_t transition_length{0}; // NOLINT
|
||||
bool has_flash_length{false}; // NOLINT
|
||||
uint32_t flash_length{0}; // NOLINT
|
||||
bool has_effect{false}; // NOLINT
|
||||
std::string effect{}; // NOLINT
|
||||
uint32_t key{0};
|
||||
bool has_state{false};
|
||||
bool state{false};
|
||||
bool has_brightness{false};
|
||||
float brightness{0.0f};
|
||||
bool has_rgb{false};
|
||||
float red{0.0f};
|
||||
float green{0.0f};
|
||||
float blue{0.0f};
|
||||
bool has_white{false};
|
||||
float white{0.0f};
|
||||
bool has_color_temperature{false};
|
||||
float color_temperature{0.0f};
|
||||
bool has_transition_length{false};
|
||||
uint32_t transition_length{0};
|
||||
bool has_flash_length{false};
|
||||
uint32_t flash_length{0};
|
||||
bool has_effect{false};
|
||||
std::string effect{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
@@ -399,15 +415,16 @@ class LightCommandRequest : public ProtoMessage {
|
||||
};
|
||||
class ListEntitiesSensorResponse : public ProtoMessage {
|
||||
public:
|
||||
std::string object_id{}; // NOLINT
|
||||
uint32_t key{0}; // NOLINT
|
||||
std::string name{}; // NOLINT
|
||||
std::string unique_id{}; // NOLINT
|
||||
std::string icon{}; // NOLINT
|
||||
std::string unit_of_measurement{}; // NOLINT
|
||||
int32_t accuracy_decimals{0}; // NOLINT
|
||||
bool force_update{false}; // NOLINT
|
||||
std::string device_class{}; // NOLINT
|
||||
std::string object_id{};
|
||||
uint32_t key{0};
|
||||
std::string name{};
|
||||
std::string unique_id{};
|
||||
std::string icon{};
|
||||
std::string unit_of_measurement{};
|
||||
int32_t accuracy_decimals{0};
|
||||
bool force_update{false};
|
||||
std::string device_class{};
|
||||
enums::SensorStateClass state_class{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
@@ -418,9 +435,9 @@ class ListEntitiesSensorResponse : public ProtoMessage {
|
||||
};
|
||||
class SensorStateResponse : public ProtoMessage {
|
||||
public:
|
||||
uint32_t key{0}; // NOLINT
|
||||
float state{0.0f}; // NOLINT
|
||||
bool missing_state{false}; // NOLINT
|
||||
uint32_t key{0};
|
||||
float state{0.0f};
|
||||
bool missing_state{false};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
@@ -430,12 +447,12 @@ class SensorStateResponse : public ProtoMessage {
|
||||
};
|
||||
class ListEntitiesSwitchResponse : public ProtoMessage {
|
||||
public:
|
||||
std::string object_id{}; // NOLINT
|
||||
uint32_t key{0}; // NOLINT
|
||||
std::string name{}; // NOLINT
|
||||
std::string unique_id{}; // NOLINT
|
||||
std::string icon{}; // NOLINT
|
||||
bool assumed_state{false}; // NOLINT
|
||||
std::string object_id{};
|
||||
uint32_t key{0};
|
||||
std::string name{};
|
||||
std::string unique_id{};
|
||||
std::string icon{};
|
||||
bool assumed_state{false};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
@@ -446,8 +463,8 @@ class ListEntitiesSwitchResponse : public ProtoMessage {
|
||||
};
|
||||
class SwitchStateResponse : public ProtoMessage {
|
||||
public:
|
||||
uint32_t key{0}; // NOLINT
|
||||
bool state{false}; // NOLINT
|
||||
uint32_t key{0};
|
||||
bool state{false};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
@@ -457,8 +474,8 @@ class SwitchStateResponse : public ProtoMessage {
|
||||
};
|
||||
class SwitchCommandRequest : public ProtoMessage {
|
||||
public:
|
||||
uint32_t key{0}; // NOLINT
|
||||
bool state{false}; // NOLINT
|
||||
uint32_t key{0};
|
||||
bool state{false};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
@@ -468,11 +485,11 @@ class SwitchCommandRequest : public ProtoMessage {
|
||||
};
|
||||
class ListEntitiesTextSensorResponse : public ProtoMessage {
|
||||
public:
|
||||
std::string object_id{}; // NOLINT
|
||||
uint32_t key{0}; // NOLINT
|
||||
std::string name{}; // NOLINT
|
||||
std::string unique_id{}; // NOLINT
|
||||
std::string icon{}; // NOLINT
|
||||
std::string object_id{};
|
||||
uint32_t key{0};
|
||||
std::string name{};
|
||||
std::string unique_id{};
|
||||
std::string icon{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
@@ -482,9 +499,9 @@ class ListEntitiesTextSensorResponse : public ProtoMessage {
|
||||
};
|
||||
class TextSensorStateResponse : public ProtoMessage {
|
||||
public:
|
||||
uint32_t key{0}; // NOLINT
|
||||
std::string state{}; // NOLINT
|
||||
bool missing_state{false}; // NOLINT
|
||||
uint32_t key{0};
|
||||
std::string state{};
|
||||
bool missing_state{false};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
@@ -495,8 +512,8 @@ class TextSensorStateResponse : public ProtoMessage {
|
||||
};
|
||||
class SubscribeLogsRequest : public ProtoMessage {
|
||||
public:
|
||||
enums::LogLevel level{}; // NOLINT
|
||||
bool dump_config{false}; // NOLINT
|
||||
enums::LogLevel level{};
|
||||
bool dump_config{false};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
@@ -505,10 +522,10 @@ class SubscribeLogsRequest : public ProtoMessage {
|
||||
};
|
||||
class SubscribeLogsResponse : public ProtoMessage {
|
||||
public:
|
||||
enums::LogLevel level{}; // NOLINT
|
||||
std::string tag{}; // NOLINT
|
||||
std::string message{}; // NOLINT
|
||||
bool send_failed{false}; // NOLINT
|
||||
enums::LogLevel level{};
|
||||
std::string tag{};
|
||||
std::string message{};
|
||||
bool send_failed{false};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
@@ -525,8 +542,8 @@ class SubscribeHomeassistantServicesRequest : public ProtoMessage {
|
||||
};
|
||||
class HomeassistantServiceMap : public ProtoMessage {
|
||||
public:
|
||||
std::string key{}; // NOLINT
|
||||
std::string value{}; // NOLINT
|
||||
std::string key{};
|
||||
std::string value{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
@@ -535,11 +552,11 @@ class HomeassistantServiceMap : public ProtoMessage {
|
||||
};
|
||||
class HomeassistantServiceResponse : public ProtoMessage {
|
||||
public:
|
||||
std::string service{}; // NOLINT
|
||||
std::vector<HomeassistantServiceMap> data{}; // NOLINT
|
||||
std::vector<HomeassistantServiceMap> data_template{}; // NOLINT
|
||||
std::vector<HomeassistantServiceMap> variables{}; // NOLINT
|
||||
bool is_event{false}; // NOLINT
|
||||
std::string service{};
|
||||
std::vector<HomeassistantServiceMap> data{};
|
||||
std::vector<HomeassistantServiceMap> data_template{};
|
||||
std::vector<HomeassistantServiceMap> variables{};
|
||||
bool is_event{false};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
@@ -556,7 +573,8 @@ class SubscribeHomeAssistantStatesRequest : public ProtoMessage {
|
||||
};
|
||||
class SubscribeHomeAssistantStateResponse : public ProtoMessage {
|
||||
public:
|
||||
std::string entity_id{}; // NOLINT
|
||||
std::string entity_id{};
|
||||
std::string attribute{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
@@ -565,8 +583,9 @@ class SubscribeHomeAssistantStateResponse : public ProtoMessage {
|
||||
};
|
||||
class HomeAssistantStateResponse : public ProtoMessage {
|
||||
public:
|
||||
std::string entity_id{}; // NOLINT
|
||||
std::string state{}; // NOLINT
|
||||
std::string entity_id{};
|
||||
std::string state{};
|
||||
std::string attribute{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
@@ -582,7 +601,7 @@ class GetTimeRequest : public ProtoMessage {
|
||||
};
|
||||
class GetTimeResponse : public ProtoMessage {
|
||||
public:
|
||||
uint32_t epoch_seconds{0}; // NOLINT
|
||||
uint32_t epoch_seconds{0};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
@@ -591,8 +610,8 @@ class GetTimeResponse : public ProtoMessage {
|
||||
};
|
||||
class ListEntitiesServicesArgument : public ProtoMessage {
|
||||
public:
|
||||
std::string name{}; // NOLINT
|
||||
enums::ServiceArgType type{}; // NOLINT
|
||||
std::string name{};
|
||||
enums::ServiceArgType type{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
@@ -602,9 +621,9 @@ class ListEntitiesServicesArgument : public ProtoMessage {
|
||||
};
|
||||
class ListEntitiesServicesResponse : public ProtoMessage {
|
||||
public:
|
||||
std::string name{}; // NOLINT
|
||||
uint32_t key{0}; // NOLINT
|
||||
std::vector<ListEntitiesServicesArgument> args{}; // NOLINT
|
||||
std::string name{};
|
||||
uint32_t key{0};
|
||||
std::vector<ListEntitiesServicesArgument> args{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
@@ -614,15 +633,15 @@ class ListEntitiesServicesResponse : public ProtoMessage {
|
||||
};
|
||||
class ExecuteServiceArgument : public ProtoMessage {
|
||||
public:
|
||||
bool bool_{false}; // NOLINT
|
||||
int32_t legacy_int{0}; // NOLINT
|
||||
float float_{0.0f}; // NOLINT
|
||||
std::string string_{}; // NOLINT
|
||||
int32_t int_{0}; // NOLINT
|
||||
std::vector<bool> bool_array{}; // NOLINT
|
||||
std::vector<int32_t> int_array{}; // NOLINT
|
||||
std::vector<float> float_array{}; // NOLINT
|
||||
std::vector<std::string> string_array{}; // NOLINT
|
||||
bool bool_{false};
|
||||
int32_t legacy_int{0};
|
||||
float float_{0.0f};
|
||||
std::string string_{};
|
||||
int32_t int_{0};
|
||||
std::vector<bool> bool_array{};
|
||||
std::vector<int32_t> int_array{};
|
||||
std::vector<float> float_array{};
|
||||
std::vector<std::string> string_array{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
@@ -633,8 +652,8 @@ class ExecuteServiceArgument : public ProtoMessage {
|
||||
};
|
||||
class ExecuteServiceRequest : public ProtoMessage {
|
||||
public:
|
||||
uint32_t key{0}; // NOLINT
|
||||
std::vector<ExecuteServiceArgument> args{}; // NOLINT
|
||||
uint32_t key{0};
|
||||
std::vector<ExecuteServiceArgument> args{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
@@ -644,10 +663,10 @@ class ExecuteServiceRequest : public ProtoMessage {
|
||||
};
|
||||
class ListEntitiesCameraResponse : public ProtoMessage {
|
||||
public:
|
||||
std::string object_id{}; // NOLINT
|
||||
uint32_t key{0}; // NOLINT
|
||||
std::string name{}; // NOLINT
|
||||
std::string unique_id{}; // NOLINT
|
||||
std::string object_id{};
|
||||
uint32_t key{0};
|
||||
std::string name{};
|
||||
std::string unique_id{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
@@ -657,9 +676,9 @@ class ListEntitiesCameraResponse : public ProtoMessage {
|
||||
};
|
||||
class CameraImageResponse : public ProtoMessage {
|
||||
public:
|
||||
uint32_t key{0}; // NOLINT
|
||||
std::string data{}; // NOLINT
|
||||
bool done{false}; // NOLINT
|
||||
uint32_t key{0};
|
||||
std::string data{};
|
||||
bool done{false};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
@@ -670,8 +689,8 @@ class CameraImageResponse : public ProtoMessage {
|
||||
};
|
||||
class CameraImageRequest : public ProtoMessage {
|
||||
public:
|
||||
bool single{false}; // NOLINT
|
||||
bool stream{false}; // NOLINT
|
||||
bool single{false};
|
||||
bool stream{false};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
@@ -680,20 +699,23 @@ class CameraImageRequest : public ProtoMessage {
|
||||
};
|
||||
class ListEntitiesClimateResponse : public ProtoMessage {
|
||||
public:
|
||||
std::string object_id{}; // NOLINT
|
||||
uint32_t key{0}; // NOLINT
|
||||
std::string name{}; // NOLINT
|
||||
std::string unique_id{}; // NOLINT
|
||||
bool supports_current_temperature{false}; // NOLINT
|
||||
bool supports_two_point_target_temperature{false}; // NOLINT
|
||||
std::vector<enums::ClimateMode> supported_modes{}; // NOLINT
|
||||
float visual_min_temperature{0.0f}; // NOLINT
|
||||
float visual_max_temperature{0.0f}; // NOLINT
|
||||
float visual_temperature_step{0.0f}; // NOLINT
|
||||
bool supports_away{false}; // NOLINT
|
||||
bool supports_action{false}; // NOLINT
|
||||
std::vector<enums::ClimateFanMode> supported_fan_modes{}; // NOLINT
|
||||
std::vector<enums::ClimateSwingMode> supported_swing_modes{}; // NOLINT
|
||||
std::string object_id{};
|
||||
uint32_t key{0};
|
||||
std::string name{};
|
||||
std::string unique_id{};
|
||||
bool supports_current_temperature{false};
|
||||
bool supports_two_point_target_temperature{false};
|
||||
std::vector<enums::ClimateMode> supported_modes{};
|
||||
float visual_min_temperature{0.0f};
|
||||
float visual_max_temperature{0.0f};
|
||||
float visual_temperature_step{0.0f};
|
||||
bool supports_away{false};
|
||||
bool supports_action{false};
|
||||
std::vector<enums::ClimateFanMode> supported_fan_modes{};
|
||||
std::vector<enums::ClimateSwingMode> supported_swing_modes{};
|
||||
std::vector<std::string> supported_custom_fan_modes{};
|
||||
std::vector<enums::ClimatePreset> supported_presets{};
|
||||
std::vector<std::string> supported_custom_presets{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
@@ -704,45 +726,56 @@ class ListEntitiesClimateResponse : public ProtoMessage {
|
||||
};
|
||||
class ClimateStateResponse : public ProtoMessage {
|
||||
public:
|
||||
uint32_t key{0}; // NOLINT
|
||||
enums::ClimateMode mode{}; // NOLINT
|
||||
float current_temperature{0.0f}; // NOLINT
|
||||
float target_temperature{0.0f}; // NOLINT
|
||||
float target_temperature_low{0.0f}; // NOLINT
|
||||
float target_temperature_high{0.0f}; // NOLINT
|
||||
bool away{false}; // NOLINT
|
||||
enums::ClimateAction action{}; // NOLINT
|
||||
enums::ClimateFanMode fan_mode{}; // NOLINT
|
||||
enums::ClimateSwingMode swing_mode{}; // NOLINT
|
||||
uint32_t key{0};
|
||||
enums::ClimateMode mode{};
|
||||
float current_temperature{0.0f};
|
||||
float target_temperature{0.0f};
|
||||
float target_temperature_low{0.0f};
|
||||
float target_temperature_high{0.0f};
|
||||
bool away{false};
|
||||
enums::ClimateAction action{};
|
||||
enums::ClimateFanMode fan_mode{};
|
||||
enums::ClimateSwingMode swing_mode{};
|
||||
std::string custom_fan_mode{};
|
||||
enums::ClimatePreset preset{};
|
||||
std::string custom_preset{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class ClimateCommandRequest : public ProtoMessage {
|
||||
public:
|
||||
uint32_t key{0}; // NOLINT
|
||||
bool has_mode{false}; // NOLINT
|
||||
enums::ClimateMode mode{}; // NOLINT
|
||||
bool has_target_temperature{false}; // NOLINT
|
||||
float target_temperature{0.0f}; // NOLINT
|
||||
bool has_target_temperature_low{false}; // NOLINT
|
||||
float target_temperature_low{0.0f}; // NOLINT
|
||||
bool has_target_temperature_high{false}; // NOLINT
|
||||
float target_temperature_high{0.0f}; // NOLINT
|
||||
bool has_away{false}; // NOLINT
|
||||
bool away{false}; // NOLINT
|
||||
bool has_fan_mode{false}; // NOLINT
|
||||
enums::ClimateFanMode fan_mode{}; // NOLINT
|
||||
bool has_swing_mode{false}; // NOLINT
|
||||
enums::ClimateSwingMode swing_mode{}; // NOLINT
|
||||
uint32_t key{0};
|
||||
bool has_mode{false};
|
||||
enums::ClimateMode mode{};
|
||||
bool has_target_temperature{false};
|
||||
float target_temperature{0.0f};
|
||||
bool has_target_temperature_low{false};
|
||||
float target_temperature_low{0.0f};
|
||||
bool has_target_temperature_high{false};
|
||||
float target_temperature_high{0.0f};
|
||||
bool has_away{false};
|
||||
bool away{false};
|
||||
bool has_fan_mode{false};
|
||||
enums::ClimateFanMode fan_mode{};
|
||||
bool has_swing_mode{false};
|
||||
enums::ClimateSwingMode swing_mode{};
|
||||
bool has_custom_fan_mode{false};
|
||||
std::string custom_fan_mode{};
|
||||
bool has_preset{false};
|
||||
enums::ClimatePreset preset{};
|
||||
bool has_custom_preset{false};
|
||||
std::string custom_preset{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
|
||||
|
||||
@@ -208,9 +208,11 @@ void APIServer::send_homeassistant_service_call(const HomeassistantServiceRespon
|
||||
}
|
||||
}
|
||||
APIServer::APIServer() { global_api_server = this; }
|
||||
void APIServer::subscribe_home_assistant_state(std::string entity_id, std::function<void(std::string)> f) {
|
||||
void APIServer::subscribe_home_assistant_state(std::string entity_id, optional<std::string> attribute,
|
||||
std::function<void(std::string)> f) {
|
||||
this->state_subs_.push_back(HomeAssistantStateSubscription{
|
||||
.entity_id = std::move(entity_id),
|
||||
.attribute = std::move(attribute),
|
||||
.callback = std::move(f),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -71,10 +71,12 @@ class APIServer : public Component, public Controller {
|
||||
|
||||
struct HomeAssistantStateSubscription {
|
||||
std::string entity_id;
|
||||
optional<std::string> attribute;
|
||||
std::function<void(std::string)> callback;
|
||||
};
|
||||
|
||||
void subscribe_home_assistant_state(std::string entity_id, std::function<void(std::string)> f);
|
||||
void subscribe_home_assistant_state(std::string entity_id, optional<std::string> attribute,
|
||||
std::function<void(std::string)> f);
|
||||
const std::vector<HomeAssistantStateSubscription> &get_state_subs() const;
|
||||
const std::vector<UserServiceDescriptor *> &get_user_services() const { return this->user_services_; }
|
||||
|
||||
|
||||
@@ -76,13 +76,13 @@ class CustomAPIDevice {
|
||||
global_api_server->register_user_service(service);
|
||||
}
|
||||
|
||||
/** Subscribe to the state of an entity from Home Assistant.
|
||||
/** Subscribe to the state (or attribute state) of an entity from Home Assistant.
|
||||
*
|
||||
* Usage:
|
||||
*
|
||||
* ```cpp
|
||||
* void setup() override {
|
||||
* subscribe_homeassistant_state(&CustomNativeAPI::on_state_changed, "sensor.weather_forecast");
|
||||
* subscribe_homeassistant_state(&CustomNativeAPI::on_state_changed, "climate.kitchen", "current_temperature");
|
||||
* }
|
||||
*
|
||||
* void on_state_changed(std::string state) {
|
||||
@@ -93,17 +93,19 @@ class CustomAPIDevice {
|
||||
* @tparam T The class type creating the service, automatically deduced from the function pointer.
|
||||
* @param callback The member function to call when the entity state changes.
|
||||
* @param entity_id The entity_id to track.
|
||||
* @param attribute The entity state attribute to track.
|
||||
*/
|
||||
template<typename T>
|
||||
void subscribe_homeassistant_state(void (T::*callback)(std::string), const std::string &entity_id) {
|
||||
void subscribe_homeassistant_state(void (T::*callback)(std::string), const std::string &entity_id,
|
||||
const std::string &attribute = "") {
|
||||
auto f = std::bind(callback, (T *) this, std::placeholders::_1);
|
||||
global_api_server->subscribe_home_assistant_state(entity_id, f);
|
||||
global_api_server->subscribe_home_assistant_state(entity_id, optional<std::string>(attribute), f);
|
||||
}
|
||||
|
||||
/** Subscribe to the state of an entity from Home Assistant.
|
||||
/** Subscribe to the state (or attribute state) of an entity from Home Assistant.
|
||||
*
|
||||
* Usage:
|
||||
*
|
||||
*å
|
||||
* ```cpp
|
||||
* void setup() override {
|
||||
* subscribe_homeassistant_state(&CustomNativeAPI::on_state_changed, "sensor.weather_forecast");
|
||||
@@ -117,11 +119,13 @@ class CustomAPIDevice {
|
||||
* @tparam T The class type creating the service, automatically deduced from the function pointer.
|
||||
* @param callback The member function to call when the entity state changes.
|
||||
* @param entity_id The entity_id to track.
|
||||
* @param attribute The entity state attribute to track.
|
||||
*/
|
||||
template<typename T>
|
||||
void subscribe_homeassistant_state(void (T::*callback)(std::string, std::string), const std::string &entity_id) {
|
||||
void subscribe_homeassistant_state(void (T::*callback)(std::string, std::string), const std::string &entity_id,
|
||||
const std::string &attribute = "") {
|
||||
auto f = std::bind(callback, (T *) this, entity_id, std::placeholders::_1);
|
||||
global_api_server->subscribe_home_assistant_state(entity_id, f);
|
||||
global_api_server->subscribe_home_assistant_state(entity_id, optional<std::string>(attribute), f);
|
||||
}
|
||||
|
||||
/** Call a Home Assistant service from ESPHome.
|
||||
|
||||
@@ -11,7 +11,6 @@ from esphome.const import (
|
||||
CONF_DIV_RATIO,
|
||||
CONF_CAPACITANCE,
|
||||
)
|
||||
from esphome.core import coroutine
|
||||
|
||||
AUTO_LOAD = ["sensor", "binary_sensor"]
|
||||
MULTI_CONF = True
|
||||
@@ -40,11 +39,10 @@ AS3935_SCHEMA = cv.Schema(
|
||||
)
|
||||
|
||||
|
||||
@coroutine
|
||||
def setup_as3935(var, config):
|
||||
yield cg.register_component(var, config)
|
||||
async def setup_as3935(var, config):
|
||||
await cg.register_component(var, config)
|
||||
|
||||
irq_pin = yield cg.gpio_pin_expression(config[CONF_IRQ_PIN])
|
||||
irq_pin = await cg.gpio_pin_expression(config[CONF_IRQ_PIN])
|
||||
cg.add(var.set_irq_pin(irq_pin))
|
||||
cg.add(var.set_indoor(config[CONF_INDOOR]))
|
||||
cg.add(var.set_noise_level(config[CONF_NOISE_LEVEL]))
|
||||
|
||||
@@ -12,7 +12,7 @@ CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend(
|
||||
)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
hub = yield cg.get_variable(config[CONF_AS3935_ID])
|
||||
var = yield binary_sensor.new_binary_sensor(config)
|
||||
async def to_code(config):
|
||||
hub = await cg.get_variable(config[CONF_AS3935_ID])
|
||||
var = await binary_sensor.new_binary_sensor(config)
|
||||
cg.add(hub.set_thunder_alert_binary_sensor(var))
|
||||
|
||||
@@ -5,6 +5,7 @@ from esphome.const import (
|
||||
CONF_DISTANCE,
|
||||
CONF_LIGHTNING_ENERGY,
|
||||
DEVICE_CLASS_EMPTY,
|
||||
STATE_CLASS_NONE,
|
||||
UNIT_KILOMETER,
|
||||
UNIT_EMPTY,
|
||||
ICON_SIGNAL_DISTANCE_VARIANT,
|
||||
@@ -18,24 +19,28 @@ CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(CONF_AS3935_ID): cv.use_id(AS3935),
|
||||
cv.Optional(CONF_DISTANCE): sensor.sensor_schema(
|
||||
UNIT_KILOMETER, ICON_SIGNAL_DISTANCE_VARIANT, 1, DEVICE_CLASS_EMPTY
|
||||
UNIT_KILOMETER,
|
||||
ICON_SIGNAL_DISTANCE_VARIANT,
|
||||
1,
|
||||
DEVICE_CLASS_EMPTY,
|
||||
STATE_CLASS_NONE,
|
||||
),
|
||||
cv.Optional(CONF_LIGHTNING_ENERGY): sensor.sensor_schema(
|
||||
UNIT_EMPTY, ICON_FLASH, 1, DEVICE_CLASS_EMPTY
|
||||
UNIT_EMPTY, ICON_FLASH, 1, DEVICE_CLASS_EMPTY, STATE_CLASS_NONE
|
||||
),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
hub = yield cg.get_variable(config[CONF_AS3935_ID])
|
||||
async def to_code(config):
|
||||
hub = await cg.get_variable(config[CONF_AS3935_ID])
|
||||
|
||||
if CONF_DISTANCE in config:
|
||||
conf = config[CONF_DISTANCE]
|
||||
distance_sensor = yield sensor.new_sensor(conf)
|
||||
distance_sensor = await sensor.new_sensor(conf)
|
||||
cg.add(hub.set_distance_sensor(distance_sensor))
|
||||
|
||||
if CONF_LIGHTNING_ENERGY in config:
|
||||
conf = config[CONF_LIGHTNING_ENERGY]
|
||||
lightning_energy_sensor = yield sensor.new_sensor(conf)
|
||||
lightning_energy_sensor = await sensor.new_sensor(conf)
|
||||
cg.add(hub.set_energy_sensor(lightning_energy_sensor))
|
||||
|
||||
@@ -20,7 +20,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
yield as3935.setup_as3935(var, config)
|
||||
yield i2c.register_i2c_device(var, config)
|
||||
await as3935.setup_as3935(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
|
||||
@@ -20,7 +20,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
yield as3935.setup_as3935(var, config)
|
||||
yield spi.register_spi_device(var, config)
|
||||
await as3935.setup_as3935(var, config)
|
||||
await spi.register_spi_device(var, config)
|
||||
|
||||
@@ -33,7 +33,7 @@ void SPIAS3935Component::write_register(uint8_t reg, uint8_t mask, uint8_t bits,
|
||||
uint8_t SPIAS3935Component::read_register(uint8_t reg) {
|
||||
uint8_t value = 0;
|
||||
this->enable();
|
||||
this->write_byte(reg |= SPI_READ_M);
|
||||
this->write_byte(reg | SPI_READ_M);
|
||||
value = this->read_byte();
|
||||
// According to datsheet, the chip select must be written HIGH, LOW, HIGH
|
||||
// to correctly end the READ command.
|
||||
|
||||
@@ -6,10 +6,10 @@ CODEOWNERS = ["@OttoWinter"]
|
||||
|
||||
|
||||
@coroutine_with_priority(200.0)
|
||||
def to_code(config):
|
||||
async def to_code(config):
|
||||
if CORE.is_esp32:
|
||||
# https://github.com/OttoWinter/AsyncTCP/blob/master/library.json
|
||||
cg.add_library("AsyncTCP-esphome", "1.1.1")
|
||||
# https://github.com/esphome/AsyncTCP/blob/master/library.json
|
||||
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")
|
||||
|
||||
@@ -13,6 +13,7 @@ from esphome.const import (
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
DEVICE_CLASS_VOLTAGE,
|
||||
ICON_EMPTY,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_CELSIUS,
|
||||
UNIT_PERCENT,
|
||||
UNIT_VOLT,
|
||||
@@ -33,16 +34,28 @@ CONFIG_SCHEMA = (
|
||||
cv.GenerateID(): cv.declare_id(ATCMiThermometer),
|
||||
cv.Required(CONF_MAC_ADDRESS): cv.mac_address,
|
||||
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
|
||||
UNIT_CELSIUS, ICON_EMPTY, 1, DEVICE_CLASS_TEMPERATURE
|
||||
UNIT_CELSIUS,
|
||||
ICON_EMPTY,
|
||||
1,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
|
||||
UNIT_PERCENT, ICON_EMPTY, 0, DEVICE_CLASS_HUMIDITY
|
||||
UNIT_PERCENT,
|
||||
ICON_EMPTY,
|
||||
0,
|
||||
DEVICE_CLASS_HUMIDITY,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema(
|
||||
UNIT_PERCENT, ICON_EMPTY, 0, DEVICE_CLASS_BATTERY
|
||||
UNIT_PERCENT,
|
||||
ICON_EMPTY,
|
||||
0,
|
||||
DEVICE_CLASS_BATTERY,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_BATTERY_VOLTAGE): sensor.sensor_schema(
|
||||
UNIT_VOLT, ICON_EMPTY, 3, DEVICE_CLASS_VOLTAGE
|
||||
UNIT_VOLT, ICON_EMPTY, 3, DEVICE_CLASS_VOLTAGE, STATE_CLASS_MEASUREMENT
|
||||
),
|
||||
}
|
||||
)
|
||||
@@ -51,22 +64,22 @@ CONFIG_SCHEMA = (
|
||||
)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
yield cg.register_component(var, config)
|
||||
yield esp32_ble_tracker.register_ble_device(var, config)
|
||||
await cg.register_component(var, config)
|
||||
await esp32_ble_tracker.register_ble_device(var, config)
|
||||
|
||||
cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex))
|
||||
|
||||
if CONF_TEMPERATURE in config:
|
||||
sens = yield sensor.new_sensor(config[CONF_TEMPERATURE])
|
||||
sens = await sensor.new_sensor(config[CONF_TEMPERATURE])
|
||||
cg.add(var.set_temperature(sens))
|
||||
if CONF_HUMIDITY in config:
|
||||
sens = yield sensor.new_sensor(config[CONF_HUMIDITY])
|
||||
sens = await sensor.new_sensor(config[CONF_HUMIDITY])
|
||||
cg.add(var.set_humidity(sens))
|
||||
if CONF_BATTERY_LEVEL in config:
|
||||
sens = yield sensor.new_sensor(config[CONF_BATTERY_LEVEL])
|
||||
sens = await sensor.new_sensor(config[CONF_BATTERY_LEVEL])
|
||||
cg.add(var.set_battery_level(sens))
|
||||
if CONF_BATTERY_VOLTAGE in config:
|
||||
sens = yield sensor.new_sensor(config[CONF_BATTERY_VOLTAGE])
|
||||
sens = await sensor.new_sensor(config[CONF_BATTERY_VOLTAGE])
|
||||
cg.add(var.set_battery_voltage(sens))
|
||||
|
||||
@@ -58,6 +58,24 @@ void ATM90E32Component::update() {
|
||||
if (this->phase_[2].power_factor_sensor_ != nullptr) {
|
||||
this->phase_[2].power_factor_sensor_->publish_state(this->get_power_factor_c_());
|
||||
}
|
||||
if (this->phase_[0].forward_active_energy_sensor_ != nullptr) {
|
||||
this->phase_[0].forward_active_energy_sensor_->publish_state(this->get_forward_active_energy_a_());
|
||||
}
|
||||
if (this->phase_[1].forward_active_energy_sensor_ != nullptr) {
|
||||
this->phase_[1].forward_active_energy_sensor_->publish_state(this->get_forward_active_energy_b_());
|
||||
}
|
||||
if (this->phase_[2].forward_active_energy_sensor_ != nullptr) {
|
||||
this->phase_[2].forward_active_energy_sensor_->publish_state(this->get_forward_active_energy_c_());
|
||||
}
|
||||
if (this->phase_[0].reverse_active_energy_sensor_ != nullptr) {
|
||||
this->phase_[0].reverse_active_energy_sensor_->publish_state(this->get_reverse_active_energy_a_());
|
||||
}
|
||||
if (this->phase_[1].reverse_active_energy_sensor_ != nullptr) {
|
||||
this->phase_[1].reverse_active_energy_sensor_->publish_state(this->get_reverse_active_energy_b_());
|
||||
}
|
||||
if (this->phase_[2].reverse_active_energy_sensor_ != nullptr) {
|
||||
this->phase_[2].reverse_active_energy_sensor_->publish_state(this->get_reverse_active_energy_c_());
|
||||
}
|
||||
if (this->freq_sensor_ != nullptr) {
|
||||
this->freq_sensor_->publish_state(this->get_frequency_());
|
||||
}
|
||||
@@ -119,16 +137,22 @@ void ATM90E32Component::dump_config() {
|
||||
LOG_SENSOR(" ", "Power A", this->phase_[0].power_sensor_);
|
||||
LOG_SENSOR(" ", "Reactive Power A", this->phase_[0].reactive_power_sensor_);
|
||||
LOG_SENSOR(" ", "PF A", this->phase_[0].power_factor_sensor_);
|
||||
LOG_SENSOR(" ", "Active Forward Energy A", this->phase_[0].forward_active_energy_sensor_);
|
||||
LOG_SENSOR(" ", "Active Reverse Energy A", this->phase_[0].reverse_active_energy_sensor_);
|
||||
LOG_SENSOR(" ", "Voltage B", this->phase_[1].voltage_sensor_);
|
||||
LOG_SENSOR(" ", "Current B", this->phase_[1].current_sensor_);
|
||||
LOG_SENSOR(" ", "Power B", this->phase_[1].power_sensor_);
|
||||
LOG_SENSOR(" ", "Reactive Power B", this->phase_[1].reactive_power_sensor_);
|
||||
LOG_SENSOR(" ", "PF B", this->phase_[1].power_factor_sensor_);
|
||||
LOG_SENSOR(" ", "Active Forward Energy B", this->phase_[1].forward_active_energy_sensor_);
|
||||
LOG_SENSOR(" ", "Active Reverse Energy B", this->phase_[1].reverse_active_energy_sensor_);
|
||||
LOG_SENSOR(" ", "Voltage C", this->phase_[2].voltage_sensor_);
|
||||
LOG_SENSOR(" ", "Current C", this->phase_[2].current_sensor_);
|
||||
LOG_SENSOR(" ", "Power C", this->phase_[2].power_sensor_);
|
||||
LOG_SENSOR(" ", "Reactive Power C", this->phase_[2].reactive_power_sensor_);
|
||||
LOG_SENSOR(" ", "PF C", this->phase_[2].power_factor_sensor_);
|
||||
LOG_SENSOR(" ", "Active Forward Energy C", this->phase_[2].forward_active_energy_sensor_);
|
||||
LOG_SENSOR(" ", "Active Reverse Energy C", this->phase_[2].reverse_active_energy_sensor_);
|
||||
LOG_SENSOR(" ", "Frequency", this->freq_sensor_);
|
||||
LOG_SENSOR(" ", "Chip Temp", this->chip_temperature_sensor_);
|
||||
}
|
||||
@@ -239,6 +263,30 @@ float ATM90E32Component::get_power_factor_c_() {
|
||||
int16_t pf = this->read16_(ATM90E32_REGISTER_PFMEANC);
|
||||
return (float) pf / 1000;
|
||||
}
|
||||
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
|
||||
}
|
||||
float ATM90E32Component::get_forward_active_energy_b_() {
|
||||
uint16_t val = this->read16_(ATM90E32_REGISTER_APENERGYB);
|
||||
return (float) val * 10 / 3200;
|
||||
}
|
||||
float ATM90E32Component::get_forward_active_energy_c_() {
|
||||
uint16_t val = this->read16_(ATM90E32_REGISTER_APENERGYC);
|
||||
return (float) val * 10 / 3200;
|
||||
}
|
||||
float ATM90E32Component::get_reverse_active_energy_a_() {
|
||||
uint16_t val = this->read16_(ATM90E32_REGISTER_ANENERGYA);
|
||||
return (float) val * 10 / 3200;
|
||||
}
|
||||
float ATM90E32Component::get_reverse_active_energy_b_() {
|
||||
uint16_t val = this->read16_(ATM90E32_REGISTER_ANENERGYB);
|
||||
return (float) val * 10 / 3200;
|
||||
}
|
||||
float ATM90E32Component::get_reverse_active_energy_c_() {
|
||||
uint16_t val = this->read16_(ATM90E32_REGISTER_ANENERGYC);
|
||||
return (float) val * 10 / 3200;
|
||||
}
|
||||
float ATM90E32Component::get_frequency_() {
|
||||
uint16_t freq = this->read16_(ATM90E32_REGISTER_FREQ);
|
||||
return (float) freq / 100;
|
||||
|
||||
@@ -20,6 +20,12 @@ class ATM90E32Component : public PollingComponent,
|
||||
void set_current_sensor(int phase, sensor::Sensor *obj) { this->phase_[phase].current_sensor_ = obj; }
|
||||
void set_power_sensor(int phase, sensor::Sensor *obj) { this->phase_[phase].power_sensor_ = obj; }
|
||||
void set_reactive_power_sensor(int phase, sensor::Sensor *obj) { this->phase_[phase].reactive_power_sensor_ = obj; }
|
||||
void set_forward_active_energy_sensor(int phase, sensor::Sensor *obj) {
|
||||
this->phase_[phase].forward_active_energy_sensor_ = obj;
|
||||
}
|
||||
void set_reverse_active_energy_sensor(int phase, sensor::Sensor *obj) {
|
||||
this->phase_[phase].reverse_active_energy_sensor_ = obj;
|
||||
}
|
||||
void set_power_factor_sensor(int phase, sensor::Sensor *obj) { this->phase_[phase].power_factor_sensor_ = obj; }
|
||||
void set_volt_gain(int phase, uint16_t gain) { this->phase_[phase].volt_gain_ = gain; }
|
||||
void set_ct_gain(int phase, uint16_t gain) { this->phase_[phase].ct_gain_ = gain; }
|
||||
@@ -52,6 +58,12 @@ class ATM90E32Component : public PollingComponent,
|
||||
float get_power_factor_a_();
|
||||
float get_power_factor_b_();
|
||||
float get_power_factor_c_();
|
||||
float get_forward_active_energy_a_();
|
||||
float get_forward_active_energy_b_();
|
||||
float get_forward_active_energy_c_();
|
||||
float get_reverse_active_energy_a_();
|
||||
float get_reverse_active_energy_b_();
|
||||
float get_reverse_active_energy_c_();
|
||||
float get_frequency_();
|
||||
float get_chip_temperature_();
|
||||
|
||||
@@ -63,6 +75,8 @@ class ATM90E32Component : public PollingComponent,
|
||||
sensor::Sensor *power_sensor_{nullptr};
|
||||
sensor::Sensor *reactive_power_sensor_{nullptr};
|
||||
sensor::Sensor *power_factor_sensor_{nullptr};
|
||||
sensor::Sensor *forward_active_energy_sensor_{nullptr};
|
||||
sensor::Sensor *reverse_active_energy_sensor_{nullptr};
|
||||
} phase_[3];
|
||||
sensor::Sensor *freq_sensor_{nullptr};
|
||||
sensor::Sensor *chip_temperature_sensor_{nullptr};
|
||||
|
||||
@@ -3,13 +3,17 @@ import esphome.config_validation as cv
|
||||
from esphome.components import sensor, spi
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_REACTIVE_POWER,
|
||||
CONF_VOLTAGE,
|
||||
CONF_CURRENT,
|
||||
CONF_POWER,
|
||||
CONF_POWER_FACTOR,
|
||||
CONF_FREQUENCY,
|
||||
CONF_FORWARD_ACTIVE_ENERGY,
|
||||
CONF_REVERSE_ACTIVE_ENERGY,
|
||||
DEVICE_CLASS_CURRENT,
|
||||
DEVICE_CLASS_EMPTY,
|
||||
DEVICE_CLASS_ENERGY,
|
||||
DEVICE_CLASS_POWER,
|
||||
DEVICE_CLASS_POWER_FACTOR,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
@@ -17,6 +21,7 @@ from esphome.const import (
|
||||
ICON_EMPTY,
|
||||
ICON_LIGHTBULB,
|
||||
ICON_CURRENT_AC,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_HERTZ,
|
||||
UNIT_VOLT,
|
||||
UNIT_AMPERE,
|
||||
@@ -24,13 +29,13 @@ from esphome.const import (
|
||||
UNIT_EMPTY,
|
||||
UNIT_CELSIUS,
|
||||
UNIT_VOLT_AMPS_REACTIVE,
|
||||
UNIT_WATT_HOURS,
|
||||
)
|
||||
|
||||
CONF_PHASE_A = "phase_a"
|
||||
CONF_PHASE_B = "phase_b"
|
||||
CONF_PHASE_C = "phase_c"
|
||||
|
||||
CONF_REACTIVE_POWER = "reactive_power"
|
||||
CONF_LINE_FREQUENCY = "line_frequency"
|
||||
CONF_CHIP_TEMPERATURE = "chip_temperature"
|
||||
CONF_GAIN_PGA = "gain_pga"
|
||||
@@ -59,19 +64,37 @@ ATM90E32Component = atm90e32_ns.class_(
|
||||
ATM90E32_PHASE_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_VOLTAGE): sensor.sensor_schema(
|
||||
UNIT_VOLT, ICON_EMPTY, 2, DEVICE_CLASS_VOLTAGE
|
||||
UNIT_VOLT,
|
||||
ICON_EMPTY,
|
||||
2,
|
||||
DEVICE_CLASS_VOLTAGE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_CURRENT): sensor.sensor_schema(
|
||||
UNIT_AMPERE, ICON_EMPTY, 2, DEVICE_CLASS_CURRENT
|
||||
UNIT_AMPERE, ICON_EMPTY, 2, DEVICE_CLASS_CURRENT, STATE_CLASS_MEASUREMENT
|
||||
),
|
||||
cv.Optional(CONF_POWER): sensor.sensor_schema(
|
||||
UNIT_WATT, ICON_EMPTY, 2, DEVICE_CLASS_POWER
|
||||
UNIT_WATT, ICON_EMPTY, 2, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT
|
||||
),
|
||||
cv.Optional(CONF_REACTIVE_POWER): sensor.sensor_schema(
|
||||
UNIT_VOLT_AMPS_REACTIVE, ICON_LIGHTBULB, 2, DEVICE_CLASS_EMPTY
|
||||
UNIT_VOLT_AMPS_REACTIVE,
|
||||
ICON_LIGHTBULB,
|
||||
2,
|
||||
DEVICE_CLASS_EMPTY,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_POWER_FACTOR): sensor.sensor_schema(
|
||||
UNIT_EMPTY, ICON_EMPTY, 2, DEVICE_CLASS_POWER_FACTOR
|
||||
UNIT_EMPTY,
|
||||
ICON_EMPTY,
|
||||
2,
|
||||
DEVICE_CLASS_POWER_FACTOR,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_FORWARD_ACTIVE_ENERGY): sensor.sensor_schema(
|
||||
UNIT_WATT_HOURS, ICON_EMPTY, 2, DEVICE_CLASS_ENERGY, STATE_CLASS_MEASUREMENT
|
||||
),
|
||||
cv.Optional(CONF_REVERSE_ACTIVE_ENERGY): sensor.sensor_schema(
|
||||
UNIT_WATT_HOURS, ICON_EMPTY, 2, DEVICE_CLASS_ENERGY, STATE_CLASS_MEASUREMENT
|
||||
),
|
||||
cv.Optional(CONF_GAIN_VOLTAGE, default=7305): cv.uint16_t,
|
||||
cv.Optional(CONF_GAIN_CT, default=27961): cv.uint16_t,
|
||||
@@ -86,10 +109,18 @@ CONFIG_SCHEMA = (
|
||||
cv.Optional(CONF_PHASE_B): ATM90E32_PHASE_SCHEMA,
|
||||
cv.Optional(CONF_PHASE_C): ATM90E32_PHASE_SCHEMA,
|
||||
cv.Optional(CONF_FREQUENCY): sensor.sensor_schema(
|
||||
UNIT_HERTZ, ICON_CURRENT_AC, 1, DEVICE_CLASS_EMPTY
|
||||
UNIT_HERTZ,
|
||||
ICON_CURRENT_AC,
|
||||
1,
|
||||
DEVICE_CLASS_EMPTY,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_CHIP_TEMPERATURE): sensor.sensor_schema(
|
||||
UNIT_CELSIUS, ICON_EMPTY, 1, DEVICE_CLASS_TEMPERATURE
|
||||
UNIT_CELSIUS,
|
||||
ICON_EMPTY,
|
||||
1,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Required(CONF_LINE_FREQUENCY): cv.enum(LINE_FREQS, upper=True),
|
||||
cv.Optional(CONF_CURRENT_PHASES, default="3"): cv.enum(
|
||||
@@ -103,10 +134,10 @@ CONFIG_SCHEMA = (
|
||||
)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
yield cg.register_component(var, config)
|
||||
yield spi.register_spi_device(var, config)
|
||||
await cg.register_component(var, config)
|
||||
await spi.register_spi_device(var, config)
|
||||
|
||||
for i, phase in enumerate([CONF_PHASE_A, CONF_PHASE_B, CONF_PHASE_C]):
|
||||
if phase not in config:
|
||||
@@ -115,25 +146,31 @@ def to_code(config):
|
||||
cg.add(var.set_volt_gain(i, conf[CONF_GAIN_VOLTAGE]))
|
||||
cg.add(var.set_ct_gain(i, conf[CONF_GAIN_CT]))
|
||||
if CONF_VOLTAGE in conf:
|
||||
sens = yield sensor.new_sensor(conf[CONF_VOLTAGE])
|
||||
sens = await sensor.new_sensor(conf[CONF_VOLTAGE])
|
||||
cg.add(var.set_voltage_sensor(i, sens))
|
||||
if CONF_CURRENT in conf:
|
||||
sens = yield sensor.new_sensor(conf[CONF_CURRENT])
|
||||
sens = await sensor.new_sensor(conf[CONF_CURRENT])
|
||||
cg.add(var.set_current_sensor(i, sens))
|
||||
if CONF_POWER in conf:
|
||||
sens = yield sensor.new_sensor(conf[CONF_POWER])
|
||||
sens = await sensor.new_sensor(conf[CONF_POWER])
|
||||
cg.add(var.set_power_sensor(i, sens))
|
||||
if CONF_REACTIVE_POWER in conf:
|
||||
sens = yield sensor.new_sensor(conf[CONF_REACTIVE_POWER])
|
||||
sens = await sensor.new_sensor(conf[CONF_REACTIVE_POWER])
|
||||
cg.add(var.set_reactive_power_sensor(i, sens))
|
||||
if CONF_POWER_FACTOR in conf:
|
||||
sens = yield sensor.new_sensor(conf[CONF_POWER_FACTOR])
|
||||
sens = await sensor.new_sensor(conf[CONF_POWER_FACTOR])
|
||||
cg.add(var.set_power_factor_sensor(i, sens))
|
||||
if CONF_FORWARD_ACTIVE_ENERGY in conf:
|
||||
sens = await sensor.new_sensor(conf[CONF_FORWARD_ACTIVE_ENERGY])
|
||||
cg.add(var.set_forward_active_energy_sensor(i, sens))
|
||||
if CONF_REVERSE_ACTIVE_ENERGY in conf:
|
||||
sens = await sensor.new_sensor(conf[CONF_REVERSE_ACTIVE_ENERGY])
|
||||
cg.add(var.set_reverse_active_energy_sensor(i, sens))
|
||||
if CONF_FREQUENCY in config:
|
||||
sens = yield sensor.new_sensor(config[CONF_FREQUENCY])
|
||||
sens = await sensor.new_sensor(config[CONF_FREQUENCY])
|
||||
cg.add(var.set_freq_sensor(sens))
|
||||
if CONF_CHIP_TEMPERATURE in config:
|
||||
sens = yield sensor.new_sensor(config[CONF_CHIP_TEMPERATURE])
|
||||
sens = await sensor.new_sensor(config[CONF_CHIP_TEMPERATURE])
|
||||
cg.add(var.set_chip_temperature_sensor(sens))
|
||||
cg.add(var.set_line_freq(config[CONF_LINE_FREQUENCY]))
|
||||
cg.add(var.set_current_phases(config[CONF_CURRENT_PHASES]))
|
||||
|
||||
0
esphome/components/b_parasite/__init__.py
Normal file
0
esphome/components/b_parasite/__init__.py
Normal file
82
esphome/components/b_parasite/b_parasite.cpp
Normal file
82
esphome/components/b_parasite/b_parasite.cpp
Normal file
@@ -0,0 +1,82 @@
|
||||
#include "b_parasite.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
|
||||
namespace esphome {
|
||||
namespace b_parasite {
|
||||
|
||||
static const char* TAG = "b_parasite";
|
||||
|
||||
void BParasite::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "b_parasite");
|
||||
LOG_SENSOR(" ", "Battery Voltage", this->battery_voltage_);
|
||||
LOG_SENSOR(" ", "Temperature", this->temperature_);
|
||||
LOG_SENSOR(" ", "Humidity", this->humidity_);
|
||||
LOG_SENSOR(" ", "Soil Moisture", this->soil_moisture_);
|
||||
}
|
||||
|
||||
bool BParasite::parse_device(const esp32_ble_tracker::ESPBTDevice& device) {
|
||||
if (device.address_uint64() != address_) {
|
||||
ESP_LOGVV(TAG, "parse_device(): unknown MAC address.");
|
||||
return false;
|
||||
}
|
||||
ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str());
|
||||
const auto& service_datas = device.get_service_datas();
|
||||
if (service_datas.size() != 1) {
|
||||
ESP_LOGE(TAG, "Unexpected service_datas size (%d)", service_datas.size());
|
||||
return false;
|
||||
}
|
||||
const auto& service_data = service_datas[0];
|
||||
|
||||
ESP_LOGVV(TAG, "Service data:");
|
||||
for (const uint8_t byte : service_data.data) {
|
||||
ESP_LOGVV(TAG, "0x%02x", byte);
|
||||
}
|
||||
|
||||
const auto& data = service_data.data;
|
||||
|
||||
// Counter for deduplicating messages.
|
||||
uint8_t counter = data[1] & 0x0f;
|
||||
if (last_processed_counter_ == counter) {
|
||||
ESP_LOGVV(TAG, "Skipping already processed counter (%u)", counter);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Battery voltage in millivolts.
|
||||
uint16_t battery_millivolt = data[2] << 8 | data[3];
|
||||
float battery_voltage = battery_millivolt / 1000.0f;
|
||||
|
||||
// Temperature in 1000 * Celcius.
|
||||
uint16_t temp_millicelcius = data[4] << 8 | data[5];
|
||||
float temp_celcius = temp_millicelcius / 1000.0f;
|
||||
|
||||
// Relative air humidity in the range [0, 2^16).
|
||||
uint16_t humidity = data[6] << 8 | data[7];
|
||||
float humidity_percent = (100.0f * humidity) / (1 << 16);
|
||||
|
||||
// Relative soil moisture in [0 - 2^16).
|
||||
uint16_t soil_moisture = data[8] << 8 | data[9];
|
||||
float moisture_percent = (100.0f * soil_moisture) / (1 << 16);
|
||||
|
||||
if (battery_voltage_ != nullptr) {
|
||||
battery_voltage_->publish_state(battery_voltage);
|
||||
}
|
||||
if (temperature_ != nullptr) {
|
||||
temperature_->publish_state(temp_celcius);
|
||||
}
|
||||
if (humidity_ != nullptr) {
|
||||
humidity_->publish_state(humidity_percent);
|
||||
}
|
||||
if (soil_moisture_ != nullptr) {
|
||||
soil_moisture_->publish_state(moisture_percent);
|
||||
}
|
||||
|
||||
last_processed_counter_ = counter;
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace b_parasite
|
||||
} // namespace esphome
|
||||
|
||||
#endif // ARDUINO_ARCH_ESP32
|
||||
40
esphome/components/b_parasite/b_parasite.h
Normal file
40
esphome/components/b_parasite/b_parasite.h
Normal file
@@ -0,0 +1,40 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
|
||||
namespace esphome {
|
||||
namespace b_parasite {
|
||||
|
||||
class BParasite : public Component, public esp32_ble_tracker::ESPBTDeviceListener {
|
||||
public:
|
||||
void set_address(uint64_t address) { address_ = address; };
|
||||
void set_bindkey(const std::string &bindkey);
|
||||
|
||||
bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||
|
||||
void set_battery_voltage(sensor::Sensor *battery_voltage) { battery_voltage_ = battery_voltage; }
|
||||
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; }
|
||||
|
||||
protected:
|
||||
// The received advertisement packet contains an unsigned 4 bits wrap-around counter
|
||||
// for deduplicating messages.
|
||||
int8_t last_processed_counter_ = -1;
|
||||
uint64_t address_;
|
||||
sensor::Sensor *battery_voltage_{nullptr};
|
||||
sensor::Sensor *temperature_{nullptr};
|
||||
sensor::Sensor *humidity_{nullptr};
|
||||
sensor::Sensor *soil_moisture_{nullptr};
|
||||
};
|
||||
|
||||
} // namespace b_parasite
|
||||
} // namespace esphome
|
||||
|
||||
#endif // ARDUINO_ARCH_ESP32
|
||||
81
esphome/components/b_parasite/sensor.py
Normal file
81
esphome/components/b_parasite/sensor.py
Normal file
@@ -0,0 +1,81 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import sensor, esp32_ble_tracker
|
||||
from esphome.const import (
|
||||
CONF_BATTERY_VOLTAGE,
|
||||
CONF_HUMIDITY,
|
||||
CONF_ID,
|
||||
CONF_MOISTURE,
|
||||
CONF_MAC_ADDRESS,
|
||||
CONF_TEMPERATURE,
|
||||
DEVICE_CLASS_HUMIDITY,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
DEVICE_CLASS_VOLTAGE,
|
||||
ICON_EMPTY,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_CELSIUS,
|
||||
UNIT_PERCENT,
|
||||
UNIT_VOLT,
|
||||
)
|
||||
|
||||
CODEOWNERS = ["@rbaron"]
|
||||
|
||||
DEPENDENCIES = ["esp32_ble_tracker"]
|
||||
|
||||
b_parasite_ns = cg.esphome_ns.namespace("b_parasite")
|
||||
BParasite = b_parasite_ns.class_(
|
||||
"BParasite", esp32_ble_tracker.ESPBTDeviceListener, cg.Component
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(BParasite),
|
||||
cv.Required(CONF_MAC_ADDRESS): cv.mac_address,
|
||||
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
|
||||
UNIT_CELSIUS,
|
||||
ICON_EMPTY,
|
||||
1,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
|
||||
UNIT_PERCENT,
|
||||
ICON_EMPTY,
|
||||
1,
|
||||
DEVICE_CLASS_HUMIDITY,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_BATTERY_VOLTAGE): sensor.sensor_schema(
|
||||
UNIT_VOLT, ICON_EMPTY, 3, DEVICE_CLASS_VOLTAGE, STATE_CLASS_MEASUREMENT
|
||||
),
|
||||
cv.Optional(CONF_MOISTURE): sensor.sensor_schema(
|
||||
UNIT_PERCENT,
|
||||
ICON_EMPTY,
|
||||
1,
|
||||
DEVICE_CLASS_HUMIDITY,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
}
|
||||
)
|
||||
.extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA)
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await esp32_ble_tracker.register_ble_device(var, config)
|
||||
|
||||
cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex))
|
||||
|
||||
for (config_key, setter) in [
|
||||
(CONF_TEMPERATURE, var.set_temperature),
|
||||
(CONF_HUMIDITY, var.set_humidity),
|
||||
(CONF_BATTERY_VOLTAGE, var.set_battery_voltage),
|
||||
(CONF_MOISTURE, var.set_soil_moisture),
|
||||
]:
|
||||
if config_key in config:
|
||||
sens = await sensor.new_sensor(config[config_key])
|
||||
cg.add(setter(sens))
|
||||
@@ -39,12 +39,12 @@ CONFIG_SCHEMA = cv.All(
|
||||
)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
yield cg.register_component(var, config)
|
||||
yield climate.register_climate(var, config)
|
||||
await cg.register_component(var, config)
|
||||
await climate.register_climate(var, config)
|
||||
|
||||
sens = yield cg.get_variable(config[CONF_SENSOR])
|
||||
sens = await cg.get_variable(config[CONF_SENSOR])
|
||||
cg.add(var.set_sensor(sens))
|
||||
|
||||
normal_config = BangBangClimateTargetTempConfig(
|
||||
@@ -53,17 +53,17 @@ def to_code(config):
|
||||
)
|
||||
cg.add(var.set_normal_config(normal_config))
|
||||
|
||||
yield automation.build_automation(
|
||||
await automation.build_automation(
|
||||
var.get_idle_trigger(), [], config[CONF_IDLE_ACTION]
|
||||
)
|
||||
|
||||
if CONF_COOL_ACTION in config:
|
||||
yield automation.build_automation(
|
||||
await automation.build_automation(
|
||||
var.get_cool_trigger(), [], config[CONF_COOL_ACTION]
|
||||
)
|
||||
cg.add(var.set_supports_cool(True))
|
||||
if CONF_HEAT_ACTION in config:
|
||||
yield automation.build_automation(
|
||||
await automation.build_automation(
|
||||
var.get_heat_trigger(), [], config[CONF_HEAT_ACTION]
|
||||
)
|
||||
cg.add(var.set_supports_heat(True))
|
||||
|
||||
@@ -17,8 +17,8 @@ void BH1750Sensor::setup() {
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t mtreg_hi = (this->measurement_time_ >> 5) & 0b111;
|
||||
uint8_t mtreg_lo = (this->measurement_time_ >> 0) & 0b11111;
|
||||
uint8_t mtreg_hi = (this->measurement_duration_ >> 5) & 0b111;
|
||||
uint8_t mtreg_lo = (this->measurement_duration_ >> 0) & 0b11111;
|
||||
this->write_bytes(BH1750_COMMAND_MT_REG_HI | mtreg_hi, nullptr, 0);
|
||||
this->write_bytes(BH1750_COMMAND_MT_REG_LO | mtreg_lo, nullptr, 0);
|
||||
}
|
||||
@@ -77,7 +77,7 @@ void BH1750Sensor::read_data_() {
|
||||
}
|
||||
|
||||
float lx = float(raw_value) / 1.2f;
|
||||
lx *= 69.0f / this->measurement_time_;
|
||||
lx *= 69.0f / this->measurement_duration_;
|
||||
ESP_LOGD(TAG, "'%s': Got illuminance=%.1flx", this->get_name().c_str(), lx);
|
||||
this->publish_state(lx);
|
||||
this->status_clear_warning();
|
||||
|
||||
@@ -28,7 +28,7 @@ class BH1750Sensor : public sensor::Sensor, public PollingComponent, public i2c:
|
||||
* @param resolution The new resolution of the sensor.
|
||||
*/
|
||||
void set_resolution(BH1750Resolution resolution);
|
||||
void set_measurement_time(uint8_t measurement_time) { measurement_time_ = measurement_time; }
|
||||
void set_measurement_duration(uint8_t measurement_duration) { measurement_duration_ = measurement_duration; }
|
||||
|
||||
// ========== INTERNAL METHODS ==========
|
||||
// (In most use cases you won't need these)
|
||||
@@ -41,7 +41,7 @@ class BH1750Sensor : public sensor::Sensor, public PollingComponent, public i2c:
|
||||
void read_data_();
|
||||
|
||||
BH1750Resolution resolution_{BH1750_RESOLUTION_0P5_LX};
|
||||
uint8_t measurement_time_;
|
||||
uint8_t measurement_duration_;
|
||||
};
|
||||
|
||||
} // namespace bh1750
|
||||
|
||||
@@ -6,7 +6,9 @@ from esphome.const import (
|
||||
CONF_RESOLUTION,
|
||||
DEVICE_CLASS_ILLUMINANCE,
|
||||
ICON_EMPTY,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_LUX,
|
||||
CONF_MEASUREMENT_DURATION,
|
||||
)
|
||||
|
||||
DEPENDENCIES = ["i2c"]
|
||||
@@ -25,16 +27,21 @@ BH1750Sensor = bh1750_ns.class_(
|
||||
|
||||
CONF_MEASUREMENT_TIME = "measurement_time"
|
||||
CONFIG_SCHEMA = (
|
||||
sensor.sensor_schema(UNIT_LUX, ICON_EMPTY, 1, DEVICE_CLASS_ILLUMINANCE)
|
||||
sensor.sensor_schema(
|
||||
UNIT_LUX, ICON_EMPTY, 1, DEVICE_CLASS_ILLUMINANCE, STATE_CLASS_MEASUREMENT
|
||||
)
|
||||
.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(BH1750Sensor),
|
||||
cv.Optional(CONF_RESOLUTION, default=0.5): cv.enum(
|
||||
BH1750_RESOLUTIONS, float=True
|
||||
),
|
||||
cv.Optional(CONF_MEASUREMENT_TIME, default=69): cv.int_range(
|
||||
cv.Optional(CONF_MEASUREMENT_DURATION, default=69): cv.int_range(
|
||||
min=31, max=254
|
||||
),
|
||||
cv.Optional(CONF_MEASUREMENT_TIME): cv.invalid(
|
||||
"The 'measurement_time' option has been replaced with 'measurement_duration' in 1.18.0"
|
||||
),
|
||||
}
|
||||
)
|
||||
.extend(cv.polling_component_schema("60s"))
|
||||
@@ -42,11 +49,11 @@ CONFIG_SCHEMA = (
|
||||
)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
yield cg.register_component(var, config)
|
||||
yield sensor.register_sensor(var, config)
|
||||
yield i2c.register_i2c_device(var, config)
|
||||
await cg.register_component(var, config)
|
||||
await sensor.register_sensor(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
|
||||
cg.add(var.set_resolution(config[CONF_RESOLUTION]))
|
||||
cg.add(var.set_measurement_time(config[CONF_MEASUREMENT_TIME]))
|
||||
cg.add(var.set_measurement_duration(config[CONF_MEASUREMENT_DURATION]))
|
||||
|
||||
@@ -21,19 +21,19 @@ CONFIG_SCHEMA = fan.FAN_SCHEMA.extend(
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_OUTPUT_ID])
|
||||
yield cg.register_component(var, config)
|
||||
await cg.register_component(var, config)
|
||||
|
||||
fan_ = yield fan.create_fan_state(config)
|
||||
fan_ = await fan.create_fan_state(config)
|
||||
cg.add(var.set_fan(fan_))
|
||||
output_ = yield cg.get_variable(config[CONF_OUTPUT])
|
||||
output_ = await cg.get_variable(config[CONF_OUTPUT])
|
||||
cg.add(var.set_output(output_))
|
||||
|
||||
if CONF_OSCILLATION_OUTPUT in config:
|
||||
oscillation_output = yield cg.get_variable(config[CONF_OSCILLATION_OUTPUT])
|
||||
oscillation_output = await cg.get_variable(config[CONF_OSCILLATION_OUTPUT])
|
||||
cg.add(var.set_oscillating(oscillation_output))
|
||||
|
||||
if CONF_DIRECTION_OUTPUT in config:
|
||||
direction_output = yield cg.get_variable(config[CONF_DIRECTION_OUTPUT])
|
||||
direction_output = await cg.get_variable(config[CONF_DIRECTION_OUTPUT])
|
||||
cg.add(var.set_direction(direction_output))
|
||||
|
||||
@@ -14,9 +14,9 @@ CONFIG_SCHEMA = light.BINARY_LIGHT_SCHEMA.extend(
|
||||
)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_OUTPUT_ID])
|
||||
yield light.register_light(var, config)
|
||||
await light.register_light(var, config)
|
||||
|
||||
out = yield cg.get_variable(config[CONF_OUTPUT])
|
||||
out = await cg.get_variable(config[CONF_OUTPUT])
|
||||
cg.add(var.set_output(out))
|
||||
|
||||
@@ -4,6 +4,7 @@ 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_FILTERS,
|
||||
CONF_ID,
|
||||
@@ -50,7 +51,7 @@ from esphome.const import (
|
||||
DEVICE_CLASS_VIBRATION,
|
||||
DEVICE_CLASS_WINDOW,
|
||||
)
|
||||
from esphome.core import CORE, coroutine, coroutine_with_priority
|
||||
from esphome.core import CORE, coroutine_with_priority
|
||||
from esphome.util import Registry
|
||||
|
||||
CODEOWNERS = ["@esphome/core"]
|
||||
@@ -120,6 +121,7 @@ DelayedOnOffFilter = binary_sensor_ns.class_("DelayedOnOffFilter", Filter, cg.Co
|
||||
DelayedOnFilter = binary_sensor_ns.class_("DelayedOnFilter", Filter, cg.Component)
|
||||
DelayedOffFilter = binary_sensor_ns.class_("DelayedOffFilter", Filter, cg.Component)
|
||||
InvertFilter = binary_sensor_ns.class_("InvertFilter", Filter)
|
||||
AutorepeatFilter = binary_sensor_ns.class_("AutorepeatFilter", Filter, cg.Component)
|
||||
LambdaFilter = binary_sensor_ns.class_("LambdaFilter", Filter)
|
||||
|
||||
FILTER_REGISTRY = Registry()
|
||||
@@ -127,43 +129,88 @@ validate_filters = cv.validate_registry("filter", FILTER_REGISTRY)
|
||||
|
||||
|
||||
@FILTER_REGISTRY.register("invert", InvertFilter, {})
|
||||
def invert_filter_to_code(config, filter_id):
|
||||
yield cg.new_Pvariable(filter_id)
|
||||
async def invert_filter_to_code(config, filter_id):
|
||||
return cg.new_Pvariable(filter_id)
|
||||
|
||||
|
||||
@FILTER_REGISTRY.register(
|
||||
"delayed_on_off", DelayedOnOffFilter, cv.positive_time_period_milliseconds
|
||||
)
|
||||
def delayed_on_off_filter_to_code(config, filter_id):
|
||||
async def delayed_on_off_filter_to_code(config, filter_id):
|
||||
var = cg.new_Pvariable(filter_id, config)
|
||||
yield cg.register_component(var, {})
|
||||
yield var
|
||||
await cg.register_component(var, {})
|
||||
return var
|
||||
|
||||
|
||||
@FILTER_REGISTRY.register(
|
||||
"delayed_on", DelayedOnFilter, cv.positive_time_period_milliseconds
|
||||
)
|
||||
def delayed_on_filter_to_code(config, filter_id):
|
||||
async def delayed_on_filter_to_code(config, filter_id):
|
||||
var = cg.new_Pvariable(filter_id, config)
|
||||
yield cg.register_component(var, {})
|
||||
yield var
|
||||
await cg.register_component(var, {})
|
||||
return var
|
||||
|
||||
|
||||
@FILTER_REGISTRY.register(
|
||||
"delayed_off", DelayedOffFilter, cv.positive_time_period_milliseconds
|
||||
)
|
||||
def delayed_off_filter_to_code(config, filter_id):
|
||||
async def delayed_off_filter_to_code(config, filter_id):
|
||||
var = cg.new_Pvariable(filter_id, config)
|
||||
yield cg.register_component(var, {})
|
||||
yield var
|
||||
await cg.register_component(var, {})
|
||||
return var
|
||||
|
||||
|
||||
CONF_TIME_OFF = "time_off"
|
||||
CONF_TIME_ON = "time_on"
|
||||
|
||||
DEFAULT_DELAY = "1s"
|
||||
DEFAULT_TIME_OFF = "100ms"
|
||||
DEFAULT_TIME_ON = "900ms"
|
||||
|
||||
|
||||
@FILTER_REGISTRY.register(
|
||||
"autorepeat",
|
||||
AutorepeatFilter,
|
||||
cv.All(
|
||||
cv.ensure_list(
|
||||
{
|
||||
cv.Optional(
|
||||
CONF_DELAY, default=DEFAULT_DELAY
|
||||
): cv.positive_time_period_milliseconds,
|
||||
cv.Optional(
|
||||
CONF_TIME_OFF, default=DEFAULT_TIME_OFF
|
||||
): cv.positive_time_period_milliseconds,
|
||||
cv.Optional(
|
||||
CONF_TIME_ON, default=DEFAULT_TIME_ON
|
||||
): cv.positive_time_period_milliseconds,
|
||||
}
|
||||
),
|
||||
),
|
||||
)
|
||||
async def autorepeat_filter_to_code(config, filter_id):
|
||||
timings = []
|
||||
if len(config) > 0:
|
||||
for conf in config:
|
||||
timings.append((conf[CONF_DELAY], conf[CONF_TIME_OFF], conf[CONF_TIME_ON]))
|
||||
else:
|
||||
timings.append(
|
||||
(
|
||||
cv.time_period_str_unit(DEFAULT_DELAY).total_milliseconds,
|
||||
cv.time_period_str_unit(DEFAULT_TIME_OFF).total_milliseconds,
|
||||
cv.time_period_str_unit(DEFAULT_TIME_ON).total_milliseconds,
|
||||
)
|
||||
)
|
||||
var = cg.new_Pvariable(filter_id, timings)
|
||||
await cg.register_component(var, {})
|
||||
return var
|
||||
|
||||
|
||||
@FILTER_REGISTRY.register("lambda", LambdaFilter, cv.returning_lambda)
|
||||
def lambda_filter_to_code(config, filter_id):
|
||||
lambda_ = yield cg.process_lambda(
|
||||
async def lambda_filter_to_code(config, filter_id):
|
||||
lambda_ = await cg.process_lambda(
|
||||
config, [(bool, "x")], return_type=cg.optional.template(bool)
|
||||
)
|
||||
yield cg.new_Pvariable(filter_id, lambda_)
|
||||
return cg.new_Pvariable(filter_id, lambda_)
|
||||
|
||||
|
||||
MULTI_CLICK_TIMING_SCHEMA = cv.Schema(
|
||||
@@ -334,8 +381,7 @@ BINARY_SENSOR_SCHEMA = cv.MQTT_COMPONENT_SCHEMA.extend(
|
||||
)
|
||||
|
||||
|
||||
@coroutine
|
||||
def setup_binary_sensor_core_(var, config):
|
||||
async def setup_binary_sensor_core_(var, config):
|
||||
cg.add(var.set_name(config[CONF_NAME]))
|
||||
if CONF_INTERNAL in config:
|
||||
cg.add(var.set_internal(config[CONF_INTERNAL]))
|
||||
@@ -344,28 +390,28 @@ def setup_binary_sensor_core_(var, config):
|
||||
if CONF_INVERTED in config:
|
||||
cg.add(var.set_inverted(config[CONF_INVERTED]))
|
||||
if CONF_FILTERS in config:
|
||||
filters = yield cg.build_registry_list(FILTER_REGISTRY, config[CONF_FILTERS])
|
||||
filters = await cg.build_registry_list(FILTER_REGISTRY, config[CONF_FILTERS])
|
||||
cg.add(var.add_filters(filters))
|
||||
|
||||
for conf in config.get(CONF_ON_PRESS, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
yield automation.build_automation(trigger, [], conf)
|
||||
await automation.build_automation(trigger, [], conf)
|
||||
|
||||
for conf in config.get(CONF_ON_RELEASE, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
yield automation.build_automation(trigger, [], conf)
|
||||
await automation.build_automation(trigger, [], conf)
|
||||
|
||||
for conf in config.get(CONF_ON_CLICK, []):
|
||||
trigger = cg.new_Pvariable(
|
||||
conf[CONF_TRIGGER_ID], var, conf[CONF_MIN_LENGTH], conf[CONF_MAX_LENGTH]
|
||||
)
|
||||
yield automation.build_automation(trigger, [], conf)
|
||||
await automation.build_automation(trigger, [], conf)
|
||||
|
||||
for conf in config.get(CONF_ON_DOUBLE_CLICK, []):
|
||||
trigger = cg.new_Pvariable(
|
||||
conf[CONF_TRIGGER_ID], var, conf[CONF_MIN_LENGTH], conf[CONF_MAX_LENGTH]
|
||||
)
|
||||
yield automation.build_automation(trigger, [], conf)
|
||||
await automation.build_automation(trigger, [], conf)
|
||||
|
||||
for conf in config.get(CONF_ON_MULTI_CLICK, []):
|
||||
timings = []
|
||||
@@ -381,31 +427,29 @@ def setup_binary_sensor_core_(var, config):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var, timings)
|
||||
if CONF_INVALID_COOLDOWN in conf:
|
||||
cg.add(trigger.set_invalid_cooldown(conf[CONF_INVALID_COOLDOWN]))
|
||||
yield cg.register_component(trigger, conf)
|
||||
yield automation.build_automation(trigger, [], conf)
|
||||
await cg.register_component(trigger, conf)
|
||||
await automation.build_automation(trigger, [], conf)
|
||||
|
||||
for conf in config.get(CONF_ON_STATE, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
yield automation.build_automation(trigger, [(bool, "x")], conf)
|
||||
await automation.build_automation(trigger, [(bool, "x")], conf)
|
||||
|
||||
if CONF_MQTT_ID in config:
|
||||
mqtt_ = cg.new_Pvariable(config[CONF_MQTT_ID], var)
|
||||
yield mqtt.register_mqtt_component(mqtt_, config)
|
||||
await mqtt.register_mqtt_component(mqtt_, config)
|
||||
|
||||
|
||||
@coroutine
|
||||
def register_binary_sensor(var, config):
|
||||
async def register_binary_sensor(var, config):
|
||||
if not CORE.has_id(config[CONF_ID]):
|
||||
var = cg.Pvariable(config[CONF_ID], var)
|
||||
cg.add(cg.App.register_binary_sensor(var))
|
||||
yield setup_binary_sensor_core_(var, config)
|
||||
await setup_binary_sensor_core_(var, config)
|
||||
|
||||
|
||||
@coroutine
|
||||
def new_binary_sensor(config):
|
||||
async def new_binary_sensor(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID], config[CONF_NAME])
|
||||
yield register_binary_sensor(var, config)
|
||||
yield var
|
||||
await register_binary_sensor(var, config)
|
||||
return var
|
||||
|
||||
|
||||
BINARY_SENSOR_CONDITION_SCHEMA = maybe_simple_id(
|
||||
@@ -422,20 +466,20 @@ BINARY_SENSOR_CONDITION_SCHEMA = maybe_simple_id(
|
||||
@automation.register_condition(
|
||||
"binary_sensor.is_on", BinarySensorCondition, BINARY_SENSOR_CONDITION_SCHEMA
|
||||
)
|
||||
def binary_sensor_is_on_to_code(config, condition_id, template_arg, args):
|
||||
paren = yield cg.get_variable(config[CONF_ID])
|
||||
yield cg.new_Pvariable(condition_id, template_arg, paren, True)
|
||||
async def binary_sensor_is_on_to_code(config, condition_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(condition_id, template_arg, paren, True)
|
||||
|
||||
|
||||
@automation.register_condition(
|
||||
"binary_sensor.is_off", BinarySensorCondition, BINARY_SENSOR_CONDITION_SCHEMA
|
||||
)
|
||||
def binary_sensor_is_off_to_code(config, condition_id, template_arg, args):
|
||||
paren = yield cg.get_variable(config[CONF_ID])
|
||||
yield cg.new_Pvariable(condition_id, template_arg, paren, False)
|
||||
async def binary_sensor_is_off_to_code(config, condition_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(condition_id, template_arg, paren, False)
|
||||
|
||||
|
||||
@coroutine_with_priority(100.0)
|
||||
def to_code(config):
|
||||
async def to_code(config):
|
||||
cg.add_define("USE_BINARY_SENSOR")
|
||||
cg.add_global(binary_sensor_ns.using)
|
||||
|
||||
@@ -64,6 +64,50 @@ float DelayedOffFilter::get_setup_priority() const { return setup_priority::HARD
|
||||
|
||||
optional<bool> InvertFilter::new_value(bool value, bool is_initial) { return !value; }
|
||||
|
||||
AutorepeatFilter::AutorepeatFilter(const std::vector<AutorepeatFilterTiming> &timings) : timings_(timings) {}
|
||||
|
||||
optional<bool> AutorepeatFilter::new_value(bool value, bool is_initial) {
|
||||
if (value) {
|
||||
// Ignore if already running
|
||||
if (this->active_timing_ != 0)
|
||||
return {};
|
||||
|
||||
this->next_timing_();
|
||||
return true;
|
||||
} else {
|
||||
this->cancel_timeout("TIMING");
|
||||
this->cancel_timeout("ON_OFF");
|
||||
this->active_timing_ = 0;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void AutorepeatFilter::next_timing_() {
|
||||
// Entering this method
|
||||
// 1st time: starts waiting the first delay
|
||||
// 2nd time: starts waiting the second delay and starts toggling with the first time_off / _on
|
||||
// last time: no delay to start but have to bump the index to reflect the last
|
||||
if (this->active_timing_ < this->timings_.size())
|
||||
this->set_timeout("TIMING", this->timings_[this->active_timing_].delay, [this]() { this->next_timing_(); });
|
||||
|
||||
if (this->active_timing_ <= this->timings_.size()) {
|
||||
this->active_timing_++;
|
||||
}
|
||||
|
||||
if (this->active_timing_ == 2)
|
||||
this->next_value_(false);
|
||||
|
||||
// Leaving this method: if the toggling is started, it has to use [active_timing_ - 2] for the intervals
|
||||
}
|
||||
|
||||
void AutorepeatFilter::next_value_(bool val) {
|
||||
const AutorepeatFilterTiming &timing = this->timings_[this->active_timing_ - 2];
|
||||
this->output(val, false); // This is at least the second one so not initial
|
||||
this->set_timeout("ON_OFF", val ? timing.time_on : timing.time_off, [this, val]() { this->next_value_(!val); });
|
||||
}
|
||||
|
||||
float AutorepeatFilter::get_setup_priority() const { return setup_priority::HARDWARE; }
|
||||
|
||||
LambdaFilter::LambdaFilter(const std::function<optional<bool>(bool)> &f) : f_(f) {}
|
||||
|
||||
optional<bool> LambdaFilter::new_value(bool value, bool is_initial) { return this->f_(value); }
|
||||
|
||||
@@ -66,6 +66,33 @@ class InvertFilter : public Filter {
|
||||
optional<bool> new_value(bool value, bool is_initial) override;
|
||||
};
|
||||
|
||||
struct AutorepeatFilterTiming {
|
||||
AutorepeatFilterTiming(uint32_t delay, uint32_t off, uint32_t on) {
|
||||
this->delay = delay;
|
||||
this->time_off = off;
|
||||
this->time_on = on;
|
||||
}
|
||||
uint32_t delay;
|
||||
uint32_t time_off;
|
||||
uint32_t time_on;
|
||||
};
|
||||
|
||||
class AutorepeatFilter : public Filter, public Component {
|
||||
public:
|
||||
explicit AutorepeatFilter(const std::vector<AutorepeatFilterTiming> &timings);
|
||||
|
||||
optional<bool> new_value(bool value, bool is_initial) override;
|
||||
|
||||
float get_setup_priority() const override;
|
||||
|
||||
protected:
|
||||
void next_timing_();
|
||||
void next_value_(bool val);
|
||||
|
||||
std::vector<AutorepeatFilterTiming> timings_;
|
||||
uint8_t active_timing_{0};
|
||||
};
|
||||
|
||||
class LambdaFilter : public Filter {
|
||||
public:
|
||||
explicit LambdaFilter(const std::function<optional<bool>(bool)> &f);
|
||||
|
||||
@@ -12,6 +12,7 @@ from esphome.const import (
|
||||
ICON_CHECK_CIRCLE_OUTLINE,
|
||||
CONF_BINARY_SENSOR,
|
||||
CONF_GROUP,
|
||||
STATE_CLASS_NONE,
|
||||
)
|
||||
|
||||
DEPENDENCIES = ["binary_sensor"]
|
||||
@@ -34,7 +35,11 @@ entry = {
|
||||
CONFIG_SCHEMA = cv.typed_schema(
|
||||
{
|
||||
CONF_GROUP: sensor.sensor_schema(
|
||||
UNIT_EMPTY, ICON_CHECK_CIRCLE_OUTLINE, 0, DEVICE_CLASS_EMPTY
|
||||
UNIT_EMPTY,
|
||||
ICON_CHECK_CIRCLE_OUTLINE,
|
||||
0,
|
||||
DEVICE_CLASS_EMPTY,
|
||||
STATE_CLASS_NONE,
|
||||
).extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(BinarySensorMap),
|
||||
@@ -48,14 +53,14 @@ CONFIG_SCHEMA = cv.typed_schema(
|
||||
)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
yield cg.register_component(var, config)
|
||||
yield sensor.register_sensor(var, config)
|
||||
await cg.register_component(var, config)
|
||||
await sensor.register_sensor(var, config)
|
||||
|
||||
constant = SENSOR_MAP_TYPES[config[CONF_TYPE]]
|
||||
cg.add(var.set_sensor_type(constant))
|
||||
|
||||
for ch in config[CONF_CHANNELS]:
|
||||
input_var = yield cg.get_variable(ch[CONF_BINARY_SENSOR])
|
||||
input_var = await cg.get_variable(ch[CONF_BINARY_SENSOR])
|
||||
cg.add(var.add_channel(input_var, ch[CONF_VALUE]))
|
||||
|
||||
85
esphome/components/ble_client/__init__.py
Normal file
85
esphome/components/ble_client/__init__.py
Normal file
@@ -0,0 +1,85 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import esp32_ble_tracker
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_MAC_ADDRESS,
|
||||
CONF_NAME,
|
||||
CONF_ON_CONNECT,
|
||||
CONF_ON_DISCONNECT,
|
||||
CONF_TRIGGER_ID,
|
||||
)
|
||||
from esphome import automation
|
||||
|
||||
CODEOWNERS = ["@buxtronix"]
|
||||
DEPENDENCIES = ["esp32_ble_tracker"]
|
||||
|
||||
ble_client_ns = cg.esphome_ns.namespace("ble_client")
|
||||
BLEClient = ble_client_ns.class_(
|
||||
"BLEClient", cg.Component, esp32_ble_tracker.ESPBTClient
|
||||
)
|
||||
BLEClientNode = ble_client_ns.class_("BLEClientNode")
|
||||
BLEClientNodeConstRef = BLEClientNode.operator("ref").operator("const")
|
||||
# Triggers
|
||||
BLEClientConnectTrigger = ble_client_ns.class_(
|
||||
"BLEClientConnectTrigger", automation.Trigger.template(BLEClientNodeConstRef)
|
||||
)
|
||||
BLEClientDisconnectTrigger = ble_client_ns.class_(
|
||||
"BLEClientDisconnectTrigger", automation.Trigger.template(BLEClientNodeConstRef)
|
||||
)
|
||||
|
||||
# Espressif platformio framework is built with MAX_BLE_CONN to 3, so
|
||||
# enforce this in yaml checks.
|
||||
MULTI_CONF = 3
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(BLEClient),
|
||||
cv.Required(CONF_MAC_ADDRESS): cv.mac_address,
|
||||
cv.Optional(CONF_NAME): cv.string,
|
||||
cv.Optional(CONF_ON_CONNECT): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
||||
BLEClientConnectTrigger
|
||||
),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_DISCONNECT): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
||||
BLEClientDisconnectTrigger
|
||||
),
|
||||
}
|
||||
),
|
||||
}
|
||||
)
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
.extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA)
|
||||
)
|
||||
|
||||
CONF_BLE_CLIENT_ID = "ble_client_id"
|
||||
|
||||
BLE_CLIENT_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_BLE_CLIENT_ID): cv.use_id(BLEClient),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def register_ble_node(var, config):
|
||||
parent = await cg.get_variable(config[CONF_BLE_CLIENT_ID])
|
||||
cg.add(parent.register_ble_node(var))
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await esp32_ble_tracker.register_client(var, config)
|
||||
cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex))
|
||||
for conf in config.get(CONF_ON_CONNECT, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [], conf)
|
||||
for conf in config.get(CONF_ON_DISCONNECT, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [], conf)
|
||||
37
esphome/components/ble_client/automation.h
Normal file
37
esphome/components/ble_client/automation.h
Normal file
@@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/components/ble_client/ble_client.h"
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
|
||||
namespace esphome {
|
||||
namespace ble_client {
|
||||
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) {
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
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) {
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace ble_client
|
||||
} // namespace esphome
|
||||
|
||||
#endif
|
||||
392
esphome/components/ble_client/ble_client.cpp
Normal file
392
esphome/components/ble_client/ble_client.cpp
Normal file
@@ -0,0 +1,392 @@
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
|
||||
#include "ble_client.h"
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
|
||||
namespace esphome {
|
||||
namespace ble_client {
|
||||
|
||||
static const char *TAG = "ble_client";
|
||||
|
||||
void BLEClient::setup() {
|
||||
auto ret = esp_ble_gattc_app_register(this->app_id);
|
||||
if (ret) {
|
||||
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->enabled = true;
|
||||
}
|
||||
|
||||
void BLEClient::loop() {
|
||||
if (this->state() == espbt::ClientState::Discovered) {
|
||||
this->connect();
|
||||
}
|
||||
for (auto *node : this->nodes_)
|
||||
node->loop();
|
||||
}
|
||||
|
||||
void BLEClient::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "BLE Client:");
|
||||
ESP_LOGCONFIG(TAG, " Address: %s", this->address_str().c_str());
|
||||
}
|
||||
|
||||
bool BLEClient::parse_device(const espbt::ESPBTDevice &device) {
|
||||
if (!this->enabled)
|
||||
return false;
|
||||
if (device.address_uint64() != this->address)
|
||||
return false;
|
||||
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);
|
||||
|
||||
auto addr = device.address_uint64();
|
||||
this->remote_bda[0] = (addr >> 40) & 0xFF;
|
||||
this->remote_bda[1] = (addr >> 32) & 0xFF;
|
||||
this->remote_bda[2] = (addr >> 24) & 0xFF;
|
||||
this->remote_bda[3] = (addr >> 16) & 0xFF;
|
||||
this->remote_bda[4] = (addr >> 8) & 0xFF;
|
||||
this->remote_bda[5] = (addr >> 0) & 0xFF;
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string BLEClient::address_str() const {
|
||||
char buf[20];
|
||||
sprintf(buf, "%02x:%02x:%02x:%02x:%02x:%02x", (uint8_t)(this->address >> 40) & 0xff,
|
||||
(uint8_t)(this->address >> 32) & 0xff, (uint8_t)(this->address >> 24) & 0xff,
|
||||
(uint8_t)(this->address >> 16) & 0xff, (uint8_t)(this->address >> 8) & 0xff,
|
||||
(uint8_t)(this->address >> 0) & 0xff);
|
||||
std::string ret;
|
||||
ret = buf;
|
||||
return ret;
|
||||
}
|
||||
|
||||
void BLEClient::set_enabled(bool enabled) {
|
||||
if (enabled == this->enabled)
|
||||
return;
|
||||
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) {
|
||||
ESP_LOGW(TAG, "esp_ble_gattc_close error, address=%s status=%d", this->address_str().c_str(), ret);
|
||||
}
|
||||
}
|
||||
this->enabled = enabled;
|
||||
}
|
||||
|
||||
void BLEClient::connect() {
|
||||
ESP_LOGI(TAG, "Attempting BLE connection to %s", this->address_str().c_str());
|
||||
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);
|
||||
} else {
|
||||
this->set_states(espbt::ClientState::Connecting);
|
||||
}
|
||||
}
|
||||
|
||||
void BLEClient::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t esp_gattc_if,
|
||||
esp_ble_gattc_cb_param_t *param) {
|
||||
if (event == ESP_GATTC_REG_EVT && this->app_id != param->reg.app_id)
|
||||
return;
|
||||
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();
|
||||
|
||||
switch (event) {
|
||||
case ESP_GATTC_REG_EVT: {
|
||||
if (param->reg.status == ESP_GATT_OK) {
|
||||
ESP_LOGV(TAG, "gattc registered app id %d", this->app_id);
|
||||
this->gattc_if = esp_gattc_if;
|
||||
} else {
|
||||
ESP_LOGE(TAG, "gattc app registration failed id=%d code=%d", param->reg.app_id, param->reg.status);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_OPEN_EVT: {
|
||||
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);
|
||||
break;
|
||||
}
|
||||
this->conn_id = param->open.conn_id;
|
||||
auto ret = esp_ble_gattc_send_mtu_req(this->gattc_if, param->open.conn_id);
|
||||
if (ret) {
|
||||
ESP_LOGW(TAG, "esp_ble_gattc_send_mtu_req failed, status=%d", ret);
|
||||
}
|
||||
break;
|
||||
}
|
||||
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);
|
||||
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);
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_DISCONNECT_EVT: {
|
||||
if (memcmp(param->disconnect.remote_bda, this->remote_bda, 6) != 0) {
|
||||
return;
|
||||
}
|
||||
ESP_LOGV(TAG, "[%s] ESP_GATTC_DISCONNECT_EVT", this->address_str().c_str());
|
||||
for (auto &svc : this->services_)
|
||||
delete svc;
|
||||
this->services_.clear();
|
||||
this->set_states(espbt::ClientState::Idle);
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_SEARCH_RES_EVT: {
|
||||
BLEService *ble_service = new BLEService();
|
||||
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;
|
||||
ble_service->client = this;
|
||||
this->services_.push_back(ble_service);
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_SEARCH_CMPL_EVT: {
|
||||
ESP_LOGV(TAG, "[%s] ESP_GATTC_SEARCH_CMPL_EVT", this->address_str().c_str());
|
||||
for (auto &svc : this->services_) {
|
||||
ESP_LOGI(TAG, "Service UUID: %s", svc->uuid.to_string().c_str());
|
||||
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);
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
|
||||
auto descr = this->get_config_descriptor(param->reg_for_notify.handle);
|
||||
if (descr == nullptr) {
|
||||
ESP_LOGW(TAG, "No descriptor found for notify of handle 0x%x", param->reg_for_notify.handle);
|
||||
break;
|
||||
}
|
||||
if (descr->uuid.get_uuid().len != ESP_UUID_LEN_16 ||
|
||||
descr->uuid.get_uuid().uuid.uuid16 != ESP_GATT_UUID_CHAR_CLIENT_CONFIG) {
|
||||
ESP_LOGW(TAG, "Handle 0x%x (uuid %s) is not a client config char uuid", param->reg_for_notify.handle,
|
||||
descr->uuid.to_string().c_str());
|
||||
break;
|
||||
}
|
||||
uint8_t notify_en = 1;
|
||||
auto status = esp_ble_gattc_write_char_descr(this->gattc_if, this->conn_id, descr->handle, sizeof(notify_en),
|
||||
¬ify_en, ESP_GATT_WRITE_TYPE_RSP, ESP_GATT_AUTH_REQ_NONE);
|
||||
if (status) {
|
||||
ESP_LOGW(TAG, "esp_ble_gattc_write_char_descr error, status=%d", status);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
for (auto *node : this->nodes_)
|
||||
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()) {
|
||||
for (auto &svc : this->services_)
|
||||
delete svc;
|
||||
this->services_.clear();
|
||||
}
|
||||
}
|
||||
|
||||
// Parse GATT values into a float for a sensor.
|
||||
// Ref: https://www.bluetooth.com/specifications/assigned-numbers/format-types/
|
||||
float BLEClient::parse_char_value(uint8_t *value, uint16_t length) {
|
||||
// A length of one means a single octet value.
|
||||
if (length == 0)
|
||||
return 0;
|
||||
if (length == 1)
|
||||
return (float) ((uint8_t) value[0]);
|
||||
|
||||
switch (value[0]) {
|
||||
case 0x1: // boolean.
|
||||
case 0x2: // 2bit.
|
||||
case 0x3: // nibble.
|
||||
case 0x4: // uint8.
|
||||
return (float) ((uint8_t) value[1]);
|
||||
case 0x5: // uint12.
|
||||
case 0x6: // uint16.
|
||||
if (length > 2) {
|
||||
return (float) ((uint16_t)(value[1] << 8) + (uint16_t) value[2]);
|
||||
}
|
||||
case 0x7: // uint24.
|
||||
if (length > 3) {
|
||||
return (float) ((uint32_t)(value[1] << 16) + (uint32_t)(value[2] << 8) + (uint32_t)(value[3]));
|
||||
}
|
||||
case 0x8: // uint32.
|
||||
if (length > 4) {
|
||||
return (float) ((uint32_t)(value[1] << 24) + (uint32_t)(value[2] << 16) + (uint32_t)(value[3] << 8) +
|
||||
(uint32_t)(value[4]));
|
||||
}
|
||||
case 0xC: // int8.
|
||||
return (float) ((int8_t) value[1]);
|
||||
case 0xD: // int12.
|
||||
case 0xE: // int16.
|
||||
if (length > 2) {
|
||||
return (float) ((int16_t)(value[1] << 8) + (int16_t) value[2]);
|
||||
}
|
||||
case 0xF: // int24.
|
||||
if (length > 3) {
|
||||
return (float) ((int32_t)(value[1] << 16) + (int32_t)(value[2] << 8) + (int32_t)(value[3]));
|
||||
}
|
||||
case 0x10: // int32.
|
||||
if (length > 4) {
|
||||
return (float) ((int32_t)(value[1] << 24) + (int32_t)(value[2] << 16) + (int32_t)(value[3] << 8) +
|
||||
(int32_t)(value[4]));
|
||||
}
|
||||
}
|
||||
ESP_LOGW(TAG, "Cannot parse characteristic value of type 0x%x length %d", value[0], length);
|
||||
return NAN;
|
||||
}
|
||||
|
||||
BLEService *BLEClient::get_service(espbt::ESPBTUUID uuid) {
|
||||
for (auto svc : this->services_)
|
||||
if (svc->uuid == uuid)
|
||||
return svc;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
BLEService *BLEClient::get_service(uint16_t uuid) { return this->get_service(espbt::ESPBTUUID::from_uint16(uuid)); }
|
||||
|
||||
BLECharacteristic *BLEClient::get_characteristic(espbt::ESPBTUUID service, espbt::ESPBTUUID chr) {
|
||||
auto svc = this->get_service(service);
|
||||
if (svc == nullptr)
|
||||
return nullptr;
|
||||
return svc->get_characteristic(chr);
|
||||
}
|
||||
|
||||
BLECharacteristic *BLEClient::get_characteristic(uint16_t service, uint16_t chr) {
|
||||
return this->get_characteristic(espbt::ESPBTUUID::from_uint16(service), espbt::ESPBTUUID::from_uint16(chr));
|
||||
}
|
||||
|
||||
BLEDescriptor *BLEClient::get_config_descriptor(uint16_t handle) {
|
||||
for (auto &svc : this->services_)
|
||||
for (auto &chr : svc->characteristics)
|
||||
if (chr->handle == handle)
|
||||
for (auto &desc : chr->descriptors)
|
||||
if (desc->uuid == espbt::ESPBTUUID::from_uint16(0x2902))
|
||||
return desc;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
BLECharacteristic *BLEService::get_characteristic(espbt::ESPBTUUID uuid) {
|
||||
for (auto &chr : this->characteristics)
|
||||
if (chr->uuid == uuid)
|
||||
return chr;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
BLECharacteristic *BLEService::get_characteristic(uint16_t uuid) {
|
||||
return this->get_characteristic(espbt::ESPBTUUID::from_uint16(uuid));
|
||||
}
|
||||
|
||||
BLEDescriptor *BLEClient::get_descriptor(espbt::ESPBTUUID service, espbt::ESPBTUUID chr, espbt::ESPBTUUID descr) {
|
||||
auto svc = this->get_service(service);
|
||||
if (svc == nullptr)
|
||||
return nullptr;
|
||||
auto ch = svc->get_characteristic(chr);
|
||||
if (ch == nullptr)
|
||||
return nullptr;
|
||||
return ch->get_descriptor(descr);
|
||||
}
|
||||
|
||||
BLEDescriptor *BLEClient::get_descriptor(uint16_t service, uint16_t chr, uint16_t descr) {
|
||||
return this->get_descriptor(espbt::ESPBTUUID::from_uint16(service), espbt::ESPBTUUID::from_uint16(chr),
|
||||
espbt::ESPBTUUID::from_uint16(descr));
|
||||
}
|
||||
|
||||
BLEService::~BLEService() {
|
||||
for (auto &chr : this->characteristics)
|
||||
delete chr;
|
||||
}
|
||||
|
||||
void BLEService::parse_characteristics() {
|
||||
uint16_t offset = 0;
|
||||
esp_gattc_char_elem_t result;
|
||||
|
||||
while (true) {
|
||||
uint16_t count = 1;
|
||||
esp_gatt_status_t status = esp_ble_gattc_get_all_char(
|
||||
this->client->gattc_if, this->client->conn_id, this->start_handle, this->end_handle, &result, &count, offset);
|
||||
if (status == ESP_GATT_INVALID_OFFSET || status == ESP_GATT_NOT_FOUND) {
|
||||
break;
|
||||
}
|
||||
if (status != ESP_GATT_OK) {
|
||||
ESP_LOGW(TAG, "esp_ble_gattc_get_all_char error, status=%d", status);
|
||||
break;
|
||||
}
|
||||
if (count == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
BLECharacteristic *characteristic = new BLECharacteristic();
|
||||
characteristic->uuid = espbt::ESPBTUUID::from_uuid(result.uuid);
|
||||
characteristic->properties = result.properties;
|
||||
characteristic->handle = result.char_handle;
|
||||
characteristic->service = this;
|
||||
this->characteristics.push_back(characteristic);
|
||||
ESP_LOGI(TAG, " characteristic %s, handle 0x%x, properties 0x%x", characteristic->uuid.to_string().c_str(),
|
||||
characteristic->handle, characteristic->properties);
|
||||
characteristic->parse_descriptors();
|
||||
offset++;
|
||||
}
|
||||
}
|
||||
|
||||
BLECharacteristic::~BLECharacteristic() {
|
||||
for (auto &desc : this->descriptors)
|
||||
delete desc;
|
||||
}
|
||||
|
||||
void BLECharacteristic::parse_descriptors() {
|
||||
uint16_t offset = 0;
|
||||
esp_gattc_descr_elem_t result;
|
||||
|
||||
while (true) {
|
||||
uint16_t count = 1;
|
||||
esp_gatt_status_t status = esp_ble_gattc_get_all_descr(
|
||||
this->service->client->gattc_if, this->service->client->conn_id, this->handle, &result, &count, offset);
|
||||
if (status == ESP_GATT_INVALID_OFFSET || status == ESP_GATT_NOT_FOUND) {
|
||||
break;
|
||||
}
|
||||
if (status != ESP_GATT_OK) {
|
||||
ESP_LOGW(TAG, "esp_ble_gattc_get_all_descr error, status=%d", status);
|
||||
break;
|
||||
}
|
||||
if (count == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
BLEDescriptor *desc = new BLEDescriptor();
|
||||
desc->uuid = espbt::ESPBTUUID::from_uuid(result.uuid);
|
||||
desc->handle = result.handle;
|
||||
desc->characteristic = this;
|
||||
this->descriptors.push_back(desc);
|
||||
ESP_LOGV(TAG, " descriptor %s, handle 0x%x", desc->uuid.to_string().c_str(), desc->handle);
|
||||
offset++;
|
||||
}
|
||||
}
|
||||
|
||||
BLEDescriptor *BLECharacteristic::get_descriptor(espbt::ESPBTUUID uuid) {
|
||||
for (auto &desc : this->descriptors)
|
||||
if (desc->uuid == uuid)
|
||||
return desc;
|
||||
return nullptr;
|
||||
}
|
||||
BLEDescriptor *BLECharacteristic::get_descriptor(uint16_t uuid) {
|
||||
return this->get_descriptor(espbt::ESPBTUUID::from_uint16(uuid));
|
||||
}
|
||||
|
||||
} // namespace ble_client
|
||||
} // namespace esphome
|
||||
|
||||
#endif
|
||||
140
esphome/components/ble_client/ble_client.h
Normal file
140
esphome/components/ble_client/ble_client.h
Normal file
@@ -0,0 +1,140 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
|
||||
#include <string>
|
||||
#include <array>
|
||||
#include <esp_gap_ble_api.h>
|
||||
#include <esp_gattc_api.h>
|
||||
#include <esp_bt_defs.h>
|
||||
|
||||
namespace esphome {
|
||||
namespace ble_client {
|
||||
|
||||
namespace espbt = esphome::esp32_ble_tracker;
|
||||
|
||||
class BLEClient;
|
||||
class BLEService;
|
||||
class BLECharacteristic;
|
||||
|
||||
class BLEClientNode {
|
||||
public:
|
||||
virtual void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
||||
esp_ble_gattc_cb_param_t *param) = 0;
|
||||
virtual void loop() = 0;
|
||||
void set_address(uint64_t address) { address_ = address; }
|
||||
espbt::ESPBTClient *client;
|
||||
// This should be transitioned to Established once the node no longer needs
|
||||
// the services/descriptors/characteristics of the parent client. This will
|
||||
// allow some memory to be freed.
|
||||
espbt::ClientState node_state;
|
||||
|
||||
BLEClient *parent() { return this->parent_; }
|
||||
void set_ble_client_parent(BLEClient *parent) { this->parent_ = parent; }
|
||||
|
||||
protected:
|
||||
BLEClient *parent_;
|
||||
uint64_t address_;
|
||||
};
|
||||
|
||||
class BLEDescriptor {
|
||||
public:
|
||||
espbt::ESPBTUUID uuid;
|
||||
uint16_t handle;
|
||||
|
||||
BLECharacteristic *characteristic;
|
||||
};
|
||||
|
||||
class BLECharacteristic {
|
||||
public:
|
||||
~BLECharacteristic();
|
||||
espbt::ESPBTUUID uuid;
|
||||
uint16_t handle;
|
||||
esp_gatt_char_prop_t properties;
|
||||
std::vector<BLEDescriptor *> descriptors;
|
||||
void parse_descriptors();
|
||||
BLEDescriptor *get_descriptor(espbt::ESPBTUUID uuid);
|
||||
BLEDescriptor *get_descriptor(uint16_t uuid);
|
||||
|
||||
BLEService *service;
|
||||
};
|
||||
|
||||
class BLEService {
|
||||
public:
|
||||
~BLEService();
|
||||
espbt::ESPBTUUID uuid;
|
||||
uint16_t start_handle;
|
||||
uint16_t end_handle;
|
||||
std::vector<BLECharacteristic *> characteristics;
|
||||
BLEClient *client;
|
||||
void parse_characteristics();
|
||||
BLECharacteristic *get_characteristic(espbt::ESPBTUUID uuid);
|
||||
BLECharacteristic *get_characteristic(uint16_t uuid);
|
||||
};
|
||||
|
||||
class BLEClient : public espbt::ESPBTClient, public Component {
|
||||
public:
|
||||
void setup() override;
|
||||
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);
|
||||
bool parse_device(const espbt::ESPBTDevice &device) override;
|
||||
void on_scan_end() override {}
|
||||
void connect();
|
||||
|
||||
void set_address(uint64_t address) { this->address = address; }
|
||||
|
||||
void set_enabled(bool enabled);
|
||||
|
||||
void register_ble_node(BLEClientNode *node) {
|
||||
node->client = this;
|
||||
node->set_ble_client_parent(this);
|
||||
this->nodes_.push_back(node);
|
||||
}
|
||||
|
||||
BLEService *get_service(espbt::ESPBTUUID uuid);
|
||||
BLEService *get_service(uint16_t uuid);
|
||||
BLECharacteristic *get_characteristic(espbt::ESPBTUUID service, espbt::ESPBTUUID chr);
|
||||
BLECharacteristic *get_characteristic(uint16_t service, uint16_t chr);
|
||||
BLEDescriptor *get_descriptor(espbt::ESPBTUUID service, espbt::ESPBTUUID chr, espbt::ESPBTUUID descr);
|
||||
BLEDescriptor *get_descriptor(uint16_t service, uint16_t chr, uint16_t descr);
|
||||
// Get the configuration descriptor for the given characteristic handle.
|
||||
BLEDescriptor *get_config_descriptor(uint16_t handle);
|
||||
|
||||
float parse_char_value(uint8_t *value, uint16_t length);
|
||||
|
||||
int gattc_if;
|
||||
esp_bd_addr_t remote_bda;
|
||||
uint16_t conn_id;
|
||||
uint64_t address;
|
||||
bool enabled;
|
||||
std::string address_str() const;
|
||||
|
||||
protected:
|
||||
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)
|
||||
return false;
|
||||
for (auto &node : nodes_)
|
||||
if (node->node_state != espbt::ClientState::Established)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<BLEClientNode *> nodes_;
|
||||
std::vector<BLEService *> services_;
|
||||
};
|
||||
|
||||
} // namespace ble_client
|
||||
} // namespace esphome
|
||||
|
||||
#endif
|
||||
129
esphome/components/ble_client/sensor/__init__.py
Normal file
129
esphome/components/ble_client/sensor/__init__.py
Normal file
@@ -0,0 +1,129 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import sensor, ble_client, esp32_ble_tracker
|
||||
from esphome.const import (
|
||||
DEVICE_CLASS_EMPTY,
|
||||
CONF_ID,
|
||||
CONF_LAMBDA,
|
||||
STATE_CLASS_NONE,
|
||||
UNIT_EMPTY,
|
||||
ICON_EMPTY,
|
||||
CONF_TRIGGER_ID,
|
||||
CONF_SERVICE_UUID,
|
||||
)
|
||||
from esphome import automation
|
||||
from .. import ble_client_ns
|
||||
|
||||
DEPENDENCIES = ["ble_client"]
|
||||
|
||||
CONF_CHARACTERISTIC_UUID = "characteristic_uuid"
|
||||
CONF_DESCRIPTOR_UUID = "descriptor_uuid"
|
||||
|
||||
CONF_NOTIFY = "notify"
|
||||
CONF_ON_NOTIFY = "on_notify"
|
||||
|
||||
adv_data_t = cg.std_vector.template(cg.uint8)
|
||||
adv_data_t_const_ref = adv_data_t.operator("ref").operator("const")
|
||||
|
||||
BLESensor = ble_client_ns.class_(
|
||||
"BLESensor", sensor.Sensor, cg.PollingComponent, ble_client.BLEClientNode
|
||||
)
|
||||
BLESensorNotifyTrigger = ble_client_ns.class_(
|
||||
"BLESensorNotifyTrigger", automation.Trigger.template(cg.float_)
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
sensor.sensor_schema(
|
||||
UNIT_EMPTY, ICON_EMPTY, 0, DEVICE_CLASS_EMPTY, STATE_CLASS_NONE
|
||||
)
|
||||
.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(BLESensor),
|
||||
cv.Required(CONF_SERVICE_UUID): esp32_ble_tracker.bt_uuid,
|
||||
cv.Required(CONF_CHARACTERISTIC_UUID): esp32_ble_tracker.bt_uuid,
|
||||
cv.Optional(CONF_DESCRIPTOR_UUID): esp32_ble_tracker.bt_uuid,
|
||||
cv.Optional(CONF_LAMBDA): cv.returning_lambda,
|
||||
cv.Optional(CONF_NOTIFY, default=False): cv.boolean,
|
||||
cv.Optional(CONF_ON_NOTIFY): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
||||
BLESensorNotifyTrigger
|
||||
),
|
||||
}
|
||||
),
|
||||
}
|
||||
)
|
||||
.extend(cv.polling_component_schema("60s"))
|
||||
.extend(ble_client.BLE_CLIENT_SCHEMA)
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
if len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid16_format):
|
||||
cg.add(
|
||||
var.set_service_uuid16(esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID]))
|
||||
)
|
||||
elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid32_format):
|
||||
cg.add(
|
||||
var.set_service_uuid32(esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID]))
|
||||
)
|
||||
elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid128_format):
|
||||
uuid128 = esp32_ble_tracker.as_hex_array(config[CONF_SERVICE_UUID])
|
||||
cg.add(var.set_service_uuid128(uuid128))
|
||||
|
||||
if len(config[CONF_CHARACTERISTIC_UUID]) == len(esp32_ble_tracker.bt_uuid16_format):
|
||||
cg.add(
|
||||
var.set_char_uuid16(
|
||||
esp32_ble_tracker.as_hex(config[CONF_CHARACTERISTIC_UUID])
|
||||
)
|
||||
)
|
||||
elif len(config[CONF_CHARACTERISTIC_UUID]) == len(
|
||||
esp32_ble_tracker.bt_uuid32_format
|
||||
):
|
||||
cg.add(
|
||||
var.set_char_uuid32(
|
||||
esp32_ble_tracker.as_hex(config[CONF_CHARACTERISTIC_UUID])
|
||||
)
|
||||
)
|
||||
elif len(config[CONF_CHARACTERISTIC_UUID]) == len(
|
||||
esp32_ble_tracker.bt_uuid128_format
|
||||
):
|
||||
uuid128 = esp32_ble_tracker.as_hex_array(config[CONF_CHARACTERISTIC_UUID])
|
||||
cg.add(var.set_char_uuid128(uuid128))
|
||||
|
||||
if CONF_DESCRIPTOR_UUID in config:
|
||||
if len(config[CONF_DESCRIPTOR_UUID]) == len(esp32_ble_tracker.bt_uuid16_format):
|
||||
cg.add(
|
||||
var.set_descr_uuid16(
|
||||
esp32_ble_tracker.as_hex(config[CONF_DESCRIPTOR_UUID])
|
||||
)
|
||||
)
|
||||
elif len(config[CONF_DESCRIPTOR_UUID]) == len(
|
||||
esp32_ble_tracker.bt_uuid32_format
|
||||
):
|
||||
cg.add(
|
||||
var.set_descr_uuid32(
|
||||
esp32_ble_tracker.as_hex(config[CONF_DESCRIPTOR_UUID])
|
||||
)
|
||||
)
|
||||
elif len(config[CONF_DESCRIPTOR_UUID]) == len(
|
||||
esp32_ble_tracker.bt_uuid128_format
|
||||
):
|
||||
uuid128 = esp32_ble_tracker.as_hex_array(config[CONF_DESCRIPTOR_UUID])
|
||||
cg.add(var.set_descr_uuid128(uuid128))
|
||||
|
||||
if CONF_LAMBDA in config:
|
||||
lambda_ = await cg.process_lambda(
|
||||
config[CONF_LAMBDA], [(adv_data_t_const_ref, "x")], return_type=cg.float_
|
||||
)
|
||||
cg.add(var.set_data_to_value(lambda_))
|
||||
|
||||
await cg.register_component(var, config)
|
||||
await ble_client.register_ble_node(var, config)
|
||||
cg.add(var.set_enable_notify(config[CONF_NOTIFY]))
|
||||
await sensor.register_sensor(var, config)
|
||||
for conf in config.get(CONF_ON_NOTIFY, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await ble_client.register_ble_node(trigger, config)
|
||||
await automation.build_automation(trigger, [(float, "x")], conf)
|
||||
37
esphome/components/ble_client/sensor/automation.h
Normal file
37
esphome/components/ble_client/sensor/automation.h
Normal file
@@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/components/ble_client/sensor/ble_sensor.h"
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
|
||||
namespace esphome {
|
||||
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) {
|
||||
switch (event) {
|
||||
case ESP_GATTC_SEARCH_CMPL_EVT: {
|
||||
this->sensor_->node_state = espbt::ClientState::Established;
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_NOTIFY_EVT: {
|
||||
if (param->notify.conn_id != this->sensor_->parent()->conn_id || param->notify.handle != this->sensor_->handle)
|
||||
break;
|
||||
this->trigger(this->sensor_->parent()->parse_char_value(param->notify.value, param->notify.value_len));
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
BLESensor *sensor_;
|
||||
};
|
||||
|
||||
} // namespace ble_client
|
||||
} // namespace esphome
|
||||
|
||||
#endif
|
||||
138
esphome/components/ble_client/sensor/ble_sensor.cpp
Normal file
138
esphome/components/ble_client/sensor/ble_sensor.cpp
Normal file
@@ -0,0 +1,138 @@
|
||||
#include "ble_sensor.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
|
||||
namespace esphome {
|
||||
namespace ble_client {
|
||||
|
||||
static const char *TAG = "ble_sensor";
|
||||
|
||||
uint32_t BLESensor::hash_base() { return 343459825UL; }
|
||||
|
||||
void BLESensor::loop() {}
|
||||
|
||||
void BLESensor::dump_config() {
|
||||
LOG_SENSOR("", "BLE Sensor", this);
|
||||
ESP_LOGCONFIG(TAG, " MAC address : %s", this->parent()->address_str().c_str());
|
||||
ESP_LOGCONFIG(TAG, " Service UUID : %s", this->service_uuid_.to_string().c_str());
|
||||
ESP_LOGCONFIG(TAG, " Characteristic UUID: %s", this->char_uuid_.to_string().c_str());
|
||||
ESP_LOGCONFIG(TAG, " Descriptor UUID : %s", this->descr_uuid_.to_string().c_str());
|
||||
ESP_LOGCONFIG(TAG, " Notifications : %s", YESNO(this->notify_));
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
}
|
||||
|
||||
void BLESensor::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, "[%s] Connected successfully!", this->get_name().c_str());
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_DISCONNECT_EVT: {
|
||||
ESP_LOGW(TAG, "[%s] Disconnected!", this->get_name().c_str());
|
||||
this->status_set_warning();
|
||||
this->publish_state(NAN);
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_SEARCH_CMPL_EVT: {
|
||||
this->handle = 0;
|
||||
auto chr = this->parent()->get_characteristic(this->service_uuid_, this->char_uuid_);
|
||||
if (chr == nullptr) {
|
||||
this->status_set_warning();
|
||||
this->publish_state(NAN);
|
||||
ESP_LOGW(TAG, "No sensor characteristic found at service %s char %s", this->service_uuid_.to_string().c_str(),
|
||||
this->char_uuid_.to_string().c_str());
|
||||
break;
|
||||
}
|
||||
this->handle = chr->handle;
|
||||
if (this->descr_uuid_.get_uuid().len > 0) {
|
||||
auto descr = chr->get_descriptor(this->descr_uuid_);
|
||||
if (descr == nullptr) {
|
||||
this->status_set_warning();
|
||||
this->publish_state(NAN);
|
||||
ESP_LOGW(TAG, "No sensor descriptor found at service %s char %s descr %s",
|
||||
this->service_uuid_.to_string().c_str(), this->char_uuid_.to_string().c_str(),
|
||||
this->descr_uuid_.to_string().c_str());
|
||||
break;
|
||||
}
|
||||
this->handle = descr->handle;
|
||||
}
|
||||
if (this->notify_) {
|
||||
auto status =
|
||||
esp_ble_gattc_register_for_notify(this->parent()->gattc_if, this->parent()->remote_bda, chr->handle);
|
||||
if (status) {
|
||||
ESP_LOGW(TAG, "esp_ble_gattc_register_for_notify failed, status=%d", status);
|
||||
}
|
||||
} else {
|
||||
this->node_state = espbt::ClientState::Established;
|
||||
}
|
||||
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) {
|
||||
this->status_clear_warning();
|
||||
this->publish_state(this->parse_data(param->read.value, param->read.value_len));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_NOTIFY_EVT: {
|
||||
if (param->notify.conn_id != this->parent()->conn_id || param->notify.handle != this->handle)
|
||||
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));
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
|
||||
this->node_state = espbt::ClientState::Established;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
} else {
|
||||
return value[0];
|
||||
}
|
||||
}
|
||||
|
||||
void BLESensor::update() {
|
||||
if (this->node_state != espbt::ClientState::Established) {
|
||||
ESP_LOGW(TAG, "[%s] Cannot poll, not connected", this->get_name().c_str());
|
||||
return;
|
||||
}
|
||||
if (this->handle == 0) {
|
||||
ESP_LOGW(TAG, "[%s] Cannot poll, no service or characteristic found", this->get_name().c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
auto status =
|
||||
esp_ble_gattc_read_char(this->parent()->gattc_if, this->parent()->conn_id, this->handle, ESP_GATT_AUTH_REQ_NONE);
|
||||
if (status) {
|
||||
this->status_set_warning();
|
||||
this->publish_state(NAN);
|
||||
ESP_LOGW(TAG, "[%s] Error sending read request for sensor, status=%d", this->get_name().c_str(), status);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ble_client
|
||||
} // namespace esphome
|
||||
#endif
|
||||
51
esphome/components/ble_client/sensor/ble_sensor.h
Normal file
51
esphome/components/ble_client/sensor/ble_sensor.h
Normal file
@@ -0,0 +1,51 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/ble_client/ble_client.h"
|
||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#include <esp_gattc_api.h>
|
||||
|
||||
namespace esphome {
|
||||
namespace ble_client {
|
||||
|
||||
namespace espbt = esphome::esp32_ble_tracker;
|
||||
|
||||
using data_to_value_t = std::function<float(std::vector<uint8_t>)>;
|
||||
|
||||
class BLESensor : public sensor::Sensor, public PollingComponent, public BLEClientNode {
|
||||
public:
|
||||
void loop() 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 dump_config() override;
|
||||
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||
void set_service_uuid16(uint16_t uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_uint16(uuid); }
|
||||
void set_service_uuid32(uint32_t uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_uint32(uuid); }
|
||||
void set_service_uuid128(uint8_t *uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_raw(uuid); }
|
||||
void set_char_uuid16(uint16_t uuid) { this->char_uuid_ = espbt::ESPBTUUID::from_uint16(uuid); }
|
||||
void set_char_uuid32(uint32_t uuid) { this->char_uuid_ = espbt::ESPBTUUID::from_uint32(uuid); }
|
||||
void set_char_uuid128(uint8_t *uuid) { this->char_uuid_ = espbt::ESPBTUUID::from_raw(uuid); }
|
||||
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_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);
|
||||
optional<data_to_value_t> data_to_value_func_{};
|
||||
bool notify_;
|
||||
espbt::ESPBTUUID service_uuid_;
|
||||
espbt::ESPBTUUID char_uuid_;
|
||||
espbt::ESPBTUUID descr_uuid_;
|
||||
};
|
||||
|
||||
} // namespace ble_client
|
||||
} // namespace esphome
|
||||
#endif
|
||||
30
esphome/components/ble_client/switch/__init__.py
Normal file
30
esphome/components/ble_client/switch/__init__.py
Normal file
@@ -0,0 +1,30 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import switch, ble_client
|
||||
from esphome.const import CONF_ICON, CONF_ID, CONF_INVERTED, ICON_BLUETOOTH
|
||||
from .. import ble_client_ns
|
||||
|
||||
BLEClientSwitch = ble_client_ns.class_(
|
||||
"BLEClientSwitch", switch.Switch, cg.Component, ble_client.BLEClientNode
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
switch.SWITCH_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(BLEClientSwitch),
|
||||
cv.Optional(CONF_INVERTED): cv.invalid(
|
||||
"BLE client switches do not support inverted mode!"
|
||||
),
|
||||
cv.Optional(CONF_ICON, default=ICON_BLUETOOTH): switch.icon,
|
||||
}
|
||||
)
|
||||
.extend(ble_client.BLE_CLIENT_SCHEMA)
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await switch.register_switch(var, config)
|
||||
await ble_client.register_ble_node(var, config)
|
||||
39
esphome/components/ble_client/switch/ble_switch.cpp
Normal file
39
esphome/components/ble_client/switch/ble_switch.cpp
Normal file
@@ -0,0 +1,39 @@
|
||||
#include "ble_switch.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/application.h"
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
|
||||
namespace esphome {
|
||||
namespace ble_client {
|
||||
|
||||
static const char *TAG = "ble_switch";
|
||||
|
||||
void BLEClientSwitch::write_state(bool state) {
|
||||
this->parent_->set_enabled(state);
|
||||
this->publish_state(state);
|
||||
}
|
||||
|
||||
void BLEClientSwitch::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_REG_EVT:
|
||||
this->publish_state(this->parent_->enabled);
|
||||
break;
|
||||
case ESP_GATTC_OPEN_EVT:
|
||||
this->node_state = espbt::ClientState::Established;
|
||||
break;
|
||||
case ESP_GATTC_DISCONNECT_EVT:
|
||||
this->node_state = espbt::ClientState::Idle;
|
||||
this->publish_state(this->parent_->enabled);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void BLEClientSwitch::dump_config() { LOG_SWITCH("", "BLE Client Switch", this); }
|
||||
|
||||
} // namespace ble_client
|
||||
} // namespace esphome
|
||||
#endif
|
||||
30
esphome/components/ble_client/switch/ble_switch.h
Normal file
30
esphome/components/ble_client/switch/ble_switch.h
Normal file
@@ -0,0 +1,30 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/ble_client/ble_client.h"
|
||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
|
||||
#include "esphome/components/switch/switch.h"
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#include <esp_gattc_api.h>
|
||||
|
||||
namespace esphome {
|
||||
namespace ble_client {
|
||||
|
||||
namespace espbt = esphome::esp32_ble_tracker;
|
||||
|
||||
class BLEClientSwitch : public switch_::Switch, public Component, public BLEClientNode {
|
||||
public:
|
||||
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) override;
|
||||
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||
|
||||
protected:
|
||||
void write_state(bool state) override;
|
||||
};
|
||||
|
||||
} // namespace ble_client
|
||||
} // namespace esphome
|
||||
#endif
|
||||
@@ -27,11 +27,11 @@ CONFIG_SCHEMA = cv.All(
|
||||
)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
yield cg.register_component(var, config)
|
||||
yield esp32_ble_tracker.register_ble_device(var, config)
|
||||
yield binary_sensor.register_binary_sensor(var, config)
|
||||
await cg.register_component(var, config)
|
||||
await esp32_ble_tracker.register_ble_device(var, config)
|
||||
await binary_sensor.register_binary_sensor(var, config)
|
||||
|
||||
if CONF_MAC_ADDRESS in config:
|
||||
cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex))
|
||||
|
||||
@@ -6,6 +6,7 @@ from esphome.const import (
|
||||
CONF_MAC_ADDRESS,
|
||||
CONF_ID,
|
||||
DEVICE_CLASS_SIGNAL_STRENGTH,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_DECIBEL,
|
||||
ICON_EMPTY,
|
||||
)
|
||||
@@ -18,7 +19,13 @@ BLERSSISensor = ble_rssi_ns.class_(
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
sensor.sensor_schema(UNIT_DECIBEL, ICON_EMPTY, 0, DEVICE_CLASS_SIGNAL_STRENGTH)
|
||||
sensor.sensor_schema(
|
||||
UNIT_DECIBEL,
|
||||
ICON_EMPTY,
|
||||
0,
|
||||
DEVICE_CLASS_SIGNAL_STRENGTH,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
)
|
||||
.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(BLERSSISensor),
|
||||
@@ -32,11 +39,11 @@ CONFIG_SCHEMA = cv.All(
|
||||
)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
yield cg.register_component(var, config)
|
||||
yield esp32_ble_tracker.register_ble_device(var, config)
|
||||
yield sensor.register_sensor(var, config)
|
||||
await cg.register_component(var, config)
|
||||
await esp32_ble_tracker.register_ble_device(var, config)
|
||||
await sensor.register_sensor(var, config)
|
||||
|
||||
if CONF_MAC_ADDRESS in config:
|
||||
cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex))
|
||||
|
||||
@@ -24,8 +24,8 @@ CONFIG_SCHEMA = cv.All(
|
||||
)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
yield cg.register_component(var, config)
|
||||
yield esp32_ble_tracker.register_ble_device(var, config)
|
||||
yield text_sensor.register_text_sensor(var, config)
|
||||
await cg.register_component(var, config)
|
||||
await esp32_ble_tracker.register_ble_device(var, config)
|
||||
await text_sensor.register_text_sensor(var, config)
|
||||
|
||||
@@ -12,6 +12,7 @@ from esphome.const import (
|
||||
DEVICE_CLASS_PRESSURE,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
ICON_EMPTY,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_CELSIUS,
|
||||
UNIT_HECTOPASCAL,
|
||||
UNIT_PERCENT,
|
||||
@@ -48,7 +49,11 @@ CONFIG_SCHEMA = (
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(BME280Component),
|
||||
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
|
||||
UNIT_CELSIUS, ICON_EMPTY, 1, DEVICE_CLASS_TEMPERATURE
|
||||
UNIT_CELSIUS,
|
||||
ICON_EMPTY,
|
||||
1,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
).extend(
|
||||
{
|
||||
cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum(
|
||||
@@ -57,7 +62,11 @@ CONFIG_SCHEMA = (
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_PRESSURE): sensor.sensor_schema(
|
||||
UNIT_HECTOPASCAL, ICON_EMPTY, 1, DEVICE_CLASS_PRESSURE
|
||||
UNIT_HECTOPASCAL,
|
||||
ICON_EMPTY,
|
||||
1,
|
||||
DEVICE_CLASS_PRESSURE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
).extend(
|
||||
{
|
||||
cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum(
|
||||
@@ -66,7 +75,11 @@ CONFIG_SCHEMA = (
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
|
||||
UNIT_PERCENT, ICON_EMPTY, 1, DEVICE_CLASS_HUMIDITY
|
||||
UNIT_PERCENT,
|
||||
ICON_EMPTY,
|
||||
1,
|
||||
DEVICE_CLASS_HUMIDITY,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
).extend(
|
||||
{
|
||||
cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum(
|
||||
@@ -84,26 +97,26 @@ CONFIG_SCHEMA = (
|
||||
)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
yield cg.register_component(var, config)
|
||||
yield i2c.register_i2c_device(var, config)
|
||||
await cg.register_component(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
|
||||
if CONF_TEMPERATURE in config:
|
||||
conf = config[CONF_TEMPERATURE]
|
||||
sens = yield sensor.new_sensor(conf)
|
||||
sens = await sensor.new_sensor(conf)
|
||||
cg.add(var.set_temperature_sensor(sens))
|
||||
cg.add(var.set_temperature_oversampling(conf[CONF_OVERSAMPLING]))
|
||||
|
||||
if CONF_PRESSURE in config:
|
||||
conf = config[CONF_PRESSURE]
|
||||
sens = yield sensor.new_sensor(conf)
|
||||
sens = await sensor.new_sensor(conf)
|
||||
cg.add(var.set_pressure_sensor(sens))
|
||||
cg.add(var.set_pressure_oversampling(conf[CONF_OVERSAMPLING]))
|
||||
|
||||
if CONF_HUMIDITY in config:
|
||||
conf = config[CONF_HUMIDITY]
|
||||
sens = yield sensor.new_sensor(conf)
|
||||
sens = await sensor.new_sensor(conf)
|
||||
cg.add(var.set_humidity_sensor(sens))
|
||||
cg.add(var.set_humidity_oversampling(conf[CONF_OVERSAMPLING]))
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ from esphome.const import (
|
||||
DEVICE_CLASS_HUMIDITY,
|
||||
DEVICE_CLASS_PRESSURE,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_OHM,
|
||||
ICON_GAS_CYLINDER,
|
||||
UNIT_CELSIUS,
|
||||
@@ -58,7 +59,11 @@ CONFIG_SCHEMA = (
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(BME680Component),
|
||||
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
|
||||
UNIT_CELSIUS, ICON_EMPTY, 1, DEVICE_CLASS_TEMPERATURE
|
||||
UNIT_CELSIUS,
|
||||
ICON_EMPTY,
|
||||
1,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
).extend(
|
||||
{
|
||||
cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum(
|
||||
@@ -67,7 +72,11 @@ CONFIG_SCHEMA = (
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_PRESSURE): sensor.sensor_schema(
|
||||
UNIT_HECTOPASCAL, ICON_EMPTY, 1, DEVICE_CLASS_PRESSURE
|
||||
UNIT_HECTOPASCAL,
|
||||
ICON_EMPTY,
|
||||
1,
|
||||
DEVICE_CLASS_PRESSURE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
).extend(
|
||||
{
|
||||
cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum(
|
||||
@@ -76,7 +85,11 @@ CONFIG_SCHEMA = (
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
|
||||
UNIT_PERCENT, ICON_EMPTY, 1, DEVICE_CLASS_HUMIDITY
|
||||
UNIT_PERCENT,
|
||||
ICON_EMPTY,
|
||||
1,
|
||||
DEVICE_CLASS_HUMIDITY,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
).extend(
|
||||
{
|
||||
cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum(
|
||||
@@ -85,7 +98,11 @@ CONFIG_SCHEMA = (
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_GAS_RESISTANCE): sensor.sensor_schema(
|
||||
UNIT_OHM, ICON_GAS_CYLINDER, 1, DEVICE_CLASS_EMPTY
|
||||
UNIT_OHM,
|
||||
ICON_GAS_CYLINDER,
|
||||
1,
|
||||
DEVICE_CLASS_EMPTY,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_IIR_FILTER, default="OFF"): cv.enum(
|
||||
IIR_FILTER_OPTIONS, upper=True
|
||||
@@ -114,32 +131,32 @@ CONFIG_SCHEMA = (
|
||||
)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
yield cg.register_component(var, config)
|
||||
yield i2c.register_i2c_device(var, config)
|
||||
await cg.register_component(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
|
||||
if CONF_TEMPERATURE in config:
|
||||
conf = config[CONF_TEMPERATURE]
|
||||
sens = yield sensor.new_sensor(conf)
|
||||
sens = await sensor.new_sensor(conf)
|
||||
cg.add(var.set_temperature_sensor(sens))
|
||||
cg.add(var.set_temperature_oversampling(conf[CONF_OVERSAMPLING]))
|
||||
|
||||
if CONF_PRESSURE in config:
|
||||
conf = config[CONF_PRESSURE]
|
||||
sens = yield sensor.new_sensor(conf)
|
||||
sens = await sensor.new_sensor(conf)
|
||||
cg.add(var.set_pressure_sensor(sens))
|
||||
cg.add(var.set_pressure_oversampling(conf[CONF_OVERSAMPLING]))
|
||||
|
||||
if CONF_HUMIDITY in config:
|
||||
conf = config[CONF_HUMIDITY]
|
||||
sens = yield sensor.new_sensor(conf)
|
||||
sens = await sensor.new_sensor(conf)
|
||||
cg.add(var.set_humidity_sensor(sens))
|
||||
cg.add(var.set_humidity_oversampling(conf[CONF_OVERSAMPLING]))
|
||||
|
||||
if CONF_GAS_RESISTANCE in config:
|
||||
conf = config[CONF_GAS_RESISTANCE]
|
||||
sens = yield sensor.new_sensor(conf)
|
||||
sens = await sensor.new_sensor(conf)
|
||||
cg.add(var.set_gas_resistance_sensor(sens))
|
||||
|
||||
cg.add(var.set_iir_filter(IIR_FILTER_OPTIONS[config[CONF_IIR_FILTER]]))
|
||||
|
||||
64
esphome/components/bme680_bsec/__init__.py
Normal file
64
esphome/components/bme680_bsec/__init__.py
Normal file
@@ -0,0 +1,64 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import i2c
|
||||
from esphome.const import CONF_ID
|
||||
|
||||
CODEOWNERS = ["@trvrnrth"]
|
||||
DEPENDENCIES = ["i2c"]
|
||||
AUTO_LOAD = ["sensor", "text_sensor"]
|
||||
|
||||
CONF_BME680_BSEC_ID = "bme680_bsec_id"
|
||||
CONF_TEMPERATURE_OFFSET = "temperature_offset"
|
||||
CONF_IAQ_MODE = "iaq_mode"
|
||||
CONF_SAMPLE_RATE = "sample_rate"
|
||||
CONF_STATE_SAVE_INTERVAL = "state_save_interval"
|
||||
|
||||
bme680_bsec_ns = cg.esphome_ns.namespace("bme680_bsec")
|
||||
|
||||
IAQMode = bme680_bsec_ns.enum("IAQMode")
|
||||
IAQ_MODE_OPTIONS = {
|
||||
"STATIC": IAQMode.IAQ_MODE_STATIC,
|
||||
"MOBILE": IAQMode.IAQ_MODE_MOBILE,
|
||||
}
|
||||
|
||||
SampleRate = bme680_bsec_ns.enum("SampleRate")
|
||||
SAMPLE_RATE_OPTIONS = {
|
||||
"LP": SampleRate.SAMPLE_RATE_LP,
|
||||
"ULP": SampleRate.SAMPLE_RATE_ULP,
|
||||
}
|
||||
|
||||
BME680BSECComponent = bme680_bsec_ns.class_(
|
||||
"BME680BSECComponent", cg.Component, i2c.I2CDevice
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(BME680BSECComponent),
|
||||
cv.Optional(CONF_TEMPERATURE_OFFSET, default=0): cv.temperature,
|
||||
cv.Optional(CONF_IAQ_MODE, default="STATIC"): cv.enum(
|
||||
IAQ_MODE_OPTIONS, upper=True
|
||||
),
|
||||
cv.Optional(CONF_SAMPLE_RATE, default="LP"): cv.enum(
|
||||
SAMPLE_RATE_OPTIONS, upper=True
|
||||
),
|
||||
cv.Optional(
|
||||
CONF_STATE_SAVE_INTERVAL, default="6hours"
|
||||
): cv.positive_time_period_minutes,
|
||||
}
|
||||
).extend(i2c.i2c_device_schema(0x76))
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
|
||||
cg.add(var.set_temperature_offset(config[CONF_TEMPERATURE_OFFSET]))
|
||||
cg.add(var.set_iaq_mode(config[CONF_IAQ_MODE]))
|
||||
cg.add(var.set_sample_rate(config[CONF_SAMPLE_RATE]))
|
||||
cg.add(
|
||||
var.set_state_save_interval(config[CONF_STATE_SAVE_INTERVAL].total_milliseconds)
|
||||
)
|
||||
|
||||
cg.add_define("USE_BSEC")
|
||||
cg.add_library("BSEC Software Library", "1.6.1480")
|
||||
426
esphome/components/bme680_bsec/bme680_bsec.cpp
Normal file
426
esphome/components/bme680_bsec/bme680_bsec.cpp
Normal file
@@ -0,0 +1,426 @@
|
||||
#include "bme680_bsec.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include <string>
|
||||
|
||||
namespace esphome {
|
||||
namespace bme680_bsec {
|
||||
#ifdef USE_BSEC
|
||||
static const char *TAG = "bme680_bsec.sensor";
|
||||
|
||||
static const std::string IAQ_ACCURACY_STATES[4] = {"Stabilizing", "Uncertain", "Calibrating", "Calibrated"};
|
||||
|
||||
BME680BSECComponent *BME680BSECComponent::instance;
|
||||
|
||||
void BME680BSECComponent::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up BME680 via BSEC...");
|
||||
BME680BSECComponent::instance = this;
|
||||
|
||||
this->bsec_status_ = bsec_init();
|
||||
if (this->bsec_status_ != BSEC_OK) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
this->bme680_.dev_id = this->address_;
|
||||
this->bme680_.intf = BME680_I2C_INTF;
|
||||
this->bme680_.read = BME680BSECComponent::read_bytes_wrapper;
|
||||
this->bme680_.write = BME680BSECComponent::write_bytes_wrapper;
|
||||
this->bme680_.delay_ms = BME680BSECComponent::delay_ms;
|
||||
this->bme680_.amb_temp = 25;
|
||||
|
||||
this->bme680_status_ = bme680_init(&this->bme680_);
|
||||
if (this->bme680_status_ != BME680_OK) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
if (this->sample_rate_ == SAMPLE_RATE_ULP) {
|
||||
const uint8_t bsec_config[] = {
|
||||
#include "config/generic_33v_300s_28d/bsec_iaq.txt"
|
||||
};
|
||||
this->set_config_(bsec_config);
|
||||
} else {
|
||||
const uint8_t bsec_config[] = {
|
||||
#include "config/generic_33v_3s_28d/bsec_iaq.txt"
|
||||
};
|
||||
this->set_config_(bsec_config);
|
||||
}
|
||||
this->update_subscription_();
|
||||
if (this->bsec_status_ != BSEC_OK) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
this->load_state_();
|
||||
}
|
||||
|
||||
void BME680BSECComponent::set_config_(const uint8_t *config) {
|
||||
uint8_t work_buffer[BSEC_MAX_WORKBUFFER_SIZE];
|
||||
this->bsec_status_ = bsec_set_configuration(config, BSEC_MAX_PROPERTY_BLOB_SIZE, work_buffer, sizeof(work_buffer));
|
||||
}
|
||||
|
||||
float BME680BSECComponent::calc_sensor_sample_rate_(SampleRate sample_rate) {
|
||||
if (sample_rate == SAMPLE_RATE_DEFAULT) {
|
||||
sample_rate = this->sample_rate_;
|
||||
}
|
||||
return sample_rate == SAMPLE_RATE_ULP ? BSEC_SAMPLE_RATE_ULP : BSEC_SAMPLE_RATE_LP;
|
||||
}
|
||||
|
||||
void BME680BSECComponent::update_subscription_() {
|
||||
bsec_sensor_configuration_t virtual_sensors[BSEC_NUMBER_OUTPUTS];
|
||||
int num_virtual_sensors = 0;
|
||||
|
||||
if (this->iaq_sensor_) {
|
||||
virtual_sensors[num_virtual_sensors].sensor_id =
|
||||
this->iaq_mode_ == IAQ_MODE_STATIC ? BSEC_OUTPUT_STATIC_IAQ : BSEC_OUTPUT_IAQ;
|
||||
virtual_sensors[num_virtual_sensors].sample_rate = this->calc_sensor_sample_rate_(SAMPLE_RATE_DEFAULT);
|
||||
num_virtual_sensors++;
|
||||
}
|
||||
|
||||
if (this->co2_equivalent_sensor_) {
|
||||
virtual_sensors[num_virtual_sensors].sensor_id = BSEC_OUTPUT_CO2_EQUIVALENT;
|
||||
virtual_sensors[num_virtual_sensors].sample_rate = this->calc_sensor_sample_rate_(SAMPLE_RATE_DEFAULT);
|
||||
num_virtual_sensors++;
|
||||
}
|
||||
|
||||
if (this->breath_voc_equivalent_sensor_) {
|
||||
virtual_sensors[num_virtual_sensors].sensor_id = BSEC_OUTPUT_BREATH_VOC_EQUIVALENT;
|
||||
virtual_sensors[num_virtual_sensors].sample_rate = this->calc_sensor_sample_rate_(SAMPLE_RATE_DEFAULT);
|
||||
num_virtual_sensors++;
|
||||
}
|
||||
|
||||
if (this->pressure_sensor_) {
|
||||
virtual_sensors[num_virtual_sensors].sensor_id = BSEC_OUTPUT_RAW_PRESSURE;
|
||||
virtual_sensors[num_virtual_sensors].sample_rate = this->calc_sensor_sample_rate_(this->pressure_sample_rate_);
|
||||
num_virtual_sensors++;
|
||||
}
|
||||
|
||||
if (this->gas_resistance_sensor_) {
|
||||
virtual_sensors[num_virtual_sensors].sensor_id = BSEC_OUTPUT_RAW_GAS;
|
||||
virtual_sensors[num_virtual_sensors].sample_rate = this->calc_sensor_sample_rate_(SAMPLE_RATE_DEFAULT);
|
||||
num_virtual_sensors++;
|
||||
}
|
||||
|
||||
if (this->temperature_sensor_) {
|
||||
virtual_sensors[num_virtual_sensors].sensor_id = BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_TEMPERATURE;
|
||||
virtual_sensors[num_virtual_sensors].sample_rate = this->calc_sensor_sample_rate_(this->temperature_sample_rate_);
|
||||
num_virtual_sensors++;
|
||||
}
|
||||
|
||||
if (this->humidity_sensor_) {
|
||||
virtual_sensors[num_virtual_sensors].sensor_id = BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_HUMIDITY;
|
||||
virtual_sensors[num_virtual_sensors].sample_rate = this->calc_sensor_sample_rate_(this->humidity_sample_rate_);
|
||||
num_virtual_sensors++;
|
||||
}
|
||||
|
||||
bsec_sensor_configuration_t sensor_settings[BSEC_MAX_PHYSICAL_SENSOR];
|
||||
uint8_t num_sensor_settings = BSEC_MAX_PHYSICAL_SENSOR;
|
||||
this->bsec_status_ =
|
||||
bsec_update_subscription(virtual_sensors, num_virtual_sensors, sensor_settings, &num_sensor_settings);
|
||||
}
|
||||
|
||||
void BME680BSECComponent::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "BME680 via BSEC:");
|
||||
|
||||
bsec_version_t version;
|
||||
bsec_get_version(&version);
|
||||
ESP_LOGCONFIG(TAG, " BSEC Version: %d.%d.%d.%d", version.major, version.minor, version.major_bugfix,
|
||||
version.minor_bugfix);
|
||||
|
||||
LOG_I2C_DEVICE(this);
|
||||
|
||||
if (this->is_failed()) {
|
||||
ESP_LOGE(TAG, "Communication failed (BSEC Status: %d, BME680 Status: %d)", this->bsec_status_,
|
||||
this->bme680_status_);
|
||||
}
|
||||
|
||||
ESP_LOGCONFIG(TAG, " Temperature Offset: %.2f", this->temperature_offset_);
|
||||
ESP_LOGCONFIG(TAG, " IAQ Mode: %s", this->iaq_mode_ == IAQ_MODE_STATIC ? "Static" : "Mobile");
|
||||
ESP_LOGCONFIG(TAG, " Sample Rate: %s", BME680_BSEC_SAMPLE_RATE_LOG(this->sample_rate_));
|
||||
ESP_LOGCONFIG(TAG, " State Save Interval: %ims", this->state_save_interval_ms_);
|
||||
|
||||
LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
|
||||
ESP_LOGCONFIG(TAG, " Sample Rate: %s", BME680_BSEC_SAMPLE_RATE_LOG(this->temperature_sample_rate_));
|
||||
LOG_SENSOR(" ", "Pressure", this->pressure_sensor_);
|
||||
ESP_LOGCONFIG(TAG, " Sample Rate: %s", BME680_BSEC_SAMPLE_RATE_LOG(this->pressure_sample_rate_));
|
||||
LOG_SENSOR(" ", "Humidity", this->humidity_sensor_);
|
||||
ESP_LOGCONFIG(TAG, " Sample Rate: %s", BME680_BSEC_SAMPLE_RATE_LOG(this->humidity_sample_rate_));
|
||||
LOG_SENSOR(" ", "Gas Resistance", this->gas_resistance_sensor_);
|
||||
LOG_SENSOR(" ", "IAQ", this->iaq_sensor_);
|
||||
LOG_SENSOR(" ", "Numeric IAQ Accuracy", this->iaq_accuracy_sensor_);
|
||||
LOG_TEXT_SENSOR(" ", "IAQ Accuracy", this->iaq_accuracy_text_sensor_);
|
||||
LOG_SENSOR(" ", "CO2 Equivalent", this->co2_equivalent_sensor_);
|
||||
LOG_SENSOR(" ", "Breath VOC Equivalent", this->breath_voc_equivalent_sensor_);
|
||||
}
|
||||
|
||||
float BME680BSECComponent::get_setup_priority() const { return setup_priority::DATA; }
|
||||
|
||||
void BME680BSECComponent::loop() {
|
||||
this->run_();
|
||||
|
||||
if (this->bsec_status_ < BSEC_OK || this->bme680_status_ < BME680_OK) {
|
||||
this->status_set_error();
|
||||
} else {
|
||||
this->status_clear_error();
|
||||
}
|
||||
if (this->bsec_status_ > BSEC_OK || this->bme680_status_ > BME680_OK) {
|
||||
this->status_set_warning();
|
||||
} else {
|
||||
this->status_clear_warning();
|
||||
}
|
||||
}
|
||||
|
||||
void BME680BSECComponent::run_() {
|
||||
int64_t curr_time_ns = this->get_time_ns_();
|
||||
if (curr_time_ns < this->next_call_ns_) {
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGV(TAG, "Performing sensor run");
|
||||
|
||||
bsec_bme_settings_t bme680_settings;
|
||||
this->bsec_status_ = bsec_sensor_control(curr_time_ns, &bme680_settings);
|
||||
if (this->bsec_status_ < BSEC_OK) {
|
||||
ESP_LOGW(TAG, "Failed to fetch sensor control settings (BSEC Error Code %d)", this->bsec_status_);
|
||||
return;
|
||||
}
|
||||
this->next_call_ns_ = bme680_settings.next_call;
|
||||
|
||||
if (bme680_settings.trigger_measurement) {
|
||||
this->bme680_.tph_sett.os_temp = bme680_settings.temperature_oversampling;
|
||||
this->bme680_.tph_sett.os_pres = bme680_settings.pressure_oversampling;
|
||||
this->bme680_.tph_sett.os_hum = bme680_settings.humidity_oversampling;
|
||||
this->bme680_.gas_sett.run_gas = bme680_settings.run_gas;
|
||||
this->bme680_.gas_sett.heatr_temp = bme680_settings.heater_temperature;
|
||||
this->bme680_.gas_sett.heatr_dur = bme680_settings.heating_duration;
|
||||
this->bme680_.power_mode = BME680_FORCED_MODE;
|
||||
uint16_t desired_settings = BME680_OST_SEL | BME680_OSP_SEL | BME680_OSH_SEL | BME680_GAS_SENSOR_SEL;
|
||||
this->bme680_status_ = bme680_set_sensor_settings(desired_settings, &this->bme680_);
|
||||
if (this->bme680_status_ != BME680_OK) {
|
||||
ESP_LOGW(TAG, "Failed to set sensor settings (BME680 Error Code %d)", this->bme680_status_);
|
||||
return;
|
||||
}
|
||||
|
||||
this->bme680_status_ = bme680_set_sensor_mode(&this->bme680_);
|
||||
if (this->bme680_status_ != BME680_OK) {
|
||||
ESP_LOGW(TAG, "Failed to set sensor mode (BME680 Error Code %d)", this->bme680_status_);
|
||||
return;
|
||||
}
|
||||
|
||||
uint16_t meas_dur = 0;
|
||||
bme680_get_profile_dur(&meas_dur, &this->bme680_);
|
||||
ESP_LOGV(TAG, "Queueing read in %ums", meas_dur);
|
||||
this->set_timeout("read", meas_dur,
|
||||
[this, curr_time_ns, bme680_settings]() { this->read_(curr_time_ns, bme680_settings); });
|
||||
} else {
|
||||
ESP_LOGV(TAG, "Measurement not required");
|
||||
this->read_(curr_time_ns, bme680_settings);
|
||||
}
|
||||
}
|
||||
|
||||
void BME680BSECComponent::read_(int64_t trigger_time_ns, bsec_bme_settings_t bme680_settings) {
|
||||
ESP_LOGV(TAG, "Reading data");
|
||||
|
||||
if (bme680_settings.trigger_measurement) {
|
||||
while (this->bme680_.power_mode != BME680_SLEEP_MODE) {
|
||||
this->bme680_status_ = bme680_get_sensor_mode(&this->bme680_);
|
||||
if (this->bme680_status_ != BME680_OK) {
|
||||
ESP_LOGW(TAG, "Failed to get sensor mode (BME680 Error Code %d)", this->bme680_status_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!bme680_settings.process_data) {
|
||||
ESP_LOGV(TAG, "Data processing not required");
|
||||
return;
|
||||
}
|
||||
|
||||
struct bme680_field_data data;
|
||||
this->bme680_status_ = bme680_get_sensor_data(&data, &this->bme680_);
|
||||
|
||||
if (this->bme680_status_ != BME680_OK) {
|
||||
ESP_LOGW(TAG, "Failed to get sensor data (BME680 Error Code %d)", this->bme680_status_);
|
||||
return;
|
||||
}
|
||||
if (!(data.status & BME680_NEW_DATA_MSK)) {
|
||||
ESP_LOGD(TAG, "BME680 did not report new data");
|
||||
return;
|
||||
}
|
||||
|
||||
bsec_input_t inputs[BSEC_MAX_PHYSICAL_SENSOR]; // Temperature, Pressure, Humidity & Gas Resistance
|
||||
uint8_t num_inputs = 0;
|
||||
|
||||
if (bme680_settings.process_data & BSEC_PROCESS_TEMPERATURE) {
|
||||
inputs[num_inputs].sensor_id = BSEC_INPUT_TEMPERATURE;
|
||||
inputs[num_inputs].signal = data.temperature / 100.0f;
|
||||
inputs[num_inputs].time_stamp = trigger_time_ns;
|
||||
num_inputs++;
|
||||
|
||||
// Temperature offset from the real temperature due to external heat sources
|
||||
inputs[num_inputs].sensor_id = BSEC_INPUT_HEATSOURCE;
|
||||
inputs[num_inputs].signal = this->temperature_offset_;
|
||||
inputs[num_inputs].time_stamp = trigger_time_ns;
|
||||
num_inputs++;
|
||||
}
|
||||
if (bme680_settings.process_data & BSEC_PROCESS_HUMIDITY) {
|
||||
inputs[num_inputs].sensor_id = BSEC_INPUT_HUMIDITY;
|
||||
inputs[num_inputs].signal = data.humidity / 1000.0f;
|
||||
inputs[num_inputs].time_stamp = trigger_time_ns;
|
||||
num_inputs++;
|
||||
}
|
||||
if (bme680_settings.process_data & BSEC_PROCESS_PRESSURE) {
|
||||
inputs[num_inputs].sensor_id = BSEC_INPUT_PRESSURE;
|
||||
inputs[num_inputs].signal = data.pressure;
|
||||
inputs[num_inputs].time_stamp = trigger_time_ns;
|
||||
num_inputs++;
|
||||
}
|
||||
if (bme680_settings.process_data & BSEC_PROCESS_GAS) {
|
||||
if (data.status & BME680_GASM_VALID_MSK) {
|
||||
inputs[num_inputs].sensor_id = BSEC_INPUT_GASRESISTOR;
|
||||
inputs[num_inputs].signal = data.gas_resistance;
|
||||
inputs[num_inputs].time_stamp = trigger_time_ns;
|
||||
num_inputs++;
|
||||
} else {
|
||||
ESP_LOGD(TAG, "BME680 did not report gas data");
|
||||
}
|
||||
}
|
||||
if (num_inputs < 1) {
|
||||
ESP_LOGD(TAG, "No signal inputs available for BSEC");
|
||||
return;
|
||||
}
|
||||
|
||||
bsec_output_t outputs[BSEC_NUMBER_OUTPUTS];
|
||||
uint8_t num_outputs = BSEC_NUMBER_OUTPUTS;
|
||||
this->bsec_status_ = bsec_do_steps(inputs, num_inputs, outputs, &num_outputs);
|
||||
if (this->bsec_status_ != BSEC_OK) {
|
||||
ESP_LOGW(TAG, "BSEC failed to process signals (BSEC Error Code %d)", this->bsec_status_);
|
||||
return;
|
||||
}
|
||||
if (num_outputs < 1) {
|
||||
ESP_LOGD(TAG, "No signal outputs provided by BSEC");
|
||||
return;
|
||||
}
|
||||
|
||||
this->publish_(outputs, num_outputs);
|
||||
}
|
||||
|
||||
void BME680BSECComponent::publish_(const bsec_output_t *outputs, uint8_t num_outputs) {
|
||||
ESP_LOGV(TAG, "Publishing sensor states");
|
||||
for (uint8_t i = 0; i < num_outputs; i++) {
|
||||
switch (outputs[i].sensor_id) {
|
||||
case BSEC_OUTPUT_IAQ:
|
||||
case BSEC_OUTPUT_STATIC_IAQ:
|
||||
uint8_t accuracy;
|
||||
accuracy = outputs[i].accuracy;
|
||||
this->publish_sensor_state_(this->iaq_sensor_, outputs[i].signal);
|
||||
this->publish_sensor_state_(this->iaq_accuracy_text_sensor_, IAQ_ACCURACY_STATES[accuracy]);
|
||||
this->publish_sensor_state_(this->iaq_accuracy_sensor_, accuracy, true);
|
||||
|
||||
// Queue up an opportunity to save state
|
||||
this->defer("save_state", [this, accuracy]() { this->save_state_(accuracy); });
|
||||
break;
|
||||
case BSEC_OUTPUT_CO2_EQUIVALENT:
|
||||
this->publish_sensor_state_(this->co2_equivalent_sensor_, outputs[i].signal);
|
||||
break;
|
||||
case BSEC_OUTPUT_BREATH_VOC_EQUIVALENT:
|
||||
this->publish_sensor_state_(this->breath_voc_equivalent_sensor_, outputs[i].signal);
|
||||
break;
|
||||
case BSEC_OUTPUT_RAW_PRESSURE:
|
||||
this->publish_sensor_state_(this->pressure_sensor_, outputs[i].signal / 100.0f);
|
||||
break;
|
||||
case BSEC_OUTPUT_RAW_GAS:
|
||||
this->publish_sensor_state_(this->gas_resistance_sensor_, outputs[i].signal);
|
||||
break;
|
||||
case BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_TEMPERATURE:
|
||||
this->publish_sensor_state_(this->temperature_sensor_, outputs[i].signal);
|
||||
break;
|
||||
case BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_HUMIDITY:
|
||||
this->publish_sensor_state_(this->humidity_sensor_, outputs[i].signal);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int64_t BME680BSECComponent::get_time_ns_() {
|
||||
int64_t time_ms = millis();
|
||||
if (this->last_time_ms_ > time_ms) {
|
||||
this->millis_overflow_counter_++;
|
||||
}
|
||||
this->last_time_ms_ = time_ms;
|
||||
|
||||
return (time_ms + ((int64_t) this->millis_overflow_counter_ << 32)) * INT64_C(1000000);
|
||||
}
|
||||
|
||||
void BME680BSECComponent::publish_sensor_state_(sensor::Sensor *sensor, float value, bool change_only) {
|
||||
if (!sensor || (change_only && sensor->has_state() && sensor->state == value)) {
|
||||
return;
|
||||
}
|
||||
sensor->publish_state(value);
|
||||
}
|
||||
|
||||
void BME680BSECComponent::publish_sensor_state_(text_sensor::TextSensor *sensor, std::string value) {
|
||||
if (!sensor || (sensor->has_state() && sensor->state == value)) {
|
||||
return;
|
||||
}
|
||||
sensor->publish_state(value);
|
||||
}
|
||||
|
||||
int8_t BME680BSECComponent::read_bytes_wrapper(uint8_t address, uint8_t a_register, uint8_t *data, uint16_t len) {
|
||||
return BME680BSECComponent::instance->read_bytes(a_register, data, len) ? 0 : -1;
|
||||
}
|
||||
|
||||
int8_t BME680BSECComponent::write_bytes_wrapper(uint8_t address, uint8_t a_register, uint8_t *data, uint16_t len) {
|
||||
return BME680BSECComponent::instance->write_bytes(a_register, data, len) ? 0 : -1;
|
||||
}
|
||||
|
||||
void BME680BSECComponent::delay_ms(uint32_t period) {
|
||||
ESP_LOGV(TAG, "Delaying for %ums", period);
|
||||
delay(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);
|
||||
|
||||
uint8_t state[BSEC_MAX_STATE_BLOB_SIZE];
|
||||
if (this->bsec_state_.load(&state)) {
|
||||
ESP_LOGV(TAG, "Loading state");
|
||||
uint8_t work_buffer[BSEC_MAX_WORKBUFFER_SIZE];
|
||||
this->bsec_status_ = bsec_set_state(state, BSEC_MAX_STATE_BLOB_SIZE, work_buffer, sizeof(work_buffer));
|
||||
if (this->bsec_status_ != BSEC_OK) {
|
||||
ESP_LOGW(TAG, "Failed to load state (BSEC Error Code %d)", this->bsec_status_);
|
||||
}
|
||||
ESP_LOGI(TAG, "Loaded state");
|
||||
}
|
||||
}
|
||||
|
||||
void BME680BSECComponent::save_state_(uint8_t accuracy) {
|
||||
if (accuracy < 3 || (millis() - this->last_state_save_ms_ < this->state_save_interval_ms_)) {
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGV(TAG, "Saving state");
|
||||
|
||||
uint8_t state[BSEC_MAX_STATE_BLOB_SIZE];
|
||||
uint8_t work_buffer[BSEC_MAX_STATE_BLOB_SIZE];
|
||||
uint32_t num_serialized_state = BSEC_MAX_STATE_BLOB_SIZE;
|
||||
|
||||
this->bsec_status_ =
|
||||
bsec_get_state(0, state, BSEC_MAX_STATE_BLOB_SIZE, work_buffer, BSEC_MAX_STATE_BLOB_SIZE, &num_serialized_state);
|
||||
if (this->bsec_status_ != BSEC_OK) {
|
||||
ESP_LOGW(TAG, "Failed fetch state for save (BSEC Error Code %d)", this->bsec_status_);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this->bsec_state_.save(&state)) {
|
||||
ESP_LOGW(TAG, "Failed to save state");
|
||||
return;
|
||||
}
|
||||
this->last_state_save_ms_ = millis();
|
||||
|
||||
ESP_LOGI(TAG, "Saved state");
|
||||
}
|
||||
#endif
|
||||
} // namespace bme680_bsec
|
||||
} // namespace esphome
|
||||
110
esphome/components/bme680_bsec/bme680_bsec.h
Normal file
110
esphome/components/bme680_bsec/bme680_bsec.h
Normal file
@@ -0,0 +1,110 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/text_sensor/text_sensor.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
#include "esphome/core/preferences.h"
|
||||
#include <map>
|
||||
|
||||
#ifdef USE_BSEC
|
||||
#include <bsec.h>
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
namespace bme680_bsec {
|
||||
#ifdef USE_BSEC
|
||||
|
||||
enum IAQMode {
|
||||
IAQ_MODE_STATIC = 0,
|
||||
IAQ_MODE_MOBILE = 1,
|
||||
};
|
||||
|
||||
enum SampleRate {
|
||||
SAMPLE_RATE_LP = 0,
|
||||
SAMPLE_RATE_ULP = 1,
|
||||
SAMPLE_RATE_DEFAULT = 2,
|
||||
};
|
||||
|
||||
#define BME680_BSEC_SAMPLE_RATE_LOG(r) (r == SAMPLE_RATE_DEFAULT ? "Default" : (r == SAMPLE_RATE_ULP ? "ULP" : "LP"))
|
||||
|
||||
class BME680BSECComponent : public Component, public i2c::I2CDevice {
|
||||
public:
|
||||
void set_temperature_offset(float offset) { this->temperature_offset_ = offset; }
|
||||
void set_iaq_mode(IAQMode iaq_mode) { this->iaq_mode_ = iaq_mode; }
|
||||
void set_state_save_interval(uint32_t interval) { this->state_save_interval_ms_ = interval; }
|
||||
|
||||
void set_sample_rate(SampleRate sample_rate) { this->sample_rate_ = sample_rate; }
|
||||
void set_temperature_sample_rate(SampleRate sample_rate) { this->temperature_sample_rate_ = sample_rate; }
|
||||
void set_pressure_sample_rate(SampleRate sample_rate) { this->pressure_sample_rate_ = sample_rate; }
|
||||
void set_humidity_sample_rate(SampleRate sample_rate) { this->humidity_sample_rate_ = sample_rate; }
|
||||
|
||||
void set_temperature_sensor(sensor::Sensor *sensor) { this->temperature_sensor_ = sensor; }
|
||||
void set_pressure_sensor(sensor::Sensor *sensor) { this->pressure_sensor_ = sensor; }
|
||||
void set_humidity_sensor(sensor::Sensor *sensor) { this->humidity_sensor_ = sensor; }
|
||||
void set_gas_resistance_sensor(sensor::Sensor *sensor) { this->gas_resistance_sensor_ = sensor; }
|
||||
void set_iaq_sensor(sensor::Sensor *sensor) { this->iaq_sensor_ = sensor; }
|
||||
void set_iaq_accuracy_text_sensor(text_sensor::TextSensor *sensor) { this->iaq_accuracy_text_sensor_ = sensor; }
|
||||
void set_iaq_accuracy_sensor(sensor::Sensor *sensor) { this->iaq_accuracy_sensor_ = sensor; }
|
||||
void set_co2_equivalent_sensor(sensor::Sensor *sensor) { this->co2_equivalent_sensor_ = sensor; }
|
||||
void set_breath_voc_equivalent_sensor(sensor::Sensor *sensor) { this->breath_voc_equivalent_sensor_ = sensor; }
|
||||
|
||||
static BME680BSECComponent *instance;
|
||||
static int8_t read_bytes_wrapper(uint8_t address, uint8_t a_register, uint8_t *data, uint16_t len);
|
||||
static int8_t write_bytes_wrapper(uint8_t address, uint8_t a_register, uint8_t *data, uint16_t len);
|
||||
static void delay_ms(uint32_t period);
|
||||
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override;
|
||||
void loop() override;
|
||||
|
||||
protected:
|
||||
void set_config_(const uint8_t *config);
|
||||
float calc_sensor_sample_rate_(SampleRate sample_rate);
|
||||
void update_subscription_();
|
||||
|
||||
void run_();
|
||||
void read_(int64_t trigger_time_ns, bsec_bme_settings_t bme680_settings);
|
||||
void publish_(const bsec_output_t *outputs, uint8_t num_outputs);
|
||||
int64_t get_time_ns_();
|
||||
|
||||
void publish_sensor_state_(sensor::Sensor *sensor, float value, bool change_only = false);
|
||||
void publish_sensor_state_(text_sensor::TextSensor *sensor, std::string value);
|
||||
|
||||
void load_state_();
|
||||
void save_state_(uint8_t accuracy);
|
||||
|
||||
struct bme680_dev bme680_;
|
||||
bsec_library_return_t bsec_status_{BSEC_OK};
|
||||
int8_t bme680_status_{BME680_OK};
|
||||
|
||||
int64_t last_time_ms_{0};
|
||||
uint32_t millis_overflow_counter_{0};
|
||||
int64_t next_call_ns_{0};
|
||||
|
||||
ESPPreferenceObject bsec_state_;
|
||||
uint32_t state_save_interval_ms_{21600000}; // 6 hours - 4 times a day
|
||||
uint32_t last_state_save_ms_ = 0;
|
||||
|
||||
float temperature_offset_{0};
|
||||
IAQMode iaq_mode_{IAQ_MODE_STATIC};
|
||||
|
||||
SampleRate sample_rate_{SAMPLE_RATE_LP}; // Core/gas sample rate
|
||||
SampleRate temperature_sample_rate_{SAMPLE_RATE_DEFAULT};
|
||||
SampleRate pressure_sample_rate_{SAMPLE_RATE_DEFAULT};
|
||||
SampleRate humidity_sample_rate_{SAMPLE_RATE_DEFAULT};
|
||||
|
||||
sensor::Sensor *temperature_sensor_;
|
||||
sensor::Sensor *pressure_sensor_;
|
||||
sensor::Sensor *humidity_sensor_;
|
||||
sensor::Sensor *gas_resistance_sensor_;
|
||||
sensor::Sensor *iaq_sensor_;
|
||||
text_sensor::TextSensor *iaq_accuracy_text_sensor_;
|
||||
sensor::Sensor *iaq_accuracy_sensor_;
|
||||
sensor::Sensor *co2_equivalent_sensor_;
|
||||
sensor::Sensor *breath_voc_equivalent_sensor_;
|
||||
};
|
||||
#endif
|
||||
} // namespace bme680_bsec
|
||||
} // namespace esphome
|
||||
122
esphome/components/bme680_bsec/sensor.py
Normal file
122
esphome/components/bme680_bsec/sensor.py
Normal file
@@ -0,0 +1,122 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import sensor
|
||||
from esphome.const import (
|
||||
CONF_GAS_RESISTANCE,
|
||||
CONF_HUMIDITY,
|
||||
CONF_PRESSURE,
|
||||
CONF_TEMPERATURE,
|
||||
DEVICE_CLASS_EMPTY,
|
||||
DEVICE_CLASS_HUMIDITY,
|
||||
DEVICE_CLASS_PRESSURE,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_CELSIUS,
|
||||
UNIT_EMPTY,
|
||||
UNIT_HECTOPASCAL,
|
||||
UNIT_OHM,
|
||||
UNIT_PARTS_PER_MILLION,
|
||||
UNIT_PERCENT,
|
||||
ICON_GAS_CYLINDER,
|
||||
ICON_GAUGE,
|
||||
ICON_THERMOMETER,
|
||||
ICON_WATER_PERCENT,
|
||||
)
|
||||
from . import (
|
||||
BME680BSECComponent,
|
||||
CONF_BME680_BSEC_ID,
|
||||
CONF_SAMPLE_RATE,
|
||||
SAMPLE_RATE_OPTIONS,
|
||||
)
|
||||
|
||||
DEPENDENCIES = ["bme680_bsec"]
|
||||
|
||||
CONF_IAQ = "iaq"
|
||||
CONF_IAQ_ACCURACY = "iaq_accuracy"
|
||||
CONF_CO2_EQUIVALENT = "co2_equivalent"
|
||||
CONF_BREATH_VOC_EQUIVALENT = "breath_voc_equivalent"
|
||||
UNIT_IAQ = "IAQ"
|
||||
ICON_ACCURACY = "mdi:checkbox-marked-circle-outline"
|
||||
ICON_TEST_TUBE = "mdi:test-tube"
|
||||
|
||||
TYPES = [
|
||||
CONF_TEMPERATURE,
|
||||
CONF_PRESSURE,
|
||||
CONF_HUMIDITY,
|
||||
CONF_GAS_RESISTANCE,
|
||||
CONF_IAQ,
|
||||
CONF_IAQ_ACCURACY,
|
||||
CONF_CO2_EQUIVALENT,
|
||||
CONF_BREATH_VOC_EQUIVALENT,
|
||||
]
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(CONF_BME680_BSEC_ID): cv.use_id(BME680BSECComponent),
|
||||
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
|
||||
UNIT_CELSIUS,
|
||||
ICON_THERMOMETER,
|
||||
1,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
).extend(
|
||||
{cv.Optional(CONF_SAMPLE_RATE): cv.enum(SAMPLE_RATE_OPTIONS, upper=True)}
|
||||
),
|
||||
cv.Optional(CONF_PRESSURE): sensor.sensor_schema(
|
||||
UNIT_HECTOPASCAL,
|
||||
ICON_GAUGE,
|
||||
1,
|
||||
DEVICE_CLASS_PRESSURE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
).extend(
|
||||
{cv.Optional(CONF_SAMPLE_RATE): cv.enum(SAMPLE_RATE_OPTIONS, upper=True)}
|
||||
),
|
||||
cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
|
||||
UNIT_PERCENT,
|
||||
ICON_WATER_PERCENT,
|
||||
1,
|
||||
DEVICE_CLASS_HUMIDITY,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
).extend(
|
||||
{cv.Optional(CONF_SAMPLE_RATE): cv.enum(SAMPLE_RATE_OPTIONS, upper=True)}
|
||||
),
|
||||
cv.Optional(CONF_GAS_RESISTANCE): sensor.sensor_schema(
|
||||
UNIT_OHM, ICON_GAS_CYLINDER, 0, DEVICE_CLASS_EMPTY, STATE_CLASS_MEASUREMENT
|
||||
),
|
||||
cv.Optional(CONF_IAQ): sensor.sensor_schema(
|
||||
UNIT_IAQ, ICON_GAUGE, 0, DEVICE_CLASS_EMPTY, STATE_CLASS_MEASUREMENT
|
||||
),
|
||||
cv.Optional(CONF_IAQ_ACCURACY): sensor.sensor_schema(
|
||||
UNIT_EMPTY, ICON_ACCURACY, 0, DEVICE_CLASS_EMPTY, STATE_CLASS_MEASUREMENT
|
||||
),
|
||||
cv.Optional(CONF_CO2_EQUIVALENT): sensor.sensor_schema(
|
||||
UNIT_PARTS_PER_MILLION,
|
||||
ICON_TEST_TUBE,
|
||||
1,
|
||||
DEVICE_CLASS_EMPTY,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_BREATH_VOC_EQUIVALENT): sensor.sensor_schema(
|
||||
UNIT_PARTS_PER_MILLION,
|
||||
ICON_TEST_TUBE,
|
||||
1,
|
||||
DEVICE_CLASS_EMPTY,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
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))
|
||||
if CONF_SAMPLE_RATE in conf:
|
||||
cg.add(getattr(hub, f"set_{key}_sample_rate")(conf[CONF_SAMPLE_RATE]))
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
hub = await cg.get_variable(config[CONF_BME680_BSEC_ID])
|
||||
for key in TYPES:
|
||||
await setup_conf(config, key, hub)
|
||||
38
esphome/components/bme680_bsec/text_sensor.py
Normal file
38
esphome/components/bme680_bsec/text_sensor.py
Normal file
@@ -0,0 +1,38 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import text_sensor
|
||||
from esphome.const import CONF_ID, CONF_ICON
|
||||
from . import BME680BSECComponent, CONF_BME680_BSEC_ID
|
||||
|
||||
DEPENDENCIES = ["bme680_bsec"]
|
||||
|
||||
CONF_IAQ_ACCURACY = "iaq_accuracy"
|
||||
ICON_ACCURACY = "mdi:checkbox-marked-circle-outline"
|
||||
|
||||
TYPES = [CONF_IAQ_ACCURACY]
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(CONF_BME680_BSEC_ID): cv.use_id(BME680BSECComponent),
|
||||
cv.Optional(CONF_IAQ_ACCURACY): text_sensor.TEXT_SENSOR_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(text_sensor.TextSensor),
|
||||
cv.Optional(CONF_ICON, default=ICON_ACCURACY): cv.icon,
|
||||
}
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
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_BME680_BSEC_ID])
|
||||
for key in TYPES:
|
||||
await setup_conf(config, key, hub)
|
||||
@@ -7,6 +7,7 @@ from esphome.const import (
|
||||
CONF_TEMPERATURE,
|
||||
DEVICE_CLASS_PRESSURE,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_CELSIUS,
|
||||
ICON_EMPTY,
|
||||
UNIT_HECTOPASCAL,
|
||||
@@ -24,10 +25,18 @@ CONFIG_SCHEMA = (
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(BMP085Component),
|
||||
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
|
||||
UNIT_CELSIUS, ICON_EMPTY, 1, DEVICE_CLASS_TEMPERATURE
|
||||
UNIT_CELSIUS,
|
||||
ICON_EMPTY,
|
||||
1,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_PRESSURE): sensor.sensor_schema(
|
||||
UNIT_HECTOPASCAL, ICON_EMPTY, 1, DEVICE_CLASS_PRESSURE
|
||||
UNIT_HECTOPASCAL,
|
||||
ICON_EMPTY,
|
||||
1,
|
||||
DEVICE_CLASS_PRESSURE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
}
|
||||
)
|
||||
@@ -36,17 +45,17 @@ CONFIG_SCHEMA = (
|
||||
)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
yield cg.register_component(var, config)
|
||||
yield i2c.register_i2c_device(var, config)
|
||||
await cg.register_component(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
|
||||
if CONF_TEMPERATURE in config:
|
||||
conf = config[CONF_TEMPERATURE]
|
||||
sens = yield sensor.new_sensor(conf)
|
||||
sens = await sensor.new_sensor(conf)
|
||||
cg.add(var.set_temperature(sens))
|
||||
|
||||
if CONF_PRESSURE in config:
|
||||
conf = config[CONF_PRESSURE]
|
||||
sens = yield sensor.new_sensor(conf)
|
||||
sens = await sensor.new_sensor(conf)
|
||||
cg.add(var.set_pressure(sens))
|
||||
|
||||
@@ -7,6 +7,7 @@ from esphome.const import (
|
||||
CONF_TEMPERATURE,
|
||||
DEVICE_CLASS_PRESSURE,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_CELSIUS,
|
||||
ICON_EMPTY,
|
||||
UNIT_HECTOPASCAL,
|
||||
@@ -45,7 +46,11 @@ CONFIG_SCHEMA = (
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(BMP280Component),
|
||||
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
|
||||
UNIT_CELSIUS, ICON_EMPTY, 1, DEVICE_CLASS_TEMPERATURE
|
||||
UNIT_CELSIUS,
|
||||
ICON_EMPTY,
|
||||
1,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
).extend(
|
||||
{
|
||||
cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum(
|
||||
@@ -54,7 +59,11 @@ CONFIG_SCHEMA = (
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_PRESSURE): sensor.sensor_schema(
|
||||
UNIT_HECTOPASCAL, ICON_EMPTY, 1, DEVICE_CLASS_PRESSURE
|
||||
UNIT_HECTOPASCAL,
|
||||
ICON_EMPTY,
|
||||
1,
|
||||
DEVICE_CLASS_PRESSURE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
).extend(
|
||||
{
|
||||
cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum(
|
||||
@@ -72,19 +81,19 @@ CONFIG_SCHEMA = (
|
||||
)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
yield cg.register_component(var, config)
|
||||
yield i2c.register_i2c_device(var, config)
|
||||
await cg.register_component(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
|
||||
if CONF_TEMPERATURE in config:
|
||||
conf = config[CONF_TEMPERATURE]
|
||||
sens = yield sensor.new_sensor(conf)
|
||||
sens = await sensor.new_sensor(conf)
|
||||
cg.add(var.set_temperature_sensor(sens))
|
||||
cg.add(var.set_temperature_oversampling(conf[CONF_OVERSAMPLING]))
|
||||
|
||||
if CONF_PRESSURE in config:
|
||||
conf = config[CONF_PRESSURE]
|
||||
sens = yield sensor.new_sensor(conf)
|
||||
sens = await sensor.new_sensor(conf)
|
||||
cg.add(var.set_pressure_sensor(sens))
|
||||
cg.add(var.set_pressure_oversampling(conf[CONF_OVERSAMPLING]))
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import automation
|
||||
from esphome.core import CORE, coroutine
|
||||
from esphome.core import CORE
|
||||
from esphome.const import CONF_ID, CONF_TRIGGER_ID, CONF_DATA
|
||||
|
||||
CODEOWNERS = ["@mvturnho", "@danielschramm"]
|
||||
@@ -82,10 +82,9 @@ CANBUS_SCHEMA = cv.Schema(
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
|
||||
@coroutine
|
||||
def setup_canbus_core_(var, config):
|
||||
async def setup_canbus_core_(var, config):
|
||||
validate_id(config[CONF_CAN_ID], config[CONF_USE_EXTENDED_ID])
|
||||
yield cg.register_component(var, config)
|
||||
await cg.register_component(var, config)
|
||||
cg.add(var.set_can_id([config[CONF_CAN_ID]]))
|
||||
cg.add(var.set_use_extended_id([config[CONF_USE_EXTENDED_ID]]))
|
||||
cg.add(var.set_bitrate(CAN_SPEEDS[config[CONF_BIT_RATE]]))
|
||||
@@ -95,17 +94,16 @@ def setup_canbus_core_(var, config):
|
||||
ext_id = conf[CONF_USE_EXTENDED_ID]
|
||||
validate_id(can_id, ext_id)
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var, can_id, ext_id)
|
||||
yield cg.register_component(trigger, conf)
|
||||
yield automation.build_automation(
|
||||
await cg.register_component(trigger, conf)
|
||||
await automation.build_automation(
|
||||
trigger, [(cg.std_vector.template(cg.uint8), "x")], conf
|
||||
)
|
||||
|
||||
|
||||
@coroutine
|
||||
def register_canbus(var, config):
|
||||
async def register_canbus(var, config):
|
||||
if not CORE.has_id(config[CONF_ID]):
|
||||
var = cg.new_Pvariable(config[CONF_ID], var)
|
||||
yield setup_canbus_core_(var, config)
|
||||
await setup_canbus_core_(var, config)
|
||||
|
||||
|
||||
# Actions
|
||||
@@ -122,16 +120,16 @@ def register_canbus(var, config):
|
||||
key=CONF_DATA,
|
||||
),
|
||||
)
|
||||
def canbus_action_to_code(config, action_id, template_arg, args):
|
||||
async def canbus_action_to_code(config, action_id, template_arg, args):
|
||||
validate_id(config[CONF_CAN_ID], config[CONF_USE_EXTENDED_ID])
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
yield cg.register_parented(var, config[CONF_CANBUS_ID])
|
||||
await cg.register_parented(var, config[CONF_CANBUS_ID])
|
||||
|
||||
if CONF_CAN_ID in config:
|
||||
can_id = yield cg.templatable(config[CONF_CAN_ID], args, cg.uint32)
|
||||
can_id = await cg.templatable(config[CONF_CAN_ID], args, cg.uint32)
|
||||
cg.add(var.set_can_id(can_id))
|
||||
|
||||
use_extended_id = yield cg.templatable(
|
||||
use_extended_id = await cg.templatable(
|
||||
config[CONF_USE_EXTENDED_ID], args, cg.uint32
|
||||
)
|
||||
cg.add(var.set_use_extended_id(use_extended_id))
|
||||
@@ -140,8 +138,8 @@ def canbus_action_to_code(config, action_id, template_arg, args):
|
||||
if isinstance(data, bytes):
|
||||
data = [int(x) for x in data]
|
||||
if cg.is_template(data):
|
||||
templ = yield cg.templatable(data, args, cg.std_vector.template(cg.uint8))
|
||||
templ = await cg.templatable(data, args, cg.std_vector.template(cg.uint8))
|
||||
cg.add(var.set_data_template(templ))
|
||||
else:
|
||||
cg.add(var.set_data_static(data))
|
||||
yield var
|
||||
return var
|
||||
|
||||
@@ -23,9 +23,9 @@ CONFIG_SCHEMA = cv.Schema(
|
||||
|
||||
|
||||
@coroutine_with_priority(64.0)
|
||||
def to_code(config):
|
||||
paren = yield cg.get_variable(config[CONF_WEB_SERVER_BASE_ID])
|
||||
async def to_code(config):
|
||||
paren = await cg.get_variable(config[CONF_WEB_SERVER_BASE_ID])
|
||||
|
||||
var = cg.new_Pvariable(config[CONF_ID], paren)
|
||||
yield cg.register_component(var, config)
|
||||
await cg.register_component(var, config)
|
||||
cg.add_define("USE_CAPTIVE_PORTAL")
|
||||
|
||||
@@ -64,32 +64,11 @@ void CaptivePortal::handle_wifisave(AsyncWebServerRequest *request) {
|
||||
ESP_LOGI(TAG, "Captive Portal Requested WiFi Settings Change:");
|
||||
ESP_LOGI(TAG, " SSID='%s'", ssid.c_str());
|
||||
ESP_LOGI(TAG, " Password=" LOG_SECRET("'%s'"), psk.c_str());
|
||||
this->override_sta_(ssid, psk);
|
||||
wifi::global_wifi_component->save_wifi_sta(ssid, psk);
|
||||
request->redirect("/?save=true");
|
||||
}
|
||||
void CaptivePortal::override_sta_(const std::string &ssid, const std::string &password) {
|
||||
CaptivePortalSettings save{};
|
||||
strcpy(save.ssid, ssid.c_str());
|
||||
strcpy(save.password, password.c_str());
|
||||
this->pref_.save(&save);
|
||||
|
||||
wifi::WiFiAP sta{};
|
||||
sta.set_ssid(ssid);
|
||||
sta.set_password(password);
|
||||
wifi::global_wifi_component->set_sta(sta);
|
||||
}
|
||||
|
||||
void CaptivePortal::setup() {
|
||||
// Hash with compilation time
|
||||
// This ensures the AP override is not applied for OTA
|
||||
uint32_t hash = fnv1_hash(App.get_compilation_time());
|
||||
this->pref_ = global_preferences.make_preference<CaptivePortalSettings>(hash, true);
|
||||
|
||||
CaptivePortalSettings save{};
|
||||
if (this->pref_.load(&save)) {
|
||||
this->override_sta_(save.ssid, save.password);
|
||||
}
|
||||
}
|
||||
void CaptivePortal::setup() {}
|
||||
void CaptivePortal::start() {
|
||||
this->base_->init();
|
||||
if (!this->initialized_) {
|
||||
|
||||
@@ -10,11 +10,6 @@ namespace esphome {
|
||||
|
||||
namespace captive_portal {
|
||||
|
||||
struct CaptivePortalSettings {
|
||||
char ssid[33];
|
||||
char password[65];
|
||||
} PACKED; // NOLINT
|
||||
|
||||
class CaptivePortal : public AsyncWebHandler, public Component {
|
||||
public:
|
||||
CaptivePortal(web_server_base::WebServerBase *base);
|
||||
@@ -67,12 +62,9 @@ class CaptivePortal : public AsyncWebHandler, public Component {
|
||||
void handleRequest(AsyncWebServerRequest *req) override;
|
||||
|
||||
protected:
|
||||
void override_sta_(const std::string &ssid, const std::string &password);
|
||||
|
||||
web_server_base::WebServerBase *base_;
|
||||
bool initialized_{false};
|
||||
bool active_{false};
|
||||
ESPPreferenceObject pref_;
|
||||
DNSServer *dns_server_{nullptr};
|
||||
};
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ from esphome.const import (
|
||||
CONF_ID,
|
||||
DEVICE_CLASS_EMPTY,
|
||||
ICON_RADIATOR,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_PARTS_PER_MILLION,
|
||||
UNIT_PARTS_PER_BILLION,
|
||||
CONF_TEMPERATURE,
|
||||
@@ -28,10 +29,18 @@ CONFIG_SCHEMA = (
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(CCS811Component),
|
||||
cv.Required(CONF_ECO2): sensor.sensor_schema(
|
||||
UNIT_PARTS_PER_MILLION, ICON_MOLECULE_CO2, 0, DEVICE_CLASS_EMPTY
|
||||
UNIT_PARTS_PER_MILLION,
|
||||
ICON_MOLECULE_CO2,
|
||||
0,
|
||||
DEVICE_CLASS_EMPTY,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Required(CONF_TVOC): sensor.sensor_schema(
|
||||
UNIT_PARTS_PER_BILLION, ICON_RADIATOR, 0, DEVICE_CLASS_EMPTY
|
||||
UNIT_PARTS_PER_BILLION,
|
||||
ICON_RADIATOR,
|
||||
0,
|
||||
DEVICE_CLASS_EMPTY,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_BASELINE): cv.hex_uint16_t,
|
||||
cv.Optional(CONF_TEMPERATURE): cv.use_id(sensor.Sensor),
|
||||
@@ -43,22 +52,22 @@ CONFIG_SCHEMA = (
|
||||
)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
yield cg.register_component(var, config)
|
||||
yield i2c.register_i2c_device(var, config)
|
||||
await cg.register_component(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
|
||||
sens = yield sensor.new_sensor(config[CONF_ECO2])
|
||||
sens = await sensor.new_sensor(config[CONF_ECO2])
|
||||
cg.add(var.set_co2(sens))
|
||||
sens = yield sensor.new_sensor(config[CONF_TVOC])
|
||||
sens = await sensor.new_sensor(config[CONF_TVOC])
|
||||
cg.add(var.set_tvoc(sens))
|
||||
|
||||
if CONF_BASELINE in config:
|
||||
cg.add(var.set_baseline(config[CONF_BASELINE]))
|
||||
|
||||
if CONF_TEMPERATURE in config:
|
||||
sens = yield cg.get_variable(config[CONF_TEMPERATURE])
|
||||
sens = await cg.get_variable(config[CONF_TEMPERATURE])
|
||||
cg.add(var.set_temperature(sens))
|
||||
if CONF_HUMIDITY in config:
|
||||
sens = yield cg.get_variable(config[CONF_HUMIDITY])
|
||||
sens = await cg.get_variable(config[CONF_HUMIDITY])
|
||||
cg.add(var.set_humidity(sens))
|
||||
|
||||
@@ -4,11 +4,14 @@ from esphome import automation
|
||||
from esphome.components import mqtt
|
||||
from esphome.const import (
|
||||
CONF_AWAY,
|
||||
CONF_CUSTOM_FAN_MODE,
|
||||
CONF_CUSTOM_PRESET,
|
||||
CONF_ID,
|
||||
CONF_INTERNAL,
|
||||
CONF_MAX_TEMPERATURE,
|
||||
CONF_MIN_TEMPERATURE,
|
||||
CONF_MODE,
|
||||
CONF_PRESET,
|
||||
CONF_TARGET_TEMPERATURE,
|
||||
CONF_TARGET_TEMPERATURE_HIGH,
|
||||
CONF_TARGET_TEMPERATURE_LOW,
|
||||
@@ -19,7 +22,7 @@ from esphome.const import (
|
||||
CONF_FAN_MODE,
|
||||
CONF_SWING_MODE,
|
||||
)
|
||||
from esphome.core import CORE, coroutine, coroutine_with_priority
|
||||
from esphome.core import CORE, coroutine_with_priority
|
||||
|
||||
IS_PLATFORM_COMPONENT = True
|
||||
|
||||
@@ -33,11 +36,12 @@ ClimateTraits = climate_ns.class_("ClimateTraits")
|
||||
ClimateMode = climate_ns.enum("ClimateMode")
|
||||
CLIMATE_MODES = {
|
||||
"OFF": ClimateMode.CLIMATE_MODE_OFF,
|
||||
"AUTO": ClimateMode.CLIMATE_MODE_AUTO,
|
||||
"HEAT_COOL": ClimateMode.CLIMATE_HEAT_COOL,
|
||||
"COOL": ClimateMode.CLIMATE_MODE_COOL,
|
||||
"HEAT": ClimateMode.CLIMATE_MODE_HEAT,
|
||||
"DRY": ClimateMode.CLIMATE_MODE_DRY,
|
||||
"FAN_ONLY": ClimateMode.CLIMATE_MODE_FAN_ONLY,
|
||||
"AUTO": ClimateMode.CLIMATE_MODE_AUTO,
|
||||
}
|
||||
validate_climate_mode = cv.enum(CLIMATE_MODES, upper=True)
|
||||
|
||||
@@ -56,6 +60,19 @@ CLIMATE_FAN_MODES = {
|
||||
|
||||
validate_climate_fan_mode = cv.enum(CLIMATE_FAN_MODES, upper=True)
|
||||
|
||||
ClimatePreset = climate_ns.enum("ClimatePreset")
|
||||
CLIMATE_PRESETS = {
|
||||
"ECO": ClimatePreset.CLIMATE_PRESET_ECO,
|
||||
"AWAY": ClimatePreset.CLIMATE_PRESET_AWAY,
|
||||
"BOOST": ClimatePreset.CLIMATE_PRESET_BOOST,
|
||||
"COMFORT": ClimatePreset.CLIMATE_PRESET_COMFORT,
|
||||
"HOME": ClimatePreset.CLIMATE_PRESET_HOME,
|
||||
"SLEEP": ClimatePreset.CLIMATE_PRESET_SLEEP,
|
||||
"ACTIVITY": ClimatePreset.CLIMATE_PRESET_ACTIVITY,
|
||||
}
|
||||
|
||||
validate_climate_preset = cv.enum(CLIMATE_PRESETS, upper=True)
|
||||
|
||||
ClimateSwingMode = climate_ns.enum("ClimateSwingMode")
|
||||
CLIMATE_SWING_MODES = {
|
||||
"OFF": ClimateSwingMode.CLIMATE_SWING_OFF,
|
||||
@@ -85,8 +102,7 @@ CLIMATE_SCHEMA = cv.MQTT_COMMAND_COMPONENT_SCHEMA.extend(
|
||||
)
|
||||
|
||||
|
||||
@coroutine
|
||||
def setup_climate_core_(var, config):
|
||||
async def setup_climate_core_(var, config):
|
||||
cg.add(var.set_name(config[CONF_NAME]))
|
||||
if CONF_INTERNAL in config:
|
||||
cg.add(var.set_internal(config[CONF_INTERNAL]))
|
||||
@@ -100,15 +116,14 @@ def setup_climate_core_(var, config):
|
||||
|
||||
if CONF_MQTT_ID in config:
|
||||
mqtt_ = cg.new_Pvariable(config[CONF_MQTT_ID], var)
|
||||
yield mqtt.register_mqtt_component(mqtt_, config)
|
||||
await mqtt.register_mqtt_component(mqtt_, config)
|
||||
|
||||
|
||||
@coroutine
|
||||
def register_climate(var, config):
|
||||
async def register_climate(var, config):
|
||||
if not CORE.has_id(config[CONF_ID]):
|
||||
var = cg.Pvariable(config[CONF_ID], var)
|
||||
cg.add(cg.App.register_climate(var))
|
||||
yield setup_climate_core_(var, config)
|
||||
await setup_climate_core_(var, config)
|
||||
|
||||
|
||||
CLIMATE_CONTROL_ACTION_SCHEMA = cv.Schema(
|
||||
@@ -119,7 +134,12 @@ CLIMATE_CONTROL_ACTION_SCHEMA = cv.Schema(
|
||||
cv.Optional(CONF_TARGET_TEMPERATURE_LOW): cv.templatable(cv.temperature),
|
||||
cv.Optional(CONF_TARGET_TEMPERATURE_HIGH): cv.templatable(cv.temperature),
|
||||
cv.Optional(CONF_AWAY): cv.templatable(cv.boolean),
|
||||
cv.Optional(CONF_FAN_MODE): cv.templatable(validate_climate_fan_mode),
|
||||
cv.Exclusive(CONF_FAN_MODE, "fan_mode"): cv.templatable(
|
||||
validate_climate_fan_mode
|
||||
),
|
||||
cv.Exclusive(CONF_CUSTOM_FAN_MODE, "fan_mode"): cv.string_strict,
|
||||
cv.Exclusive(CONF_PRESET, "preset"): cv.templatable(validate_climate_preset),
|
||||
cv.Exclusive(CONF_CUSTOM_PRESET, "preset"): cv.string_strict,
|
||||
cv.Optional(CONF_SWING_MODE): cv.templatable(validate_climate_swing_mode),
|
||||
}
|
||||
)
|
||||
@@ -128,40 +148,49 @@ CLIMATE_CONTROL_ACTION_SCHEMA = cv.Schema(
|
||||
@automation.register_action(
|
||||
"climate.control", ControlAction, CLIMATE_CONTROL_ACTION_SCHEMA
|
||||
)
|
||||
def climate_control_to_code(config, action_id, template_arg, args):
|
||||
paren = yield cg.get_variable(config[CONF_ID])
|
||||
async def climate_control_to_code(config, action_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
var = cg.new_Pvariable(action_id, template_arg, paren)
|
||||
if CONF_MODE in config:
|
||||
template_ = yield cg.templatable(config[CONF_MODE], args, ClimateMode)
|
||||
template_ = await cg.templatable(config[CONF_MODE], args, ClimateMode)
|
||||
cg.add(var.set_mode(template_))
|
||||
if CONF_TARGET_TEMPERATURE in config:
|
||||
template_ = yield cg.templatable(config[CONF_TARGET_TEMPERATURE], args, float)
|
||||
template_ = await cg.templatable(config[CONF_TARGET_TEMPERATURE], args, float)
|
||||
cg.add(var.set_target_temperature(template_))
|
||||
if CONF_TARGET_TEMPERATURE_LOW in config:
|
||||
template_ = yield cg.templatable(
|
||||
template_ = await cg.templatable(
|
||||
config[CONF_TARGET_TEMPERATURE_LOW], args, float
|
||||
)
|
||||
cg.add(var.set_target_temperature_low(template_))
|
||||
if CONF_TARGET_TEMPERATURE_HIGH in config:
|
||||
template_ = yield cg.templatable(
|
||||
template_ = await cg.templatable(
|
||||
config[CONF_TARGET_TEMPERATURE_HIGH], args, float
|
||||
)
|
||||
cg.add(var.set_target_temperature_high(template_))
|
||||
if CONF_AWAY in config:
|
||||
template_ = yield cg.templatable(config[CONF_AWAY], args, bool)
|
||||
template_ = await cg.templatable(config[CONF_AWAY], args, bool)
|
||||
cg.add(var.set_away(template_))
|
||||
if CONF_FAN_MODE in config:
|
||||
template_ = yield cg.templatable(config[CONF_FAN_MODE], args, ClimateFanMode)
|
||||
template_ = await cg.templatable(config[CONF_FAN_MODE], args, ClimateFanMode)
|
||||
cg.add(var.set_fan_mode(template_))
|
||||
if CONF_CUSTOM_FAN_MODE in config:
|
||||
template_ = await cg.templatable(config[CONF_CUSTOM_FAN_MODE], args, str)
|
||||
cg.add(var.set_custom_fan_mode(template_))
|
||||
if CONF_PRESET in config:
|
||||
template_ = await cg.templatable(config[CONF_PRESET], args, ClimatePreset)
|
||||
cg.add(var.set_preset(template_))
|
||||
if CONF_CUSTOM_PRESET in config:
|
||||
template_ = await cg.templatable(config[CONF_CUSTOM_PRESET], args, str)
|
||||
cg.add(var.set_custom_preset(template_))
|
||||
if CONF_SWING_MODE in config:
|
||||
template_ = yield cg.templatable(
|
||||
template_ = await cg.templatable(
|
||||
config[CONF_SWING_MODE], args, ClimateSwingMode
|
||||
)
|
||||
cg.add(var.set_swing_mode(template_))
|
||||
yield var
|
||||
return var
|
||||
|
||||
|
||||
@coroutine_with_priority(100.0)
|
||||
def to_code(config):
|
||||
async def to_code(config):
|
||||
cg.add_define("USE_CLIMATE")
|
||||
cg.add_global(climate_ns.using)
|
||||
|
||||
@@ -16,6 +16,9 @@ template<typename... Ts> class ControlAction : public Action<Ts...> {
|
||||
TEMPLATABLE_VALUE(float, target_temperature_high)
|
||||
TEMPLATABLE_VALUE(bool, away)
|
||||
TEMPLATABLE_VALUE(ClimateFanMode, fan_mode)
|
||||
TEMPLATABLE_VALUE(std::string, custom_fan_mode)
|
||||
TEMPLATABLE_VALUE(ClimatePreset, preset)
|
||||
TEMPLATABLE_VALUE(std::string, custom_preset)
|
||||
TEMPLATABLE_VALUE(ClimateSwingMode, swing_mode)
|
||||
|
||||
void play(Ts... x) override {
|
||||
@@ -26,6 +29,9 @@ template<typename... Ts> class ControlAction : public Action<Ts...> {
|
||||
call.set_target_temperature_high(this->target_temperature_high_.optional_value(x...));
|
||||
call.set_away(this->away_.optional_value(x...));
|
||||
call.set_fan_mode(this->fan_mode_.optional_value(x...));
|
||||
call.set_fan_mode(this->custom_fan_mode_.optional_value(x...));
|
||||
call.set_preset(this->preset_.optional_value(x...));
|
||||
call.set_preset(this->custom_preset_.optional_value(x...));
|
||||
call.set_swing_mode(this->swing_mode_.optional_value(x...));
|
||||
call.perform();
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
#include "climate.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace climate {
|
||||
@@ -13,10 +12,24 @@ void ClimateCall::perform() {
|
||||
const char *mode_s = climate_mode_to_string(*this->mode_);
|
||||
ESP_LOGD(TAG, " Mode: %s", mode_s);
|
||||
}
|
||||
if (this->custom_fan_mode_.has_value()) {
|
||||
this->fan_mode_.reset();
|
||||
ESP_LOGD(TAG, " Custom Fan: %s", this->custom_fan_mode_.value().c_str());
|
||||
}
|
||||
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);
|
||||
}
|
||||
if (this->custom_preset_.has_value()) {
|
||||
this->preset_.reset();
|
||||
ESP_LOGD(TAG, " Custom Preset: %s", this->custom_preset_.value().c_str());
|
||||
}
|
||||
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);
|
||||
}
|
||||
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);
|
||||
@@ -44,13 +57,32 @@ void ClimateCall::validate_() {
|
||||
this->mode_.reset();
|
||||
}
|
||||
}
|
||||
if (this->fan_mode_.has_value()) {
|
||||
if (this->custom_fan_mode_.has_value()) {
|
||||
auto custom_fan_mode = *this->custom_fan_mode_;
|
||||
if (!traits.supports_custom_fan_mode(custom_fan_mode)) {
|
||||
ESP_LOGW(TAG, " Fan Mode %s is not supported by this device!", custom_fan_mode.c_str());
|
||||
this->custom_fan_mode_.reset();
|
||||
}
|
||||
} 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));
|
||||
this->fan_mode_.reset();
|
||||
}
|
||||
}
|
||||
if (this->custom_preset_.has_value()) {
|
||||
auto custom_preset = *this->custom_preset_;
|
||||
if (!traits.supports_custom_preset(custom_preset)) {
|
||||
ESP_LOGW(TAG, " Preset %s is not supported by this device!", custom_preset.c_str());
|
||||
this->custom_preset_.reset();
|
||||
}
|
||||
} 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));
|
||||
this->preset_.reset();
|
||||
}
|
||||
}
|
||||
if (this->swing_mode_.has_value()) {
|
||||
auto swing_mode = *this->swing_mode_;
|
||||
if (!traits.supports_swing_mode(swing_mode)) {
|
||||
@@ -117,6 +149,8 @@ ClimateCall &ClimateCall::set_mode(const std::string &mode) {
|
||||
this->set_mode(CLIMATE_MODE_FAN_ONLY);
|
||||
} else if (str_equals_case_insensitive(mode, "DRY")) {
|
||||
this->set_mode(CLIMATE_MODE_DRY);
|
||||
} else if (str_equals_case_insensitive(mode, "HEAT_COOL")) {
|
||||
this->set_mode(CLIMATE_MODE_HEAT_COOL);
|
||||
} else {
|
||||
ESP_LOGW(TAG, "'%s' - Unrecognized mode %s", this->parent_->get_name().c_str(), mode.c_str());
|
||||
}
|
||||
@@ -124,6 +158,7 @@ ClimateCall &ClimateCall::set_mode(const std::string &mode) {
|
||||
}
|
||||
ClimateCall &ClimateCall::set_fan_mode(ClimateFanMode fan_mode) {
|
||||
this->fan_mode_ = fan_mode;
|
||||
this->custom_fan_mode_.reset();
|
||||
return *this;
|
||||
}
|
||||
ClimateCall &ClimateCall::set_fan_mode(const std::string &fan_mode) {
|
||||
@@ -146,11 +181,59 @@ ClimateCall &ClimateCall::set_fan_mode(const std::string &fan_mode) {
|
||||
} else if (str_equals_case_insensitive(fan_mode, "DIFFUSE")) {
|
||||
this->set_fan_mode(CLIMATE_FAN_DIFFUSE);
|
||||
} else {
|
||||
ESP_LOGW(TAG, "'%s' - Unrecognized fan mode %s", this->parent_->get_name().c_str(), fan_mode.c_str());
|
||||
auto custom_fan_modes = this->parent_->get_traits().get_supported_custom_fan_modes();
|
||||
if (std::find(custom_fan_modes.begin(), custom_fan_modes.end(), fan_mode) != custom_fan_modes.end()) {
|
||||
this->custom_fan_mode_ = fan_mode;
|
||||
this->fan_mode_.reset();
|
||||
} else {
|
||||
ESP_LOGW(TAG, "'%s' - Unrecognized fan mode %s", this->parent_->get_name().c_str(), fan_mode.c_str());
|
||||
}
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
ClimateCall &ClimateCall::set_fan_mode(optional<std::string> fan_mode) {
|
||||
if (fan_mode.has_value()) {
|
||||
this->set_fan_mode(fan_mode.value());
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
ClimateCall &ClimateCall::set_preset(ClimatePreset preset) {
|
||||
this->preset_ = preset;
|
||||
this->custom_preset_.reset();
|
||||
return *this;
|
||||
}
|
||||
ClimateCall &ClimateCall::set_preset(const std::string &preset) {
|
||||
if (str_equals_case_insensitive(preset, "ECO")) {
|
||||
this->set_preset(CLIMATE_PRESET_ECO);
|
||||
} else if (str_equals_case_insensitive(preset, "AWAY")) {
|
||||
this->set_preset(CLIMATE_PRESET_AWAY);
|
||||
} else if (str_equals_case_insensitive(preset, "BOOST")) {
|
||||
this->set_preset(CLIMATE_PRESET_BOOST);
|
||||
} else if (str_equals_case_insensitive(preset, "COMFORT")) {
|
||||
this->set_preset(CLIMATE_PRESET_COMFORT);
|
||||
} else if (str_equals_case_insensitive(preset, "HOME")) {
|
||||
this->set_preset(CLIMATE_PRESET_HOME);
|
||||
} else if (str_equals_case_insensitive(preset, "SLEEP")) {
|
||||
this->set_preset(CLIMATE_PRESET_SLEEP);
|
||||
} else if (str_equals_case_insensitive(preset, "ACTIVITY")) {
|
||||
this->set_preset(CLIMATE_PRESET_ACTIVITY);
|
||||
} else {
|
||||
auto custom_presets = this->parent_->get_traits().get_supported_custom_presets();
|
||||
if (std::find(custom_presets.begin(), custom_presets.end(), preset) != custom_presets.end()) {
|
||||
this->custom_preset_ = preset;
|
||||
this->preset_.reset();
|
||||
} else {
|
||||
ESP_LOGW(TAG, "'%s' - Unrecognized preset %s", this->parent_->get_name().c_str(), preset.c_str());
|
||||
}
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
ClimateCall &ClimateCall::set_preset(optional<std::string> preset) {
|
||||
if (preset.has_value()) {
|
||||
this->set_preset(preset.value());
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
ClimateCall &ClimateCall::set_swing_mode(ClimateSwingMode swing_mode) {
|
||||
this->swing_mode_ = swing_mode;
|
||||
return *this;
|
||||
@@ -188,6 +271,9 @@ const optional<float> &ClimateCall::get_target_temperature_low() const { return
|
||||
const optional<float> &ClimateCall::get_target_temperature_high() const { return this->target_temperature_high_; }
|
||||
const optional<bool> &ClimateCall::get_away() const { return this->away_; }
|
||||
const optional<ClimateFanMode> &ClimateCall::get_fan_mode() const { return this->fan_mode_; }
|
||||
const optional<std::string> &ClimateCall::get_custom_fan_mode() const { return this->custom_fan_mode_; }
|
||||
const optional<ClimatePreset> &ClimateCall::get_preset() const { return this->preset_; }
|
||||
const optional<std::string> &ClimateCall::get_custom_preset() const { return this->custom_preset_; }
|
||||
const optional<ClimateSwingMode> &ClimateCall::get_swing_mode() const { return this->swing_mode_; }
|
||||
ClimateCall &ClimateCall::set_away(bool away) {
|
||||
this->away_ = away;
|
||||
@@ -215,6 +301,12 @@ ClimateCall &ClimateCall::set_mode(optional<ClimateMode> mode) {
|
||||
}
|
||||
ClimateCall &ClimateCall::set_fan_mode(optional<ClimateFanMode> fan_mode) {
|
||||
this->fan_mode_ = fan_mode;
|
||||
this->custom_fan_mode_.reset();
|
||||
return *this;
|
||||
}
|
||||
ClimateCall &ClimateCall::set_preset(optional<ClimatePreset> preset) {
|
||||
this->preset_ = preset;
|
||||
this->custom_preset_.reset();
|
||||
return *this;
|
||||
}
|
||||
ClimateCall &ClimateCall::set_swing_mode(optional<ClimateSwingMode> swing_mode) {
|
||||
@@ -249,8 +341,31 @@ void Climate::save_state_() {
|
||||
if (traits.get_supports_away()) {
|
||||
state.away = this->away;
|
||||
}
|
||||
if (traits.get_supports_fan_modes()) {
|
||||
state.fan_mode = this->fan_mode;
|
||||
if (traits.get_supports_fan_modes() && fan_mode.has_value()) {
|
||||
state.uses_custom_fan_mode = false;
|
||||
state.fan_mode = this->fan_mode.value();
|
||||
}
|
||||
if (!traits.get_supported_custom_fan_modes().empty() && custom_fan_mode.has_value()) {
|
||||
state.uses_custom_fan_mode = true;
|
||||
auto &custom_fan_modes = traits.get_supported_custom_fan_modes();
|
||||
auto it = std::find(custom_fan_modes.begin(), custom_fan_modes.end(), this->custom_fan_mode.value());
|
||||
// only set custom fan mode if value exists, otherwise leave it as is
|
||||
if (it != custom_fan_modes.cend()) {
|
||||
state.custom_fan_mode = std::distance(custom_fan_modes.begin(), it);
|
||||
}
|
||||
}
|
||||
if (traits.get_supports_presets() && preset.has_value()) {
|
||||
state.uses_custom_preset = false;
|
||||
state.preset = this->preset.value();
|
||||
}
|
||||
if (!traits.get_supported_custom_presets().empty() && custom_preset.has_value()) {
|
||||
state.uses_custom_preset = true;
|
||||
auto custom_presets = traits.get_supported_custom_presets();
|
||||
auto it = std::find(custom_presets.begin(), custom_presets.end(), this->custom_preset.value());
|
||||
// only set custom preset if value exists, otherwise leave it as is
|
||||
if (it != custom_presets.cend()) {
|
||||
state.custom_preset = std::distance(custom_presets.begin(), it);
|
||||
}
|
||||
}
|
||||
if (traits.get_supports_swing_modes()) {
|
||||
state.swing_mode = this->swing_mode;
|
||||
@@ -266,8 +381,17 @@ void Climate::publish_state() {
|
||||
if (traits.get_supports_action()) {
|
||||
ESP_LOGD(TAG, " Action: %s", climate_action_to_string(this->action));
|
||||
}
|
||||
if (traits.get_supports_fan_modes()) {
|
||||
ESP_LOGD(TAG, " Fan Mode: %s", climate_fan_mode_to_string(this->fan_mode));
|
||||
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()));
|
||||
}
|
||||
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()));
|
||||
}
|
||||
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));
|
||||
@@ -332,9 +456,12 @@ ClimateCall ClimateDeviceRestoreState::to_call(Climate *climate) {
|
||||
if (traits.get_supports_away()) {
|
||||
call.set_away(this->away);
|
||||
}
|
||||
if (traits.get_supports_fan_modes()) {
|
||||
if (traits.get_supports_fan_modes() || !traits.get_supported_custom_fan_modes().empty()) {
|
||||
call.set_fan_mode(this->fan_mode);
|
||||
}
|
||||
if (traits.get_supports_presets() || !traits.get_supported_custom_presets().empty()) {
|
||||
call.set_preset(this->preset);
|
||||
}
|
||||
if (traits.get_supports_swing_modes()) {
|
||||
call.set_swing_mode(this->swing_mode);
|
||||
}
|
||||
@@ -352,9 +479,21 @@ void ClimateDeviceRestoreState::apply(Climate *climate) {
|
||||
if (traits.get_supports_away()) {
|
||||
climate->away = this->away;
|
||||
}
|
||||
if (traits.get_supports_fan_modes()) {
|
||||
if (traits.get_supports_fan_modes() && !this->uses_custom_fan_mode) {
|
||||
climate->fan_mode = this->fan_mode;
|
||||
}
|
||||
if (!traits.get_supported_custom_fan_modes().empty() && this->uses_custom_fan_mode) {
|
||||
climate->custom_fan_mode = traits.get_supported_custom_fan_modes()[this->custom_fan_mode];
|
||||
}
|
||||
if (traits.get_supports_presets() && !this->uses_custom_preset) {
|
||||
climate->preset = this->preset;
|
||||
}
|
||||
if (!traits.get_supported_custom_presets().empty() && this->uses_custom_preset) {
|
||||
climate->custom_preset = traits.get_supported_custom_presets()[this->custom_preset];
|
||||
}
|
||||
if (!traits.get_supported_custom_presets().empty() && uses_custom_preset) {
|
||||
climate->custom_preset = traits.get_supported_custom_presets()[this->preset];
|
||||
}
|
||||
if (traits.get_supports_swing_modes()) {
|
||||
climate->swing_mode = this->swing_mode;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/preferences.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "climate_mode.h"
|
||||
#include "climate_traits.h"
|
||||
|
||||
@@ -70,12 +71,22 @@ class ClimateCall {
|
||||
ClimateCall &set_fan_mode(optional<ClimateFanMode> fan_mode);
|
||||
/// Set the fan mode of the climate device based on a string.
|
||||
ClimateCall &set_fan_mode(const std::string &fan_mode);
|
||||
/// Set the fan mode of the climate device based on a string.
|
||||
ClimateCall &set_fan_mode(optional<std::string> fan_mode);
|
||||
/// Set the swing mode of the climate device.
|
||||
ClimateCall &set_swing_mode(ClimateSwingMode swing_mode);
|
||||
/// Set the swing mode of the climate device.
|
||||
ClimateCall &set_swing_mode(optional<ClimateSwingMode> swing_mode);
|
||||
/// Set the swing mode of the climate device based on a string.
|
||||
ClimateCall &set_swing_mode(const std::string &swing_mode);
|
||||
/// Set the preset of the climate device.
|
||||
ClimateCall &set_preset(ClimatePreset preset);
|
||||
/// Set the preset of the climate device.
|
||||
ClimateCall &set_preset(optional<ClimatePreset> preset);
|
||||
/// Set the preset of the climate device based on a string.
|
||||
ClimateCall &set_preset(const std::string &preset);
|
||||
/// Set the preset of the climate device based on a string.
|
||||
ClimateCall &set_preset(optional<std::string> preset);
|
||||
|
||||
void perform();
|
||||
|
||||
@@ -86,6 +97,9 @@ class ClimateCall {
|
||||
const optional<bool> &get_away() const;
|
||||
const optional<ClimateFanMode> &get_fan_mode() const;
|
||||
const optional<ClimateSwingMode> &get_swing_mode() const;
|
||||
const optional<std::string> &get_custom_fan_mode() const;
|
||||
const optional<ClimatePreset> &get_preset() const;
|
||||
const optional<std::string> &get_custom_preset() const;
|
||||
|
||||
protected:
|
||||
void validate_();
|
||||
@@ -98,13 +112,25 @@ class ClimateCall {
|
||||
optional<bool> away_;
|
||||
optional<ClimateFanMode> fan_mode_;
|
||||
optional<ClimateSwingMode> swing_mode_;
|
||||
optional<std::string> custom_fan_mode_;
|
||||
optional<ClimatePreset> preset_;
|
||||
optional<std::string> custom_preset_;
|
||||
};
|
||||
|
||||
/// Struct used to save the state of the climate device in restore memory.
|
||||
struct ClimateDeviceRestoreState {
|
||||
ClimateMode mode;
|
||||
bool away;
|
||||
ClimateFanMode fan_mode;
|
||||
bool uses_custom_fan_mode{false};
|
||||
union {
|
||||
ClimateFanMode fan_mode;
|
||||
uint8_t custom_fan_mode;
|
||||
};
|
||||
bool uses_custom_preset{false};
|
||||
union {
|
||||
ClimatePreset preset;
|
||||
uint8_t custom_preset;
|
||||
};
|
||||
ClimateSwingMode swing_mode;
|
||||
union {
|
||||
float target_temperature;
|
||||
@@ -168,11 +194,20 @@ class Climate : public Nameable {
|
||||
bool away{false};
|
||||
|
||||
/// The active fan mode of the climate device.
|
||||
ClimateFanMode fan_mode;
|
||||
optional<ClimateFanMode> fan_mode;
|
||||
|
||||
/// The active swing mode of the climate device.
|
||||
ClimateSwingMode swing_mode;
|
||||
|
||||
/// The active custom fan mode of the climate device.
|
||||
optional<std::string> custom_fan_mode;
|
||||
|
||||
/// The active preset of the climate device.
|
||||
optional<ClimatePreset> preset;
|
||||
|
||||
/// The active custom preset mode of the climate device.
|
||||
optional<std::string> custom_preset;
|
||||
|
||||
/** Add a callback for the climate device state, each time the state of the climate device is updated
|
||||
* (using publish_state), this callback will be called.
|
||||
*
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user