mirror of
https://github.com/esphome/esphome.git
synced 2025-11-02 08:01:50 +00:00
Compare commits
332 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
59c192becc | ||
|
|
a800816750 | ||
|
|
99d9ab4e40 | ||
|
|
f310ca1b74 | ||
|
|
f763daa577 | ||
|
|
970563e07b | ||
|
|
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 | ||
|
|
b91a1aa027 | ||
|
|
13dbdd9b16 | ||
|
|
37bc0b3b5a | ||
|
|
be70a96651 | ||
|
|
d83d214497 | ||
|
|
6ec0f80b76 | ||
|
|
06f566346d | ||
|
|
b680649113 | ||
|
|
5ab2ef4079 | ||
|
|
392ed64375 | ||
|
|
566c129435 | ||
|
|
c903eb2d01 | ||
|
|
69c78651d5 | ||
|
|
f7232b199a | ||
|
|
ffc6fe9ca0 | ||
|
|
06b8e4df27 | ||
|
|
b103be99e8 | ||
|
|
28ed42d879 | ||
|
|
98f0d75180 | ||
|
|
34487c9de4 | ||
|
|
822377be8b | ||
|
|
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 | ||
|
|
0fe61d9ec7 | ||
|
|
6114d331c9 | ||
|
|
e2e074a3fd | ||
|
|
4d340dc029 | ||
|
|
fb6c5ebe9a | ||
|
|
af3273d930 | ||
|
|
8f1eb77e05 | ||
|
|
89d0d41c5a | ||
|
|
452ca8e4c6 | ||
|
|
e51b0ca15e | ||
|
|
5eeb110d74 | ||
|
|
60b2d57dc3 | ||
|
|
91898cb814 | ||
|
|
818a7f1c78 | ||
|
|
dedf343bd5 | ||
|
|
251240cc90 | ||
|
|
e5b45b6b4b | ||
|
|
a77784a6da | ||
|
|
f63f9168ff | ||
|
|
b5b2036971 | ||
|
|
a96b6e7002 | ||
|
|
f34c9b33fc | ||
|
|
faf577a9dd | ||
|
|
7708b81ef5 | ||
|
|
08998caabc | ||
|
|
848a5f1680 | ||
|
|
2e7c1d7345 | ||
|
|
28a72fa56b | ||
|
|
cd1353ae54 | ||
|
|
c9ee513fa8 | ||
|
|
2a12caa955 | ||
|
|
5e5f8d5f9b | ||
|
|
2c0fe49b86 | ||
|
|
1e227e8051 | ||
|
|
d5cf4b7eac | ||
|
|
570ec36fe3 | ||
|
|
69879920eb | ||
|
|
2b60b0f1fa | ||
|
|
c17624adab | ||
|
|
0f151a8f6b | ||
|
|
e62bf333a2 | ||
|
|
88b46b7487 | ||
|
|
fa290fbce8 | ||
|
|
1883ce1876 | ||
|
|
f3fe2bde38 | ||
|
|
811c13d7d1 | ||
|
|
521dfe08f2 | ||
|
|
2c77d121da | ||
|
|
c5dc736c53 | ||
|
|
8e93735861 | ||
|
|
ac25b138f5 | ||
|
|
b17e0c298e | ||
|
|
422f0ad7a9 | ||
|
|
34d37961c3 | ||
|
|
bdf004867d | ||
|
|
08ecca86bc | ||
|
|
520c4331e3 | ||
|
|
342d5166a0 | ||
|
|
69d39ef0cd | ||
|
|
6588e9380e | ||
|
|
4d6277330b | ||
|
|
87154e9b6f | ||
|
|
b91723344e | ||
|
|
92b36720b6 | ||
|
|
3d0310d0e0 | ||
|
|
f81cddf22e | ||
|
|
808ce6eecb | ||
|
|
665d0fd759 | ||
|
|
c519c02de8 | ||
|
|
eacac78099 | ||
|
|
a925036ff8 | ||
|
|
1468293f3e | ||
|
|
25924ca4e8 | ||
|
|
6c8ace0ce8 | ||
|
|
acc1af0f51 | ||
|
|
c92c439d17 | ||
|
|
410fad3b41 | ||
|
|
dce20680d7 | ||
|
|
f95be6a0df | ||
|
|
a342302114 | ||
|
|
925a992d1b | ||
|
|
61ecbe4273 | ||
|
|
65c7e27a43 | ||
|
|
57b56010da | ||
|
|
ef89249019 | ||
|
|
bc64cf3e47 | ||
|
|
def70dde72 | ||
|
|
b52f7cfe86 | ||
|
|
81b512a7b3 | ||
|
|
57d6185374 | ||
|
|
e288aa07fb | ||
|
|
50006e4c42 | ||
|
|
23cf120977 | ||
|
|
04d8593f38 | ||
|
|
28e39f7f76 | ||
|
|
de3377132d | ||
|
|
b351cd94d7 | ||
|
|
bccaa78a90 | ||
|
|
f402c89539 | ||
|
|
4eeb111fa3 | ||
|
|
1d378e416d | ||
|
|
d3b758d10a | ||
|
|
7b9c2d2978 | ||
|
|
9d38543cb0 | ||
|
|
36a2ce2c23 | ||
|
|
c7c71089ce | ||
|
|
52c67d715b | ||
|
|
f084ab339b | ||
|
|
8352f52fef | ||
|
|
b28735d63b | ||
|
|
4c105398f7 | ||
|
|
d9a2651a5a | ||
|
|
5fcd1e391d | ||
|
|
36089a1400 | ||
|
|
e7b1d2efaa | ||
|
|
bf453ad8cd | ||
|
|
fbc1b3e316 | ||
|
|
400819175d | ||
|
|
3c34b539b0 | ||
|
|
86385a1c19 | ||
|
|
96ab6b51b8 | ||
|
|
02dc49c272 | ||
|
|
5df398ec31 | ||
|
|
699696e8d1 | ||
|
|
93e35a53ed | ||
|
|
a269098a0e |
38
.clang-tidy
38
.clang-tidy
@@ -4,14 +4,35 @@ Checks: >-
|
|||||||
-abseil-*,
|
-abseil-*,
|
||||||
-android-*,
|
-android-*,
|
||||||
-boost-*,
|
-boost-*,
|
||||||
|
-bugprone-branch-clone,
|
||||||
-bugprone-macro-parentheses,
|
-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-dcl50-cpp,
|
||||||
|
-cert-dcl51-cpp,
|
||||||
-cert-err58-cpp,
|
-cert-err58-cpp,
|
||||||
|
-cert-oop54-cpp,
|
||||||
|
-cert-oop57-cpp,
|
||||||
|
-cert-str34-c,
|
||||||
-clang-analyzer-core.CallAndMessage,
|
-clang-analyzer-core.CallAndMessage,
|
||||||
|
-clang-analyzer-optin.*,
|
||||||
-clang-analyzer-osx.*,
|
-clang-analyzer-osx.*,
|
||||||
-clang-analyzer-security.*,
|
-clang-analyzer-security.*,
|
||||||
|
-clang-diagnostic-shadow-field,
|
||||||
|
-cppcoreguidelines-avoid-c-arrays,
|
||||||
-cppcoreguidelines-avoid-goto,
|
-cppcoreguidelines-avoid-goto,
|
||||||
|
-cppcoreguidelines-avoid-magic-numbers,
|
||||||
|
-cppcoreguidelines-avoid-non-const-global-variables,
|
||||||
-cppcoreguidelines-c-copy-assignment-signature,
|
-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-owning-memory,
|
||||||
-cppcoreguidelines-pro-bounds-array-to-pointer-decay,
|
-cppcoreguidelines-pro-bounds-array-to-pointer-decay,
|
||||||
-cppcoreguidelines-pro-bounds-constant-array-index,
|
-cppcoreguidelines-pro-bounds-constant-array-index,
|
||||||
@@ -37,10 +58,16 @@ Checks: >-
|
|||||||
-google-runtime-int,
|
-google-runtime-int,
|
||||||
-google-runtime-references,
|
-google-runtime-references,
|
||||||
-hicpp-*,
|
-hicpp-*,
|
||||||
|
-llvm-else-after-return,
|
||||||
-llvm-header-guard,
|
-llvm-header-guard,
|
||||||
-llvm-include-order,
|
-llvm-include-order,
|
||||||
|
-llvm-qualified-auto,
|
||||||
|
-llvmlibc-*,
|
||||||
|
-misc-non-private-member-variables-in-classes,
|
||||||
|
-misc-no-recursion,
|
||||||
-misc-unconventional-assign-operator,
|
-misc-unconventional-assign-operator,
|
||||||
-misc-unused-parameters,
|
-misc-unused-parameters,
|
||||||
|
-modernize-avoid-c-arrays,
|
||||||
-modernize-deprecated-headers,
|
-modernize-deprecated-headers,
|
||||||
-modernize-pass-by-value,
|
-modernize-pass-by-value,
|
||||||
-modernize-pass-by-value,
|
-modernize-pass-by-value,
|
||||||
@@ -48,14 +75,25 @@ Checks: >-
|
|||||||
-modernize-use-auto,
|
-modernize-use-auto,
|
||||||
-modernize-use-default-member-init,
|
-modernize-use-default-member-init,
|
||||||
-modernize-use-equals-default,
|
-modernize-use-equals-default,
|
||||||
|
-modernize-use-trailing-return-type,
|
||||||
-mpi-*,
|
-mpi-*,
|
||||||
-objc-*,
|
-objc-*,
|
||||||
-performance-unnecessary-value-param,
|
-performance-unnecessary-value-param,
|
||||||
-readability-braces-around-statements,
|
-readability-braces-around-statements,
|
||||||
|
-readability-const-return-type,
|
||||||
|
-readability-convert-member-functions-to-static,
|
||||||
-readability-else-after-return,
|
-readability-else-after-return,
|
||||||
-readability-implicit-bool-conversion,
|
-readability-implicit-bool-conversion,
|
||||||
|
-readability-isolate-declaration,
|
||||||
|
-readability-magic-numbers,
|
||||||
|
-readability-make-member-function-const,
|
||||||
-readability-named-parameter,
|
-readability-named-parameter,
|
||||||
|
-readability-qualified-auto,
|
||||||
|
-readability-redundant-access-specifiers,
|
||||||
-readability-redundant-member-init,
|
-readability-redundant-member-init,
|
||||||
|
-readability-redundant-string-init,
|
||||||
|
-readability-uppercase-literal-suffix,
|
||||||
|
-readability-use-anyofallof,
|
||||||
-warnings-as-errors,
|
-warnings-as-errors,
|
||||||
-zircon-*
|
-zircon-*
|
||||||
WarningsAsErrors: '*'
|
WarningsAsErrors: '*'
|
||||||
|
|||||||
7
.github/FUNDING.yml
vendored
7
.github/FUNDING.yml
vendored
@@ -1,8 +1,3 @@
|
|||||||
# These are supported funding model platforms
|
# These are supported funding model platforms
|
||||||
|
|
||||||
github:
|
custom: https://www.nabucasa.com
|
||||||
patreon: ottowinter
|
|
||||||
open_collective:
|
|
||||||
ko_fi:
|
|
||||||
tidelift:
|
|
||||||
custom: https://esphome.io/guides/supporters.html
|
|
||||||
|
|||||||
32
.github/PULL_REQUEST_TEMPLATE.md
vendored
32
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -1,13 +1,39 @@
|
|||||||
## Description:
|
# What does this implement/fix?
|
||||||
|
|
||||||
|
Quick description and explanation of changes
|
||||||
|
|
||||||
**Related issue (if applicable):** fixes <link to issue>
|
## 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)
|
||||||
|
- [ ] 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>
|
**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
|
||||||
|
|
||||||
|
- [ ] ESP32
|
||||||
|
- [ ] ESP8266
|
||||||
|
|
||||||
|
## Example entry for `config.yaml`:
|
||||||
|
<!--
|
||||||
|
Supplying a configuration snippet, makes it easier for a maintainer to test
|
||||||
|
your PR. Furthermore, for new integrations, it gives an impression of how
|
||||||
|
the configuration would look like.
|
||||||
|
Note: Remove this section if this PR does not have an example entry.
|
||||||
|
-->
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# Example config.yaml
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
## Checklist:
|
## Checklist:
|
||||||
- [ ] The code change is tested and works locally.
|
- [ ] The code change is tested and works locally.
|
||||||
- [ ] Tests have been added to verify that the new code works (under `tests/` folder).
|
- [ ] Tests have been added to verify that the new code works (under `tests/` folder).
|
||||||
|
|
||||||
If user exposed functionality or configuration variables are added/changed:
|
If user exposed functionality or configuration variables are added/changed:
|
||||||
- [ ] Documentation added/updated in [esphome-docs](https://github.com/esphome/esphome-docs).
|
- [ ] Documentation added/updated in [esphome-docs](https://github.com/esphome/esphome-docs).
|
||||||
|
|||||||
4
.github/workflows/ci-docker.yml
vendored
4
.github/workflows/ci-docker.yml
vendored
@@ -26,7 +26,7 @@ jobs:
|
|||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Set up env variables
|
- name: Set up env variables
|
||||||
run: |
|
run: |
|
||||||
base_version="2.6.0"
|
base_version="3.4.0"
|
||||||
|
|
||||||
if [[ "${{ matrix.build_type }}" == "hassio" ]]; then
|
if [[ "${{ matrix.build_type }}" == "hassio" ]]; then
|
||||||
build_from="esphome/esphome-hassio-base-${{ matrix.arch }}:${base_version}"
|
build_from="esphome/esphome-hassio-base-${{ matrix.arch }}:${base_version}"
|
||||||
@@ -45,7 +45,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
docker pull "${BUILD_TO}:dev" || true
|
docker pull "${BUILD_TO}:dev" || true
|
||||||
- name: Register QEMU binfmt
|
- name: Register QEMU binfmt
|
||||||
run: docker run --rm --privileged multiarch/qemu-user-static:5.0.0-2 --reset -p yes
|
run: docker run --rm --privileged multiarch/qemu-user-static:5.2.0-2 --reset -p yes
|
||||||
- run: |
|
- run: |
|
||||||
docker build \
|
docker build \
|
||||||
--build-arg "BUILD_FROM=${BUILD_FROM}" \
|
--build-arg "BUILD_FROM=${BUILD_FROM}" \
|
||||||
|
|||||||
27
.github/workflows/ci.yml
vendored
27
.github/workflows/ci.yml
vendored
@@ -15,18 +15,9 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
# cpp lint job runs with esphome-lint docker image so that clang-format-*
|
# cpp lint job runs with esphome-lint docker image so that clang-format-*
|
||||||
# doesn't have to be installed
|
# doesn't have to be installed
|
||||||
container: esphome/esphome-lint:latest
|
container: esphome/esphome-lint:1.1
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
# Cache platformio intermediary files (like libraries etc)
|
|
||||||
# Note: platformio platform versions should be cached via the esphome-lint image
|
|
||||||
- name: Cache Platformio
|
|
||||||
uses: actions/cache@v1
|
|
||||||
with:
|
|
||||||
path: .pio
|
|
||||||
key: lint-cpp-pio-${{ hashFiles('platformio.ini') }}
|
|
||||||
restore-keys: |
|
|
||||||
lint-cpp-pio-
|
|
||||||
# Set up the pio project so that the cpp checks know how files are compiled
|
# Set up the pio project so that the cpp checks know how files are compiled
|
||||||
# (build flags, libraries etc)
|
# (build flags, libraries etc)
|
||||||
- name: Set up platformio environment
|
- name: Set up platformio environment
|
||||||
@@ -41,7 +32,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
# cpp lint job runs with esphome-lint docker image so that clang-format-*
|
# cpp lint job runs with esphome-lint docker image so that clang-format-*
|
||||||
# doesn't have to be installed
|
# 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
|
# Split clang-tidy check into 4 jobs. Each one will check 1/4th of the .cpp files
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
@@ -49,15 +40,6 @@ jobs:
|
|||||||
split: [1, 2, 3, 4]
|
split: [1, 2, 3, 4]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
# Cache platformio intermediary files (like libraries etc)
|
|
||||||
# Note: platformio platform versions should be cached via the esphome-lint image
|
|
||||||
- name: Cache Platformio
|
|
||||||
uses: actions/cache@v1
|
|
||||||
with:
|
|
||||||
path: .pio
|
|
||||||
key: lint-cpp-pio-${{ hashFiles('platformio.ini') }}
|
|
||||||
restore-keys: |
|
|
||||||
lint-cpp-pio-
|
|
||||||
# Set up the pio project so that the cpp checks know how files are compiled
|
# Set up the pio project so that the cpp checks know how files are compiled
|
||||||
# (build flags, libraries etc)
|
# (build flags, libraries etc)
|
||||||
- name: Set up platformio environment
|
- name: Set up platformio environment
|
||||||
@@ -115,6 +97,7 @@ jobs:
|
|||||||
- test2
|
- test2
|
||||||
- test3
|
- test3
|
||||||
- test4
|
- test4
|
||||||
|
- test5
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
@@ -133,7 +116,7 @@ jobs:
|
|||||||
uses: actions/cache@v1
|
uses: actions/cache@v1
|
||||||
with:
|
with:
|
||||||
path: ~/.platformio
|
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: |
|
restore-keys: |
|
||||||
test-home-platformio-${{ matrix.test }}-
|
test-home-platformio-${{ matrix.test }}-
|
||||||
- name: Set up environment
|
- name: Set up environment
|
||||||
@@ -144,7 +127,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
echo "::add-matcher::.github/workflows/matchers/gcc.json"
|
echo "::add-matcher::.github/workflows/matchers/gcc.json"
|
||||||
echo "::add-matcher::.github/workflows/matchers/python.json"
|
echo "::add-matcher::.github/workflows/matchers/python.json"
|
||||||
- run: esphome tests/${{ matrix.test }}.yaml compile
|
- run: esphome compile tests/${{ matrix.test }}.yaml
|
||||||
|
|
||||||
pytest:
|
pytest:
|
||||||
runs-on: ubuntu-latest
|
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"
|
||||||
31
.github/workflows/release-dev.yml
vendored
31
.github/workflows/release-dev.yml
vendored
@@ -12,18 +12,9 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
# cpp lint job runs with esphome-lint docker image so that clang-format-*
|
# cpp lint job runs with esphome-lint docker image so that clang-format-*
|
||||||
# doesn't have to be installed
|
# doesn't have to be installed
|
||||||
container: esphome/esphome-lint:latest
|
container: esphome/esphome-lint:1.1
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
# Cache platformio intermediary files (like libraries etc)
|
|
||||||
# Note: platformio platform versions should be cached via the esphome-lint image
|
|
||||||
- name: Cache Platformio
|
|
||||||
uses: actions/cache@v1
|
|
||||||
with:
|
|
||||||
path: .pio
|
|
||||||
key: lint-cpp-pio-${{ hashFiles('platformio.ini') }}
|
|
||||||
restore-keys: |
|
|
||||||
lint-cpp-pio-
|
|
||||||
# Set up the pio project so that the cpp checks know how files are compiled
|
# Set up the pio project so that the cpp checks know how files are compiled
|
||||||
# (build flags, libraries etc)
|
# (build flags, libraries etc)
|
||||||
- name: Set up platformio environment
|
- name: Set up platformio environment
|
||||||
@@ -38,7 +29,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
# cpp lint job runs with esphome-lint docker image so that clang-format-*
|
# cpp lint job runs with esphome-lint docker image so that clang-format-*
|
||||||
# doesn't have to be installed
|
# 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
|
# Split clang-tidy check into 4 jobs. Each one will check 1/4th of the .cpp files
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
@@ -46,15 +37,6 @@ jobs:
|
|||||||
split: [1, 2, 3, 4]
|
split: [1, 2, 3, 4]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
# Cache platformio intermediary files (like libraries etc)
|
|
||||||
# Note: platformio platform versions should be cached via the esphome-lint image
|
|
||||||
- name: Cache Platformio
|
|
||||||
uses: actions/cache@v1
|
|
||||||
with:
|
|
||||||
path: .pio
|
|
||||||
key: lint-cpp-pio-${{ hashFiles('platformio.ini') }}
|
|
||||||
restore-keys: |
|
|
||||||
lint-cpp-pio-
|
|
||||||
# Set up the pio project so that the cpp checks know how files are compiled
|
# Set up the pio project so that the cpp checks know how files are compiled
|
||||||
# (build flags, libraries etc)
|
# (build flags, libraries etc)
|
||||||
- name: Set up platformio environment
|
- name: Set up platformio environment
|
||||||
@@ -112,6 +94,7 @@ jobs:
|
|||||||
- test2
|
- test2
|
||||||
- test3
|
- test3
|
||||||
- test4
|
- test4
|
||||||
|
- test5
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
@@ -130,7 +113,7 @@ jobs:
|
|||||||
uses: actions/cache@v1
|
uses: actions/cache@v1
|
||||||
with:
|
with:
|
||||||
path: ~/.platformio
|
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: |
|
restore-keys: |
|
||||||
test-home-platformio-${{ matrix.test }}-
|
test-home-platformio-${{ matrix.test }}-
|
||||||
- name: Set up environment
|
- name: Set up environment
|
||||||
@@ -141,7 +124,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
echo "::add-matcher::.github/workflows/matchers/gcc.json"
|
echo "::add-matcher::.github/workflows/matchers/gcc.json"
|
||||||
echo "::add-matcher::.github/workflows/matchers/python.json"
|
echo "::add-matcher::.github/workflows/matchers/python.json"
|
||||||
- run: esphome tests/${{ matrix.test }}.yaml compile
|
- run: esphome compile tests/${{ matrix.test }}.yaml
|
||||||
|
|
||||||
pytest:
|
pytest:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@@ -192,7 +175,7 @@ jobs:
|
|||||||
echo "TAG=${TAG}" >> $GITHUB_ENV
|
echo "TAG=${TAG}" >> $GITHUB_ENV
|
||||||
- name: Set up env variables
|
- name: Set up env variables
|
||||||
run: |
|
run: |
|
||||||
base_version="2.6.0"
|
base_version="3.4.0"
|
||||||
|
|
||||||
if [[ "${{ matrix.build_type }}" == "hassio" ]]; then
|
if [[ "${{ matrix.build_type }}" == "hassio" ]]; then
|
||||||
build_from="esphome/esphome-hassio-base-${{ matrix.arch }}:${base_version}"
|
build_from="esphome/esphome-hassio-base-${{ matrix.arch }}:${base_version}"
|
||||||
@@ -211,7 +194,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
docker pull "${BUILD_TO}:dev" || true
|
docker pull "${BUILD_TO}:dev" || true
|
||||||
- name: Register QEMU binfmt
|
- name: Register QEMU binfmt
|
||||||
run: docker run --rm --privileged multiarch/qemu-user-static:5.0.0-2 --reset -p yes
|
run: docker run --rm --privileged multiarch/qemu-user-static:5.2.0-2 --reset -p yes
|
||||||
- run: |
|
- run: |
|
||||||
docker build \
|
docker build \
|
||||||
--build-arg "BUILD_FROM=${BUILD_FROM}" \
|
--build-arg "BUILD_FROM=${BUILD_FROM}" \
|
||||||
|
|||||||
31
.github/workflows/release.yml
vendored
31
.github/workflows/release.yml
vendored
@@ -11,18 +11,9 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
# cpp lint job runs with esphome-lint docker image so that clang-format-*
|
# cpp lint job runs with esphome-lint docker image so that clang-format-*
|
||||||
# doesn't have to be installed
|
# doesn't have to be installed
|
||||||
container: esphome/esphome-lint:latest
|
container: esphome/esphome-lint:1.1
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
# Cache platformio intermediary files (like libraries etc)
|
|
||||||
# Note: platformio platform versions should be cached via the esphome-lint image
|
|
||||||
- name: Cache Platformio
|
|
||||||
uses: actions/cache@v1
|
|
||||||
with:
|
|
||||||
path: .pio
|
|
||||||
key: lint-cpp-pio-${{ hashFiles('platformio.ini') }}
|
|
||||||
restore-keys: |
|
|
||||||
lint-cpp-pio-
|
|
||||||
# Set up the pio project so that the cpp checks know how files are compiled
|
# Set up the pio project so that the cpp checks know how files are compiled
|
||||||
# (build flags, libraries etc)
|
# (build flags, libraries etc)
|
||||||
- name: Set up platformio environment
|
- name: Set up platformio environment
|
||||||
@@ -37,7 +28,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
# cpp lint job runs with esphome-lint docker image so that clang-format-*
|
# cpp lint job runs with esphome-lint docker image so that clang-format-*
|
||||||
# doesn't have to be installed
|
# 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
|
# Split clang-tidy check into 4 jobs. Each one will check 1/4th of the .cpp files
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
@@ -45,15 +36,6 @@ jobs:
|
|||||||
split: [1, 2, 3, 4]
|
split: [1, 2, 3, 4]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
# Cache platformio intermediary files (like libraries etc)
|
|
||||||
# Note: platformio platform versions should be cached via the esphome-lint image
|
|
||||||
- name: Cache Platformio
|
|
||||||
uses: actions/cache@v1
|
|
||||||
with:
|
|
||||||
path: .pio
|
|
||||||
key: lint-cpp-pio-${{ hashFiles('platformio.ini') }}
|
|
||||||
restore-keys: |
|
|
||||||
lint-cpp-pio-
|
|
||||||
# Set up the pio project so that the cpp checks know how files are compiled
|
# Set up the pio project so that the cpp checks know how files are compiled
|
||||||
# (build flags, libraries etc)
|
# (build flags, libraries etc)
|
||||||
- name: Set up platformio environment
|
- name: Set up platformio environment
|
||||||
@@ -111,6 +93,7 @@ jobs:
|
|||||||
- test2
|
- test2
|
||||||
- test3
|
- test3
|
||||||
- test4
|
- test4
|
||||||
|
- test5
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
@@ -129,7 +112,7 @@ jobs:
|
|||||||
uses: actions/cache@v1
|
uses: actions/cache@v1
|
||||||
with:
|
with:
|
||||||
path: ~/.platformio
|
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: |
|
restore-keys: |
|
||||||
test-home-platformio-${{ matrix.test }}-
|
test-home-platformio-${{ matrix.test }}-
|
||||||
- name: Set up environment
|
- name: Set up environment
|
||||||
@@ -139,7 +122,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
echo "::add-matcher::.github/workflows/matchers/gcc.json"
|
echo "::add-matcher::.github/workflows/matchers/gcc.json"
|
||||||
echo "::add-matcher::.github/workflows/matchers/python.json"
|
echo "::add-matcher::.github/workflows/matchers/python.json"
|
||||||
- run: esphome tests/${{ matrix.test }}.yaml compile
|
- run: esphome compile tests/${{ matrix.test }}.yaml
|
||||||
|
|
||||||
pytest:
|
pytest:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@@ -212,7 +195,7 @@ jobs:
|
|||||||
echo "TAG=${TAG}" >> $GITHUB_ENV
|
echo "TAG=${TAG}" >> $GITHUB_ENV
|
||||||
- name: Set up env variables
|
- name: Set up env variables
|
||||||
run: |
|
run: |
|
||||||
base_version="2.6.0"
|
base_version="3.4.0"
|
||||||
|
|
||||||
if [[ "${{ matrix.build_type }}" == "hassio" ]]; then
|
if [[ "${{ matrix.build_type }}" == "hassio" ]]; then
|
||||||
build_from="esphome/esphome-hassio-base-${{ matrix.arch }}:${base_version}"
|
build_from="esphome/esphome-hassio-base-${{ matrix.arch }}:${base_version}"
|
||||||
@@ -239,7 +222,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
docker pull "${BUILD_TO}:${CACHE_TAG}" || true
|
docker pull "${BUILD_TO}:${CACHE_TAG}" || true
|
||||||
- name: Register QEMU binfmt
|
- name: Register QEMU binfmt
|
||||||
run: docker run --rm --privileged multiarch/qemu-user-static:5.0.0-2 --reset -p yes
|
run: docker run --rm --privileged multiarch/qemu-user-static:5.2.0-2 --reset -p yes
|
||||||
- run: |
|
- run: |
|
||||||
docker build \
|
docker build \
|
||||||
--build-arg "BUILD_FROM=${BUILD_FROM}" \
|
--build-arg "BUILD_FROM=${BUILD_FROM}" \
|
||||||
|
|||||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -100,6 +100,8 @@ CMakeLists.txt
|
|||||||
|
|
||||||
# CMake
|
# CMake
|
||||||
cmake-build-debug/
|
cmake-build-debug/
|
||||||
|
cmake-build-livingroom8266/
|
||||||
|
cmake-build-livingroom32/
|
||||||
cmake-build-release/
|
cmake-build-release/
|
||||||
|
|
||||||
CMakeCache.txt
|
CMakeCache.txt
|
||||||
|
|||||||
@@ -1,11 +1,27 @@
|
|||||||
# See https://pre-commit.com for more information
|
# See https://pre-commit.com for more information
|
||||||
# See https://pre-commit.com/hooks.html for more hooks
|
# See https://pre-commit.com/hooks.html for more hooks
|
||||||
repos:
|
repos:
|
||||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
- repo: https://github.com/ambv/black
|
||||||
rev: v2.4.0
|
rev: 20.8b1
|
||||||
hooks:
|
hooks:
|
||||||
- id: trailing-whitespace
|
- id: black
|
||||||
- id: end-of-file-fixer
|
args:
|
||||||
- id: check-yaml
|
- --safe
|
||||||
- id: check-added-large-files
|
- --quiet
|
||||||
- id: flake8
|
files: ^((esphome|script|tests)/.+)?[^/]+\.py$
|
||||||
|
- repo: https://gitlab.com/pycqa/flake8
|
||||||
|
rev: 3.8.4
|
||||||
|
hooks:
|
||||||
|
- id: flake8
|
||||||
|
additional_dependencies:
|
||||||
|
- flake8-docstrings==1.5.0
|
||||||
|
- pydocstyle==5.1.1
|
||||||
|
files: ^(esphome|tests)/.+\.py$
|
||||||
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||||
|
rev: v3.4.0
|
||||||
|
hooks:
|
||||||
|
- id: no-commit-to-branch
|
||||||
|
args:
|
||||||
|
- --branch=dev
|
||||||
|
- --branch=master
|
||||||
|
- --branch=beta
|
||||||
|
|||||||
2
.vscode/tasks.json
vendored
2
.vscode/tasks.json
vendored
@@ -4,7 +4,7 @@
|
|||||||
{
|
{
|
||||||
"label": "run",
|
"label": "run",
|
||||||
"type": "shell",
|
"type": "shell",
|
||||||
"command": "python3 -m esphome config dashboard",
|
"command": "python3 -m esphome dashboard config",
|
||||||
"problemMatcher": []
|
"problemMatcher": []
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
33
CODEOWNERS
33
CODEOWNERS
@@ -13,30 +13,42 @@ esphome/core/* @esphome/core
|
|||||||
# Integrations
|
# Integrations
|
||||||
esphome/components/ac_dimmer/* @glmnet
|
esphome/components/ac_dimmer/* @glmnet
|
||||||
esphome/components/adc/* @esphome/core
|
esphome/components/adc/* @esphome/core
|
||||||
|
esphome/components/addressable_light/* @justfalter
|
||||||
esphome/components/animation/* @syndlex
|
esphome/components/animation/* @syndlex
|
||||||
esphome/components/api/* @OttoWinter
|
esphome/components/api/* @OttoWinter
|
||||||
esphome/components/async_tcp/* @OttoWinter
|
esphome/components/async_tcp/* @OttoWinter
|
||||||
esphome/components/atc_mithermometer/* @ahpohl
|
esphome/components/atc_mithermometer/* @ahpohl
|
||||||
|
esphome/components/b_parasite/* @rbaron
|
||||||
esphome/components/bang_bang/* @OttoWinter
|
esphome/components/bang_bang/* @OttoWinter
|
||||||
esphome/components/binary_sensor/* @esphome/core
|
esphome/components/binary_sensor/* @esphome/core
|
||||||
|
esphome/components/ble_client/* @buxtronix
|
||||||
|
esphome/components/bme680_bsec/* @trvrnrth
|
||||||
esphome/components/canbus/* @danielschramm @mvturnho
|
esphome/components/canbus/* @danielschramm @mvturnho
|
||||||
esphome/components/captive_portal/* @OttoWinter
|
esphome/components/captive_portal/* @OttoWinter
|
||||||
esphome/components/climate/* @esphome/core
|
esphome/components/climate/* @esphome/core
|
||||||
esphome/components/climate_ir/* @glmnet
|
esphome/components/climate_ir/* @glmnet
|
||||||
esphome/components/coolix/* @glmnet
|
esphome/components/coolix/* @glmnet
|
||||||
esphome/components/cover/* @esphome/core
|
esphome/components/cover/* @esphome/core
|
||||||
|
esphome/components/cs5460a/* @balrog-kun
|
||||||
esphome/components/ct_clamp/* @jesserockz
|
esphome/components/ct_clamp/* @jesserockz
|
||||||
esphome/components/debug/* @OttoWinter
|
esphome/components/debug/* @OttoWinter
|
||||||
esphome/components/dfplayer/* @glmnet
|
esphome/components/dfplayer/* @glmnet
|
||||||
esphome/components/dht/* @OttoWinter
|
esphome/components/dht/* @OttoWinter
|
||||||
esphome/components/ds1307/* @badbadc0ffee
|
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/exposure_notifications/* @OttoWinter
|
||||||
esphome/components/ezo/* @ssieb
|
esphome/components/ezo/* @ssieb
|
||||||
esphome/components/fastled_base/* @OttoWinter
|
esphome/components/fastled_base/* @OttoWinter
|
||||||
|
esphome/components/fingerprint_grow/* @OnFreund @loongyh
|
||||||
esphome/components/globals/* @esphome/core
|
esphome/components/globals/* @esphome/core
|
||||||
esphome/components/gpio/* @esphome/core
|
esphome/components/gpio/* @esphome/core
|
||||||
|
esphome/components/gps/* @coogle
|
||||||
esphome/components/homeassistant/* @OttoWinter
|
esphome/components/homeassistant/* @OttoWinter
|
||||||
esphome/components/i2c/* @esphome/core
|
esphome/components/i2c/* @esphome/core
|
||||||
|
esphome/components/improv/* @jesserockz
|
||||||
|
esphome/components/inkbird_ibsth1_mini/* @fkirill
|
||||||
esphome/components/inkplate6/* @jesserockz
|
esphome/components/inkplate6/* @jesserockz
|
||||||
esphome/components/integration/* @OttoWinter
|
esphome/components/integration/* @OttoWinter
|
||||||
esphome/components/interval/* @esphome/core
|
esphome/components/interval/* @esphome/core
|
||||||
@@ -44,10 +56,19 @@ esphome/components/json/* @OttoWinter
|
|||||||
esphome/components/ledc/* @OttoWinter
|
esphome/components/ledc/* @OttoWinter
|
||||||
esphome/components/light/* @esphome/core
|
esphome/components/light/* @esphome/core
|
||||||
esphome/components/logger/* @esphome/core
|
esphome/components/logger/* @esphome/core
|
||||||
esphome/components/mcp23s08/* @SenexCrenshaw
|
esphome/components/max7219digit/* @rspaargaren
|
||||||
esphome/components/mcp23s17/* @SenexCrenshaw
|
esphome/components/mcp23008/* @jesserockz
|
||||||
|
esphome/components/mcp23017/* @jesserockz
|
||||||
|
esphome/components/mcp23s08/* @SenexCrenshaw @jesserockz
|
||||||
|
esphome/components/mcp23s17/* @SenexCrenshaw @jesserockz
|
||||||
|
esphome/components/mcp23x08_base/* @jesserockz
|
||||||
|
esphome/components/mcp23x17_base/* @jesserockz
|
||||||
|
esphome/components/mcp23xxx_base/* @jesserockz
|
||||||
esphome/components/mcp2515/* @danielschramm @mvturnho
|
esphome/components/mcp2515/* @danielschramm @mvturnho
|
||||||
esphome/components/mcp9808/* @k7hpn
|
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/network/* @esphome/core
|
||||||
esphome/components/nfc/* @jesserockz
|
esphome/components/nfc/* @jesserockz
|
||||||
esphome/components/ota/* @esphome/core
|
esphome/components/ota/* @esphome/core
|
||||||
@@ -57,6 +78,7 @@ esphome/components/pn532/* @OttoWinter @jesserockz
|
|||||||
esphome/components/pn532_i2c/* @OttoWinter @jesserockz
|
esphome/components/pn532_i2c/* @OttoWinter @jesserockz
|
||||||
esphome/components/pn532_spi/* @OttoWinter @jesserockz
|
esphome/components/pn532_spi/* @OttoWinter @jesserockz
|
||||||
esphome/components/power_supply/* @esphome/core
|
esphome/components/power_supply/* @esphome/core
|
||||||
|
esphome/components/pulse_meter/* @stevebaxter
|
||||||
esphome/components/rc522/* @glmnet
|
esphome/components/rc522/* @glmnet
|
||||||
esphome/components/rc522_i2c/* @glmnet
|
esphome/components/rc522_i2c/* @glmnet
|
||||||
esphome/components/rc522_spi/* @glmnet
|
esphome/components/rc522_spi/* @glmnet
|
||||||
@@ -64,9 +86,13 @@ esphome/components/restart/* @esphome/core
|
|||||||
esphome/components/rf_bridge/* @jesserockz
|
esphome/components/rf_bridge/* @jesserockz
|
||||||
esphome/components/rtttl/* @glmnet
|
esphome/components/rtttl/* @glmnet
|
||||||
esphome/components/script/* @esphome/core
|
esphome/components/script/* @esphome/core
|
||||||
|
esphome/components/sdm_meter/* @jesserockz @polyfaces
|
||||||
esphome/components/sensor/* @esphome/core
|
esphome/components/sensor/* @esphome/core
|
||||||
|
esphome/components/sgp40/* @SenexCrenshaw
|
||||||
|
esphome/components/sht4x/* @sjtrny
|
||||||
esphome/components/shutdown/* @esphome/core
|
esphome/components/shutdown/* @esphome/core
|
||||||
esphome/components/sim800l/* @glmnet
|
esphome/components/sim800l/* @glmnet
|
||||||
|
esphome/components/sm2135/* @BoukeHaarsma23
|
||||||
esphome/components/spi/* @esphome/core
|
esphome/components/spi/* @esphome/core
|
||||||
esphome/components/ssd1322_base/* @kbx81
|
esphome/components/ssd1322_base/* @kbx81
|
||||||
esphome/components/ssd1322_spi/* @kbx81
|
esphome/components/ssd1322_spi/* @kbx81
|
||||||
@@ -84,12 +110,14 @@ esphome/components/st7789v/* @kbx81
|
|||||||
esphome/components/substitutions/* @esphome/core
|
esphome/components/substitutions/* @esphome/core
|
||||||
esphome/components/sun/* @OttoWinter
|
esphome/components/sun/* @OttoWinter
|
||||||
esphome/components/switch/* @esphome/core
|
esphome/components/switch/* @esphome/core
|
||||||
|
esphome/components/tca9548a/* @andreashergert1984
|
||||||
esphome/components/tcl112/* @glmnet
|
esphome/components/tcl112/* @glmnet
|
||||||
esphome/components/teleinfo/* @0hax
|
esphome/components/teleinfo/* @0hax
|
||||||
esphome/components/thermostat/* @kbx81
|
esphome/components/thermostat/* @kbx81
|
||||||
esphome/components/time/* @OttoWinter
|
esphome/components/time/* @OttoWinter
|
||||||
esphome/components/tm1637/* @glmnet
|
esphome/components/tm1637/* @glmnet
|
||||||
esphome/components/tmp102/* @timsavage
|
esphome/components/tmp102/* @timsavage
|
||||||
|
esphome/components/tof10120/* @wstrzalka
|
||||||
esphome/components/tuya/binary_sensor/* @jesserockz
|
esphome/components/tuya/binary_sensor/* @jesserockz
|
||||||
esphome/components/tuya/climate/* @jesserockz
|
esphome/components/tuya/climate/* @jesserockz
|
||||||
esphome/components/tuya/sensor/* @jesserockz
|
esphome/components/tuya/sensor/* @jesserockz
|
||||||
@@ -101,3 +129,4 @@ esphome/components/web_server_base/* @OttoWinter
|
|||||||
esphome/components/whirlpool/* @glmnet
|
esphome/components/whirlpool/* @glmnet
|
||||||
esphome/components/xiaomi_lywsd03mmc/* @ahpohl
|
esphome/components/xiaomi_lywsd03mmc/* @ahpohl
|
||||||
esphome/components/xiaomi_mhoc401/* @vevsvevs
|
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:
|
Examples of behavior that contributes to creating a positive environment include:
|
||||||
|
|
||||||
* Using welcoming and inclusive language
|
- Using welcoming and inclusive language
|
||||||
* Being respectful of differing viewpoints and experiences
|
- Being respectful of differing viewpoints and experiences
|
||||||
* Gracefully accepting constructive criticism
|
- Gracefully accepting constructive criticism
|
||||||
* Focusing on what is best for the community
|
- Focusing on what is best for the community
|
||||||
* Showing empathy towards other community members
|
- Showing empathy towards other community members
|
||||||
|
|
||||||
Examples of unacceptable behavior by participants include:
|
Examples of unacceptable behavior by participants include:
|
||||||
|
|
||||||
* The use of sexualized language or imagery and unwelcome sexual attention or advances
|
- The use of sexualized language or imagery and unwelcome sexual attention or advances
|
||||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
- Trolling, insulting/derogatory comments, and personal or political attacks
|
||||||
* Public or private harassment
|
- Public or private harassment
|
||||||
* Publishing others' private information, such as a physical or electronic address, without explicit permission
|
- 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
|
- Other conduct which could reasonably be considered inappropriate in a professional setting
|
||||||
|
|
||||||
## Our Responsibilities
|
## Our Responsibilities
|
||||||
|
|
||||||
@@ -34,7 +34,7 @@ This Code of Conduct applies both within project spaces and in public spaces whe
|
|||||||
|
|
||||||
## Enforcement
|
## 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.
|
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,9 +1,11 @@
|
|||||||
ARG BUILD_FROM=esphome/esphome-base-amd64:2.6.0
|
ARG BUILD_FROM=esphome/esphome-base-amd64:3.4.0
|
||||||
FROM ${BUILD_FROM}
|
FROM ${BUILD_FROM}
|
||||||
|
|
||||||
# First install requirements to leverage caching when requirements don't change
|
# First install requirements to leverage caching when requirements don't change
|
||||||
COPY requirements.txt /
|
COPY requirements.txt requirements_optional.txt docker/platformio_install_deps.py platformio.ini /
|
||||||
RUN pip3 install --no-cache-dir -r /requirements.txt
|
RUN \
|
||||||
|
pip3 install --no-cache-dir -r /requirements.txt -r /requirements_optional.txt \
|
||||||
|
&& /platformio_install_deps.py /platformio.ini
|
||||||
|
|
||||||
# Then copy esphome and install
|
# Then copy esphome and install
|
||||||
COPY . .
|
COPY . .
|
||||||
@@ -16,7 +18,7 @@ ENV USERNAME="" PASSWORD=""
|
|||||||
EXPOSE 6052
|
EXPOSE 6052
|
||||||
|
|
||||||
# Run healthcheck (heartbeat)
|
# Run healthcheck (heartbeat)
|
||||||
HEALTHCHECK --interval=5m --timeout=3s \
|
HEALTHCHECK --interval=30s --timeout=30s \
|
||||||
CMD curl --fail http://localhost:6052 || exit 1
|
CMD curl --fail http://localhost:6052 || exit 1
|
||||||
|
|
||||||
# The directory the user should mount their configuration files to
|
# The directory the user should mount their configuration files to
|
||||||
@@ -25,4 +27,4 @@ WORKDIR /config
|
|||||||
# in every docker command twice
|
# in every docker command twice
|
||||||
ENTRYPOINT ["esphome"]
|
ENTRYPOINT ["esphome"]
|
||||||
# When no arguments given, start the dashboard in the workdir
|
# 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:2.6.0
|
FROM esphome/esphome-base-amd64:3.4.0
|
||||||
|
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,10 @@ ARG BUILD_FROM
|
|||||||
FROM ${BUILD_FROM}
|
FROM ${BUILD_FROM}
|
||||||
|
|
||||||
# First install requirements to leverage caching when requirements don't change
|
# First install requirements to leverage caching when requirements don't change
|
||||||
COPY requirements.txt /
|
COPY requirements.txt requirements_optional.txt docker/platformio_install_deps.py platformio.ini /
|
||||||
RUN pip3 install --no-cache-dir -r /requirements.txt
|
RUN \
|
||||||
|
pip3 install --no-cache-dir -r /requirements.txt -r /requirements_optional.txt \
|
||||||
|
&& /platformio_install_deps.py /platformio.ini
|
||||||
|
|
||||||
# Copy root filesystem
|
# Copy root filesystem
|
||||||
COPY docker/rootfs/ /
|
COPY docker/rootfs/ /
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
FROM esphome/esphome-lint-base:2.6.0
|
FROM esphome/esphome-lint-base:3.4.0
|
||||||
|
|
||||||
COPY requirements.txt requirements_test.txt /
|
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
|
RUN \
|
||||||
|
pip3 install --no-cache-dir -r /requirements.txt -r /requirements_optional.txt -r /requirements_test.txt \
|
||||||
|
&& /platformio_install_deps.py /platformio.ini
|
||||||
|
|
||||||
VOLUME ["/esphome"]
|
VOLUME ["/esphome"]
|
||||||
WORKDIR /esphome
|
WORKDIR /esphome
|
||||||
|
|||||||
20
docker/platformio_install_deps.py
Executable file
20
docker/platformio_install_deps.py
Executable file
@@ -0,0 +1,20 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# This script is used in the docker containers to preinstall
|
||||||
|
# all platformio libraries in the global storage
|
||||||
|
|
||||||
|
import configparser
|
||||||
|
import re
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
|
||||||
|
config = configparser.ConfigParser()
|
||||||
|
config.read(sys.argv[1])
|
||||||
|
libs = []
|
||||||
|
for line in config['common']['lib_deps'].splitlines():
|
||||||
|
# Format: '1655@1.0.2 ; TinyGPSPlus (has name conflict)' (includes comment)
|
||||||
|
m = re.search(r'([a-zA-Z0-9-_/]+@[0-9\.]+)', line)
|
||||||
|
if m is None:
|
||||||
|
continue
|
||||||
|
libs.append(m.group(1))
|
||||||
|
|
||||||
|
subprocess.check_call(['platformio', 'lib', '-g', 'install', *libs])
|
||||||
@@ -23,4 +23,4 @@ if bashio::config.has_value 'relative_url'; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
bashio::log.info "Starting ESPHome dashboard..."
|
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
|
||||||
|
|||||||
@@ -8,21 +8,37 @@ from datetime import datetime
|
|||||||
from esphome import const, writer, yaml_util
|
from esphome import const, writer, yaml_util
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
from esphome.config import iter_components, read_config, strip_default_ids
|
from esphome.config import iter_components, read_config, strip_default_ids
|
||||||
from esphome.const import CONF_BAUD_RATE, CONF_BROKER, CONF_LOGGER, CONF_OTA, \
|
from esphome.const import (
|
||||||
CONF_PASSWORD, CONF_PORT, CONF_ESPHOME, CONF_PLATFORMIO_OPTIONS
|
CONF_BAUD_RATE,
|
||||||
from esphome.core import CORE, EsphomeError, coroutine, coroutine_with_priority
|
CONF_BROKER,
|
||||||
from esphome.helpers import color, indent
|
CONF_LOGGER,
|
||||||
from esphome.util import run_external_command, run_external_process, safe_print, list_yaml_files, \
|
CONF_OTA,
|
||||||
get_serial_ports
|
CONF_PASSWORD,
|
||||||
|
CONF_PORT,
|
||||||
|
CONF_ESPHOME,
|
||||||
|
CONF_PLATFORMIO_OPTIONS,
|
||||||
|
)
|
||||||
|
from esphome.core import CORE, EsphomeError, coroutine
|
||||||
|
from esphome.helpers import indent
|
||||||
|
from esphome.util import (
|
||||||
|
run_external_command,
|
||||||
|
run_external_process,
|
||||||
|
safe_print,
|
||||||
|
list_yaml_files,
|
||||||
|
get_serial_ports,
|
||||||
|
)
|
||||||
|
from esphome.log import color, setup_log, Fore
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def choose_prompt(options):
|
def choose_prompt(options):
|
||||||
if not options:
|
if not options:
|
||||||
raise EsphomeError("Found no valid options for upload/logging, please make sure relevant "
|
raise EsphomeError(
|
||||||
"sections (ota, api, mqtt, ...) are in your configuration and/or the "
|
"Found no valid options for upload/logging, please make sure relevant "
|
||||||
"device is plugged in.")
|
"sections (ota, api, mqtt, ...) are in your configuration and/or the "
|
||||||
|
"device is plugged in."
|
||||||
|
)
|
||||||
|
|
||||||
if len(options) == 1:
|
if len(options) == 1:
|
||||||
return options[0][1]
|
return options[0][1]
|
||||||
@@ -32,7 +48,7 @@ def choose_prompt(options):
|
|||||||
safe_print(f" [{i+1}] {desc}")
|
safe_print(f" [{i+1}] {desc}")
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
opt = input('(number): ')
|
opt = input("(number): ")
|
||||||
if opt in options:
|
if opt in options:
|
||||||
opt = options.index(opt)
|
opt = options.index(opt)
|
||||||
break
|
break
|
||||||
@@ -42,7 +58,7 @@ def choose_prompt(options):
|
|||||||
raise ValueError
|
raise ValueError
|
||||||
break
|
break
|
||||||
except ValueError:
|
except ValueError:
|
||||||
safe_print(color('red', f"Invalid option: '{opt}'"))
|
safe_print(color(Fore.RED, f"Invalid option: '{opt}'"))
|
||||||
return options[opt - 1][1]
|
return options[opt - 1][1]
|
||||||
|
|
||||||
|
|
||||||
@@ -50,14 +66,14 @@ def choose_upload_log_host(default, check_default, show_ota, show_mqtt, show_api
|
|||||||
options = []
|
options = []
|
||||||
for port in get_serial_ports():
|
for port in get_serial_ports():
|
||||||
options.append((f"{port.path} ({port.description})", port.path))
|
options.append((f"{port.path} ({port.description})", port.path))
|
||||||
if (show_ota and 'ota' in CORE.config) or (show_api and 'api' in CORE.config):
|
if (show_ota and "ota" in CORE.config) or (show_api and "api" in CORE.config):
|
||||||
options.append((f"Over The Air ({CORE.address})", CORE.address))
|
options.append((f"Over The Air ({CORE.address})", CORE.address))
|
||||||
if default == 'OTA':
|
if default == "OTA":
|
||||||
return CORE.address
|
return CORE.address
|
||||||
if show_mqtt and 'mqtt' in CORE.config:
|
if show_mqtt and "mqtt" in CORE.config:
|
||||||
options.append(("MQTT ({})".format(CORE.config['mqtt'][CONF_BROKER]), 'MQTT'))
|
options.append(("MQTT ({})".format(CORE.config["mqtt"][CONF_BROKER]), "MQTT"))
|
||||||
if default == 'OTA':
|
if default == "OTA":
|
||||||
return 'MQTT'
|
return "MQTT"
|
||||||
if default is not None:
|
if default is not None:
|
||||||
return default
|
return default
|
||||||
if check_default is not None and check_default in [opt[1] for opt in options]:
|
if check_default is not None and check_default in [opt[1] for opt in options]:
|
||||||
@@ -66,11 +82,11 @@ def choose_upload_log_host(default, check_default, show_ota, show_mqtt, show_api
|
|||||||
|
|
||||||
|
|
||||||
def get_port_type(port):
|
def get_port_type(port):
|
||||||
if port.startswith('/') or port.startswith('COM'):
|
if port.startswith("/") or port.startswith("COM"):
|
||||||
return 'SERIAL'
|
return "SERIAL"
|
||||||
if port == 'MQTT':
|
if port == "MQTT":
|
||||||
return 'MQTT'
|
return "MQTT"
|
||||||
return 'NETWORK'
|
return "NETWORK"
|
||||||
|
|
||||||
|
|
||||||
def run_miniterm(config, port):
|
def run_miniterm(config, port):
|
||||||
@@ -80,7 +96,7 @@ def run_miniterm(config, port):
|
|||||||
if CONF_LOGGER not in config:
|
if CONF_LOGGER not in config:
|
||||||
_LOGGER.info("Logger is not enabled. Not starting UART logs.")
|
_LOGGER.info("Logger is not enabled. Not starting UART logs.")
|
||||||
return
|
return
|
||||||
baud_rate = config['logger'][CONF_BAUD_RATE]
|
baud_rate = config["logger"][CONF_BAUD_RATE]
|
||||||
if baud_rate == 0:
|
if baud_rate == 0:
|
||||||
_LOGGER.info("UART logging is disabled (baud_rate=0). Not starting UART logs.")
|
_LOGGER.info("UART logging is disabled (baud_rate=0). Not starting UART logs.")
|
||||||
_LOGGER.info("Starting log output from %s with baud rate %s", port, baud_rate)
|
_LOGGER.info("Starting log output from %s with baud rate %s", port, baud_rate)
|
||||||
@@ -93,28 +109,34 @@ def run_miniterm(config, port):
|
|||||||
except serial.SerialException:
|
except serial.SerialException:
|
||||||
_LOGGER.error("Serial port closed!")
|
_LOGGER.error("Serial port closed!")
|
||||||
return
|
return
|
||||||
line = raw.replace(b'\r', b'').replace(b'\n', b'').decode('utf8', 'backslashreplace')
|
line = (
|
||||||
time = datetime.now().time().strftime('[%H:%M:%S]')
|
raw.replace(b"\r", b"")
|
||||||
|
.replace(b"\n", b"")
|
||||||
|
.decode("utf8", "backslashreplace")
|
||||||
|
)
|
||||||
|
time = datetime.now().time().strftime("[%H:%M:%S]")
|
||||||
message = time + line
|
message = time + line
|
||||||
safe_print(message)
|
safe_print(message)
|
||||||
|
|
||||||
backtrace_state = platformio_api.process_stacktrace(
|
backtrace_state = platformio_api.process_stacktrace(
|
||||||
config, line, backtrace_state=backtrace_state)
|
config, line, backtrace_state=backtrace_state
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def wrap_to_code(name, comp):
|
def wrap_to_code(name, comp):
|
||||||
coro = coroutine(comp.to_code)
|
coro = coroutine(comp.to_code)
|
||||||
|
|
||||||
@functools.wraps(comp.to_code)
|
@functools.wraps(comp.to_code)
|
||||||
@coroutine_with_priority(coro.priority)
|
async def wrapped(conf):
|
||||||
def wrapped(conf):
|
|
||||||
cg.add(cg.LineComment(f"{name}:"))
|
cg.add(cg.LineComment(f"{name}:"))
|
||||||
if comp.config_schema is not None:
|
if comp.config_schema is not None:
|
||||||
conf_str = yaml_util.dump(conf)
|
conf_str = yaml_util.dump(conf)
|
||||||
conf_str = conf_str.replace('//', '')
|
conf_str = conf_str.replace("//", "")
|
||||||
cg.add(cg.LineComment(indent(conf_str)))
|
cg.add(cg.LineComment(indent(conf_str)))
|
||||||
yield coro(conf)
|
await coro(conf)
|
||||||
|
|
||||||
|
if hasattr(coro, "priority"):
|
||||||
|
wrapped.priority = coro.priority
|
||||||
return wrapped
|
return wrapped
|
||||||
|
|
||||||
|
|
||||||
@@ -151,15 +173,31 @@ def compile_program(args, config):
|
|||||||
|
|
||||||
def upload_using_esptool(config, port):
|
def upload_using_esptool(config, port):
|
||||||
path = CORE.firmware_bin
|
path = CORE.firmware_bin
|
||||||
first_baudrate = config[CONF_ESPHOME][CONF_PLATFORMIO_OPTIONS].get('upload_speed', 460800)
|
first_baudrate = config[CONF_ESPHOME][CONF_PLATFORMIO_OPTIONS].get(
|
||||||
|
"upload_speed", 460800
|
||||||
|
)
|
||||||
|
|
||||||
def run_esptool(baud_rate):
|
def run_esptool(baud_rate):
|
||||||
cmd = ['esptool.py', '--before', 'default_reset', '--after', 'hard_reset',
|
cmd = [
|
||||||
'--baud', str(baud_rate),
|
"esptool.py",
|
||||||
'--chip', 'esp8266', '--port', port, 'write_flash', '0x0', path]
|
"--before",
|
||||||
|
"default_reset",
|
||||||
|
"--after",
|
||||||
|
"hard_reset",
|
||||||
|
"--baud",
|
||||||
|
str(baud_rate),
|
||||||
|
"--chip",
|
||||||
|
"esp8266",
|
||||||
|
"--port",
|
||||||
|
port,
|
||||||
|
"write_flash",
|
||||||
|
"0x0",
|
||||||
|
path,
|
||||||
|
]
|
||||||
|
|
||||||
if os.environ.get('ESPHOME_USE_SUBPROCESS') is None:
|
if os.environ.get("ESPHOME_USE_SUBPROCESS") is None:
|
||||||
import esptool
|
import esptool
|
||||||
|
|
||||||
# pylint: disable=protected-access
|
# pylint: disable=protected-access
|
||||||
return run_external_command(esptool._main, *cmd)
|
return run_external_command(esptool._main, *cmd)
|
||||||
|
|
||||||
@@ -169,14 +207,16 @@ def upload_using_esptool(config, port):
|
|||||||
if rc == 0 or first_baudrate == 115200:
|
if rc == 0 or first_baudrate == 115200:
|
||||||
return rc
|
return rc
|
||||||
# Try with 115200 baud rate, with some serial chips the faster baud rates do not work well
|
# Try with 115200 baud rate, with some serial chips the faster baud rates do not work well
|
||||||
_LOGGER.info("Upload with baud rate %s failed. Trying again with baud rate 115200.",
|
_LOGGER.info(
|
||||||
first_baudrate)
|
"Upload with baud rate %s failed. Trying again with baud rate 115200.",
|
||||||
|
first_baudrate,
|
||||||
|
)
|
||||||
return run_esptool(115200)
|
return run_esptool(115200)
|
||||||
|
|
||||||
|
|
||||||
def upload_program(config, args, host):
|
def upload_program(config, args, host):
|
||||||
# if upload is to a serial port use platformio, otherwise assume ota
|
# if upload is to a serial port use platformio, otherwise assume ota
|
||||||
if get_port_type(host) == 'SERIAL':
|
if get_port_type(host) == "SERIAL":
|
||||||
from esphome import platformio_api
|
from esphome import platformio_api
|
||||||
|
|
||||||
if CORE.is_esp8266:
|
if CORE.is_esp8266:
|
||||||
@@ -186,8 +226,10 @@ def upload_program(config, args, host):
|
|||||||
from esphome import espota2
|
from esphome import espota2
|
||||||
|
|
||||||
if CONF_OTA not in config:
|
if CONF_OTA not in config:
|
||||||
raise EsphomeError("Cannot upload Over the Air as the config does not include the ota: "
|
raise EsphomeError(
|
||||||
"component")
|
"Cannot upload Over the Air as the config does not include the ota: "
|
||||||
|
"component"
|
||||||
|
)
|
||||||
|
|
||||||
ota_conf = config[CONF_OTA]
|
ota_conf = config[CONF_OTA]
|
||||||
remote_port = ota_conf[CONF_PORT]
|
remote_port = ota_conf[CONF_PORT]
|
||||||
@@ -196,19 +238,21 @@ def upload_program(config, args, host):
|
|||||||
|
|
||||||
|
|
||||||
def show_logs(config, args, port):
|
def show_logs(config, args, port):
|
||||||
if 'logger' not in config:
|
if "logger" not in config:
|
||||||
raise EsphomeError("Logger is not configured!")
|
raise EsphomeError("Logger is not configured!")
|
||||||
if get_port_type(port) == 'SERIAL':
|
if get_port_type(port) == "SERIAL":
|
||||||
run_miniterm(config, port)
|
run_miniterm(config, port)
|
||||||
return 0
|
return 0
|
||||||
if get_port_type(port) == 'NETWORK' and 'api' in config:
|
if get_port_type(port) == "NETWORK" and "api" in config:
|
||||||
from esphome.api.client import run_logs
|
from esphome.api.client import run_logs
|
||||||
|
|
||||||
return run_logs(config, port)
|
return run_logs(config, port)
|
||||||
if get_port_type(port) == 'MQTT' and 'mqtt' in config:
|
if get_port_type(port) == "MQTT" and "mqtt" in config:
|
||||||
from esphome import mqtt
|
from esphome import mqtt
|
||||||
|
|
||||||
return mqtt.show_logs(config, args.topic, args.username, args.password, args.client_id)
|
return mqtt.show_logs(
|
||||||
|
config, args.topic, args.username, args.password, args.client_id
|
||||||
|
)
|
||||||
|
|
||||||
raise EsphomeError("No remote or local logging method configured (api/mqtt/logger)")
|
raise EsphomeError("No remote or local logging method configured (api/mqtt/logger)")
|
||||||
|
|
||||||
@@ -216,49 +260,15 @@ def show_logs(config, args, port):
|
|||||||
def clean_mqtt(config, args):
|
def clean_mqtt(config, args):
|
||||||
from esphome import mqtt
|
from esphome import mqtt
|
||||||
|
|
||||||
return mqtt.clear_topic(config, args.topic, args.username, args.password, args.client_id)
|
return mqtt.clear_topic(
|
||||||
|
config, args.topic, args.username, args.password, args.client_id
|
||||||
|
)
|
||||||
def setup_log(debug=False, quiet=False):
|
|
||||||
if debug:
|
|
||||||
log_level = logging.DEBUG
|
|
||||||
CORE.verbose = True
|
|
||||||
elif quiet:
|
|
||||||
log_level = logging.CRITICAL
|
|
||||||
else:
|
|
||||||
log_level = logging.INFO
|
|
||||||
logging.basicConfig(level=log_level)
|
|
||||||
fmt = "%(levelname)s %(message)s"
|
|
||||||
colorfmt = f"%(log_color)s{fmt}%(reset)s"
|
|
||||||
datefmt = '%H:%M:%S'
|
|
||||||
|
|
||||||
logging.getLogger('urllib3').setLevel(logging.WARNING)
|
|
||||||
|
|
||||||
try:
|
|
||||||
import colorama
|
|
||||||
colorama.init(strip=True)
|
|
||||||
|
|
||||||
from colorlog import ColoredFormatter
|
|
||||||
logging.getLogger().handlers[0].setFormatter(ColoredFormatter(
|
|
||||||
colorfmt,
|
|
||||||
datefmt=datefmt,
|
|
||||||
reset=True,
|
|
||||||
log_colors={
|
|
||||||
'DEBUG': 'cyan',
|
|
||||||
'INFO': 'green',
|
|
||||||
'WARNING': 'yellow',
|
|
||||||
'ERROR': 'red',
|
|
||||||
'CRITICAL': 'red',
|
|
||||||
}
|
|
||||||
))
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def command_wizard(args):
|
def command_wizard(args):
|
||||||
from esphome import wizard
|
from esphome import wizard
|
||||||
|
|
||||||
return wizard.wizard(args.configuration[0])
|
return wizard.wizard(args.configuration)
|
||||||
|
|
||||||
|
|
||||||
def command_config(args, config):
|
def command_config(args, config):
|
||||||
@@ -272,7 +282,9 @@ def command_config(args, config):
|
|||||||
def command_vscode(args):
|
def command_vscode(args):
|
||||||
from esphome import vscode
|
from esphome import vscode
|
||||||
|
|
||||||
CORE.config_path = args.configuration[0]
|
logging.disable(logging.INFO)
|
||||||
|
logging.disable(logging.WARNING)
|
||||||
|
CORE.config_path = args.configuration
|
||||||
vscode.read_config(args)
|
vscode.read_config(args)
|
||||||
|
|
||||||
|
|
||||||
@@ -291,8 +303,13 @@ def command_compile(args, config):
|
|||||||
|
|
||||||
|
|
||||||
def command_upload(args, config):
|
def command_upload(args, config):
|
||||||
port = choose_upload_log_host(default=args.upload_port, check_default=None,
|
port = choose_upload_log_host(
|
||||||
show_ota=True, show_mqtt=False, show_api=False)
|
default=args.device,
|
||||||
|
check_default=None,
|
||||||
|
show_ota=True,
|
||||||
|
show_mqtt=False,
|
||||||
|
show_api=False,
|
||||||
|
)
|
||||||
exit_code = upload_program(config, args, port)
|
exit_code = upload_program(config, args, port)
|
||||||
if exit_code != 0:
|
if exit_code != 0:
|
||||||
return exit_code
|
return exit_code
|
||||||
@@ -301,8 +318,13 @@ def command_upload(args, config):
|
|||||||
|
|
||||||
|
|
||||||
def command_logs(args, config):
|
def command_logs(args, config):
|
||||||
port = choose_upload_log_host(default=args.serial_port, check_default=None,
|
port = choose_upload_log_host(
|
||||||
show_ota=False, show_mqtt=True, show_api=True)
|
default=args.device,
|
||||||
|
check_default=None,
|
||||||
|
show_ota=False,
|
||||||
|
show_mqtt=True,
|
||||||
|
show_api=True,
|
||||||
|
)
|
||||||
return show_logs(config, args, port)
|
return show_logs(config, args, port)
|
||||||
|
|
||||||
|
|
||||||
@@ -314,16 +336,26 @@ def command_run(args, config):
|
|||||||
if exit_code != 0:
|
if exit_code != 0:
|
||||||
return exit_code
|
return exit_code
|
||||||
_LOGGER.info("Successfully compiled program.")
|
_LOGGER.info("Successfully compiled program.")
|
||||||
port = choose_upload_log_host(default=args.upload_port, check_default=None,
|
port = choose_upload_log_host(
|
||||||
show_ota=True, show_mqtt=False, show_api=True)
|
default=args.device,
|
||||||
|
check_default=None,
|
||||||
|
show_ota=True,
|
||||||
|
show_mqtt=False,
|
||||||
|
show_api=True,
|
||||||
|
)
|
||||||
exit_code = upload_program(config, args, port)
|
exit_code = upload_program(config, args, port)
|
||||||
if exit_code != 0:
|
if exit_code != 0:
|
||||||
return exit_code
|
return exit_code
|
||||||
_LOGGER.info("Successfully uploaded program.")
|
_LOGGER.info("Successfully uploaded program.")
|
||||||
if args.no_logs:
|
if args.no_logs:
|
||||||
return 0
|
return 0
|
||||||
port = choose_upload_log_host(default=args.upload_port, check_default=port,
|
port = choose_upload_log_host(
|
||||||
show_ota=False, show_mqtt=True, show_api=True)
|
default=args.device,
|
||||||
|
check_default=port,
|
||||||
|
show_ota=False,
|
||||||
|
show_mqtt=True,
|
||||||
|
show_api=True,
|
||||||
|
)
|
||||||
return show_logs(config, args, port)
|
return show_logs(config, args, port)
|
||||||
|
|
||||||
|
|
||||||
@@ -372,137 +404,280 @@ def command_update_all(args):
|
|||||||
click.echo(f"{half_line}{middle_text}{half_line}")
|
click.echo(f"{half_line}{middle_text}{half_line}")
|
||||||
|
|
||||||
for f in files:
|
for f in files:
|
||||||
print("Updating {}".format(color('cyan', f)))
|
print("Updating {}".format(color(Fore.CYAN, f)))
|
||||||
print('-' * twidth)
|
print("-" * twidth)
|
||||||
print()
|
print()
|
||||||
rc = run_external_process('esphome', '--dashboard', f, 'run', '--no-logs', '--upload-port',
|
rc = run_external_process(
|
||||||
'OTA')
|
"esphome", "--dashboard", "run", "--no-logs", "--device", "OTA", f
|
||||||
|
)
|
||||||
if rc == 0:
|
if rc == 0:
|
||||||
print_bar("[{}] {}".format(color('bold_green', 'SUCCESS'), f))
|
print_bar("[{}] {}".format(color(Fore.BOLD_GREEN, "SUCCESS"), f))
|
||||||
success[f] = True
|
success[f] = True
|
||||||
else:
|
else:
|
||||||
print_bar("[{}] {}".format(color('bold_red', 'ERROR'), f))
|
print_bar("[{}] {}".format(color(Fore.BOLD_RED, "ERROR"), f))
|
||||||
success[f] = False
|
success[f] = False
|
||||||
|
|
||||||
print()
|
print()
|
||||||
print()
|
print()
|
||||||
print()
|
print()
|
||||||
|
|
||||||
print_bar('[{}]'.format(color('bold_white', 'SUMMARY')))
|
print_bar("[{}]".format(color(Fore.BOLD_WHITE, "SUMMARY")))
|
||||||
failed = 0
|
failed = 0
|
||||||
for f in files:
|
for f in files:
|
||||||
if success[f]:
|
if success[f]:
|
||||||
print(" - {}: {}".format(f, color('green', 'SUCCESS')))
|
print(" - {}: {}".format(f, color(Fore.GREEN, "SUCCESS")))
|
||||||
else:
|
else:
|
||||||
print(" - {}: {}".format(f, color('bold_red', 'FAILED')))
|
print(" - {}: {}".format(f, color(Fore.BOLD_RED, "FAILED")))
|
||||||
failed += 1
|
failed += 1
|
||||||
return failed
|
return failed
|
||||||
|
|
||||||
|
|
||||||
PRE_CONFIG_ACTIONS = {
|
PRE_CONFIG_ACTIONS = {
|
||||||
'wizard': command_wizard,
|
"wizard": command_wizard,
|
||||||
'version': command_version,
|
"version": command_version,
|
||||||
'dashboard': command_dashboard,
|
"dashboard": command_dashboard,
|
||||||
'vscode': command_vscode,
|
"vscode": command_vscode,
|
||||||
'update-all': command_update_all,
|
"update-all": command_update_all,
|
||||||
}
|
}
|
||||||
|
|
||||||
POST_CONFIG_ACTIONS = {
|
POST_CONFIG_ACTIONS = {
|
||||||
'config': command_config,
|
"config": command_config,
|
||||||
'compile': command_compile,
|
"compile": command_compile,
|
||||||
'upload': command_upload,
|
"upload": command_upload,
|
||||||
'logs': command_logs,
|
"logs": command_logs,
|
||||||
'run': command_run,
|
"run": command_run,
|
||||||
'clean-mqtt': command_clean_mqtt,
|
"clean-mqtt": command_clean_mqtt,
|
||||||
'mqtt-fingerprint': command_mqtt_fingerprint,
|
"mqtt-fingerprint": command_mqtt_fingerprint,
|
||||||
'clean': command_clean,
|
"clean": command_clean,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def parse_args(argv):
|
def parse_args(argv):
|
||||||
parser = argparse.ArgumentParser(description=f'ESPHome v{const.__version__}')
|
options_parser = argparse.ArgumentParser(add_help=False)
|
||||||
parser.add_argument('-v', '--verbose', help="Enable verbose esphome logs.",
|
options_parser.add_argument(
|
||||||
action='store_true')
|
"-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(
|
||||||
parser.add_argument('--dashboard', help=argparse.SUPPRESS, action='store_true')
|
"-q", "--quiet", help="Disable all ESPHome logs.", action="store_true"
|
||||||
parser.add_argument('-s', '--substitution', nargs=2, action='append',
|
)
|
||||||
help='Add a substitution', metavar=('key', 'value'))
|
options_parser.add_argument(
|
||||||
parser.add_argument('configuration', help='Your YAML configuration file.', nargs='*')
|
"--dashboard", help=argparse.SUPPRESS, action="store_true"
|
||||||
|
)
|
||||||
|
options_parser.add_argument(
|
||||||
|
"-s",
|
||||||
|
"--substitution",
|
||||||
|
nargs=2,
|
||||||
|
action="append",
|
||||||
|
help="Add a substitution",
|
||||||
|
metavar=("key", "value"),
|
||||||
|
)
|
||||||
|
|
||||||
subparsers = parser.add_subparsers(help='Commands', dest='command')
|
# 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",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
# 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.required = True
|
||||||
subparsers.add_parser('config', help='Validate the configuration and spit it out.')
|
|
||||||
|
|
||||||
parser_compile = subparsers.add_parser('compile',
|
parser_config = subparsers.add_parser(
|
||||||
help='Read the configuration and compile a program.')
|
"config", help="Validate the configuration and spit it out."
|
||||||
parser_compile.add_argument('--only-generate',
|
)
|
||||||
help="Only generate source code, do not compile.",
|
parser_config.add_argument(
|
||||||
action='store_true')
|
"configuration", help="Your YAML configuration file(s).", nargs="+"
|
||||||
|
)
|
||||||
|
|
||||||
parser_upload = subparsers.add_parser('upload', help='Validate the configuration '
|
parser_compile = subparsers.add_parser(
|
||||||
'and upload the latest binary.')
|
"compile", help="Read the configuration and compile a program."
|
||||||
parser_upload.add_argument('--upload-port', help="Manually specify the upload port to use. "
|
)
|
||||||
"For example /dev/cu.SLAB_USBtoUART.")
|
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.",
|
||||||
|
action="store_true",
|
||||||
|
)
|
||||||
|
|
||||||
parser_logs = subparsers.add_parser('logs', help='Validate the configuration '
|
parser_upload = subparsers.add_parser(
|
||||||
'and show all MQTT logs.')
|
"upload", help="Validate the configuration and upload the latest binary."
|
||||||
parser_logs.add_argument('--topic', help='Manually set the topic to subscribe to.')
|
)
|
||||||
parser_logs.add_argument('--username', help='Manually set the username.')
|
parser_upload.add_argument(
|
||||||
parser_logs.add_argument('--password', help='Manually set the password.')
|
"configuration", help="Your YAML configuration file(s).", nargs="+"
|
||||||
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"
|
parser_upload.add_argument(
|
||||||
"For example /dev/cu.SLAB_USBtoUART.")
|
"--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, '
|
parser_logs = subparsers.add_parser(
|
||||||
'upload it, and start MQTT logs.')
|
"logs",
|
||||||
parser_run.add_argument('--upload-port', help="Manually specify the upload port/ip to use. "
|
help="Validate the configuration and show all logs.",
|
||||||
"For example /dev/cu.SLAB_USBtoUART.")
|
parents=[mqtt_options],
|
||||||
parser_run.add_argument('--no-logs', help='Disable starting MQTT logs.',
|
)
|
||||||
action='store_true')
|
parser_logs.add_argument(
|
||||||
parser_run.add_argument('--topic', help='Manually set the topic to subscribe to for logs.')
|
"configuration", help="Your YAML configuration file.", nargs=1
|
||||||
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_logs.add_argument(
|
||||||
parser_run.add_argument('--client-id', help='Manually set the client id for logs.')
|
"--device",
|
||||||
|
help="Manually specify the serial port/address to use, for example /dev/ttyUSB0.",
|
||||||
|
)
|
||||||
|
|
||||||
parser_clean = subparsers.add_parser('clean-mqtt', help="Helper to clear an MQTT topic from "
|
parser_run = subparsers.add_parser(
|
||||||
"retain messages.")
|
"run",
|
||||||
parser_clean.add_argument('--topic', help='Manually set the topic to subscribe to.')
|
help="Validate the configuration, create a binary, upload it, and start logs.",
|
||||||
parser_clean.add_argument('--username', help='Manually set the username.')
|
parents=[mqtt_options],
|
||||||
parser_clean.add_argument('--password', help='Manually set the password.')
|
)
|
||||||
parser_clean.add_argument('--client-id', help='Manually set the client id.')
|
parser_run.add_argument(
|
||||||
|
"configuration", help="Your YAML configuration file(s).", nargs="+"
|
||||||
|
)
|
||||||
|
parser_run.add_argument(
|
||||||
|
"--device",
|
||||||
|
help="Manually specify the serial port/address to use, for example /dev/ttyUSB0.",
|
||||||
|
)
|
||||||
|
parser_run.add_argument(
|
||||||
|
"--no-logs", help="Disable starting logs.", action="store_true"
|
||||||
|
)
|
||||||
|
|
||||||
subparsers.add_parser('wizard', help="A helpful setup wizard that will guide "
|
parser_clean = subparsers.add_parser(
|
||||||
"you through setting up esphome.")
|
"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="+"
|
||||||
|
)
|
||||||
|
|
||||||
subparsers.add_parser('mqtt-fingerprint', help="Get the SSL fingerprint from a MQTT broker.")
|
parser_wizard = subparsers.add_parser(
|
||||||
|
"wizard",
|
||||||
|
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('version', help="Print the esphome version and exit.")
|
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('clean', help="Delete all temporary build files.")
|
subparsers.add_parser("version", help="Print the ESPHome version and exit.")
|
||||||
|
|
||||||
dashboard = subparsers.add_parser('dashboard',
|
parser_clean = subparsers.add_parser(
|
||||||
help="Create a simple web server for a dashboard.")
|
"clean", help="Delete all temporary build files."
|
||||||
dashboard.add_argument("--port", help="The HTTP port to open connections on. Defaults to 6052.",
|
)
|
||||||
type=int, default=6052)
|
parser_clean.add_argument(
|
||||||
dashboard.add_argument("--username", help="The optional username to require "
|
"configuration", help="Your YAML configuration file(s).", nargs="+"
|
||||||
"for authentication.",
|
)
|
||||||
type=str, default='')
|
|
||||||
dashboard.add_argument("--password", help="The optional password to require "
|
|
||||||
"for authentication.",
|
|
||||||
type=str, default='')
|
|
||||||
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("--socket",
|
|
||||||
help="Make the dashboard serve under a unix socket", type=str)
|
|
||||||
|
|
||||||
vscode = subparsers.add_parser('vscode', help=argparse.SUPPRESS)
|
parser_dashboard = subparsers.add_parser(
|
||||||
vscode.add_argument('--ace', action='store_true')
|
"dashboard", help="Create a simple web server for a dashboard."
|
||||||
|
)
|
||||||
|
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,
|
||||||
|
)
|
||||||
|
parser_dashboard.add_argument(
|
||||||
|
"--username",
|
||||||
|
help="The optional username to require for authentication.",
|
||||||
|
type=str,
|
||||||
|
default="",
|
||||||
|
)
|
||||||
|
parser_dashboard.add_argument(
|
||||||
|
"--password",
|
||||||
|
help="The optional password to require for authentication.",
|
||||||
|
type=str,
|
||||||
|
default="",
|
||||||
|
)
|
||||||
|
parser_dashboard.add_argument(
|
||||||
|
"--open-ui", help="Open the dashboard UI in a browser.", action="store_true"
|
||||||
|
)
|
||||||
|
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
|
||||||
|
)
|
||||||
|
|
||||||
subparsers.add_parser('update-all', help=argparse.SUPPRESS)
|
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")
|
||||||
|
|
||||||
|
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:])
|
return parser.parse_args(argv[1:])
|
||||||
|
|
||||||
@@ -512,20 +687,26 @@ def run_esphome(argv):
|
|||||||
CORE.dashboard = args.dashboard
|
CORE.dashboard = args.dashboard
|
||||||
|
|
||||||
setup_log(args.verbose, args.quiet)
|
setup_log(args.verbose, args.quiet)
|
||||||
if args.command != 'version' and not args.configuration:
|
if args.deprecated_argv_suggestion is not None and args.command != "vscode":
|
||||||
_LOGGER.error("Missing configuration parameter, see esphome --help.")
|
_LOGGER.warning(
|
||||||
return 1
|
"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 "
|
_LOGGER.error(
|
||||||
"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
|
return 1
|
||||||
|
|
||||||
if args.command in PRE_CONFIG_ACTIONS:
|
if args.command in PRE_CONFIG_ACTIONS:
|
||||||
try:
|
try:
|
||||||
return PRE_CONFIG_ACTIONS[args.command](args)
|
return PRE_CONFIG_ACTIONS[args.command](args)
|
||||||
except EsphomeError as e:
|
except EsphomeError as e:
|
||||||
_LOGGER.error(e)
|
_LOGGER.error(e, exc_info=args.verbose)
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
for conf_path in args.configuration:
|
for conf_path in args.configuration:
|
||||||
@@ -543,7 +724,7 @@ def run_esphome(argv):
|
|||||||
try:
|
try:
|
||||||
rc = POST_CONFIG_ACTIONS[args.command](args, config)
|
rc = POST_CONFIG_ACTIONS[args.command](args, config)
|
||||||
except EsphomeError as e:
|
except EsphomeError as e:
|
||||||
_LOGGER.error(e)
|
_LOGGER.error(e, exc_info=args.verbose)
|
||||||
return 1
|
return 1
|
||||||
if rc != 0:
|
if rc != 0:
|
||||||
return rc
|
return rc
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -13,7 +13,8 @@ from esphome import const
|
|||||||
import esphome.api.api_pb2 as pb
|
import esphome.api.api_pb2 as pb
|
||||||
from esphome.const import CONF_PASSWORD, CONF_PORT
|
from esphome.const import CONF_PASSWORD, CONF_PORT
|
||||||
from esphome.core import EsphomeError
|
from esphome.core import EsphomeError
|
||||||
from esphome.helpers import resolve_ip_address, indent, color
|
from esphome.helpers import resolve_ip_address, indent
|
||||||
|
from esphome.log import color, Fore
|
||||||
from esphome.util import safe_print
|
from esphome.util import safe_print
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
@@ -177,10 +178,14 @@ class APIClient(threading.Thread):
|
|||||||
try:
|
try:
|
||||||
ip = resolve_ip_address(self._address)
|
ip = resolve_ip_address(self._address)
|
||||||
except EsphomeError as err:
|
except EsphomeError as err:
|
||||||
_LOGGER.warning("Error resolving IP address of %s. Is it connected to WiFi?",
|
_LOGGER.warning(
|
||||||
self._address)
|
"Error resolving IP address of %s. Is it connected to WiFi?",
|
||||||
_LOGGER.warning("(If this error persists, please set a static IP address: "
|
self._address,
|
||||||
"https://esphome.io/components/wifi.html#manual-ips)")
|
)
|
||||||
|
_LOGGER.warning(
|
||||||
|
"(If this error persists, please set a static IP address: "
|
||||||
|
"https://esphome.io/components/wifi.html#manual-ips)"
|
||||||
|
)
|
||||||
raise APIConnectionError(err) from err
|
raise APIConnectionError(err) from err
|
||||||
|
|
||||||
_LOGGER.info("Connecting to %s:%s (%s)", self._address, self._port, ip)
|
_LOGGER.info("Connecting to %s:%s (%s)", self._address, self._port, ip)
|
||||||
@@ -198,14 +203,19 @@ class APIClient(threading.Thread):
|
|||||||
self._socket_open_event.set()
|
self._socket_open_event.set()
|
||||||
|
|
||||||
hello = pb.HelloRequest()
|
hello = pb.HelloRequest()
|
||||||
hello.client_info = f'ESPHome v{const.__version__}'
|
hello.client_info = f"ESPHome v{const.__version__}"
|
||||||
try:
|
try:
|
||||||
resp = self._send_message_await_response(hello, pb.HelloResponse)
|
resp = self._send_message_await_response(hello, pb.HelloResponse)
|
||||||
except APIConnectionError as err:
|
except APIConnectionError as err:
|
||||||
self._fatal_error(err)
|
self._fatal_error(err)
|
||||||
raise err
|
raise err
|
||||||
_LOGGER.debug("Successfully connected to %s ('%s' API=%s.%s)", self._address,
|
_LOGGER.debug(
|
||||||
resp.server_info, resp.api_version_major, resp.api_version_minor)
|
"Successfully connected to %s ('%s' API=%s.%s)",
|
||||||
|
self._address,
|
||||||
|
resp.server_info,
|
||||||
|
resp.api_version_major,
|
||||||
|
resp.api_version_minor,
|
||||||
|
)
|
||||||
self._connected = True
|
self._connected = True
|
||||||
self._refresh_ping()
|
self._refresh_ping()
|
||||||
if self.on_connect is not None:
|
if self.on_connect is not None:
|
||||||
@@ -270,7 +280,9 @@ class APIClient(threading.Thread):
|
|||||||
req += encoded
|
req += encoded
|
||||||
self._write(req)
|
self._write(req)
|
||||||
|
|
||||||
def _send_message_await_response_complex(self, send_msg, do_append, do_stop, timeout=5):
|
def _send_message_await_response_complex(
|
||||||
|
self, send_msg, do_append, do_stop, timeout=5
|
||||||
|
):
|
||||||
event = threading.Event()
|
event = threading.Event()
|
||||||
responses = []
|
responses = []
|
||||||
|
|
||||||
@@ -295,12 +307,15 @@ class APIClient(threading.Thread):
|
|||||||
def is_response(msg):
|
def is_response(msg):
|
||||||
return isinstance(msg, response_type)
|
return isinstance(msg, response_type)
|
||||||
|
|
||||||
return self._send_message_await_response_complex(send_msg, is_response, is_response,
|
return self._send_message_await_response_complex(
|
||||||
timeout)[0]
|
send_msg, is_response, is_response, timeout
|
||||||
|
)[0]
|
||||||
|
|
||||||
def device_info(self):
|
def device_info(self):
|
||||||
self._check_connected()
|
self._check_connected()
|
||||||
return self._send_message_await_response(pb.DeviceInfoRequest(), pb.DeviceInfoResponse)
|
return self._send_message_await_response(
|
||||||
|
pb.DeviceInfoRequest(), pb.DeviceInfoResponse
|
||||||
|
)
|
||||||
|
|
||||||
def ping(self):
|
def ping(self):
|
||||||
self._check_connected()
|
self._check_connected()
|
||||||
@@ -310,7 +325,9 @@ class APIClient(threading.Thread):
|
|||||||
self._check_connected()
|
self._check_connected()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self._send_message_await_response(pb.DisconnectRequest(), pb.DisconnectResponse)
|
self._send_message_await_response(
|
||||||
|
pb.DisconnectRequest(), pb.DisconnectResponse
|
||||||
|
)
|
||||||
except APIConnectionError:
|
except APIConnectionError:
|
||||||
pass
|
pass
|
||||||
self._close_socket()
|
self._close_socket()
|
||||||
@@ -415,7 +432,7 @@ class APIClient(threading.Thread):
|
|||||||
|
|
||||||
|
|
||||||
def run_logs(config, address):
|
def run_logs(config, address):
|
||||||
conf = config['api']
|
conf = config["api"]
|
||||||
port = conf[CONF_PORT]
|
port = conf[CONF_PORT]
|
||||||
password = conf[CONF_PASSWORD]
|
password = conf[CONF_PASSWORD]
|
||||||
_LOGGER.info("Starting log output from %s using esphome API", address)
|
_LOGGER.info("Starting log output from %s using esphome API", address)
|
||||||
@@ -447,24 +464,35 @@ def run_logs(config, address):
|
|||||||
_LOGGER.info("Successfully connected to %s", address)
|
_LOGGER.info("Successfully connected to %s", address)
|
||||||
return
|
return
|
||||||
|
|
||||||
wait_time = int(min(1.5**min(tries, 100), 30))
|
wait_time = int(min(1.5 ** min(tries, 100), 30))
|
||||||
if not has_connects:
|
if not has_connects:
|
||||||
_LOGGER.warning("Initial connection failed. The ESP might not be connected "
|
_LOGGER.warning(
|
||||||
"to WiFi yet (%s). Re-Trying in %s seconds",
|
"Initial connection failed. The ESP might not be connected "
|
||||||
error, wait_time)
|
"to WiFi yet (%s). Re-Trying in %s seconds",
|
||||||
|
error,
|
||||||
|
wait_time,
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
_LOGGER.warning("Couldn't connect to API (%s). Trying to reconnect in %s seconds",
|
_LOGGER.warning(
|
||||||
error, wait_time)
|
"Couldn't connect to API (%s). Trying to reconnect in %s seconds",
|
||||||
timer = threading.Timer(wait_time, functools.partial(try_connect, None, tries + 1))
|
error,
|
||||||
|
wait_time,
|
||||||
|
)
|
||||||
|
timer = threading.Timer(
|
||||||
|
wait_time, functools.partial(try_connect, None, tries + 1)
|
||||||
|
)
|
||||||
timer.start()
|
timer.start()
|
||||||
retry_timer.append(timer)
|
retry_timer.append(timer)
|
||||||
|
|
||||||
def on_log(msg):
|
def on_log(msg):
|
||||||
time_ = datetime.now().time().strftime('[%H:%M:%S]')
|
time_ = datetime.now().time().strftime("[%H:%M:%S]")
|
||||||
text = msg.message
|
text = msg.message
|
||||||
if msg.send_failed:
|
if msg.send_failed:
|
||||||
text = color('white', '(Message skipped because it was too big to fit in '
|
text = color(
|
||||||
'TCP buffer - This is only cosmetic)')
|
Fore.WHITE,
|
||||||
|
"(Message skipped because it was too big to fit in "
|
||||||
|
"TCP buffer - This is only cosmetic)",
|
||||||
|
)
|
||||||
safe_print(time_ + text)
|
safe_print(time_ + text)
|
||||||
|
|
||||||
def on_login():
|
def on_login():
|
||||||
|
|||||||
@@ -1,8 +1,16 @@
|
|||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.const import CONF_AUTOMATION_ID, CONF_CONDITION, CONF_ELSE, CONF_ID, CONF_THEN, \
|
from esphome.const import (
|
||||||
CONF_TRIGGER_ID, CONF_TYPE_ID, CONF_TIME
|
CONF_AUTOMATION_ID,
|
||||||
from esphome.core import coroutine
|
CONF_CONDITION,
|
||||||
|
CONF_ELSE,
|
||||||
|
CONF_ID,
|
||||||
|
CONF_THEN,
|
||||||
|
CONF_TRIGGER_ID,
|
||||||
|
CONF_TYPE_ID,
|
||||||
|
CONF_TIME,
|
||||||
|
)
|
||||||
|
from esphome.jsonschema import jschema_extractor
|
||||||
from esphome.util import Registry
|
from esphome.util import Registry
|
||||||
|
|
||||||
|
|
||||||
@@ -13,7 +21,12 @@ def maybe_simple_id(*validators):
|
|||||||
def maybe_conf(conf, *validators):
|
def maybe_conf(conf, *validators):
|
||||||
validator = cv.All(*validators)
|
validator = cv.All(*validators)
|
||||||
|
|
||||||
|
@jschema_extractor("maybe")
|
||||||
def validate(value):
|
def validate(value):
|
||||||
|
# pylint: disable=comparison-with-callable
|
||||||
|
if value == jschema_extractor:
|
||||||
|
return validator
|
||||||
|
|
||||||
if isinstance(value, dict):
|
if isinstance(value, dict):
|
||||||
return validator(value)
|
return validator(value)
|
||||||
with cv.remove_prepend_path([conf]):
|
with cv.remove_prepend_path([conf]):
|
||||||
@@ -30,36 +43,34 @@ def register_condition(name, condition_type, schema):
|
|||||||
return CONDITION_REGISTRY.register(name, condition_type, schema)
|
return CONDITION_REGISTRY.register(name, condition_type, schema)
|
||||||
|
|
||||||
|
|
||||||
Action = cg.esphome_ns.class_('Action')
|
Action = cg.esphome_ns.class_("Action")
|
||||||
Trigger = cg.esphome_ns.class_('Trigger')
|
Trigger = cg.esphome_ns.class_("Trigger")
|
||||||
ACTION_REGISTRY = Registry()
|
ACTION_REGISTRY = Registry()
|
||||||
Condition = cg.esphome_ns.class_('Condition')
|
Condition = cg.esphome_ns.class_("Condition")
|
||||||
CONDITION_REGISTRY = Registry()
|
CONDITION_REGISTRY = Registry()
|
||||||
validate_action = cv.validate_registry_entry('action', ACTION_REGISTRY)
|
validate_action = cv.validate_registry_entry("action", ACTION_REGISTRY)
|
||||||
validate_action_list = cv.validate_registry('action', ACTION_REGISTRY)
|
validate_action_list = cv.validate_registry("action", ACTION_REGISTRY)
|
||||||
validate_condition = cv.validate_registry_entry('condition', CONDITION_REGISTRY)
|
validate_condition = cv.validate_registry_entry("condition", CONDITION_REGISTRY)
|
||||||
validate_condition_list = cv.validate_registry('condition', CONDITION_REGISTRY)
|
validate_condition_list = cv.validate_registry("condition", CONDITION_REGISTRY)
|
||||||
|
|
||||||
|
|
||||||
def validate_potentially_and_condition(value):
|
def validate_potentially_and_condition(value):
|
||||||
if isinstance(value, list):
|
if isinstance(value, list):
|
||||||
with cv.remove_prepend_path(['and']):
|
with cv.remove_prepend_path(["and"]):
|
||||||
return validate_condition({
|
return validate_condition({"and": value})
|
||||||
'and': value
|
|
||||||
})
|
|
||||||
return validate_condition(value)
|
return validate_condition(value)
|
||||||
|
|
||||||
|
|
||||||
DelayAction = cg.esphome_ns.class_('DelayAction', Action, cg.Component)
|
DelayAction = cg.esphome_ns.class_("DelayAction", Action, cg.Component)
|
||||||
LambdaAction = cg.esphome_ns.class_('LambdaAction', Action)
|
LambdaAction = cg.esphome_ns.class_("LambdaAction", Action)
|
||||||
IfAction = cg.esphome_ns.class_('IfAction', Action)
|
IfAction = cg.esphome_ns.class_("IfAction", Action)
|
||||||
WhileAction = cg.esphome_ns.class_('WhileAction', Action)
|
WhileAction = cg.esphome_ns.class_("WhileAction", Action)
|
||||||
WaitUntilAction = cg.esphome_ns.class_('WaitUntilAction', Action, cg.Component)
|
WaitUntilAction = cg.esphome_ns.class_("WaitUntilAction", Action, cg.Component)
|
||||||
UpdateComponentAction = cg.esphome_ns.class_('UpdateComponentAction', Action)
|
UpdateComponentAction = cg.esphome_ns.class_("UpdateComponentAction", Action)
|
||||||
Automation = cg.esphome_ns.class_('Automation')
|
Automation = cg.esphome_ns.class_("Automation")
|
||||||
|
|
||||||
LambdaCondition = cg.esphome_ns.class_('LambdaCondition', Condition)
|
LambdaCondition = cg.esphome_ns.class_("LambdaCondition", Condition)
|
||||||
ForCondition = cg.esphome_ns.class_('ForCondition', Condition, cg.Component)
|
ForCondition = cg.esphome_ns.class_("ForCondition", Condition, cg.Component)
|
||||||
|
|
||||||
|
|
||||||
def validate_automation(extra_schema=None, extra_validators=None, single=False):
|
def validate_automation(extra_schema=None, extra_validators=None, single=False):
|
||||||
@@ -83,10 +94,10 @@ def validate_automation(extra_schema=None, extra_validators=None, single=False):
|
|||||||
try:
|
try:
|
||||||
return cv.Schema([schema])(value)
|
return cv.Schema([schema])(value)
|
||||||
except cv.Invalid as err2:
|
except cv.Invalid as err2:
|
||||||
if 'extra keys not allowed' in str(err2) and len(err2.path) == 2:
|
if "extra keys not allowed" in str(err2) and len(err2.path) == 2:
|
||||||
# pylint: disable=raise-missing-from
|
# pylint: disable=raise-missing-from
|
||||||
raise err
|
raise err
|
||||||
if 'Unable to find action' in str(err):
|
if "Unable to find action" in str(err):
|
||||||
raise err2
|
raise err2
|
||||||
raise cv.MultipleInvalid([err, err2])
|
raise cv.MultipleInvalid([err, err2])
|
||||||
elif isinstance(value, dict):
|
elif isinstance(value, dict):
|
||||||
@@ -97,7 +108,13 @@ def validate_automation(extra_schema=None, extra_validators=None, single=False):
|
|||||||
# This should only happen with invalid configs, but let's have a nice error message.
|
# This should only happen with invalid configs, but let's have a nice error message.
|
||||||
return [schema(value)]
|
return [schema(value)]
|
||||||
|
|
||||||
|
@jschema_extractor("automation")
|
||||||
def validator(value):
|
def validator(value):
|
||||||
|
# hack to get the schema
|
||||||
|
# pylint: disable=comparison-with-callable
|
||||||
|
if value == jschema_extractor:
|
||||||
|
return schema
|
||||||
|
|
||||||
value = validator_(value)
|
value = validator_(value)
|
||||||
if extra_validators is not None:
|
if extra_validators is not None:
|
||||||
value = cv.Schema([extra_validators])(value)
|
value = cv.Schema([extra_validators])(value)
|
||||||
@@ -110,162 +127,198 @@ def validate_automation(extra_schema=None, extra_validators=None, single=False):
|
|||||||
return validator
|
return validator
|
||||||
|
|
||||||
|
|
||||||
AUTOMATION_SCHEMA = cv.Schema({
|
AUTOMATION_SCHEMA = cv.Schema(
|
||||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(Trigger),
|
{
|
||||||
cv.GenerateID(CONF_AUTOMATION_ID): cv.declare_id(Automation),
|
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(Trigger),
|
||||||
cv.Required(CONF_THEN): validate_action_list,
|
cv.GenerateID(CONF_AUTOMATION_ID): cv.declare_id(Automation),
|
||||||
})
|
cv.Required(CONF_THEN): validate_action_list,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
AndCondition = cg.esphome_ns.class_('AndCondition', Condition)
|
AndCondition = cg.esphome_ns.class_("AndCondition", Condition)
|
||||||
OrCondition = cg.esphome_ns.class_('OrCondition', Condition)
|
OrCondition = cg.esphome_ns.class_("OrCondition", Condition)
|
||||||
NotCondition = cg.esphome_ns.class_('NotCondition', Condition)
|
NotCondition = cg.esphome_ns.class_("NotCondition", Condition)
|
||||||
|
|
||||||
|
|
||||||
@register_condition('and', AndCondition, validate_condition_list)
|
@register_condition("and", AndCondition, validate_condition_list)
|
||||||
def and_condition_to_code(config, condition_id, template_arg, args):
|
async def and_condition_to_code(config, condition_id, template_arg, args):
|
||||||
conditions = yield build_condition_list(config, template_arg, args)
|
conditions = await build_condition_list(config, template_arg, args)
|
||||||
yield cg.new_Pvariable(condition_id, template_arg, conditions)
|
return cg.new_Pvariable(condition_id, template_arg, conditions)
|
||||||
|
|
||||||
|
|
||||||
@register_condition('or', OrCondition, validate_condition_list)
|
@register_condition("or", OrCondition, validate_condition_list)
|
||||||
def or_condition_to_code(config, condition_id, template_arg, args):
|
async def or_condition_to_code(config, condition_id, template_arg, args):
|
||||||
conditions = yield build_condition_list(config, template_arg, args)
|
conditions = await build_condition_list(config, template_arg, args)
|
||||||
yield cg.new_Pvariable(condition_id, template_arg, conditions)
|
return cg.new_Pvariable(condition_id, template_arg, conditions)
|
||||||
|
|
||||||
|
|
||||||
@register_condition('not', NotCondition, validate_potentially_and_condition)
|
@register_condition("not", NotCondition, validate_potentially_and_condition)
|
||||||
def not_condition_to_code(config, condition_id, template_arg, args):
|
async def not_condition_to_code(config, condition_id, template_arg, args):
|
||||||
condition = yield build_condition(config, template_arg, args)
|
condition = await build_condition(config, template_arg, args)
|
||||||
yield cg.new_Pvariable(condition_id, template_arg, condition)
|
return cg.new_Pvariable(condition_id, template_arg, condition)
|
||||||
|
|
||||||
|
|
||||||
@register_condition('lambda', LambdaCondition, cv.lambda_)
|
@register_condition("lambda", LambdaCondition, cv.returning_lambda)
|
||||||
def lambda_condition_to_code(config, condition_id, template_arg, args):
|
async def lambda_condition_to_code(config, condition_id, template_arg, args):
|
||||||
lambda_ = yield cg.process_lambda(config, args, return_type=bool)
|
lambda_ = await cg.process_lambda(config, args, return_type=bool)
|
||||||
yield cg.new_Pvariable(condition_id, template_arg, lambda_)
|
return cg.new_Pvariable(condition_id, template_arg, lambda_)
|
||||||
|
|
||||||
|
|
||||||
@register_condition('for', ForCondition, cv.Schema({
|
@register_condition(
|
||||||
cv.Required(CONF_TIME): cv.templatable(cv.positive_time_period_milliseconds),
|
"for",
|
||||||
cv.Required(CONF_CONDITION): validate_potentially_and_condition,
|
ForCondition,
|
||||||
}).extend(cv.COMPONENT_SCHEMA))
|
cv.Schema(
|
||||||
def for_condition_to_code(config, condition_id, template_arg, args):
|
{
|
||||||
condition = yield build_condition(config[CONF_CONDITION], cg.TemplateArguments(), [])
|
cv.Required(CONF_TIME): cv.templatable(
|
||||||
|
cv.positive_time_period_milliseconds
|
||||||
|
),
|
||||||
|
cv.Required(CONF_CONDITION): validate_potentially_and_condition,
|
||||||
|
}
|
||||||
|
).extend(cv.COMPONENT_SCHEMA),
|
||||||
|
)
|
||||||
|
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)
|
var = cg.new_Pvariable(condition_id, template_arg, condition)
|
||||||
yield cg.register_component(var, config)
|
await cg.register_component(var, config)
|
||||||
templ = yield cg.templatable(config[CONF_TIME], args, cg.uint32)
|
templ = await cg.templatable(config[CONF_TIME], args, cg.uint32)
|
||||||
cg.add(var.set_time(templ))
|
cg.add(var.set_time(templ))
|
||||||
yield var
|
return var
|
||||||
|
|
||||||
|
|
||||||
@register_action('delay', DelayAction, cv.templatable(cv.positive_time_period_milliseconds))
|
@register_action(
|
||||||
def delay_action_to_code(config, action_id, template_arg, args):
|
"delay", DelayAction, cv.templatable(cv.positive_time_period_milliseconds)
|
||||||
|
)
|
||||||
|
async def delay_action_to_code(config, action_id, template_arg, args):
|
||||||
var = cg.new_Pvariable(action_id, template_arg)
|
var = cg.new_Pvariable(action_id, template_arg)
|
||||||
yield cg.register_component(var, {})
|
await cg.register_component(var, {})
|
||||||
template_ = yield cg.templatable(config, args, cg.uint32)
|
template_ = await cg.templatable(config, args, cg.uint32)
|
||||||
cg.add(var.set_delay(template_))
|
cg.add(var.set_delay(template_))
|
||||||
yield var
|
return var
|
||||||
|
|
||||||
|
|
||||||
@register_action('if', IfAction, cv.All({
|
@register_action(
|
||||||
cv.Required(CONF_CONDITION): validate_potentially_and_condition,
|
"if",
|
||||||
cv.Optional(CONF_THEN): validate_action_list,
|
IfAction,
|
||||||
cv.Optional(CONF_ELSE): validate_action_list,
|
cv.All(
|
||||||
}, cv.has_at_least_one_key(CONF_THEN, CONF_ELSE)))
|
{
|
||||||
def if_action_to_code(config, action_id, template_arg, args):
|
cv.Required(CONF_CONDITION): validate_potentially_and_condition,
|
||||||
conditions = yield build_condition(config[CONF_CONDITION], template_arg, args)
|
cv.Optional(CONF_THEN): validate_action_list,
|
||||||
|
cv.Optional(CONF_ELSE): validate_action_list,
|
||||||
|
},
|
||||||
|
cv.has_at_least_one_key(CONF_THEN, CONF_ELSE),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
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)
|
var = cg.new_Pvariable(action_id, template_arg, conditions)
|
||||||
if CONF_THEN in config:
|
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))
|
cg.add(var.add_then(actions))
|
||||||
if CONF_ELSE in config:
|
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))
|
cg.add(var.add_else(actions))
|
||||||
yield var
|
return var
|
||||||
|
|
||||||
|
|
||||||
@register_action('while', WhileAction, cv.Schema({
|
@register_action(
|
||||||
cv.Required(CONF_CONDITION): validate_potentially_and_condition,
|
"while",
|
||||||
cv.Required(CONF_THEN): validate_action_list,
|
WhileAction,
|
||||||
}))
|
cv.Schema(
|
||||||
def while_action_to_code(config, action_id, template_arg, args):
|
{
|
||||||
conditions = yield build_condition(config[CONF_CONDITION], template_arg, args)
|
cv.Required(CONF_CONDITION): validate_potentially_and_condition,
|
||||||
|
cv.Required(CONF_THEN): validate_action_list,
|
||||||
|
}
|
||||||
|
),
|
||||||
|
)
|
||||||
|
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)
|
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))
|
cg.add(var.add_then(actions))
|
||||||
yield var
|
return var
|
||||||
|
|
||||||
|
|
||||||
def validate_wait_until(value):
|
def validate_wait_until(value):
|
||||||
schema = cv.Schema({
|
schema = cv.Schema(
|
||||||
cv.Required(CONF_CONDITION): validate_potentially_and_condition,
|
{
|
||||||
})
|
cv.Required(CONF_CONDITION): validate_potentially_and_condition,
|
||||||
|
}
|
||||||
|
)
|
||||||
if isinstance(value, dict) and CONF_CONDITION in value:
|
if isinstance(value, dict) and CONF_CONDITION in value:
|
||||||
return schema(value)
|
return schema(value)
|
||||||
return validate_wait_until({CONF_CONDITION: value})
|
return validate_wait_until({CONF_CONDITION: value})
|
||||||
|
|
||||||
|
|
||||||
@register_action('wait_until', WaitUntilAction, validate_wait_until)
|
@register_action("wait_until", WaitUntilAction, validate_wait_until)
|
||||||
def wait_until_action_to_code(config, action_id, template_arg, args):
|
async def wait_until_action_to_code(config, action_id, template_arg, args):
|
||||||
conditions = yield build_condition(config[CONF_CONDITION], template_arg, args)
|
conditions = await build_condition(config[CONF_CONDITION], template_arg, args)
|
||||||
var = cg.new_Pvariable(action_id, template_arg, conditions)
|
var = cg.new_Pvariable(action_id, template_arg, conditions)
|
||||||
yield cg.register_component(var, {})
|
await cg.register_component(var, {})
|
||||||
yield var
|
return var
|
||||||
|
|
||||||
|
|
||||||
@register_action('lambda', LambdaAction, cv.lambda_)
|
@register_action("lambda", LambdaAction, cv.lambda_)
|
||||||
def lambda_action_to_code(config, action_id, template_arg, args):
|
async def lambda_action_to_code(config, action_id, template_arg, args):
|
||||||
lambda_ = yield cg.process_lambda(config, args, return_type=cg.void)
|
lambda_ = await cg.process_lambda(config, args, return_type=cg.void)
|
||||||
yield cg.new_Pvariable(action_id, template_arg, lambda_)
|
return cg.new_Pvariable(action_id, template_arg, lambda_)
|
||||||
|
|
||||||
|
|
||||||
@register_action('component.update', UpdateComponentAction, maybe_simple_id({
|
@register_action(
|
||||||
cv.Required(CONF_ID): cv.use_id(cg.PollingComponent),
|
"component.update",
|
||||||
}))
|
UpdateComponentAction,
|
||||||
def component_update_action_to_code(config, action_id, template_arg, args):
|
maybe_simple_id(
|
||||||
comp = yield cg.get_variable(config[CONF_ID])
|
{
|
||||||
yield cg.new_Pvariable(action_id, template_arg, comp)
|
cv.Required(CONF_ID): cv.use_id(cg.PollingComponent),
|
||||||
|
}
|
||||||
|
),
|
||||||
|
)
|
||||||
|
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
|
async def build_action(full_config, template_arg, args):
|
||||||
def build_action(full_config, template_arg, args):
|
registry_entry, config = cg.extract_registry_entry_config(
|
||||||
registry_entry, config = cg.extract_registry_entry_config(ACTION_REGISTRY, full_config)
|
ACTION_REGISTRY, full_config
|
||||||
|
)
|
||||||
action_id = full_config[CONF_TYPE_ID]
|
action_id = full_config[CONF_TYPE_ID]
|
||||||
builder = registry_entry.coroutine_fun
|
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
|
async def build_action_list(config, templ, arg_type):
|
||||||
def build_action_list(config, templ, arg_type):
|
|
||||||
actions = []
|
actions = []
|
||||||
for conf in config:
|
for conf in config:
|
||||||
action = yield build_action(conf, templ, arg_type)
|
action = await build_action(conf, templ, arg_type)
|
||||||
actions.append(action)
|
actions.append(action)
|
||||||
yield actions
|
return actions
|
||||||
|
|
||||||
|
|
||||||
@coroutine
|
async def build_condition(full_config, template_arg, args):
|
||||||
def build_condition(full_config, template_arg, args):
|
registry_entry, config = cg.extract_registry_entry_config(
|
||||||
registry_entry, config = cg.extract_registry_entry_config(CONDITION_REGISTRY, full_config)
|
CONDITION_REGISTRY, full_config
|
||||||
|
)
|
||||||
action_id = full_config[CONF_TYPE_ID]
|
action_id = full_config[CONF_TYPE_ID]
|
||||||
builder = registry_entry.coroutine_fun
|
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
|
async def build_condition_list(config, templ, args):
|
||||||
def build_condition_list(config, templ, args):
|
|
||||||
conditions = []
|
conditions = []
|
||||||
for conf in config:
|
for conf in config:
|
||||||
condition = yield build_condition(conf, templ, args)
|
condition = await build_condition(conf, templ, args)
|
||||||
conditions.append(condition)
|
conditions.append(condition)
|
||||||
yield conditions
|
return conditions
|
||||||
|
|
||||||
|
|
||||||
@coroutine
|
async def build_automation(trigger, args, config):
|
||||||
def build_automation(trigger, args, config):
|
|
||||||
arg_types = [arg[0] for arg in args]
|
arg_types = [arg[0] for arg in args]
|
||||||
templ = cg.TemplateArguments(*arg_types)
|
templ = cg.TemplateArguments(*arg_types)
|
||||||
obj = cg.new_Pvariable(config[CONF_AUTOMATION_ID], templ, trigger)
|
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))
|
cg.add(obj.add_actions(actions))
|
||||||
yield obj
|
return obj
|
||||||
|
|||||||
@@ -9,18 +9,72 @@
|
|||||||
|
|
||||||
# pylint: disable=unused-import
|
# pylint: disable=unused-import
|
||||||
from esphome.cpp_generator import ( # noqa
|
from esphome.cpp_generator import ( # noqa
|
||||||
Expression, RawExpression, RawStatement, TemplateArguments,
|
Expression,
|
||||||
StructInitializer, ArrayInitializer, safe_exp, Statement, LineComment,
|
RawExpression,
|
||||||
progmem_array, statement, variable, Pvariable, new_Pvariable,
|
RawStatement,
|
||||||
add, add_global, add_library, add_build_flag, add_define,
|
TemplateArguments,
|
||||||
get_variable, get_variable_with_full_id, process_lambda, is_template, templatable, MockObj,
|
StructInitializer,
|
||||||
MockObjClass)
|
ArrayInitializer,
|
||||||
|
safe_exp,
|
||||||
|
Statement,
|
||||||
|
LineComment,
|
||||||
|
progmem_array,
|
||||||
|
static_const_array,
|
||||||
|
statement,
|
||||||
|
variable,
|
||||||
|
new_variable,
|
||||||
|
Pvariable,
|
||||||
|
new_Pvariable,
|
||||||
|
add,
|
||||||
|
add_global,
|
||||||
|
add_library,
|
||||||
|
add_build_flag,
|
||||||
|
add_define,
|
||||||
|
get_variable,
|
||||||
|
get_variable_with_full_id,
|
||||||
|
process_lambda,
|
||||||
|
is_template,
|
||||||
|
templatable,
|
||||||
|
MockObj,
|
||||||
|
MockObjClass,
|
||||||
|
)
|
||||||
from esphome.cpp_helpers import ( # noqa
|
from esphome.cpp_helpers import ( # noqa
|
||||||
gpio_pin_expression, register_component, build_registry_entry,
|
gpio_pin_expression,
|
||||||
build_registry_list, extract_registry_entry_config, register_parented)
|
register_component,
|
||||||
|
build_registry_entry,
|
||||||
|
build_registry_list,
|
||||||
|
extract_registry_entry_config,
|
||||||
|
register_parented,
|
||||||
|
)
|
||||||
from esphome.cpp_types import ( # noqa
|
from esphome.cpp_types import ( # noqa
|
||||||
global_ns, void, nullptr, float_, double, bool_, int_, std_ns, std_string,
|
global_ns,
|
||||||
std_vector, uint8, uint16, uint32, int32, const_char_ptr, NAN,
|
void,
|
||||||
esphome_ns, App, Nameable, Component, ComponentPtr,
|
nullptr,
|
||||||
PollingComponent, Application, optional, arduino_json_ns, JsonObject,
|
float_,
|
||||||
JsonObjectRef, JsonObjectConstRef, Controller, GPIOPin)
|
double,
|
||||||
|
bool_,
|
||||||
|
int_,
|
||||||
|
std_ns,
|
||||||
|
std_string,
|
||||||
|
std_vector,
|
||||||
|
uint8,
|
||||||
|
uint16,
|
||||||
|
uint32,
|
||||||
|
int32,
|
||||||
|
const_char_ptr,
|
||||||
|
NAN,
|
||||||
|
esphome_ns,
|
||||||
|
App,
|
||||||
|
Nameable,
|
||||||
|
Component,
|
||||||
|
ComponentPtr,
|
||||||
|
PollingComponent,
|
||||||
|
Application,
|
||||||
|
optional,
|
||||||
|
arduino_json_ns,
|
||||||
|
JsonObject,
|
||||||
|
JsonObjectRef,
|
||||||
|
JsonObjectConstRef,
|
||||||
|
Controller,
|
||||||
|
GPIOPin,
|
||||||
|
)
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ void A4988::setup() {
|
|||||||
if (this->sleep_pin_ != nullptr) {
|
if (this->sleep_pin_ != nullptr) {
|
||||||
this->sleep_pin_->setup();
|
this->sleep_pin_->setup();
|
||||||
this->sleep_pin_->digital_write(false);
|
this->sleep_pin_->digital_write(false);
|
||||||
|
this->sleep_pin_state_ = false;
|
||||||
}
|
}
|
||||||
this->step_pin_->setup();
|
this->step_pin_->setup();
|
||||||
this->step_pin_->digital_write(false);
|
this->step_pin_->digital_write(false);
|
||||||
@@ -27,7 +28,12 @@ void A4988::dump_config() {
|
|||||||
void A4988::loop() {
|
void A4988::loop() {
|
||||||
bool at_target = this->has_reached_target();
|
bool at_target = this->has_reached_target();
|
||||||
if (this->sleep_pin_ != nullptr) {
|
if (this->sleep_pin_ != nullptr) {
|
||||||
|
bool sleep_rising_edge = !sleep_pin_state_ & !at_target;
|
||||||
this->sleep_pin_->digital_write(!at_target);
|
this->sleep_pin_->digital_write(!at_target);
|
||||||
|
this->sleep_pin_state_ = !at_target;
|
||||||
|
if (sleep_rising_edge) {
|
||||||
|
delayMicroseconds(1000);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (at_target) {
|
if (at_target) {
|
||||||
this->high_freq_.stop();
|
this->high_freq_.stop();
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ class A4988 : public stepper::Stepper, public Component {
|
|||||||
GPIOPin *step_pin_;
|
GPIOPin *step_pin_;
|
||||||
GPIOPin *dir_pin_;
|
GPIOPin *dir_pin_;
|
||||||
GPIOPin *sleep_pin_{nullptr};
|
GPIOPin *sleep_pin_{nullptr};
|
||||||
|
bool sleep_pin_state_;
|
||||||
HighFrequencyLoopRequester high_freq_;
|
HighFrequencyLoopRequester high_freq_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -5,27 +5,29 @@ import esphome.codegen as cg
|
|||||||
from esphome.const import CONF_DIR_PIN, CONF_ID, CONF_SLEEP_PIN, CONF_STEP_PIN
|
from esphome.const import CONF_DIR_PIN, CONF_ID, CONF_SLEEP_PIN, CONF_STEP_PIN
|
||||||
|
|
||||||
|
|
||||||
a4988_ns = cg.esphome_ns.namespace('a4988')
|
a4988_ns = cg.esphome_ns.namespace("a4988")
|
||||||
A4988 = a4988_ns.class_('A4988', stepper.Stepper, cg.Component)
|
A4988 = a4988_ns.class_("A4988", stepper.Stepper, cg.Component)
|
||||||
|
|
||||||
CONFIG_SCHEMA = stepper.STEPPER_SCHEMA.extend({
|
CONFIG_SCHEMA = stepper.STEPPER_SCHEMA.extend(
|
||||||
cv.Required(CONF_ID): cv.declare_id(A4988),
|
{
|
||||||
cv.Required(CONF_STEP_PIN): pins.gpio_output_pin_schema,
|
cv.Required(CONF_ID): cv.declare_id(A4988),
|
||||||
cv.Required(CONF_DIR_PIN): pins.gpio_output_pin_schema,
|
cv.Required(CONF_STEP_PIN): pins.gpio_output_pin_schema,
|
||||||
cv.Optional(CONF_SLEEP_PIN): pins.gpio_output_pin_schema,
|
cv.Required(CONF_DIR_PIN): pins.gpio_output_pin_schema,
|
||||||
}).extend(cv.COMPONENT_SCHEMA)
|
cv.Optional(CONF_SLEEP_PIN): pins.gpio_output_pin_schema,
|
||||||
|
}
|
||||||
|
).extend(cv.COMPONENT_SCHEMA)
|
||||||
|
|
||||||
|
|
||||||
def to_code(config):
|
async def to_code(config):
|
||||||
var = cg.new_Pvariable(config[CONF_ID])
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
yield cg.register_component(var, config)
|
await cg.register_component(var, config)
|
||||||
yield stepper.register_stepper(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))
|
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))
|
cg.add(var.set_dir_pin(dir_pin))
|
||||||
|
|
||||||
if CONF_SLEEP_PIN in config:
|
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))
|
cg.add(var.set_sleep_pin(sleep_pin))
|
||||||
|
|||||||
@@ -4,42 +4,46 @@ from esphome import pins
|
|||||||
from esphome.components import output
|
from esphome.components import output
|
||||||
from esphome.const import CONF_ID, CONF_MIN_POWER, CONF_METHOD
|
from esphome.const import CONF_ID, CONF_MIN_POWER, CONF_METHOD
|
||||||
|
|
||||||
CODEOWNERS = ['@glmnet']
|
CODEOWNERS = ["@glmnet"]
|
||||||
|
|
||||||
ac_dimmer_ns = cg.esphome_ns.namespace('ac_dimmer')
|
ac_dimmer_ns = cg.esphome_ns.namespace("ac_dimmer")
|
||||||
AcDimmer = ac_dimmer_ns.class_('AcDimmer', output.FloatOutput, cg.Component)
|
AcDimmer = ac_dimmer_ns.class_("AcDimmer", output.FloatOutput, cg.Component)
|
||||||
|
|
||||||
DimMethod = ac_dimmer_ns.enum('DimMethod')
|
DimMethod = ac_dimmer_ns.enum("DimMethod")
|
||||||
DIM_METHODS = {
|
DIM_METHODS = {
|
||||||
'LEADING_PULSE': DimMethod.DIM_METHOD_LEADING_PULSE,
|
"LEADING_PULSE": DimMethod.DIM_METHOD_LEADING_PULSE,
|
||||||
'LEADING': DimMethod.DIM_METHOD_LEADING,
|
"LEADING": DimMethod.DIM_METHOD_LEADING,
|
||||||
'TRAILING': DimMethod.DIM_METHOD_TRAILING,
|
"TRAILING": DimMethod.DIM_METHOD_TRAILING,
|
||||||
}
|
}
|
||||||
|
|
||||||
CONF_GATE_PIN = 'gate_pin'
|
CONF_GATE_PIN = "gate_pin"
|
||||||
CONF_ZERO_CROSS_PIN = 'zero_cross_pin'
|
CONF_ZERO_CROSS_PIN = "zero_cross_pin"
|
||||||
CONF_INIT_WITH_HALF_CYCLE = 'init_with_half_cycle'
|
CONF_INIT_WITH_HALF_CYCLE = "init_with_half_cycle"
|
||||||
CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend({
|
CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend(
|
||||||
cv.Required(CONF_ID): cv.declare_id(AcDimmer),
|
{
|
||||||
cv.Required(CONF_GATE_PIN): pins.internal_gpio_output_pin_schema,
|
cv.Required(CONF_ID): cv.declare_id(AcDimmer),
|
||||||
cv.Required(CONF_ZERO_CROSS_PIN): pins.internal_gpio_input_pin_schema,
|
cv.Required(CONF_GATE_PIN): pins.internal_gpio_output_pin_schema,
|
||||||
cv.Optional(CONF_INIT_WITH_HALF_CYCLE, default=True): cv.boolean,
|
cv.Required(CONF_ZERO_CROSS_PIN): pins.internal_gpio_input_pin_schema,
|
||||||
cv.Optional(CONF_METHOD, default='leading pulse'): cv.enum(DIM_METHODS, upper=True, space='_'),
|
cv.Optional(CONF_INIT_WITH_HALF_CYCLE, default=True): cv.boolean,
|
||||||
}).extend(cv.COMPONENT_SCHEMA)
|
cv.Optional(CONF_METHOD, default="leading pulse"): cv.enum(
|
||||||
|
DIM_METHODS, upper=True, space="_"
|
||||||
|
),
|
||||||
|
}
|
||||||
|
).extend(cv.COMPONENT_SCHEMA)
|
||||||
|
|
||||||
|
|
||||||
def to_code(config):
|
async def to_code(config):
|
||||||
var = cg.new_Pvariable(config[CONF_ID])
|
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%
|
# override default min power to 10%
|
||||||
if CONF_MIN_POWER not in config:
|
if CONF_MIN_POWER not in config:
|
||||||
config[CONF_MIN_POWER] = 0.1
|
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))
|
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_zero_cross_pin(pin))
|
||||||
cg.add(var.set_init_with_half_cycle(config[CONF_INIT_WITH_HALF_CYCLE]))
|
cg.add(var.set_init_with_half_cycle(config[CONF_INIT_WITH_HALF_CYCLE]))
|
||||||
cg.add(var.set_method(config[CONF_METHOD]))
|
cg.add(var.set_method(config[CONF_METHOD]))
|
||||||
|
|||||||
@@ -5,20 +5,23 @@ from esphome.components.light.types import AddressableLightEffect
|
|||||||
from esphome.components.light.effects import register_addressable_effect
|
from esphome.components.light.effects import register_addressable_effect
|
||||||
from esphome.const import CONF_NAME, CONF_UART_ID
|
from esphome.const import CONF_NAME, CONF_UART_ID
|
||||||
|
|
||||||
DEPENDENCIES = ['uart']
|
DEPENDENCIES = ["uart"]
|
||||||
|
|
||||||
adalight_ns = cg.esphome_ns.namespace('adalight')
|
adalight_ns = cg.esphome_ns.namespace("adalight")
|
||||||
AdalightLightEffect = adalight_ns.class_(
|
AdalightLightEffect = adalight_ns.class_(
|
||||||
'AdalightLightEffect', uart.UARTDevice, AddressableLightEffect)
|
"AdalightLightEffect", uart.UARTDevice, AddressableLightEffect
|
||||||
|
)
|
||||||
|
|
||||||
CONFIG_SCHEMA = cv.Schema({})
|
CONFIG_SCHEMA = cv.Schema({})
|
||||||
|
|
||||||
|
|
||||||
@register_addressable_effect('adalight', AdalightLightEffect, "Adalight", {
|
@register_addressable_effect(
|
||||||
cv.GenerateID(CONF_UART_ID): cv.use_id(uart.UARTComponent)
|
"adalight",
|
||||||
})
|
AdalightLightEffect,
|
||||||
def adalight_light_effect_to_code(config, effect_id):
|
"Adalight",
|
||||||
|
{cv.GenerateID(CONF_UART_ID): cv.use_id(uart.UARTComponent)},
|
||||||
|
)
|
||||||
|
async def adalight_light_effect_to_code(config, effect_id):
|
||||||
effect = cg.new_Pvariable(effect_id, config[CONF_NAME])
|
effect = cg.new_Pvariable(effect_id, config[CONF_NAME])
|
||||||
yield uart.register_uart_device(effect, config)
|
await uart.register_uart_device(effect, config)
|
||||||
|
return effect
|
||||||
yield effect
|
|
||||||
|
|||||||
@@ -42,11 +42,11 @@ void AdalightLightEffect::reset_frame_(light::AddressableLight &it) {
|
|||||||
|
|
||||||
void AdalightLightEffect::blank_all_leds_(light::AddressableLight &it) {
|
void AdalightLightEffect::blank_all_leds_(light::AddressableLight &it) {
|
||||||
for (int led = it.size(); led-- > 0;) {
|
for (int led = it.size(); led-- > 0;) {
|
||||||
it[led].set(light::ESPColor::BLACK);
|
it[led].set(COLOR_BLACK);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AdalightLightEffect::apply(light::AddressableLight &it, const light::ESPColor ¤t_color) {
|
void AdalightLightEffect::apply(light::AddressableLight &it, const Color ¤t_color) {
|
||||||
const uint32_t now = millis();
|
const uint32_t now = millis();
|
||||||
|
|
||||||
if (now - this->last_ack_ >= ADALIGHT_ACK_INTERVAL) {
|
if (now - this->last_ack_ >= ADALIGHT_ACK_INTERVAL) {
|
||||||
@@ -130,7 +130,7 @@ AdalightLightEffect::Frame AdalightLightEffect::parse_frame_(light::AddressableL
|
|||||||
for (int led = 0; led < accepted_led_count; led++, led_data += 3) {
|
for (int led = 0; led < accepted_led_count; led++, led_data += 3) {
|
||||||
auto white = std::min(std::min(led_data[0], led_data[1]), led_data[2]);
|
auto white = std::min(std::min(led_data[0], led_data[1]), led_data[2]);
|
||||||
|
|
||||||
it[led].set(light::ESPColor(led_data[0], led_data[1], led_data[2], white));
|
it[led].set(Color(led_data[0], led_data[1], led_data[2], white));
|
||||||
}
|
}
|
||||||
|
|
||||||
return CONSUMED;
|
return CONSUMED;
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ class AdalightLightEffect : public light::AddressableLightEffect, public uart::U
|
|||||||
public:
|
public:
|
||||||
void start() override;
|
void start() override;
|
||||||
void stop() override;
|
void stop() override;
|
||||||
void apply(light::AddressableLight &it, const light::ESPColor ¤t_color) override;
|
void apply(light::AddressableLight &it, const Color ¤t_color) override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
enum Frame {
|
enum Frame {
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
CODEOWNERS = ['@esphome/core']
|
CODEOWNERS = ["@esphome/core"]
|
||||||
|
|||||||
@@ -16,7 +16,9 @@ void ADCSensor::set_attenuation(adc_attenuation_t attenuation) { this->attenuati
|
|||||||
|
|
||||||
void ADCSensor::setup() {
|
void ADCSensor::setup() {
|
||||||
ESP_LOGCONFIG(TAG, "Setting up ADC '%s'...", this->get_name().c_str());
|
ESP_LOGCONFIG(TAG, "Setting up ADC '%s'...", this->get_name().c_str());
|
||||||
|
#ifndef USE_ADC_SENSOR_VCC
|
||||||
GPIOPin(this->pin_, INPUT).setup();
|
GPIOPin(this->pin_, INPUT).setup();
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef ARDUINO_ARCH_ESP32
|
#ifdef ARDUINO_ARCH_ESP32
|
||||||
analogSetPinAttenuation(this->pin_, this->attenuation_);
|
analogSetPinAttenuation(this->pin_, this->attenuation_);
|
||||||
|
|||||||
@@ -2,45 +2,63 @@ import esphome.codegen as cg
|
|||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome import pins
|
from esphome import pins
|
||||||
from esphome.components import sensor, voltage_sampler
|
from esphome.components import sensor, voltage_sampler
|
||||||
from esphome.const import CONF_ATTENUATION, CONF_ID, CONF_PIN, ICON_FLASH, UNIT_VOLT
|
from esphome.const import (
|
||||||
|
CONF_ATTENUATION,
|
||||||
|
CONF_ID,
|
||||||
|
CONF_PIN,
|
||||||
|
DEVICE_CLASS_VOLTAGE,
|
||||||
|
ICON_EMPTY,
|
||||||
|
STATE_CLASS_MEASUREMENT,
|
||||||
|
UNIT_VOLT,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
AUTO_LOAD = ['voltage_sampler']
|
AUTO_LOAD = ["voltage_sampler"]
|
||||||
|
|
||||||
ATTENUATION_MODES = {
|
ATTENUATION_MODES = {
|
||||||
'0db': cg.global_ns.ADC_0db,
|
"0db": cg.global_ns.ADC_0db,
|
||||||
'2.5db': cg.global_ns.ADC_2_5db,
|
"2.5db": cg.global_ns.ADC_2_5db,
|
||||||
'6db': cg.global_ns.ADC_6db,
|
"6db": cg.global_ns.ADC_6db,
|
||||||
'11db': cg.global_ns.ADC_11db,
|
"11db": cg.global_ns.ADC_11db,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def validate_adc_pin(value):
|
def validate_adc_pin(value):
|
||||||
vcc = str(value).upper()
|
vcc = str(value).upper()
|
||||||
if vcc == 'VCC':
|
if vcc == "VCC":
|
||||||
return cv.only_on_esp8266(vcc)
|
return cv.only_on_esp8266(vcc)
|
||||||
return pins.analog_pin(value)
|
return pins.analog_pin(value)
|
||||||
|
|
||||||
|
|
||||||
adc_ns = cg.esphome_ns.namespace('adc')
|
adc_ns = cg.esphome_ns.namespace("adc")
|
||||||
ADCSensor = adc_ns.class_('ADCSensor', sensor.Sensor, cg.PollingComponent,
|
ADCSensor = adc_ns.class_(
|
||||||
voltage_sampler.VoltageSampler)
|
"ADCSensor", sensor.Sensor, cg.PollingComponent, voltage_sampler.VoltageSampler
|
||||||
|
)
|
||||||
|
|
||||||
CONFIG_SCHEMA = sensor.sensor_schema(UNIT_VOLT, ICON_FLASH, 2).extend({
|
CONFIG_SCHEMA = (
|
||||||
cv.GenerateID(): cv.declare_id(ADCSensor),
|
sensor.sensor_schema(
|
||||||
cv.Required(CONF_PIN): validate_adc_pin,
|
UNIT_VOLT, ICON_EMPTY, 2, DEVICE_CLASS_VOLTAGE, STATE_CLASS_MEASUREMENT
|
||||||
cv.SplitDefault(CONF_ATTENUATION, esp32='0db'):
|
)
|
||||||
cv.All(cv.only_on_esp32, cv.enum(ATTENUATION_MODES, lower=True)),
|
.extend(
|
||||||
}).extend(cv.polling_component_schema('60s'))
|
{
|
||||||
|
cv.GenerateID(): cv.declare_id(ADCSensor),
|
||||||
|
cv.Required(CONF_PIN): validate_adc_pin,
|
||||||
|
cv.SplitDefault(CONF_ATTENUATION, esp32="0db"): cv.All(
|
||||||
|
cv.only_on_esp32, cv.enum(ATTENUATION_MODES, lower=True)
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.extend(cv.polling_component_schema("60s"))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def to_code(config):
|
async def to_code(config):
|
||||||
var = cg.new_Pvariable(config[CONF_ID])
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
yield cg.register_component(var, config)
|
await cg.register_component(var, config)
|
||||||
yield sensor.register_sensor(var, config)
|
await sensor.register_sensor(var, config)
|
||||||
|
|
||||||
if config[CONF_PIN] == 'VCC':
|
if config[CONF_PIN] == "VCC":
|
||||||
cg.add_define('USE_ADC_SENSOR_VCC')
|
cg.add_define("USE_ADC_SENSOR_VCC")
|
||||||
else:
|
else:
|
||||||
cg.add(var.set_pin(config[CONF_PIN]))
|
cg.add(var.set_pin(config[CONF_PIN]))
|
||||||
|
|
||||||
|
|||||||
0
esphome/components/addressable_light/__init__.py
Normal file
0
esphome/components/addressable_light/__init__.py
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
#include "addressable_light_display.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace addressable_light {
|
||||||
|
|
||||||
|
static const char* TAG = "addressable_light.display";
|
||||||
|
|
||||||
|
int AddressableLightDisplay::get_width_internal() { return this->width_; }
|
||||||
|
int AddressableLightDisplay::get_height_internal() { return this->height_; }
|
||||||
|
|
||||||
|
void AddressableLightDisplay::setup() {
|
||||||
|
this->addressable_light_buffer_.resize(this->width_ * this->height_, {0, 0, 0, 0});
|
||||||
|
}
|
||||||
|
|
||||||
|
void AddressableLightDisplay::update() {
|
||||||
|
if (!this->enabled_)
|
||||||
|
return;
|
||||||
|
|
||||||
|
this->do_update_();
|
||||||
|
this->display();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AddressableLightDisplay::display() {
|
||||||
|
bool dirty = false;
|
||||||
|
uint8_t old_r, old_g, old_b, old_w;
|
||||||
|
Color* c;
|
||||||
|
|
||||||
|
for (uint32_t offset = 0; offset < this->addressable_light_buffer_.size(); offset++) {
|
||||||
|
c = &(this->addressable_light_buffer_[offset]);
|
||||||
|
|
||||||
|
light::ESPColorView pixel = (*this->light_)[offset];
|
||||||
|
|
||||||
|
// Track the original values for the pixel view. If it has changed updating, then
|
||||||
|
// we trigger a redraw. Avoiding redraws == avoiding flicker!
|
||||||
|
old_r = pixel.get_red();
|
||||||
|
old_g = pixel.get_green();
|
||||||
|
old_b = pixel.get_blue();
|
||||||
|
old_w = pixel.get_white();
|
||||||
|
|
||||||
|
pixel.set_rgbw(c->r, c->g, c->b, c->w);
|
||||||
|
|
||||||
|
// If the actual value of the pixel changed, then schedule a redraw.
|
||||||
|
if (pixel.get_red() != old_r || pixel.get_green() != old_g || pixel.get_blue() != old_b ||
|
||||||
|
pixel.get_white() != old_w) {
|
||||||
|
dirty = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dirty) {
|
||||||
|
this->light_->schedule_show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void HOT AddressableLightDisplay::draw_absolute_pixel_internal(int x, int y, Color color) {
|
||||||
|
if (x >= this->get_width_internal() || x < 0 || y >= this->get_height_internal() || y < 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (this->pixel_mapper_f_.has_value()) {
|
||||||
|
// Params are passed by reference, so they may be modified in call.
|
||||||
|
this->addressable_light_buffer_[(*this->pixel_mapper_f_)(x, y)] = color;
|
||||||
|
} else {
|
||||||
|
this->addressable_light_buffer_[y * this->get_width_internal() + x] = color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // namespace addressable_light
|
||||||
|
} // namespace esphome
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
#include "esphome/core/color.h"
|
||||||
|
#include "esphome/components/display/display_buffer.h"
|
||||||
|
#include "esphome/components/light/addressable_light.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace addressable_light {
|
||||||
|
|
||||||
|
class AddressableLightDisplay : public display::DisplayBuffer, public PollingComponent {
|
||||||
|
public:
|
||||||
|
light::AddressableLight *get_light() const { return this->light_; }
|
||||||
|
|
||||||
|
void set_width(int32_t width) { width_ = width; }
|
||||||
|
void set_height(int32_t height) { height_ = height; }
|
||||||
|
void set_light(light::LightState *state) {
|
||||||
|
light_state_ = state;
|
||||||
|
light_ = static_cast<light::AddressableLight *>(state->get_output());
|
||||||
|
}
|
||||||
|
void set_enabled(bool enabled) {
|
||||||
|
if (light_state_) {
|
||||||
|
if (enabled_ && !enabled) { // enabled -> disabled
|
||||||
|
// - Tell the parent light to refresh, effectively wiping the display. Also
|
||||||
|
// restores the previous effect (if any).
|
||||||
|
light_state_->make_call().set_effect(this->last_effect_).perform();
|
||||||
|
|
||||||
|
} else if (!enabled_ && enabled) { // disabled -> enabled
|
||||||
|
// - Save the current effect.
|
||||||
|
this->last_effect_ = light_state_->get_effect_name();
|
||||||
|
// - Disable any current effect.
|
||||||
|
light_state_->make_call().set_effect(0).perform();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
enabled_ = enabled;
|
||||||
|
}
|
||||||
|
bool get_enabled() { return enabled_; }
|
||||||
|
|
||||||
|
void set_pixel_mapper(std::function<int(int, int)> &&pixel_mapper_f) { this->pixel_mapper_f_ = pixel_mapper_f; }
|
||||||
|
void setup() override;
|
||||||
|
void display();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
int get_width_internal() override;
|
||||||
|
int get_height_internal() override;
|
||||||
|
void draw_absolute_pixel_internal(int x, int y, Color color) override;
|
||||||
|
void update() override;
|
||||||
|
|
||||||
|
light::LightState *light_state_;
|
||||||
|
light::AddressableLight *light_;
|
||||||
|
bool enabled_{true};
|
||||||
|
int32_t width_;
|
||||||
|
int32_t height_;
|
||||||
|
std::vector<Color> addressable_light_buffer_;
|
||||||
|
optional<std::string> last_effect_;
|
||||||
|
optional<std::function<int(int, int)>> pixel_mapper_f_;
|
||||||
|
};
|
||||||
|
} // namespace addressable_light
|
||||||
|
} // namespace esphome
|
||||||
63
esphome/components/addressable_light/display.py
Normal file
63
esphome/components/addressable_light/display.py
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
import esphome.codegen as cg
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
from esphome.components import display, light
|
||||||
|
from esphome.const import (
|
||||||
|
CONF_ID,
|
||||||
|
CONF_LAMBDA,
|
||||||
|
CONF_PAGES,
|
||||||
|
CONF_ADDRESSABLE_LIGHT_ID,
|
||||||
|
CONF_HEIGHT,
|
||||||
|
CONF_WIDTH,
|
||||||
|
CONF_UPDATE_INTERVAL,
|
||||||
|
CONF_PIXEL_MAPPER,
|
||||||
|
)
|
||||||
|
|
||||||
|
CODEOWNERS = ["@justfalter"]
|
||||||
|
|
||||||
|
addressable_light_ns = cg.esphome_ns.namespace("addressable_light")
|
||||||
|
AddressableLightDisplay = addressable_light_ns.class_(
|
||||||
|
"AddressableLightDisplay", display.DisplayBuffer, cg.PollingComponent
|
||||||
|
)
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = cv.All(
|
||||||
|
display.FULL_DISPLAY_SCHEMA.extend(
|
||||||
|
{
|
||||||
|
cv.GenerateID(): cv.declare_id(AddressableLightDisplay),
|
||||||
|
cv.Required(CONF_ADDRESSABLE_LIGHT_ID): cv.use_id(
|
||||||
|
light.AddressableLightState
|
||||||
|
),
|
||||||
|
cv.Required(CONF_WIDTH): cv.positive_int,
|
||||||
|
cv.Required(CONF_HEIGHT): cv.positive_int,
|
||||||
|
cv.Optional(
|
||||||
|
CONF_UPDATE_INTERVAL, default="16ms"
|
||||||
|
): cv.positive_time_period_milliseconds,
|
||||||
|
cv.Optional(CONF_PIXEL_MAPPER): cv.returning_lambda,
|
||||||
|
}
|
||||||
|
),
|
||||||
|
cv.has_at_most_one_key(CONF_PAGES, CONF_LAMBDA),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def to_code(config):
|
||||||
|
var = cg.new_Pvariable(config[CONF_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))
|
||||||
|
|
||||||
|
await cg.register_component(var, config)
|
||||||
|
await display.register_display(var, config)
|
||||||
|
|
||||||
|
if CONF_PIXEL_MAPPER in config:
|
||||||
|
pixel_mapper_template_ = await cg.process_lambda(
|
||||||
|
config[CONF_PIXEL_MAPPER],
|
||||||
|
[(int, "x"), (int, "y")],
|
||||||
|
return_type=cg.int_,
|
||||||
|
)
|
||||||
|
cg.add(var.set_pixel_mapper(pixel_mapper_template_))
|
||||||
|
|
||||||
|
if CONF_LAMBDA in config:
|
||||||
|
lambda_ = await cg.process_lambda(
|
||||||
|
config[CONF_LAMBDA], [(display.DisplayBufferRef, "it")], return_type=cg.void
|
||||||
|
)
|
||||||
|
cg.add(var.set_writer(lambda_))
|
||||||
@@ -2,43 +2,82 @@ import esphome.codegen as cg
|
|||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.components import sensor, i2c
|
from esphome.components import sensor, i2c
|
||||||
from esphome import pins
|
from esphome import pins
|
||||||
from esphome.const import CONF_ID, CONF_VOLTAGE, \
|
from esphome.const import (
|
||||||
UNIT_VOLT, ICON_FLASH, UNIT_AMPERE, UNIT_WATT
|
CONF_ID,
|
||||||
|
CONF_VOLTAGE,
|
||||||
|
DEVICE_CLASS_CURRENT,
|
||||||
|
DEVICE_CLASS_POWER,
|
||||||
|
DEVICE_CLASS_VOLTAGE,
|
||||||
|
ICON_EMPTY,
|
||||||
|
STATE_CLASS_MEASUREMENT,
|
||||||
|
UNIT_VOLT,
|
||||||
|
UNIT_AMPERE,
|
||||||
|
UNIT_WATT,
|
||||||
|
)
|
||||||
|
|
||||||
DEPENDENCIES = ['i2c']
|
DEPENDENCIES = ["i2c"]
|
||||||
|
|
||||||
ade7953_ns = cg.esphome_ns.namespace('ade7953')
|
ade7953_ns = cg.esphome_ns.namespace("ade7953")
|
||||||
ADE7953 = ade7953_ns.class_('ADE7953', cg.PollingComponent, i2c.I2CDevice)
|
ADE7953 = ade7953_ns.class_("ADE7953", cg.PollingComponent, i2c.I2CDevice)
|
||||||
|
|
||||||
CONF_IRQ_PIN = 'irq_pin'
|
CONF_IRQ_PIN = "irq_pin"
|
||||||
CONF_CURRENT_A = 'current_a'
|
CONF_CURRENT_A = "current_a"
|
||||||
CONF_CURRENT_B = 'current_b'
|
CONF_CURRENT_B = "current_b"
|
||||||
CONF_ACTIVE_POWER_A = 'active_power_a'
|
CONF_ACTIVE_POWER_A = "active_power_a"
|
||||||
CONF_ACTIVE_POWER_B = 'active_power_b'
|
CONF_ACTIVE_POWER_B = "active_power_b"
|
||||||
|
|
||||||
CONFIG_SCHEMA = cv.Schema({
|
CONFIG_SCHEMA = (
|
||||||
cv.GenerateID(): cv.declare_id(ADE7953),
|
cv.Schema(
|
||||||
cv.Optional(CONF_IRQ_PIN): pins.input_pin,
|
{
|
||||||
cv.Optional(CONF_VOLTAGE): sensor.sensor_schema(UNIT_VOLT, ICON_FLASH, 1),
|
cv.GenerateID(): cv.declare_id(ADE7953),
|
||||||
cv.Optional(CONF_CURRENT_A): sensor.sensor_schema(UNIT_AMPERE, ICON_FLASH, 2),
|
cv.Optional(CONF_IRQ_PIN): pins.input_pin,
|
||||||
cv.Optional(CONF_CURRENT_B): sensor.sensor_schema(UNIT_AMPERE, ICON_FLASH, 2),
|
cv.Optional(CONF_VOLTAGE): sensor.sensor_schema(
|
||||||
cv.Optional(CONF_ACTIVE_POWER_A): sensor.sensor_schema(UNIT_WATT, ICON_FLASH, 1),
|
UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE, STATE_CLASS_MEASUREMENT
|
||||||
cv.Optional(CONF_ACTIVE_POWER_B): sensor.sensor_schema(UNIT_WATT, ICON_FLASH, 1),
|
),
|
||||||
}).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x38))
|
cv.Optional(CONF_CURRENT_A): sensor.sensor_schema(
|
||||||
|
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,
|
||||||
|
STATE_CLASS_MEASUREMENT,
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_ACTIVE_POWER_A): sensor.sensor_schema(
|
||||||
|
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, STATE_CLASS_MEASUREMENT
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.extend(cv.polling_component_schema("60s"))
|
||||||
|
.extend(i2c.i2c_device_schema(0x38))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def to_code(config):
|
async def to_code(config):
|
||||||
var = cg.new_Pvariable(config[CONF_ID])
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
yield cg.register_component(var, config)
|
await cg.register_component(var, config)
|
||||||
yield i2c.register_i2c_device(var, config)
|
await i2c.register_i2c_device(var, config)
|
||||||
|
|
||||||
if CONF_IRQ_PIN in config:
|
if CONF_IRQ_PIN in config:
|
||||||
cg.add(var.set_irq_pin(config[CONF_IRQ_PIN]))
|
cg.add(var.set_irq_pin(config[CONF_IRQ_PIN]))
|
||||||
|
|
||||||
for key in [CONF_VOLTAGE, CONF_CURRENT_A, CONF_CURRENT_B, CONF_ACTIVE_POWER_A,
|
for key in [
|
||||||
CONF_ACTIVE_POWER_B]:
|
CONF_VOLTAGE,
|
||||||
|
CONF_CURRENT_A,
|
||||||
|
CONF_CURRENT_B,
|
||||||
|
CONF_ACTIVE_POWER_A,
|
||||||
|
CONF_ACTIVE_POWER_B,
|
||||||
|
]:
|
||||||
if key not in config:
|
if key not in config:
|
||||||
continue
|
continue
|
||||||
conf = config[key]
|
conf = config[key]
|
||||||
sens = yield sensor.new_sensor(conf)
|
sens = await sensor.new_sensor(conf)
|
||||||
cg.add(getattr(var, f'set_{key}_sensor')(sens))
|
cg.add(getattr(var, f"set_{key}_sensor")(sens))
|
||||||
|
|||||||
@@ -3,23 +3,29 @@ import esphome.config_validation as cv
|
|||||||
from esphome.components import i2c
|
from esphome.components import i2c
|
||||||
from esphome.const import CONF_ID
|
from esphome.const import CONF_ID
|
||||||
|
|
||||||
DEPENDENCIES = ['i2c']
|
DEPENDENCIES = ["i2c"]
|
||||||
AUTO_LOAD = ['sensor', 'voltage_sampler']
|
AUTO_LOAD = ["sensor", "voltage_sampler"]
|
||||||
MULTI_CONF = True
|
MULTI_CONF = True
|
||||||
|
|
||||||
ads1115_ns = cg.esphome_ns.namespace('ads1115')
|
ads1115_ns = cg.esphome_ns.namespace("ads1115")
|
||||||
ADS1115Component = ads1115_ns.class_('ADS1115Component', cg.Component, i2c.I2CDevice)
|
ADS1115Component = ads1115_ns.class_("ADS1115Component", cg.Component, i2c.I2CDevice)
|
||||||
|
|
||||||
CONF_CONTINUOUS_MODE = 'continuous_mode'
|
CONF_CONTINUOUS_MODE = "continuous_mode"
|
||||||
CONFIG_SCHEMA = cv.Schema({
|
CONFIG_SCHEMA = (
|
||||||
cv.GenerateID(): cv.declare_id(ADS1115Component),
|
cv.Schema(
|
||||||
cv.Optional(CONF_CONTINUOUS_MODE, default=False): cv.boolean,
|
{
|
||||||
}).extend(cv.COMPONENT_SCHEMA).extend(i2c.i2c_device_schema(None))
|
cv.GenerateID(): cv.declare_id(ADS1115Component),
|
||||||
|
cv.Optional(CONF_CONTINUOUS_MODE, default=False): cv.boolean,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.extend(cv.COMPONENT_SCHEMA)
|
||||||
|
.extend(i2c.i2c_device_schema(None))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def to_code(config):
|
async def to_code(config):
|
||||||
var = cg.new_Pvariable(config[CONF_ID])
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
yield cg.register_component(var, config)
|
await cg.register_component(var, config)
|
||||||
yield i2c.register_i2c_device(var, config)
|
await i2c.register_i2c_device(var, config)
|
||||||
|
|
||||||
cg.add(var.set_continuous_mode(config[CONF_CONTINUOUS_MODE]))
|
cg.add(var.set_continuous_mode(config[CONF_CONTINUOUS_MODE]))
|
||||||
|
|||||||
@@ -1,60 +1,77 @@
|
|||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.components import sensor, voltage_sampler
|
from esphome.components import sensor, voltage_sampler
|
||||||
from esphome.const import CONF_GAIN, CONF_MULTIPLEXER, ICON_FLASH, UNIT_VOLT, CONF_ID
|
from esphome.const import (
|
||||||
|
CONF_GAIN,
|
||||||
|
CONF_MULTIPLEXER,
|
||||||
|
DEVICE_CLASS_VOLTAGE,
|
||||||
|
ICON_EMPTY,
|
||||||
|
STATE_CLASS_MEASUREMENT,
|
||||||
|
UNIT_VOLT,
|
||||||
|
CONF_ID,
|
||||||
|
)
|
||||||
from . import ads1115_ns, ADS1115Component
|
from . import ads1115_ns, ADS1115Component
|
||||||
|
|
||||||
DEPENDENCIES = ['ads1115']
|
DEPENDENCIES = ["ads1115"]
|
||||||
|
|
||||||
ADS1115Multiplexer = ads1115_ns.enum('ADS1115Multiplexer')
|
ADS1115Multiplexer = ads1115_ns.enum("ADS1115Multiplexer")
|
||||||
MUX = {
|
MUX = {
|
||||||
'A0_A1': ADS1115Multiplexer.ADS1115_MULTIPLEXER_P0_N1,
|
"A0_A1": ADS1115Multiplexer.ADS1115_MULTIPLEXER_P0_N1,
|
||||||
'A0_A3': ADS1115Multiplexer.ADS1115_MULTIPLEXER_P0_N3,
|
"A0_A3": ADS1115Multiplexer.ADS1115_MULTIPLEXER_P0_N3,
|
||||||
'A1_A3': ADS1115Multiplexer.ADS1115_MULTIPLEXER_P1_N3,
|
"A1_A3": ADS1115Multiplexer.ADS1115_MULTIPLEXER_P1_N3,
|
||||||
'A2_A3': ADS1115Multiplexer.ADS1115_MULTIPLEXER_P2_N3,
|
"A2_A3": ADS1115Multiplexer.ADS1115_MULTIPLEXER_P2_N3,
|
||||||
'A0_GND': ADS1115Multiplexer.ADS1115_MULTIPLEXER_P0_NG,
|
"A0_GND": ADS1115Multiplexer.ADS1115_MULTIPLEXER_P0_NG,
|
||||||
'A1_GND': ADS1115Multiplexer.ADS1115_MULTIPLEXER_P1_NG,
|
"A1_GND": ADS1115Multiplexer.ADS1115_MULTIPLEXER_P1_NG,
|
||||||
'A2_GND': ADS1115Multiplexer.ADS1115_MULTIPLEXER_P2_NG,
|
"A2_GND": ADS1115Multiplexer.ADS1115_MULTIPLEXER_P2_NG,
|
||||||
'A3_GND': ADS1115Multiplexer.ADS1115_MULTIPLEXER_P3_NG,
|
"A3_GND": ADS1115Multiplexer.ADS1115_MULTIPLEXER_P3_NG,
|
||||||
}
|
}
|
||||||
|
|
||||||
ADS1115Gain = ads1115_ns.enum('ADS1115Gain')
|
ADS1115Gain = ads1115_ns.enum("ADS1115Gain")
|
||||||
GAIN = {
|
GAIN = {
|
||||||
'6.144': ADS1115Gain.ADS1115_GAIN_6P144,
|
"6.144": ADS1115Gain.ADS1115_GAIN_6P144,
|
||||||
'4.096': ADS1115Gain.ADS1115_GAIN_4P096,
|
"4.096": ADS1115Gain.ADS1115_GAIN_4P096,
|
||||||
'2.048': ADS1115Gain.ADS1115_GAIN_2P048,
|
"2.048": ADS1115Gain.ADS1115_GAIN_2P048,
|
||||||
'1.024': ADS1115Gain.ADS1115_GAIN_1P024,
|
"1.024": ADS1115Gain.ADS1115_GAIN_1P024,
|
||||||
'0.512': ADS1115Gain.ADS1115_GAIN_0P512,
|
"0.512": ADS1115Gain.ADS1115_GAIN_0P512,
|
||||||
'0.256': ADS1115Gain.ADS1115_GAIN_0P256,
|
"0.256": ADS1115Gain.ADS1115_GAIN_0P256,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def validate_gain(value):
|
def validate_gain(value):
|
||||||
if isinstance(value, float):
|
if isinstance(value, float):
|
||||||
value = f'{value:0.03f}'
|
value = f"{value:0.03f}"
|
||||||
elif not isinstance(value, str):
|
elif not isinstance(value, str):
|
||||||
raise cv.Invalid(f'invalid gain "{value}"')
|
raise cv.Invalid(f'invalid gain "{value}"')
|
||||||
|
|
||||||
return cv.enum(GAIN)(value)
|
return cv.enum(GAIN)(value)
|
||||||
|
|
||||||
|
|
||||||
ADS1115Sensor = ads1115_ns.class_('ADS1115Sensor', sensor.Sensor, cg.PollingComponent,
|
ADS1115Sensor = ads1115_ns.class_(
|
||||||
voltage_sampler.VoltageSampler)
|
"ADS1115Sensor", sensor.Sensor, cg.PollingComponent, voltage_sampler.VoltageSampler
|
||||||
|
)
|
||||||
|
|
||||||
CONF_ADS1115_ID = 'ads1115_id'
|
CONF_ADS1115_ID = "ads1115_id"
|
||||||
CONFIG_SCHEMA = sensor.sensor_schema(UNIT_VOLT, ICON_FLASH, 3).extend({
|
CONFIG_SCHEMA = (
|
||||||
cv.GenerateID(): cv.declare_id(ADS1115Sensor),
|
sensor.sensor_schema(
|
||||||
cv.GenerateID(CONF_ADS1115_ID): cv.use_id(ADS1115Component),
|
UNIT_VOLT, ICON_EMPTY, 3, DEVICE_CLASS_VOLTAGE, STATE_CLASS_MEASUREMENT
|
||||||
cv.Required(CONF_MULTIPLEXER): cv.enum(MUX, upper=True, space='_'),
|
)
|
||||||
cv.Required(CONF_GAIN): validate_gain,
|
.extend(
|
||||||
}).extend(cv.polling_component_schema('60s'))
|
{
|
||||||
|
cv.GenerateID(): cv.declare_id(ADS1115Sensor),
|
||||||
|
cv.GenerateID(CONF_ADS1115_ID): cv.use_id(ADS1115Component),
|
||||||
|
cv.Required(CONF_MULTIPLEXER): cv.enum(MUX, upper=True, space="_"),
|
||||||
|
cv.Required(CONF_GAIN): validate_gain,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.extend(cv.polling_component_schema("60s"))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def to_code(config):
|
async def to_code(config):
|
||||||
paren = yield cg.get_variable(config[CONF_ADS1115_ID])
|
paren = await cg.get_variable(config[CONF_ADS1115_ID])
|
||||||
var = cg.new_Pvariable(config[CONF_ID], paren)
|
var = cg.new_Pvariable(config[CONF_ID], paren)
|
||||||
yield sensor.register_sensor(var, config)
|
await sensor.register_sensor(var, config)
|
||||||
yield cg.register_component(var, config)
|
await cg.register_component(var, config)
|
||||||
|
|
||||||
cg.add(var.set_multiplexer(config[CONF_MULTIPLEXER]))
|
cg.add(var.set_multiplexer(config[CONF_MULTIPLEXER]))
|
||||||
cg.add(var.set_gain(config[CONF_GAIN]))
|
cg.add(var.set_gain(config[CONF_GAIN]))
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ void AHT10Component::update() {
|
|||||||
delay = AHT10_HUMIDITY_DELAY;
|
delay = AHT10_HUMIDITY_DELAY;
|
||||||
for (int i = 0; i < AHT10_ATTEMPS; ++i) {
|
for (int i = 0; i < AHT10_ATTEMPS; ++i) {
|
||||||
ESP_LOGVV(TAG, "Attemps %u at %6ld", i, millis());
|
ESP_LOGVV(TAG, "Attemps %u at %6ld", i, millis());
|
||||||
|
delay_microseconds_accurate(4);
|
||||||
if (!this->read_bytes(0, data, 6, delay)) {
|
if (!this->read_bytes(0, data, 6, delay)) {
|
||||||
ESP_LOGD(TAG, "Communication with AHT10 failed, waiting...");
|
ESP_LOGD(TAG, "Communication with AHT10 failed, waiting...");
|
||||||
} else if ((data[0] & 0x80) == 0x80) { // Bit[7] = 0b1, device is busy
|
} else if ((data[0] & 0x80) == 0x80) { // Bit[7] = 0b1, device is busy
|
||||||
|
|||||||
@@ -1,30 +1,57 @@
|
|||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.components import i2c, sensor
|
from esphome.components import i2c, sensor
|
||||||
from esphome.const import CONF_HUMIDITY, CONF_ID, CONF_TEMPERATURE, \
|
from esphome.const import (
|
||||||
UNIT_CELSIUS, ICON_THERMOMETER, ICON_WATER_PERCENT, UNIT_PERCENT
|
CONF_HUMIDITY,
|
||||||
|
CONF_ID,
|
||||||
|
CONF_TEMPERATURE,
|
||||||
|
DEVICE_CLASS_HUMIDITY,
|
||||||
|
DEVICE_CLASS_TEMPERATURE,
|
||||||
|
ICON_EMPTY,
|
||||||
|
STATE_CLASS_MEASUREMENT,
|
||||||
|
UNIT_CELSIUS,
|
||||||
|
UNIT_PERCENT,
|
||||||
|
)
|
||||||
|
|
||||||
DEPENDENCIES = ['i2c']
|
DEPENDENCIES = ["i2c"]
|
||||||
|
|
||||||
aht10_ns = cg.esphome_ns.namespace('aht10')
|
aht10_ns = cg.esphome_ns.namespace("aht10")
|
||||||
AHT10Component = aht10_ns.class_('AHT10Component', cg.PollingComponent, i2c.I2CDevice)
|
AHT10Component = aht10_ns.class_("AHT10Component", cg.PollingComponent, i2c.I2CDevice)
|
||||||
|
|
||||||
CONFIG_SCHEMA = cv.Schema({
|
CONFIG_SCHEMA = (
|
||||||
cv.GenerateID(): cv.declare_id(AHT10Component),
|
cv.Schema(
|
||||||
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 2),
|
{
|
||||||
cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(UNIT_PERCENT, ICON_WATER_PERCENT, 2),
|
cv.GenerateID(): cv.declare_id(AHT10Component),
|
||||||
}).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x38))
|
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
|
||||||
|
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,
|
||||||
|
STATE_CLASS_MEASUREMENT,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.extend(cv.polling_component_schema("60s"))
|
||||||
|
.extend(i2c.i2c_device_schema(0x38))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def to_code(config):
|
async def to_code(config):
|
||||||
var = cg.new_Pvariable(config[CONF_ID])
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
yield cg.register_component(var, config)
|
await cg.register_component(var, config)
|
||||||
yield i2c.register_i2c_device(var, config)
|
await i2c.register_i2c_device(var, config)
|
||||||
|
|
||||||
if CONF_TEMPERATURE in 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))
|
cg.add(var.set_temperature_sensor(sens))
|
||||||
|
|
||||||
if CONF_HUMIDITY in config:
|
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))
|
cg.add(var.set_humidity_sensor(sens))
|
||||||
|
|||||||
@@ -1,30 +1,59 @@
|
|||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.components import i2c, sensor
|
from esphome.components import i2c, sensor
|
||||||
from esphome.const import CONF_HUMIDITY, CONF_ID, CONF_TEMPERATURE, \
|
from esphome.const import (
|
||||||
UNIT_CELSIUS, ICON_THERMOMETER, ICON_WATER_PERCENT, UNIT_PERCENT
|
CONF_HUMIDITY,
|
||||||
|
CONF_ID,
|
||||||
|
CONF_TEMPERATURE,
|
||||||
|
DEVICE_CLASS_HUMIDITY,
|
||||||
|
DEVICE_CLASS_TEMPERATURE,
|
||||||
|
STATE_CLASS_MEASUREMENT,
|
||||||
|
UNIT_CELSIUS,
|
||||||
|
ICON_EMPTY,
|
||||||
|
UNIT_PERCENT,
|
||||||
|
)
|
||||||
|
|
||||||
DEPENDENCIES = ['i2c']
|
DEPENDENCIES = ["i2c"]
|
||||||
|
|
||||||
am2320_ns = cg.esphome_ns.namespace('am2320')
|
am2320_ns = cg.esphome_ns.namespace("am2320")
|
||||||
AM2320Component = am2320_ns.class_('AM2320Component', cg.PollingComponent, i2c.I2CDevice)
|
AM2320Component = am2320_ns.class_(
|
||||||
|
"AM2320Component", cg.PollingComponent, i2c.I2CDevice
|
||||||
|
)
|
||||||
|
|
||||||
CONFIG_SCHEMA = cv.Schema({
|
CONFIG_SCHEMA = (
|
||||||
cv.GenerateID(): cv.declare_id(AM2320Component),
|
cv.Schema(
|
||||||
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 1),
|
{
|
||||||
cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(UNIT_PERCENT, ICON_WATER_PERCENT, 1),
|
cv.GenerateID(): cv.declare_id(AM2320Component),
|
||||||
}).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x5C))
|
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,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.extend(cv.polling_component_schema("60s"))
|
||||||
|
.extend(i2c.i2c_device_schema(0x5C))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def to_code(config):
|
async def to_code(config):
|
||||||
var = cg.new_Pvariable(config[CONF_ID])
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
yield cg.register_component(var, config)
|
await cg.register_component(var, config)
|
||||||
yield i2c.register_i2c_device(var, config)
|
await i2c.register_i2c_device(var, config)
|
||||||
|
|
||||||
if CONF_TEMPERATURE in 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))
|
cg.add(var.set_temperature_sensor(sens))
|
||||||
|
|
||||||
if CONF_HUMIDITY in config:
|
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))
|
cg.add(var.set_humidity_sensor(sens))
|
||||||
|
|||||||
@@ -10,27 +10,31 @@ from esphome.core import CORE, HexInt
|
|||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
DEPENDENCIES = ['display']
|
DEPENDENCIES = ["display"]
|
||||||
MULTI_CONF = True
|
MULTI_CONF = True
|
||||||
|
|
||||||
Animation_ = display.display_ns.class_('Animation')
|
Animation_ = display.display_ns.class_("Animation")
|
||||||
|
|
||||||
CONF_RAW_DATA_ID = 'raw_data_id'
|
CONF_RAW_DATA_ID = "raw_data_id"
|
||||||
|
|
||||||
ANIMATION_SCHEMA = cv.Schema({
|
ANIMATION_SCHEMA = cv.Schema(
|
||||||
cv.Required(CONF_ID): cv.declare_id(Animation_),
|
{
|
||||||
cv.Required(CONF_FILE): cv.file_,
|
cv.Required(CONF_ID): cv.declare_id(Animation_),
|
||||||
cv.Optional(CONF_RESIZE): cv.dimensions,
|
cv.Required(CONF_FILE): cv.file_,
|
||||||
cv.Optional(CONF_TYPE, default='BINARY'): cv.enum(espImage.IMAGE_TYPE, upper=True),
|
cv.Optional(CONF_RESIZE): cv.dimensions,
|
||||||
cv.GenerateID(CONF_RAW_DATA_ID): cv.declare_id(cg.uint8),
|
cv.Optional(CONF_TYPE, default="BINARY"): cv.enum(
|
||||||
})
|
espImage.IMAGE_TYPE, upper=True
|
||||||
|
),
|
||||||
|
cv.GenerateID(CONF_RAW_DATA_ID): cv.declare_id(cg.uint8),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
CONFIG_SCHEMA = cv.All(font.validate_pillow_installed, ANIMATION_SCHEMA)
|
CONFIG_SCHEMA = cv.All(font.validate_pillow_installed, ANIMATION_SCHEMA)
|
||||||
|
|
||||||
CODEOWNERS = ['@syndlex']
|
CODEOWNERS = ["@syndlex"]
|
||||||
|
|
||||||
|
|
||||||
def to_code(config):
|
async def to_code(config):
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
path = CORE.relative_config_path(config[CONF_FILE])
|
path = CORE.relative_config_path(config[CONF_FILE])
|
||||||
@@ -46,26 +50,28 @@ def to_code(config):
|
|||||||
width, height = image.size
|
width, height = image.size
|
||||||
else:
|
else:
|
||||||
if width > 500 or height > 500:
|
if width > 500 or height > 500:
|
||||||
_LOGGER.warning("The image you requested is very big. Please consider using"
|
_LOGGER.warning(
|
||||||
" the resize parameter.")
|
"The image you requested is very big. Please consider using"
|
||||||
|
" the resize parameter."
|
||||||
|
)
|
||||||
|
|
||||||
if config[CONF_TYPE] == 'GRAYSCALE':
|
if config[CONF_TYPE] == "GRAYSCALE":
|
||||||
data = [0 for _ in range(height * width * frames)]
|
data = [0 for _ in range(height * width * frames)]
|
||||||
pos = 0
|
pos = 0
|
||||||
for frameIndex in range(frames):
|
for frameIndex in range(frames):
|
||||||
image.seek(frameIndex)
|
image.seek(frameIndex)
|
||||||
frame = image.convert('L', dither=Image.NONE)
|
frame = image.convert("L", dither=Image.NONE)
|
||||||
pixels = list(frame.getdata())
|
pixels = list(frame.getdata())
|
||||||
for pix in pixels:
|
for pix in pixels:
|
||||||
data[pos] = pix
|
data[pos] = pix
|
||||||
pos += 1
|
pos += 1
|
||||||
|
|
||||||
elif config[CONF_TYPE] == 'RGB24':
|
elif config[CONF_TYPE] == "RGB24":
|
||||||
data = [0 for _ in range(height * width * 3 * frames)]
|
data = [0 for _ in range(height * width * 3 * frames)]
|
||||||
pos = 0
|
pos = 0
|
||||||
for frameIndex in range(frames):
|
for frameIndex in range(frames):
|
||||||
image.seek(frameIndex)
|
image.seek(frameIndex)
|
||||||
frame = image.convert('RGB')
|
frame = image.convert("RGB")
|
||||||
pixels = list(frame.getdata())
|
pixels = list(frame.getdata())
|
||||||
for pix in pixels:
|
for pix in pixels:
|
||||||
data[pos] = pix[0]
|
data[pos] = pix[0]
|
||||||
@@ -75,12 +81,12 @@ def to_code(config):
|
|||||||
data[pos] = pix[2]
|
data[pos] = pix[2]
|
||||||
pos += 1
|
pos += 1
|
||||||
|
|
||||||
elif config[CONF_TYPE] == 'BINARY':
|
elif config[CONF_TYPE] == "BINARY":
|
||||||
width8 = ((width + 7) // 8) * 8
|
width8 = ((width + 7) // 8) * 8
|
||||||
data = [0 for _ in range((height * width8 // 8) * frames)]
|
data = [0 for _ in range((height * width8 // 8) * frames)]
|
||||||
for frameIndex in range(frames):
|
for frameIndex in range(frames):
|
||||||
image.seek(frameIndex)
|
image.seek(frameIndex)
|
||||||
frame = image.convert('1', dither=Image.NONE)
|
frame = image.convert("1", dither=Image.NONE)
|
||||||
for y in range(height):
|
for y in range(height):
|
||||||
for x in range(width):
|
for x in range(width):
|
||||||
if frame.getpixel((x, y)):
|
if frame.getpixel((x, y)):
|
||||||
@@ -90,5 +96,11 @@ def to_code(config):
|
|||||||
|
|
||||||
rhs = [HexInt(x) for x in data]
|
rhs = [HexInt(x) for x in data]
|
||||||
prog_arr = cg.progmem_array(config[CONF_RAW_DATA_ID], rhs)
|
prog_arr = cg.progmem_array(config[CONF_RAW_DATA_ID], rhs)
|
||||||
cg.new_Pvariable(config[CONF_ID], prog_arr, width, height, frames,
|
cg.new_Pvariable(
|
||||||
espImage.IMAGE_TYPE[config[CONF_TYPE]])
|
config[CONF_ID],
|
||||||
|
prog_arr,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
frames,
|
||||||
|
espImage.IMAGE_TYPE[config[CONF_TYPE]],
|
||||||
|
)
|
||||||
|
|||||||
@@ -3,21 +3,27 @@ import esphome.config_validation as cv
|
|||||||
from esphome.components import i2c
|
from esphome.components import i2c
|
||||||
from esphome.const import CONF_ID
|
from esphome.const import CONF_ID
|
||||||
|
|
||||||
DEPENDENCIES = ['i2c']
|
DEPENDENCIES = ["i2c"]
|
||||||
AUTO_LOAD = ['sensor', 'binary_sensor']
|
AUTO_LOAD = ["sensor", "binary_sensor"]
|
||||||
MULTI_CONF = True
|
MULTI_CONF = True
|
||||||
|
|
||||||
CONF_APDS9960_ID = 'apds9960_id'
|
CONF_APDS9960_ID = "apds9960_id"
|
||||||
|
|
||||||
apds9960_nds = cg.esphome_ns.namespace('apds9960')
|
apds9960_nds = cg.esphome_ns.namespace("apds9960")
|
||||||
APDS9960 = apds9960_nds.class_('APDS9960', cg.PollingComponent, i2c.I2CDevice)
|
APDS9960 = apds9960_nds.class_("APDS9960", cg.PollingComponent, i2c.I2CDevice)
|
||||||
|
|
||||||
CONFIG_SCHEMA = cv.Schema({
|
CONFIG_SCHEMA = (
|
||||||
cv.GenerateID(): cv.declare_id(APDS9960),
|
cv.Schema(
|
||||||
}).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x39))
|
{
|
||||||
|
cv.GenerateID(): cv.declare_id(APDS9960),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.extend(cv.polling_component_schema("60s"))
|
||||||
|
.extend(i2c.i2c_device_schema(0x39))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def to_code(config):
|
async def to_code(config):
|
||||||
var = cg.new_Pvariable(config[CONF_ID])
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
yield cg.register_component(var, config)
|
await cg.register_component(var, config)
|
||||||
yield i2c.register_i2c_device(var, config)
|
await i2c.register_i2c_device(var, config)
|
||||||
|
|||||||
@@ -4,24 +4,28 @@ from esphome.components import binary_sensor
|
|||||||
from esphome.const import CONF_DIRECTION, CONF_DEVICE_CLASS, DEVICE_CLASS_MOVING
|
from esphome.const import CONF_DIRECTION, CONF_DEVICE_CLASS, DEVICE_CLASS_MOVING
|
||||||
from . import APDS9960, CONF_APDS9960_ID
|
from . import APDS9960, CONF_APDS9960_ID
|
||||||
|
|
||||||
DEPENDENCIES = ['apds9960']
|
DEPENDENCIES = ["apds9960"]
|
||||||
|
|
||||||
DIRECTIONS = {
|
DIRECTIONS = {
|
||||||
'UP': 'set_up_direction',
|
"UP": "set_up_direction",
|
||||||
'DOWN': 'set_down_direction',
|
"DOWN": "set_down_direction",
|
||||||
'LEFT': 'set_left_direction',
|
"LEFT": "set_left_direction",
|
||||||
'RIGHT': 'set_right_direction',
|
"RIGHT": "set_right_direction",
|
||||||
}
|
}
|
||||||
|
|
||||||
CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend({
|
CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend(
|
||||||
cv.Required(CONF_DIRECTION): cv.one_of(*DIRECTIONS, upper=True),
|
{
|
||||||
cv.GenerateID(CONF_APDS9960_ID): cv.use_id(APDS9960),
|
cv.Required(CONF_DIRECTION): cv.one_of(*DIRECTIONS, upper=True),
|
||||||
cv.Optional(CONF_DEVICE_CLASS, default=DEVICE_CLASS_MOVING): binary_sensor.device_class,
|
cv.GenerateID(CONF_APDS9960_ID): cv.use_id(APDS9960),
|
||||||
})
|
cv.Optional(
|
||||||
|
CONF_DEVICE_CLASS, default=DEVICE_CLASS_MOVING
|
||||||
|
): binary_sensor.device_class,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def to_code(config):
|
async def to_code(config):
|
||||||
hub = yield cg.get_variable(config[CONF_APDS9960_ID])
|
hub = await cg.get_variable(config[CONF_APDS9960_ID])
|
||||||
var = yield binary_sensor.new_binary_sensor(config)
|
var = await binary_sensor.new_binary_sensor(config)
|
||||||
func = getattr(hub, DIRECTIONS[config[CONF_DIRECTION]])
|
func = getattr(hub, DIRECTIONS[config[CONF_DIRECTION]])
|
||||||
cg.add(func(var))
|
cg.add(func(var))
|
||||||
|
|||||||
@@ -1,27 +1,37 @@
|
|||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.components import sensor
|
from esphome.components import sensor
|
||||||
from esphome.const import CONF_TYPE, 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
|
from . import APDS9960, CONF_APDS9960_ID
|
||||||
|
|
||||||
DEPENDENCIES = ['apds9960']
|
DEPENDENCIES = ["apds9960"]
|
||||||
|
|
||||||
TYPES = {
|
TYPES = {
|
||||||
'CLEAR': 'set_clear_channel',
|
"CLEAR": "set_clear_channel",
|
||||||
'RED': 'set_red_channel',
|
"RED": "set_red_channel",
|
||||||
'GREEN': 'set_green_channel',
|
"GREEN": "set_green_channel",
|
||||||
'BLUE': 'set_blue_channel',
|
"BLUE": "set_blue_channel",
|
||||||
'PROXIMITY': 'set_proximity',
|
"PROXIMITY": "set_proximity",
|
||||||
}
|
}
|
||||||
|
|
||||||
CONFIG_SCHEMA = sensor.sensor_schema(UNIT_PERCENT, ICON_LIGHTBULB, 1).extend({
|
CONFIG_SCHEMA = sensor.sensor_schema(
|
||||||
cv.Required(CONF_TYPE): cv.one_of(*TYPES, upper=True),
|
UNIT_PERCENT, ICON_LIGHTBULB, 1, DEVICE_CLASS_EMPTY, STATE_CLASS_MEASUREMENT
|
||||||
cv.GenerateID(CONF_APDS9960_ID): cv.use_id(APDS9960),
|
).extend(
|
||||||
})
|
{
|
||||||
|
cv.Required(CONF_TYPE): cv.one_of(*TYPES, upper=True),
|
||||||
|
cv.GenerateID(CONF_APDS9960_ID): cv.use_id(APDS9960),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def to_code(config):
|
async def to_code(config):
|
||||||
hub = yield cg.get_variable(config[CONF_APDS9960_ID])
|
hub = await cg.get_variable(config[CONF_APDS9960_ID])
|
||||||
var = yield sensor.new_sensor(config)
|
var = await sensor.new_sensor(config)
|
||||||
func = getattr(hub, TYPES[config[CONF_TYPE]])
|
func = getattr(hub, TYPES[config[CONF_TYPE]])
|
||||||
cg.add(func(var))
|
cg.add(func(var))
|
||||||
|
|||||||
@@ -2,52 +2,75 @@ import esphome.codegen as cg
|
|||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome import automation
|
from esphome import automation
|
||||||
from esphome.automation import Condition
|
from esphome.automation import Condition
|
||||||
from esphome.const import CONF_DATA, CONF_DATA_TEMPLATE, CONF_ID, CONF_PASSWORD, CONF_PORT, \
|
from esphome.const import (
|
||||||
CONF_REBOOT_TIMEOUT, CONF_SERVICE, CONF_VARIABLES, CONF_SERVICES, CONF_TRIGGER_ID, CONF_EVENT, \
|
CONF_DATA,
|
||||||
CONF_TAG
|
CONF_DATA_TEMPLATE,
|
||||||
|
CONF_ID,
|
||||||
|
CONF_PASSWORD,
|
||||||
|
CONF_PORT,
|
||||||
|
CONF_REBOOT_TIMEOUT,
|
||||||
|
CONF_SERVICE,
|
||||||
|
CONF_VARIABLES,
|
||||||
|
CONF_SERVICES,
|
||||||
|
CONF_TRIGGER_ID,
|
||||||
|
CONF_EVENT,
|
||||||
|
CONF_TAG,
|
||||||
|
)
|
||||||
from esphome.core import coroutine_with_priority
|
from esphome.core import coroutine_with_priority
|
||||||
|
|
||||||
DEPENDENCIES = ['network']
|
DEPENDENCIES = ["network"]
|
||||||
AUTO_LOAD = ['async_tcp']
|
AUTO_LOAD = ["async_tcp"]
|
||||||
CODEOWNERS = ['@OttoWinter']
|
CODEOWNERS = ["@OttoWinter"]
|
||||||
|
|
||||||
api_ns = cg.esphome_ns.namespace('api')
|
api_ns = cg.esphome_ns.namespace("api")
|
||||||
APIServer = api_ns.class_('APIServer', cg.Component, cg.Controller)
|
APIServer = api_ns.class_("APIServer", cg.Component, cg.Controller)
|
||||||
HomeAssistantServiceCallAction = api_ns.class_('HomeAssistantServiceCallAction', automation.Action)
|
HomeAssistantServiceCallAction = api_ns.class_(
|
||||||
APIConnectedCondition = api_ns.class_('APIConnectedCondition', Condition)
|
"HomeAssistantServiceCallAction", automation.Action
|
||||||
|
)
|
||||||
|
APIConnectedCondition = api_ns.class_("APIConnectedCondition", Condition)
|
||||||
|
|
||||||
UserServiceTrigger = api_ns.class_('UserServiceTrigger', automation.Trigger)
|
UserServiceTrigger = api_ns.class_("UserServiceTrigger", automation.Trigger)
|
||||||
ListEntitiesServicesArgument = api_ns.class_('ListEntitiesServicesArgument')
|
ListEntitiesServicesArgument = api_ns.class_("ListEntitiesServicesArgument")
|
||||||
SERVICE_ARG_NATIVE_TYPES = {
|
SERVICE_ARG_NATIVE_TYPES = {
|
||||||
'bool': bool,
|
"bool": bool,
|
||||||
'int': cg.int32,
|
"int": cg.int32,
|
||||||
'float': float,
|
"float": float,
|
||||||
'string': cg.std_string,
|
"string": cg.std_string,
|
||||||
'bool[]': cg.std_vector.template(bool),
|
"bool[]": cg.std_vector.template(bool),
|
||||||
'int[]': cg.std_vector.template(cg.int32),
|
"int[]": cg.std_vector.template(cg.int32),
|
||||||
'float[]': cg.std_vector.template(float),
|
"float[]": cg.std_vector.template(float),
|
||||||
'string[]': cg.std_vector.template(cg.std_string),
|
"string[]": cg.std_vector.template(cg.std_string),
|
||||||
}
|
}
|
||||||
|
|
||||||
CONFIG_SCHEMA = cv.Schema({
|
CONFIG_SCHEMA = cv.Schema(
|
||||||
cv.GenerateID(): cv.declare_id(APIServer),
|
{
|
||||||
cv.Optional(CONF_PORT, default=6053): cv.port,
|
cv.GenerateID(): cv.declare_id(APIServer),
|
||||||
cv.Optional(CONF_PASSWORD, default=''): cv.string_strict,
|
cv.Optional(CONF_PORT, default=6053): cv.port,
|
||||||
cv.Optional(CONF_REBOOT_TIMEOUT, default='15min'): cv.positive_time_period_milliseconds,
|
cv.Optional(CONF_PASSWORD, default=""): cv.string_strict,
|
||||||
cv.Optional(CONF_SERVICES): automation.validate_automation({
|
cv.Optional(
|
||||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(UserServiceTrigger),
|
CONF_REBOOT_TIMEOUT, default="15min"
|
||||||
cv.Required(CONF_SERVICE): cv.valid_name,
|
): cv.positive_time_period_milliseconds,
|
||||||
cv.Optional(CONF_VARIABLES, default={}): cv.Schema({
|
cv.Optional(CONF_SERVICES): automation.validate_automation(
|
||||||
cv.validate_id_name: cv.one_of(*SERVICE_ARG_NATIVE_TYPES, lower=True),
|
{
|
||||||
}),
|
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(UserServiceTrigger),
|
||||||
}),
|
cv.Required(CONF_SERVICE): cv.valid_name,
|
||||||
}).extend(cv.COMPONENT_SCHEMA)
|
cv.Optional(CONF_VARIABLES, default={}): cv.Schema(
|
||||||
|
{
|
||||||
|
cv.validate_id_name: cv.one_of(
|
||||||
|
*SERVICE_ARG_NATIVE_TYPES, lower=True
|
||||||
|
),
|
||||||
|
}
|
||||||
|
),
|
||||||
|
}
|
||||||
|
),
|
||||||
|
}
|
||||||
|
).extend(cv.COMPONENT_SCHEMA)
|
||||||
|
|
||||||
|
|
||||||
@coroutine_with_priority(40.0)
|
@coroutine_with_priority(40.0)
|
||||||
def to_code(config):
|
async def to_code(config):
|
||||||
var = cg.new_Pvariable(config[CONF_ID])
|
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_port(config[CONF_PORT]))
|
||||||
cg.add(var.set_password(config[CONF_PASSWORD]))
|
cg.add(var.set_password(config[CONF_PASSWORD]))
|
||||||
@@ -63,98 +86,119 @@ def to_code(config):
|
|||||||
func_args.append((native, name))
|
func_args.append((native, name))
|
||||||
service_arg_names.append(name)
|
service_arg_names.append(name)
|
||||||
templ = cg.TemplateArguments(*template_args)
|
templ = cg.TemplateArguments(*template_args)
|
||||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], templ,
|
trigger = cg.new_Pvariable(
|
||||||
conf[CONF_SERVICE], service_arg_names)
|
conf[CONF_TRIGGER_ID], templ, conf[CONF_SERVICE], service_arg_names
|
||||||
|
)
|
||||||
cg.add(var.register_user_service(trigger))
|
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_define("USE_API")
|
||||||
cg.add_global(api_ns.using)
|
cg.add_global(api_ns.using)
|
||||||
|
|
||||||
|
|
||||||
KEY_VALUE_SCHEMA = cv.Schema({cv.string: cv.templatable(cv.string_strict)})
|
KEY_VALUE_SCHEMA = cv.Schema({cv.string: cv.templatable(cv.string_strict)})
|
||||||
|
|
||||||
HOMEASSISTANT_SERVICE_ACTION_SCHEMA = cv.Schema({
|
HOMEASSISTANT_SERVICE_ACTION_SCHEMA = cv.Schema(
|
||||||
cv.GenerateID(): cv.use_id(APIServer),
|
{
|
||||||
cv.Required(CONF_SERVICE): cv.templatable(cv.string),
|
cv.GenerateID(): cv.use_id(APIServer),
|
||||||
cv.Optional(CONF_DATA, default={}): KEY_VALUE_SCHEMA,
|
cv.Required(CONF_SERVICE): cv.templatable(cv.string),
|
||||||
cv.Optional(CONF_DATA_TEMPLATE, default={}): KEY_VALUE_SCHEMA,
|
cv.Optional(CONF_DATA, default={}): KEY_VALUE_SCHEMA,
|
||||||
cv.Optional(CONF_VARIABLES, default={}): cv.Schema({cv.string: cv.returning_lambda}),
|
cv.Optional(CONF_DATA_TEMPLATE, default={}): KEY_VALUE_SCHEMA,
|
||||||
})
|
cv.Optional(CONF_VARIABLES, default={}): cv.Schema(
|
||||||
|
{cv.string: cv.returning_lambda}
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@automation.register_action('homeassistant.service', HomeAssistantServiceCallAction,
|
@automation.register_action(
|
||||||
HOMEASSISTANT_SERVICE_ACTION_SCHEMA)
|
"homeassistant.service",
|
||||||
def homeassistant_service_to_code(config, action_id, template_arg, args):
|
HomeAssistantServiceCallAction,
|
||||||
serv = yield cg.get_variable(config[CONF_ID])
|
HOMEASSISTANT_SERVICE_ACTION_SCHEMA,
|
||||||
|
)
|
||||||
|
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)
|
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))
|
cg.add(var.set_service(templ))
|
||||||
for key, value in config[CONF_DATA].items():
|
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))
|
cg.add(var.add_data(key, templ))
|
||||||
for key, value in config[CONF_DATA_TEMPLATE].items():
|
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))
|
cg.add(var.add_data_template(key, templ))
|
||||||
for key, value in config[CONF_VARIABLES].items():
|
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))
|
cg.add(var.add_variable(key, templ))
|
||||||
yield var
|
return var
|
||||||
|
|
||||||
|
|
||||||
def validate_homeassistant_event(value):
|
def validate_homeassistant_event(value):
|
||||||
value = cv.string(value)
|
value = cv.string(value)
|
||||||
if not value.startswith('esphome.'):
|
if not value.startswith("esphome."):
|
||||||
raise cv.Invalid("ESPHome can only generate Home Assistant events that begin with "
|
raise cv.Invalid(
|
||||||
"esphome. For example 'esphome.xyz'")
|
"ESPHome can only generate Home Assistant events that begin with "
|
||||||
|
"esphome. For example 'esphome.xyz'"
|
||||||
|
)
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
HOMEASSISTANT_EVENT_ACTION_SCHEMA = cv.Schema({
|
HOMEASSISTANT_EVENT_ACTION_SCHEMA = cv.Schema(
|
||||||
cv.GenerateID(): cv.use_id(APIServer),
|
{
|
||||||
cv.Required(CONF_EVENT): validate_homeassistant_event,
|
cv.GenerateID(): cv.use_id(APIServer),
|
||||||
cv.Optional(CONF_DATA, default={}): KEY_VALUE_SCHEMA,
|
cv.Required(CONF_EVENT): validate_homeassistant_event,
|
||||||
cv.Optional(CONF_DATA_TEMPLATE, default={}): KEY_VALUE_SCHEMA,
|
cv.Optional(CONF_DATA, default={}): KEY_VALUE_SCHEMA,
|
||||||
cv.Optional(CONF_VARIABLES, default={}): KEY_VALUE_SCHEMA,
|
cv.Optional(CONF_DATA_TEMPLATE, default={}): KEY_VALUE_SCHEMA,
|
||||||
})
|
cv.Optional(CONF_VARIABLES, default={}): KEY_VALUE_SCHEMA,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@automation.register_action('homeassistant.event', HomeAssistantServiceCallAction,
|
@automation.register_action(
|
||||||
HOMEASSISTANT_EVENT_ACTION_SCHEMA)
|
"homeassistant.event",
|
||||||
def homeassistant_event_to_code(config, action_id, template_arg, args):
|
HomeAssistantServiceCallAction,
|
||||||
serv = yield cg.get_variable(config[CONF_ID])
|
HOMEASSISTANT_EVENT_ACTION_SCHEMA,
|
||||||
|
)
|
||||||
|
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)
|
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))
|
cg.add(var.set_service(templ))
|
||||||
for key, value in config[CONF_DATA].items():
|
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))
|
cg.add(var.add_data(key, templ))
|
||||||
for key, value in config[CONF_DATA_TEMPLATE].items():
|
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))
|
cg.add(var.add_data_template(key, templ))
|
||||||
for key, value in config[CONF_VARIABLES].items():
|
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))
|
cg.add(var.add_variable(key, templ))
|
||||||
yield var
|
return var
|
||||||
|
|
||||||
|
|
||||||
HOMEASSISTANT_TAG_SCANNED_ACTION_SCHEMA = cv.maybe_simple_value({
|
HOMEASSISTANT_TAG_SCANNED_ACTION_SCHEMA = cv.maybe_simple_value(
|
||||||
cv.GenerateID(): cv.use_id(APIServer),
|
{
|
||||||
cv.Required(CONF_TAG): cv.templatable(cv.string_strict),
|
cv.GenerateID(): cv.use_id(APIServer),
|
||||||
}, key=CONF_TAG)
|
cv.Required(CONF_TAG): cv.templatable(cv.string_strict),
|
||||||
|
},
|
||||||
|
key=CONF_TAG,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@automation.register_action('homeassistant.tag_scanned', HomeAssistantServiceCallAction,
|
@automation.register_action(
|
||||||
HOMEASSISTANT_TAG_SCANNED_ACTION_SCHEMA)
|
"homeassistant.tag_scanned",
|
||||||
def homeassistant_tag_scanned_to_code(config, action_id, template_arg, args):
|
HomeAssistantServiceCallAction,
|
||||||
serv = yield cg.get_variable(config[CONF_ID])
|
HOMEASSISTANT_TAG_SCANNED_ACTION_SCHEMA,
|
||||||
|
)
|
||||||
|
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)
|
var = cg.new_Pvariable(action_id, template_arg, serv, True)
|
||||||
cg.add(var.set_service('esphome.tag_scanned'))
|
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))
|
cg.add(var.add_data("tag_id", templ))
|
||||||
yield var
|
return var
|
||||||
|
|
||||||
|
|
||||||
@automation.register_condition('api.connected', APIConnectedCondition, {})
|
@automation.register_condition("api.connected", APIConnectedCondition, {})
|
||||||
def api_connected_to_code(config, condition_id, template_arg, args):
|
async def api_connected_to_code(config, condition_id, template_arg, args):
|
||||||
yield cg.new_Pvariable(condition_id, template_arg)
|
return cg.new_Pvariable(condition_id, template_arg)
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ service APIConnection {
|
|||||||
// The Home Assistant protocol is structured as a simple
|
// The Home Assistant protocol is structured as a simple
|
||||||
// TCP socket with short binary messages encoded in the protocol buffers format
|
// TCP socket with short binary messages encoded in the protocol buffers format
|
||||||
// First, a message in this protocol has a specific format:
|
// First, a message in this protocol has a specific format:
|
||||||
|
// * A zero byte.
|
||||||
// * VarInt denoting the size of the message object. (type is not part of this)
|
// * VarInt denoting the size of the message object. (type is not part of this)
|
||||||
// * VarInt denoting the type of message.
|
// * VarInt denoting the type of message.
|
||||||
// * The message object encoded as a ProtoBuf message
|
// * The message object encoded as a ProtoBuf message
|
||||||
@@ -175,6 +176,10 @@ message DeviceInfoResponse {
|
|||||||
string model = 6;
|
string model = 6;
|
||||||
|
|
||||||
bool has_deep_sleep = 7;
|
bool has_deep_sleep = 7;
|
||||||
|
|
||||||
|
// The esphome project details if set
|
||||||
|
string project_name = 8;
|
||||||
|
string project_version = 9;
|
||||||
}
|
}
|
||||||
|
|
||||||
message ListEntitiesRequest {
|
message ListEntitiesRequest {
|
||||||
@@ -302,6 +307,7 @@ message ListEntitiesFanResponse {
|
|||||||
bool supports_oscillation = 5;
|
bool supports_oscillation = 5;
|
||||||
bool supports_speed = 6;
|
bool supports_speed = 6;
|
||||||
bool supports_direction = 7;
|
bool supports_direction = 7;
|
||||||
|
int32 supported_speed_count = 8;
|
||||||
}
|
}
|
||||||
enum FanSpeed {
|
enum FanSpeed {
|
||||||
FAN_SPEED_LOW = 0;
|
FAN_SPEED_LOW = 0;
|
||||||
@@ -321,8 +327,9 @@ message FanStateResponse {
|
|||||||
fixed32 key = 1;
|
fixed32 key = 1;
|
||||||
bool state = 2;
|
bool state = 2;
|
||||||
bool oscillating = 3;
|
bool oscillating = 3;
|
||||||
FanSpeed speed = 4;
|
FanSpeed speed = 4 [deprecated = true];
|
||||||
FanDirection direction = 5;
|
FanDirection direction = 5;
|
||||||
|
int32 speed_level = 6;
|
||||||
}
|
}
|
||||||
message FanCommandRequest {
|
message FanCommandRequest {
|
||||||
option (id) = 31;
|
option (id) = 31;
|
||||||
@@ -333,12 +340,14 @@ message FanCommandRequest {
|
|||||||
fixed32 key = 1;
|
fixed32 key = 1;
|
||||||
bool has_state = 2;
|
bool has_state = 2;
|
||||||
bool state = 3;
|
bool state = 3;
|
||||||
bool has_speed = 4;
|
bool has_speed = 4 [deprecated = true];
|
||||||
FanSpeed speed = 5;
|
FanSpeed speed = 5 [deprecated = true];
|
||||||
bool has_oscillating = 6;
|
bool has_oscillating = 6;
|
||||||
bool oscillating = 7;
|
bool oscillating = 7;
|
||||||
bool has_direction = 8;
|
bool has_direction = 8;
|
||||||
FanDirection direction = 9;
|
FanDirection direction = 9;
|
||||||
|
bool has_speed_level = 10;
|
||||||
|
int32 speed_level = 11;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== LIGHT ====================
|
// ==================== LIGHT ====================
|
||||||
@@ -404,6 +413,11 @@ message LightCommandRequest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ==================== SENSOR ====================
|
// ==================== SENSOR ====================
|
||||||
|
enum SensorStateClass {
|
||||||
|
STATE_CLASS_NONE = 0;
|
||||||
|
STATE_CLASS_MEASUREMENT = 1;
|
||||||
|
}
|
||||||
|
|
||||||
message ListEntitiesSensorResponse {
|
message ListEntitiesSensorResponse {
|
||||||
option (id) = 16;
|
option (id) = 16;
|
||||||
option (source) = SOURCE_SERVER;
|
option (source) = SOURCE_SERVER;
|
||||||
@@ -418,6 +432,8 @@ message ListEntitiesSensorResponse {
|
|||||||
string unit_of_measurement = 6;
|
string unit_of_measurement = 6;
|
||||||
int32 accuracy_decimals = 7;
|
int32 accuracy_decimals = 7;
|
||||||
bool force_update = 8;
|
bool force_update = 8;
|
||||||
|
string device_class = 9;
|
||||||
|
SensorStateClass state_class = 10;
|
||||||
}
|
}
|
||||||
message SensorStateResponse {
|
message SensorStateResponse {
|
||||||
option (id) = 25;
|
option (id) = 25;
|
||||||
@@ -555,6 +571,7 @@ message SubscribeHomeAssistantStateResponse {
|
|||||||
option (id) = 39;
|
option (id) = 39;
|
||||||
option (source) = SOURCE_SERVER;
|
option (source) = SOURCE_SERVER;
|
||||||
string entity_id = 1;
|
string entity_id = 1;
|
||||||
|
string attribute = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
message HomeAssistantStateResponse {
|
message HomeAssistantStateResponse {
|
||||||
@@ -564,6 +581,7 @@ message HomeAssistantStateResponse {
|
|||||||
|
|
||||||
string entity_id = 1;
|
string entity_id = 1;
|
||||||
string state = 2;
|
string state = 2;
|
||||||
|
string attribute = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== IMPORT TIME ====================
|
// ==================== IMPORT TIME ====================
|
||||||
@@ -658,11 +676,12 @@ message CameraImageRequest {
|
|||||||
// ==================== CLIMATE ====================
|
// ==================== CLIMATE ====================
|
||||||
enum ClimateMode {
|
enum ClimateMode {
|
||||||
CLIMATE_MODE_OFF = 0;
|
CLIMATE_MODE_OFF = 0;
|
||||||
CLIMATE_MODE_AUTO = 1;
|
CLIMATE_MODE_HEAT_COOL = 1;
|
||||||
CLIMATE_MODE_COOL = 2;
|
CLIMATE_MODE_COOL = 2;
|
||||||
CLIMATE_MODE_HEAT = 3;
|
CLIMATE_MODE_HEAT = 3;
|
||||||
CLIMATE_MODE_FAN_ONLY = 4;
|
CLIMATE_MODE_FAN_ONLY = 4;
|
||||||
CLIMATE_MODE_DRY = 5;
|
CLIMATE_MODE_DRY = 5;
|
||||||
|
CLIMATE_MODE_AUTO = 6;
|
||||||
}
|
}
|
||||||
enum ClimateFanMode {
|
enum ClimateFanMode {
|
||||||
CLIMATE_FAN_ON = 0;
|
CLIMATE_FAN_ON = 0;
|
||||||
@@ -690,6 +709,15 @@ enum ClimateAction {
|
|||||||
CLIMATE_ACTION_DRYING = 5;
|
CLIMATE_ACTION_DRYING = 5;
|
||||||
CLIMATE_ACTION_FAN = 6;
|
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 {
|
message ListEntitiesClimateResponse {
|
||||||
option (id) = 46;
|
option (id) = 46;
|
||||||
option (source) = SOURCE_SERVER;
|
option (source) = SOURCE_SERVER;
|
||||||
@@ -710,6 +738,9 @@ message ListEntitiesClimateResponse {
|
|||||||
bool supports_action = 12;
|
bool supports_action = 12;
|
||||||
repeated ClimateFanMode supported_fan_modes = 13;
|
repeated ClimateFanMode supported_fan_modes = 13;
|
||||||
repeated ClimateSwingMode supported_swing_modes = 14;
|
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 {
|
message ClimateStateResponse {
|
||||||
option (id) = 47;
|
option (id) = 47;
|
||||||
@@ -727,6 +758,9 @@ message ClimateStateResponse {
|
|||||||
ClimateAction action = 8;
|
ClimateAction action = 8;
|
||||||
ClimateFanMode fan_mode = 9;
|
ClimateFanMode fan_mode = 9;
|
||||||
ClimateSwingMode swing_mode = 10;
|
ClimateSwingMode swing_mode = 10;
|
||||||
|
string custom_fan_mode = 11;
|
||||||
|
ClimatePreset preset = 12;
|
||||||
|
string custom_preset = 13;
|
||||||
}
|
}
|
||||||
message ClimateCommandRequest {
|
message ClimateCommandRequest {
|
||||||
option (id) = 48;
|
option (id) = 48;
|
||||||
@@ -749,4 +783,10 @@ message ClimateCommandRequest {
|
|||||||
ClimateFanMode fan_mode = 13;
|
ClimateFanMode fan_mode = 13;
|
||||||
bool has_swing_mode = 14;
|
bool has_swing_mode = 14;
|
||||||
ClimateSwingMode swing_mode = 15;
|
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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,9 @@
|
|||||||
#ifdef USE_HOMEASSISTANT_TIME
|
#ifdef USE_HOMEASSISTANT_TIME
|
||||||
#include "esphome/components/homeassistant/time/homeassistant_time.h"
|
#include "esphome/components/homeassistant/time/homeassistant_time.h"
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef USE_FAN
|
||||||
|
#include "esphome/components/fan/fan_helpers.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace api {
|
namespace api {
|
||||||
@@ -246,8 +249,10 @@ bool APIConnection::send_fan_state(fan::FanState *fan) {
|
|||||||
resp.state = fan->state;
|
resp.state = fan->state;
|
||||||
if (traits.supports_oscillation())
|
if (traits.supports_oscillation())
|
||||||
resp.oscillating = fan->oscillating;
|
resp.oscillating = fan->oscillating;
|
||||||
if (traits.supports_speed())
|
if (traits.supports_speed()) {
|
||||||
resp.speed = static_cast<enums::FanSpeed>(fan->speed);
|
resp.speed_level = fan->speed;
|
||||||
|
resp.speed = static_cast<enums::FanSpeed>(fan::speed_level_to_enum(fan->speed, traits.supported_speed_count()));
|
||||||
|
}
|
||||||
if (traits.supports_direction())
|
if (traits.supports_direction())
|
||||||
resp.direction = static_cast<enums::FanDirection>(fan->direction);
|
resp.direction = static_cast<enums::FanDirection>(fan->direction);
|
||||||
return this->send_fan_state_response(resp);
|
return this->send_fan_state_response(resp);
|
||||||
@@ -262,6 +267,7 @@ bool APIConnection::send_fan_info(fan::FanState *fan) {
|
|||||||
msg.supports_oscillation = traits.supports_oscillation();
|
msg.supports_oscillation = traits.supports_oscillation();
|
||||||
msg.supports_speed = traits.supports_speed();
|
msg.supports_speed = traits.supports_speed();
|
||||||
msg.supports_direction = traits.supports_direction();
|
msg.supports_direction = traits.supports_direction();
|
||||||
|
msg.supported_speed_count = traits.supported_speed_count();
|
||||||
return this->send_list_entities_fan_response(msg);
|
return this->send_list_entities_fan_response(msg);
|
||||||
}
|
}
|
||||||
void APIConnection::fan_command(const FanCommandRequest &msg) {
|
void APIConnection::fan_command(const FanCommandRequest &msg) {
|
||||||
@@ -269,13 +275,19 @@ void APIConnection::fan_command(const FanCommandRequest &msg) {
|
|||||||
if (fan == nullptr)
|
if (fan == nullptr)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
auto traits = fan->get_traits();
|
||||||
|
|
||||||
auto call = fan->make_call();
|
auto call = fan->make_call();
|
||||||
if (msg.has_state)
|
if (msg.has_state)
|
||||||
call.set_state(msg.state);
|
call.set_state(msg.state);
|
||||||
if (msg.has_oscillating)
|
if (msg.has_oscillating)
|
||||||
call.set_oscillating(msg.oscillating);
|
call.set_oscillating(msg.oscillating);
|
||||||
if (msg.has_speed)
|
if (msg.has_speed_level) {
|
||||||
call.set_speed(static_cast<fan::FanSpeed>(msg.speed));
|
// Prefer level
|
||||||
|
call.set_speed(msg.speed_level);
|
||||||
|
} else if (msg.has_speed) {
|
||||||
|
call.set_speed(fan::speed_enum_to_level(static_cast<fan::FanSpeed>(msg.speed), traits.supported_speed_count()));
|
||||||
|
}
|
||||||
if (msg.has_direction)
|
if (msg.has_direction)
|
||||||
call.set_direction(static_cast<fan::FanDirection>(msg.direction));
|
call.set_direction(static_cast<fan::FanDirection>(msg.direction));
|
||||||
call.perform();
|
call.perform();
|
||||||
@@ -382,6 +394,9 @@ bool APIConnection::send_sensor_info(sensor::Sensor *sensor) {
|
|||||||
msg.unit_of_measurement = sensor->get_unit_of_measurement();
|
msg.unit_of_measurement = sensor->get_unit_of_measurement();
|
||||||
msg.accuracy_decimals = sensor->get_accuracy_decimals();
|
msg.accuracy_decimals = sensor->get_accuracy_decimals();
|
||||||
msg.force_update = sensor->get_force_update();
|
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);
|
return this->send_list_entities_sensor_response(msg);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@@ -462,8 +477,14 @@ bool APIConnection::send_climate_state(climate::Climate *climate) {
|
|||||||
}
|
}
|
||||||
if (traits.get_supports_away())
|
if (traits.get_supports_away())
|
||||||
resp.away = climate->away;
|
resp.away = climate->away;
|
||||||
if (traits.get_supports_fan_modes())
|
if (traits.get_supports_fan_modes() && climate->fan_mode.has_value())
|
||||||
resp.fan_mode = static_cast<enums::ClimateFanMode>(climate->fan_mode);
|
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())
|
if (traits.get_supports_swing_modes())
|
||||||
resp.swing_mode = static_cast<enums::ClimateSwingMode>(climate->swing_mode);
|
resp.swing_mode = static_cast<enums::ClimateSwingMode>(climate->swing_mode);
|
||||||
return this->send_climate_state_response(resp);
|
return this->send_climate_state_response(resp);
|
||||||
@@ -477,8 +498,9 @@ bool APIConnection::send_climate_info(climate::Climate *climate) {
|
|||||||
msg.unique_id = get_default_unique_id("climate", climate);
|
msg.unique_id = get_default_unique_id("climate", climate);
|
||||||
msg.supports_current_temperature = traits.get_supports_current_temperature();
|
msg.supports_current_temperature = traits.get_supports_current_temperature();
|
||||||
msg.supports_two_point_target_temperature = traits.get_supports_two_point_target_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,
|
for (auto mode :
|
||||||
climate::CLIMATE_MODE_HEAT, climate::CLIMATE_MODE_DRY, climate::CLIMATE_MODE_FAN_ONLY}) {
|
{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))
|
if (traits.supports_mode(mode))
|
||||||
msg.supported_modes.push_back(static_cast<enums::ClimateMode>(mode));
|
msg.supported_modes.push_back(static_cast<enums::ClimateMode>(mode));
|
||||||
}
|
}
|
||||||
@@ -493,6 +515,18 @@ bool APIConnection::send_climate_info(climate::Climate *climate) {
|
|||||||
if (traits.supports_fan_mode(fan_mode))
|
if (traits.supports_fan_mode(fan_mode))
|
||||||
msg.supported_fan_modes.push_back(static_cast<enums::ClimateFanMode>(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,
|
for (auto swing_mode : {climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_BOTH, climate::CLIMATE_SWING_VERTICAL,
|
||||||
climate::CLIMATE_SWING_HORIZONTAL}) {
|
climate::CLIMATE_SWING_HORIZONTAL}) {
|
||||||
if (traits.supports_swing_mode(swing_mode))
|
if (traits.supports_swing_mode(swing_mode))
|
||||||
@@ -518,6 +552,12 @@ void APIConnection::climate_command(const ClimateCommandRequest &msg) {
|
|||||||
call.set_away(msg.away);
|
call.set_away(msg.away);
|
||||||
if (msg.has_fan_mode)
|
if (msg.has_fan_mode)
|
||||||
call.set_fan_mode(static_cast<climate::ClimateFanMode>(msg.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)
|
if (msg.has_swing_mode)
|
||||||
call.set_swing_mode(static_cast<climate::ClimateSwingMode>(msg.swing_mode));
|
call.set_swing_mode(static_cast<climate::ClimateSwingMode>(msg.swing_mode));
|
||||||
call.perform();
|
call.perform();
|
||||||
@@ -589,7 +629,7 @@ HelloResponse APIConnection::hello(const HelloRequest &msg) {
|
|||||||
|
|
||||||
HelloResponse resp;
|
HelloResponse resp;
|
||||||
resp.api_version_major = 1;
|
resp.api_version_major = 1;
|
||||||
resp.api_version_minor = 3;
|
resp.api_version_minor = 4;
|
||||||
resp.server_info = App.get_name() + " (esphome v" ESPHOME_VERSION ")";
|
resp.server_info = App.get_name() + " (esphome v" ESPHOME_VERSION ")";
|
||||||
this->connection_state_ = ConnectionState::CONNECTED;
|
this->connection_state_ = ConnectionState::CONNECTED;
|
||||||
return resp;
|
return resp;
|
||||||
@@ -624,13 +664,18 @@ DeviceInfoResponse APIConnection::device_info(const DeviceInfoRequest &msg) {
|
|||||||
#endif
|
#endif
|
||||||
#ifdef USE_DEEP_SLEEP
|
#ifdef USE_DEEP_SLEEP
|
||||||
resp.has_deep_sleep = deep_sleep::global_has_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
|
#endif
|
||||||
return resp;
|
return resp;
|
||||||
}
|
}
|
||||||
void APIConnection::on_home_assistant_state_response(const HomeAssistantStateResponse &msg) {
|
void APIConnection::on_home_assistant_state_response(const HomeAssistantStateResponse &msg) {
|
||||||
for (auto &it : this->parent_->get_state_subs())
|
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);
|
it.callback(msg.state);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
void APIConnection::execute_service(const ExecuteServiceRequest &msg) {
|
void APIConnection::execute_service(const ExecuteServiceRequest &msg) {
|
||||||
bool found = false;
|
bool found = false;
|
||||||
@@ -647,6 +692,7 @@ void APIConnection::subscribe_home_assistant_states(const SubscribeHomeAssistant
|
|||||||
for (auto &it : this->parent_->get_state_subs()) {
|
for (auto &it : this->parent_->get_state_subs()) {
|
||||||
SubscribeHomeAssistantStateResponse resp;
|
SubscribeHomeAssistantStateResponse resp;
|
||||||
resp.entity_id = it.entity_id;
|
resp.entity_id = it.entity_id;
|
||||||
|
resp.attribute = it.attribute.value();
|
||||||
if (!this->send_subscribe_home_assistant_state_response(resp)) {
|
if (!this->send_subscribe_home_assistant_state_response(resp)) {
|
||||||
this->on_fatal_error();
|
this->on_fatal_error();
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -62,6 +62,16 @@ template<> const char *proto_enum_to_string<enums::FanDirection>(enums::FanDirec
|
|||||||
return "UNKNOWN";
|
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) {
|
template<> const char *proto_enum_to_string<enums::LogLevel>(enums::LogLevel value) {
|
||||||
switch (value) {
|
switch (value) {
|
||||||
case enums::LOG_LEVEL_NONE:
|
case enums::LOG_LEVEL_NONE:
|
||||||
@@ -108,8 +118,8 @@ template<> const char *proto_enum_to_string<enums::ClimateMode>(enums::ClimateMo
|
|||||||
switch (value) {
|
switch (value) {
|
||||||
case enums::CLIMATE_MODE_OFF:
|
case enums::CLIMATE_MODE_OFF:
|
||||||
return "CLIMATE_MODE_OFF";
|
return "CLIMATE_MODE_OFF";
|
||||||
case enums::CLIMATE_MODE_AUTO:
|
case enums::CLIMATE_MODE_HEAT_COOL:
|
||||||
return "CLIMATE_MODE_AUTO";
|
return "CLIMATE_MODE_HEAT_COOL";
|
||||||
case enums::CLIMATE_MODE_COOL:
|
case enums::CLIMATE_MODE_COOL:
|
||||||
return "CLIMATE_MODE_COOL";
|
return "CLIMATE_MODE_COOL";
|
||||||
case enums::CLIMATE_MODE_HEAT:
|
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";
|
return "CLIMATE_MODE_FAN_ONLY";
|
||||||
case enums::CLIMATE_MODE_DRY:
|
case enums::CLIMATE_MODE_DRY:
|
||||||
return "CLIMATE_MODE_DRY";
|
return "CLIMATE_MODE_DRY";
|
||||||
|
case enums::CLIMATE_MODE_AUTO:
|
||||||
|
return "CLIMATE_MODE_AUTO";
|
||||||
default:
|
default:
|
||||||
return "UNKNOWN";
|
return "UNKNOWN";
|
||||||
}
|
}
|
||||||
@@ -178,6 +190,26 @@ template<> const char *proto_enum_to_string<enums::ClimateAction>(enums::Climate
|
|||||||
return "UNKNOWN";
|
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) {
|
bool HelloRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
|
||||||
switch (field_id) {
|
switch (field_id) {
|
||||||
case 1: {
|
case 1: {
|
||||||
@@ -328,6 +360,14 @@ bool DeviceInfoResponse::decode_length(uint32_t field_id, ProtoLengthDelimited v
|
|||||||
this->model = value.as_string();
|
this->model = value.as_string();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
case 8: {
|
||||||
|
this->project_name = value.as_string();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case 9: {
|
||||||
|
this->project_version = value.as_string();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -340,6 +380,8 @@ void DeviceInfoResponse::encode(ProtoWriteBuffer buffer) const {
|
|||||||
buffer.encode_string(5, this->compilation_time);
|
buffer.encode_string(5, this->compilation_time);
|
||||||
buffer.encode_string(6, this->model);
|
buffer.encode_string(6, this->model);
|
||||||
buffer.encode_bool(7, this->has_deep_sleep);
|
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 {
|
void DeviceInfoResponse::dump_to(std::string &out) const {
|
||||||
char buffer[64];
|
char buffer[64];
|
||||||
@@ -371,6 +413,14 @@ void DeviceInfoResponse::dump_to(std::string &out) const {
|
|||||||
out.append(" has_deep_sleep: ");
|
out.append(" has_deep_sleep: ");
|
||||||
out.append(YESNO(this->has_deep_sleep));
|
out.append(YESNO(this->has_deep_sleep));
|
||||||
out.append("\n");
|
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("}");
|
out.append("}");
|
||||||
}
|
}
|
||||||
void ListEntitiesRequest::encode(ProtoWriteBuffer buffer) const {}
|
void ListEntitiesRequest::encode(ProtoWriteBuffer buffer) const {}
|
||||||
@@ -774,6 +824,10 @@ bool ListEntitiesFanResponse::decode_varint(uint32_t field_id, ProtoVarInt value
|
|||||||
this->supports_direction = value.as_bool();
|
this->supports_direction = value.as_bool();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
case 8: {
|
||||||
|
this->supported_speed_count = value.as_int32();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -814,6 +868,7 @@ void ListEntitiesFanResponse::encode(ProtoWriteBuffer buffer) const {
|
|||||||
buffer.encode_bool(5, this->supports_oscillation);
|
buffer.encode_bool(5, this->supports_oscillation);
|
||||||
buffer.encode_bool(6, this->supports_speed);
|
buffer.encode_bool(6, this->supports_speed);
|
||||||
buffer.encode_bool(7, this->supports_direction);
|
buffer.encode_bool(7, this->supports_direction);
|
||||||
|
buffer.encode_int32(8, this->supported_speed_count);
|
||||||
}
|
}
|
||||||
void ListEntitiesFanResponse::dump_to(std::string &out) const {
|
void ListEntitiesFanResponse::dump_to(std::string &out) const {
|
||||||
char buffer[64];
|
char buffer[64];
|
||||||
@@ -846,6 +901,11 @@ void ListEntitiesFanResponse::dump_to(std::string &out) const {
|
|||||||
out.append(" supports_direction: ");
|
out.append(" supports_direction: ");
|
||||||
out.append(YESNO(this->supports_direction));
|
out.append(YESNO(this->supports_direction));
|
||||||
out.append("\n");
|
out.append("\n");
|
||||||
|
|
||||||
|
out.append(" supported_speed_count: ");
|
||||||
|
sprintf(buffer, "%d", this->supported_speed_count);
|
||||||
|
out.append(buffer);
|
||||||
|
out.append("\n");
|
||||||
out.append("}");
|
out.append("}");
|
||||||
}
|
}
|
||||||
bool FanStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
bool FanStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||||
@@ -866,6 +926,10 @@ bool FanStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
|||||||
this->direction = value.as_enum<enums::FanDirection>();
|
this->direction = value.as_enum<enums::FanDirection>();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
case 6: {
|
||||||
|
this->speed_level = value.as_int32();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -886,6 +950,7 @@ void FanStateResponse::encode(ProtoWriteBuffer buffer) const {
|
|||||||
buffer.encode_bool(3, this->oscillating);
|
buffer.encode_bool(3, this->oscillating);
|
||||||
buffer.encode_enum<enums::FanSpeed>(4, this->speed);
|
buffer.encode_enum<enums::FanSpeed>(4, this->speed);
|
||||||
buffer.encode_enum<enums::FanDirection>(5, this->direction);
|
buffer.encode_enum<enums::FanDirection>(5, this->direction);
|
||||||
|
buffer.encode_int32(6, this->speed_level);
|
||||||
}
|
}
|
||||||
void FanStateResponse::dump_to(std::string &out) const {
|
void FanStateResponse::dump_to(std::string &out) const {
|
||||||
char buffer[64];
|
char buffer[64];
|
||||||
@@ -910,6 +975,11 @@ void FanStateResponse::dump_to(std::string &out) const {
|
|||||||
out.append(" direction: ");
|
out.append(" direction: ");
|
||||||
out.append(proto_enum_to_string<enums::FanDirection>(this->direction));
|
out.append(proto_enum_to_string<enums::FanDirection>(this->direction));
|
||||||
out.append("\n");
|
out.append("\n");
|
||||||
|
|
||||||
|
out.append(" speed_level: ");
|
||||||
|
sprintf(buffer, "%d", this->speed_level);
|
||||||
|
out.append(buffer);
|
||||||
|
out.append("\n");
|
||||||
out.append("}");
|
out.append("}");
|
||||||
}
|
}
|
||||||
bool FanCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
bool FanCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||||
@@ -946,6 +1016,14 @@ bool FanCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
|||||||
this->direction = value.as_enum<enums::FanDirection>();
|
this->direction = value.as_enum<enums::FanDirection>();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
case 10: {
|
||||||
|
this->has_speed_level = value.as_bool();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case 11: {
|
||||||
|
this->speed_level = value.as_int32();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -970,6 +1048,8 @@ void FanCommandRequest::encode(ProtoWriteBuffer buffer) const {
|
|||||||
buffer.encode_bool(7, this->oscillating);
|
buffer.encode_bool(7, this->oscillating);
|
||||||
buffer.encode_bool(8, this->has_direction);
|
buffer.encode_bool(8, this->has_direction);
|
||||||
buffer.encode_enum<enums::FanDirection>(9, this->direction);
|
buffer.encode_enum<enums::FanDirection>(9, this->direction);
|
||||||
|
buffer.encode_bool(10, this->has_speed_level);
|
||||||
|
buffer.encode_int32(11, this->speed_level);
|
||||||
}
|
}
|
||||||
void FanCommandRequest::dump_to(std::string &out) const {
|
void FanCommandRequest::dump_to(std::string &out) const {
|
||||||
char buffer[64];
|
char buffer[64];
|
||||||
@@ -1010,6 +1090,15 @@ void FanCommandRequest::dump_to(std::string &out) const {
|
|||||||
out.append(" direction: ");
|
out.append(" direction: ");
|
||||||
out.append(proto_enum_to_string<enums::FanDirection>(this->direction));
|
out.append(proto_enum_to_string<enums::FanDirection>(this->direction));
|
||||||
out.append("\n");
|
out.append("\n");
|
||||||
|
|
||||||
|
out.append(" has_speed_level: ");
|
||||||
|
out.append(YESNO(this->has_speed_level));
|
||||||
|
out.append("\n");
|
||||||
|
|
||||||
|
out.append(" speed_level: ");
|
||||||
|
sprintf(buffer, "%d", this->speed_level);
|
||||||
|
out.append(buffer);
|
||||||
|
out.append("\n");
|
||||||
out.append("}");
|
out.append("}");
|
||||||
}
|
}
|
||||||
bool ListEntitiesLightResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
bool ListEntitiesLightResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||||
@@ -1468,6 +1557,10 @@ bool ListEntitiesSensorResponse::decode_varint(uint32_t field_id, ProtoVarInt va
|
|||||||
this->force_update = value.as_bool();
|
this->force_update = value.as_bool();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
case 10: {
|
||||||
|
this->state_class = value.as_enum<enums::SensorStateClass>();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -1494,6 +1587,10 @@ bool ListEntitiesSensorResponse::decode_length(uint32_t field_id, ProtoLengthDel
|
|||||||
this->unit_of_measurement = value.as_string();
|
this->unit_of_measurement = value.as_string();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
case 9: {
|
||||||
|
this->device_class = value.as_string();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -1517,6 +1614,8 @@ void ListEntitiesSensorResponse::encode(ProtoWriteBuffer buffer) const {
|
|||||||
buffer.encode_string(6, this->unit_of_measurement);
|
buffer.encode_string(6, this->unit_of_measurement);
|
||||||
buffer.encode_int32(7, this->accuracy_decimals);
|
buffer.encode_int32(7, this->accuracy_decimals);
|
||||||
buffer.encode_bool(8, this->force_update);
|
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 {
|
void ListEntitiesSensorResponse::dump_to(std::string &out) const {
|
||||||
char buffer[64];
|
char buffer[64];
|
||||||
@@ -1554,6 +1653,14 @@ void ListEntitiesSensorResponse::dump_to(std::string &out) const {
|
|||||||
out.append(" force_update: ");
|
out.append(" force_update: ");
|
||||||
out.append(YESNO(this->force_update));
|
out.append(YESNO(this->force_update));
|
||||||
out.append("\n");
|
out.append("\n");
|
||||||
|
|
||||||
|
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("}");
|
out.append("}");
|
||||||
}
|
}
|
||||||
bool SensorStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
bool SensorStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||||
@@ -2075,12 +2182,17 @@ bool SubscribeHomeAssistantStateResponse::decode_length(uint32_t field_id, Proto
|
|||||||
this->entity_id = value.as_string();
|
this->entity_id = value.as_string();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
case 2: {
|
||||||
|
this->attribute = value.as_string();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void SubscribeHomeAssistantStateResponse::encode(ProtoWriteBuffer buffer) const {
|
void SubscribeHomeAssistantStateResponse::encode(ProtoWriteBuffer buffer) const {
|
||||||
buffer.encode_string(1, this->entity_id);
|
buffer.encode_string(1, this->entity_id);
|
||||||
|
buffer.encode_string(2, this->attribute);
|
||||||
}
|
}
|
||||||
void SubscribeHomeAssistantStateResponse::dump_to(std::string &out) const {
|
void SubscribeHomeAssistantStateResponse::dump_to(std::string &out) const {
|
||||||
char buffer[64];
|
char buffer[64];
|
||||||
@@ -2088,6 +2200,10 @@ void SubscribeHomeAssistantStateResponse::dump_to(std::string &out) const {
|
|||||||
out.append(" entity_id: ");
|
out.append(" entity_id: ");
|
||||||
out.append("'").append(this->entity_id).append("'");
|
out.append("'").append(this->entity_id).append("'");
|
||||||
out.append("\n");
|
out.append("\n");
|
||||||
|
|
||||||
|
out.append(" attribute: ");
|
||||||
|
out.append("'").append(this->attribute).append("'");
|
||||||
|
out.append("\n");
|
||||||
out.append("}");
|
out.append("}");
|
||||||
}
|
}
|
||||||
bool HomeAssistantStateResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
|
bool HomeAssistantStateResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
|
||||||
@@ -2100,6 +2216,10 @@ bool HomeAssistantStateResponse::decode_length(uint32_t field_id, ProtoLengthDel
|
|||||||
this->state = value.as_string();
|
this->state = value.as_string();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
case 3: {
|
||||||
|
this->attribute = value.as_string();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -2107,6 +2227,7 @@ bool HomeAssistantStateResponse::decode_length(uint32_t field_id, ProtoLengthDel
|
|||||||
void HomeAssistantStateResponse::encode(ProtoWriteBuffer buffer) const {
|
void HomeAssistantStateResponse::encode(ProtoWriteBuffer buffer) const {
|
||||||
buffer.encode_string(1, this->entity_id);
|
buffer.encode_string(1, this->entity_id);
|
||||||
buffer.encode_string(2, this->state);
|
buffer.encode_string(2, this->state);
|
||||||
|
buffer.encode_string(3, this->attribute);
|
||||||
}
|
}
|
||||||
void HomeAssistantStateResponse::dump_to(std::string &out) const {
|
void HomeAssistantStateResponse::dump_to(std::string &out) const {
|
||||||
char buffer[64];
|
char buffer[64];
|
||||||
@@ -2118,6 +2239,10 @@ void HomeAssistantStateResponse::dump_to(std::string &out) const {
|
|||||||
out.append(" state: ");
|
out.append(" state: ");
|
||||||
out.append("'").append(this->state).append("'");
|
out.append("'").append(this->state).append("'");
|
||||||
out.append("\n");
|
out.append("\n");
|
||||||
|
|
||||||
|
out.append(" attribute: ");
|
||||||
|
out.append("'").append(this->attribute).append("'");
|
||||||
|
out.append("\n");
|
||||||
out.append("}");
|
out.append("}");
|
||||||
}
|
}
|
||||||
void GetTimeRequest::encode(ProtoWriteBuffer buffer) const {}
|
void GetTimeRequest::encode(ProtoWriteBuffer buffer) const {}
|
||||||
@@ -2562,6 +2687,10 @@ bool ListEntitiesClimateResponse::decode_varint(uint32_t field_id, ProtoVarInt v
|
|||||||
this->supported_swing_modes.push_back(value.as_enum<enums::ClimateSwingMode>());
|
this->supported_swing_modes.push_back(value.as_enum<enums::ClimateSwingMode>());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
case 16: {
|
||||||
|
this->supported_presets.push_back(value.as_enum<enums::ClimatePreset>());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -2580,6 +2709,14 @@ bool ListEntitiesClimateResponse::decode_length(uint32_t field_id, ProtoLengthDe
|
|||||||
this->unique_id = value.as_string();
|
this->unique_id = value.as_string();
|
||||||
return true;
|
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:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -2627,6 +2764,15 @@ void ListEntitiesClimateResponse::encode(ProtoWriteBuffer buffer) const {
|
|||||||
for (auto &it : this->supported_swing_modes) {
|
for (auto &it : this->supported_swing_modes) {
|
||||||
buffer.encode_enum<enums::ClimateSwingMode>(14, it, true);
|
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 {
|
void ListEntitiesClimateResponse::dump_to(std::string &out) const {
|
||||||
char buffer[64];
|
char buffer[64];
|
||||||
@@ -2696,6 +2842,24 @@ void ListEntitiesClimateResponse::dump_to(std::string &out) const {
|
|||||||
out.append(proto_enum_to_string<enums::ClimateSwingMode>(it));
|
out.append(proto_enum_to_string<enums::ClimateSwingMode>(it));
|
||||||
out.append("\n");
|
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("}");
|
out.append("}");
|
||||||
}
|
}
|
||||||
bool ClimateStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
bool ClimateStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||||
@@ -2720,6 +2884,24 @@ bool ClimateStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
|||||||
this->swing_mode = value.as_enum<enums::ClimateSwingMode>();
|
this->swing_mode = value.as_enum<enums::ClimateSwingMode>();
|
||||||
return true;
|
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:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -2761,6 +2943,9 @@ void ClimateStateResponse::encode(ProtoWriteBuffer buffer) const {
|
|||||||
buffer.encode_enum<enums::ClimateAction>(8, this->action);
|
buffer.encode_enum<enums::ClimateAction>(8, this->action);
|
||||||
buffer.encode_enum<enums::ClimateFanMode>(9, this->fan_mode);
|
buffer.encode_enum<enums::ClimateFanMode>(9, this->fan_mode);
|
||||||
buffer.encode_enum<enums::ClimateSwingMode>(10, this->swing_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 {
|
void ClimateStateResponse::dump_to(std::string &out) const {
|
||||||
char buffer[64];
|
char buffer[64];
|
||||||
@@ -2809,6 +2994,18 @@ void ClimateStateResponse::dump_to(std::string &out) const {
|
|||||||
out.append(" swing_mode: ");
|
out.append(" swing_mode: ");
|
||||||
out.append(proto_enum_to_string<enums::ClimateSwingMode>(this->swing_mode));
|
out.append(proto_enum_to_string<enums::ClimateSwingMode>(this->swing_mode));
|
||||||
out.append("\n");
|
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("}");
|
out.append("}");
|
||||||
}
|
}
|
||||||
bool ClimateCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
bool ClimateCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||||
@@ -2857,6 +3054,36 @@ bool ClimateCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value)
|
|||||||
this->swing_mode = value.as_enum<enums::ClimateSwingMode>();
|
this->swing_mode = value.as_enum<enums::ClimateSwingMode>();
|
||||||
return true;
|
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:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -2899,6 +3126,12 @@ void ClimateCommandRequest::encode(ProtoWriteBuffer buffer) const {
|
|||||||
buffer.encode_enum<enums::ClimateFanMode>(13, this->fan_mode);
|
buffer.encode_enum<enums::ClimateFanMode>(13, this->fan_mode);
|
||||||
buffer.encode_bool(14, this->has_swing_mode);
|
buffer.encode_bool(14, this->has_swing_mode);
|
||||||
buffer.encode_enum<enums::ClimateSwingMode>(15, this->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 {
|
void ClimateCommandRequest::dump_to(std::string &out) const {
|
||||||
char buffer[64];
|
char buffer[64];
|
||||||
@@ -2966,6 +3199,30 @@ void ClimateCommandRequest::dump_to(std::string &out) const {
|
|||||||
out.append(" swing_mode: ");
|
out.append(" swing_mode: ");
|
||||||
out.append(proto_enum_to_string<enums::ClimateSwingMode>(this->swing_mode));
|
out.append(proto_enum_to_string<enums::ClimateSwingMode>(this->swing_mode));
|
||||||
out.append("\n");
|
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("}");
|
out.append("}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -32,6 +32,10 @@ enum FanDirection : uint32_t {
|
|||||||
FAN_DIRECTION_FORWARD = 0,
|
FAN_DIRECTION_FORWARD = 0,
|
||||||
FAN_DIRECTION_REVERSE = 1,
|
FAN_DIRECTION_REVERSE = 1,
|
||||||
};
|
};
|
||||||
|
enum SensorStateClass : uint32_t {
|
||||||
|
STATE_CLASS_NONE = 0,
|
||||||
|
STATE_CLASS_MEASUREMENT = 1,
|
||||||
|
};
|
||||||
enum LogLevel : uint32_t {
|
enum LogLevel : uint32_t {
|
||||||
LOG_LEVEL_NONE = 0,
|
LOG_LEVEL_NONE = 0,
|
||||||
LOG_LEVEL_ERROR = 1,
|
LOG_LEVEL_ERROR = 1,
|
||||||
@@ -53,11 +57,12 @@ enum ServiceArgType : uint32_t {
|
|||||||
};
|
};
|
||||||
enum ClimateMode : uint32_t {
|
enum ClimateMode : uint32_t {
|
||||||
CLIMATE_MODE_OFF = 0,
|
CLIMATE_MODE_OFF = 0,
|
||||||
CLIMATE_MODE_AUTO = 1,
|
CLIMATE_MODE_HEAT_COOL = 1,
|
||||||
CLIMATE_MODE_COOL = 2,
|
CLIMATE_MODE_COOL = 2,
|
||||||
CLIMATE_MODE_HEAT = 3,
|
CLIMATE_MODE_HEAT = 3,
|
||||||
CLIMATE_MODE_FAN_ONLY = 4,
|
CLIMATE_MODE_FAN_ONLY = 4,
|
||||||
CLIMATE_MODE_DRY = 5,
|
CLIMATE_MODE_DRY = 5,
|
||||||
|
CLIMATE_MODE_AUTO = 6,
|
||||||
};
|
};
|
||||||
enum ClimateFanMode : uint32_t {
|
enum ClimateFanMode : uint32_t {
|
||||||
CLIMATE_FAN_ON = 0,
|
CLIMATE_FAN_ON = 0,
|
||||||
@@ -84,12 +89,21 @@ enum ClimateAction : uint32_t {
|
|||||||
CLIMATE_ACTION_DRYING = 5,
|
CLIMATE_ACTION_DRYING = 5,
|
||||||
CLIMATE_ACTION_FAN = 6,
|
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
|
} // namespace enums
|
||||||
|
|
||||||
class HelloRequest : public ProtoMessage {
|
class HelloRequest : public ProtoMessage {
|
||||||
public:
|
public:
|
||||||
std::string client_info{}; // NOLINT
|
std::string client_info{};
|
||||||
void encode(ProtoWriteBuffer buffer) const override;
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
void dump_to(std::string &out) const override;
|
void dump_to(std::string &out) const override;
|
||||||
|
|
||||||
@@ -98,9 +112,9 @@ class HelloRequest : public ProtoMessage {
|
|||||||
};
|
};
|
||||||
class HelloResponse : public ProtoMessage {
|
class HelloResponse : public ProtoMessage {
|
||||||
public:
|
public:
|
||||||
uint32_t api_version_major{0}; // NOLINT
|
uint32_t api_version_major{0};
|
||||||
uint32_t api_version_minor{0}; // NOLINT
|
uint32_t api_version_minor{0};
|
||||||
std::string server_info{}; // NOLINT
|
std::string server_info{};
|
||||||
void encode(ProtoWriteBuffer buffer) const override;
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
void dump_to(std::string &out) const override;
|
void dump_to(std::string &out) const override;
|
||||||
|
|
||||||
@@ -110,7 +124,7 @@ class HelloResponse : public ProtoMessage {
|
|||||||
};
|
};
|
||||||
class ConnectRequest : public ProtoMessage {
|
class ConnectRequest : public ProtoMessage {
|
||||||
public:
|
public:
|
||||||
std::string password{}; // NOLINT
|
std::string password{};
|
||||||
void encode(ProtoWriteBuffer buffer) const override;
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
void dump_to(std::string &out) const override;
|
void dump_to(std::string &out) const override;
|
||||||
|
|
||||||
@@ -119,7 +133,7 @@ class ConnectRequest : public ProtoMessage {
|
|||||||
};
|
};
|
||||||
class ConnectResponse : public ProtoMessage {
|
class ConnectResponse : public ProtoMessage {
|
||||||
public:
|
public:
|
||||||
bool invalid_password{false}; // NOLINT
|
bool invalid_password{false};
|
||||||
void encode(ProtoWriteBuffer buffer) const override;
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
void dump_to(std::string &out) const override;
|
void dump_to(std::string &out) const override;
|
||||||
|
|
||||||
@@ -163,13 +177,15 @@ class DeviceInfoRequest : public ProtoMessage {
|
|||||||
};
|
};
|
||||||
class DeviceInfoResponse : public ProtoMessage {
|
class DeviceInfoResponse : public ProtoMessage {
|
||||||
public:
|
public:
|
||||||
bool uses_password{false}; // NOLINT
|
bool uses_password{false};
|
||||||
std::string name{}; // NOLINT
|
std::string name{};
|
||||||
std::string mac_address{}; // NOLINT
|
std::string mac_address{};
|
||||||
std::string esphome_version{}; // NOLINT
|
std::string esphome_version{};
|
||||||
std::string compilation_time{}; // NOLINT
|
std::string compilation_time{};
|
||||||
std::string model{}; // NOLINT
|
std::string model{};
|
||||||
bool has_deep_sleep{false}; // NOLINT
|
bool has_deep_sleep{false};
|
||||||
|
std::string project_name{};
|
||||||
|
std::string project_version{};
|
||||||
void encode(ProtoWriteBuffer buffer) const override;
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
void dump_to(std::string &out) const override;
|
void dump_to(std::string &out) const override;
|
||||||
|
|
||||||
@@ -200,12 +216,12 @@ class SubscribeStatesRequest : public ProtoMessage {
|
|||||||
};
|
};
|
||||||
class ListEntitiesBinarySensorResponse : public ProtoMessage {
|
class ListEntitiesBinarySensorResponse : public ProtoMessage {
|
||||||
public:
|
public:
|
||||||
std::string object_id{}; // NOLINT
|
std::string object_id{};
|
||||||
uint32_t key{0}; // NOLINT
|
uint32_t key{0};
|
||||||
std::string name{}; // NOLINT
|
std::string name{};
|
||||||
std::string unique_id{}; // NOLINT
|
std::string unique_id{};
|
||||||
std::string device_class{}; // NOLINT
|
std::string device_class{};
|
||||||
bool is_status_binary_sensor{false}; // NOLINT
|
bool is_status_binary_sensor{false};
|
||||||
void encode(ProtoWriteBuffer buffer) const override;
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
void dump_to(std::string &out) const override;
|
void dump_to(std::string &out) const override;
|
||||||
|
|
||||||
@@ -216,9 +232,9 @@ class ListEntitiesBinarySensorResponse : public ProtoMessage {
|
|||||||
};
|
};
|
||||||
class BinarySensorStateResponse : public ProtoMessage {
|
class BinarySensorStateResponse : public ProtoMessage {
|
||||||
public:
|
public:
|
||||||
uint32_t key{0}; // NOLINT
|
uint32_t key{0};
|
||||||
bool state{false}; // NOLINT
|
bool state{false};
|
||||||
bool missing_state{false}; // NOLINT
|
bool missing_state{false};
|
||||||
void encode(ProtoWriteBuffer buffer) const override;
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
void dump_to(std::string &out) const override;
|
void dump_to(std::string &out) const override;
|
||||||
|
|
||||||
@@ -228,14 +244,14 @@ class BinarySensorStateResponse : public ProtoMessage {
|
|||||||
};
|
};
|
||||||
class ListEntitiesCoverResponse : public ProtoMessage {
|
class ListEntitiesCoverResponse : public ProtoMessage {
|
||||||
public:
|
public:
|
||||||
std::string object_id{}; // NOLINT
|
std::string object_id{};
|
||||||
uint32_t key{0}; // NOLINT
|
uint32_t key{0};
|
||||||
std::string name{}; // NOLINT
|
std::string name{};
|
||||||
std::string unique_id{}; // NOLINT
|
std::string unique_id{};
|
||||||
bool assumed_state{false}; // NOLINT
|
bool assumed_state{false};
|
||||||
bool supports_position{false}; // NOLINT
|
bool supports_position{false};
|
||||||
bool supports_tilt{false}; // NOLINT
|
bool supports_tilt{false};
|
||||||
std::string device_class{}; // NOLINT
|
std::string device_class{};
|
||||||
void encode(ProtoWriteBuffer buffer) const override;
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
void dump_to(std::string &out) const override;
|
void dump_to(std::string &out) const override;
|
||||||
|
|
||||||
@@ -246,11 +262,11 @@ class ListEntitiesCoverResponse : public ProtoMessage {
|
|||||||
};
|
};
|
||||||
class CoverStateResponse : public ProtoMessage {
|
class CoverStateResponse : public ProtoMessage {
|
||||||
public:
|
public:
|
||||||
uint32_t key{0}; // NOLINT
|
uint32_t key{0};
|
||||||
enums::LegacyCoverState legacy_state{}; // NOLINT
|
enums::LegacyCoverState legacy_state{};
|
||||||
float position{0.0f}; // NOLINT
|
float position{0.0f};
|
||||||
float tilt{0.0f}; // NOLINT
|
float tilt{0.0f};
|
||||||
enums::CoverOperation current_operation{}; // NOLINT
|
enums::CoverOperation current_operation{};
|
||||||
void encode(ProtoWriteBuffer buffer) const override;
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
void dump_to(std::string &out) const override;
|
void dump_to(std::string &out) const override;
|
||||||
|
|
||||||
@@ -260,14 +276,14 @@ class CoverStateResponse : public ProtoMessage {
|
|||||||
};
|
};
|
||||||
class CoverCommandRequest : public ProtoMessage {
|
class CoverCommandRequest : public ProtoMessage {
|
||||||
public:
|
public:
|
||||||
uint32_t key{0}; // NOLINT
|
uint32_t key{0};
|
||||||
bool has_legacy_command{false}; // NOLINT
|
bool has_legacy_command{false};
|
||||||
enums::LegacyCoverCommand legacy_command{}; // NOLINT
|
enums::LegacyCoverCommand legacy_command{};
|
||||||
bool has_position{false}; // NOLINT
|
bool has_position{false};
|
||||||
float position{0.0f}; // NOLINT
|
float position{0.0f};
|
||||||
bool has_tilt{false}; // NOLINT
|
bool has_tilt{false};
|
||||||
float tilt{0.0f}; // NOLINT
|
float tilt{0.0f};
|
||||||
bool stop{false}; // NOLINT
|
bool stop{false};
|
||||||
void encode(ProtoWriteBuffer buffer) const override;
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
void dump_to(std::string &out) const override;
|
void dump_to(std::string &out) const override;
|
||||||
|
|
||||||
@@ -277,13 +293,14 @@ class CoverCommandRequest : public ProtoMessage {
|
|||||||
};
|
};
|
||||||
class ListEntitiesFanResponse : public ProtoMessage {
|
class ListEntitiesFanResponse : public ProtoMessage {
|
||||||
public:
|
public:
|
||||||
std::string object_id{}; // NOLINT
|
std::string object_id{};
|
||||||
uint32_t key{0}; // NOLINT
|
uint32_t key{0};
|
||||||
std::string name{}; // NOLINT
|
std::string name{};
|
||||||
std::string unique_id{}; // NOLINT
|
std::string unique_id{};
|
||||||
bool supports_oscillation{false}; // NOLINT
|
bool supports_oscillation{false};
|
||||||
bool supports_speed{false}; // NOLINT
|
bool supports_speed{false};
|
||||||
bool supports_direction{false}; // NOLINT
|
bool supports_direction{false};
|
||||||
|
int32_t supported_speed_count{0};
|
||||||
void encode(ProtoWriteBuffer buffer) const override;
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
void dump_to(std::string &out) const override;
|
void dump_to(std::string &out) const override;
|
||||||
|
|
||||||
@@ -294,11 +311,12 @@ class ListEntitiesFanResponse : public ProtoMessage {
|
|||||||
};
|
};
|
||||||
class FanStateResponse : public ProtoMessage {
|
class FanStateResponse : public ProtoMessage {
|
||||||
public:
|
public:
|
||||||
uint32_t key{0}; // NOLINT
|
uint32_t key{0};
|
||||||
bool state{false}; // NOLINT
|
bool state{false};
|
||||||
bool oscillating{false}; // NOLINT
|
bool oscillating{false};
|
||||||
enums::FanSpeed speed{}; // NOLINT
|
enums::FanSpeed speed{};
|
||||||
enums::FanDirection direction{}; // NOLINT
|
enums::FanDirection direction{};
|
||||||
|
int32_t speed_level{0};
|
||||||
void encode(ProtoWriteBuffer buffer) const override;
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
void dump_to(std::string &out) const override;
|
void dump_to(std::string &out) const override;
|
||||||
|
|
||||||
@@ -308,15 +326,17 @@ class FanStateResponse : public ProtoMessage {
|
|||||||
};
|
};
|
||||||
class FanCommandRequest : public ProtoMessage {
|
class FanCommandRequest : public ProtoMessage {
|
||||||
public:
|
public:
|
||||||
uint32_t key{0}; // NOLINT
|
uint32_t key{0};
|
||||||
bool has_state{false}; // NOLINT
|
bool has_state{false};
|
||||||
bool state{false}; // NOLINT
|
bool state{false};
|
||||||
bool has_speed{false}; // NOLINT
|
bool has_speed{false};
|
||||||
enums::FanSpeed speed{}; // NOLINT
|
enums::FanSpeed speed{};
|
||||||
bool has_oscillating{false}; // NOLINT
|
bool has_oscillating{false};
|
||||||
bool oscillating{false}; // NOLINT
|
bool oscillating{false};
|
||||||
bool has_direction{false}; // NOLINT
|
bool has_direction{false};
|
||||||
enums::FanDirection direction{}; // NOLINT
|
enums::FanDirection direction{};
|
||||||
|
bool has_speed_level{false};
|
||||||
|
int32_t speed_level{0};
|
||||||
void encode(ProtoWriteBuffer buffer) const override;
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
void dump_to(std::string &out) const override;
|
void dump_to(std::string &out) const override;
|
||||||
|
|
||||||
@@ -326,17 +346,17 @@ class FanCommandRequest : public ProtoMessage {
|
|||||||
};
|
};
|
||||||
class ListEntitiesLightResponse : public ProtoMessage {
|
class ListEntitiesLightResponse : public ProtoMessage {
|
||||||
public:
|
public:
|
||||||
std::string object_id{}; // NOLINT
|
std::string object_id{};
|
||||||
uint32_t key{0}; // NOLINT
|
uint32_t key{0};
|
||||||
std::string name{}; // NOLINT
|
std::string name{};
|
||||||
std::string unique_id{}; // NOLINT
|
std::string unique_id{};
|
||||||
bool supports_brightness{false}; // NOLINT
|
bool supports_brightness{false};
|
||||||
bool supports_rgb{false}; // NOLINT
|
bool supports_rgb{false};
|
||||||
bool supports_white_value{false}; // NOLINT
|
bool supports_white_value{false};
|
||||||
bool supports_color_temperature{false}; // NOLINT
|
bool supports_color_temperature{false};
|
||||||
float min_mireds{0.0f}; // NOLINT
|
float min_mireds{0.0f};
|
||||||
float max_mireds{0.0f}; // NOLINT
|
float max_mireds{0.0f};
|
||||||
std::vector<std::string> effects{}; // NOLINT
|
std::vector<std::string> effects{};
|
||||||
void encode(ProtoWriteBuffer buffer) const override;
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
void dump_to(std::string &out) const override;
|
void dump_to(std::string &out) const override;
|
||||||
|
|
||||||
@@ -347,15 +367,15 @@ class ListEntitiesLightResponse : public ProtoMessage {
|
|||||||
};
|
};
|
||||||
class LightStateResponse : public ProtoMessage {
|
class LightStateResponse : public ProtoMessage {
|
||||||
public:
|
public:
|
||||||
uint32_t key{0}; // NOLINT
|
uint32_t key{0};
|
||||||
bool state{false}; // NOLINT
|
bool state{false};
|
||||||
float brightness{0.0f}; // NOLINT
|
float brightness{0.0f};
|
||||||
float red{0.0f}; // NOLINT
|
float red{0.0f};
|
||||||
float green{0.0f}; // NOLINT
|
float green{0.0f};
|
||||||
float blue{0.0f}; // NOLINT
|
float blue{0.0f};
|
||||||
float white{0.0f}; // NOLINT
|
float white{0.0f};
|
||||||
float color_temperature{0.0f}; // NOLINT
|
float color_temperature{0.0f};
|
||||||
std::string effect{}; // NOLINT
|
std::string effect{};
|
||||||
void encode(ProtoWriteBuffer buffer) const override;
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
void dump_to(std::string &out) const override;
|
void dump_to(std::string &out) const override;
|
||||||
|
|
||||||
@@ -366,25 +386,25 @@ class LightStateResponse : public ProtoMessage {
|
|||||||
};
|
};
|
||||||
class LightCommandRequest : public ProtoMessage {
|
class LightCommandRequest : public ProtoMessage {
|
||||||
public:
|
public:
|
||||||
uint32_t key{0}; // NOLINT
|
uint32_t key{0};
|
||||||
bool has_state{false}; // NOLINT
|
bool has_state{false};
|
||||||
bool state{false}; // NOLINT
|
bool state{false};
|
||||||
bool has_brightness{false}; // NOLINT
|
bool has_brightness{false};
|
||||||
float brightness{0.0f}; // NOLINT
|
float brightness{0.0f};
|
||||||
bool has_rgb{false}; // NOLINT
|
bool has_rgb{false};
|
||||||
float red{0.0f}; // NOLINT
|
float red{0.0f};
|
||||||
float green{0.0f}; // NOLINT
|
float green{0.0f};
|
||||||
float blue{0.0f}; // NOLINT
|
float blue{0.0f};
|
||||||
bool has_white{false}; // NOLINT
|
bool has_white{false};
|
||||||
float white{0.0f}; // NOLINT
|
float white{0.0f};
|
||||||
bool has_color_temperature{false}; // NOLINT
|
bool has_color_temperature{false};
|
||||||
float color_temperature{0.0f}; // NOLINT
|
float color_temperature{0.0f};
|
||||||
bool has_transition_length{false}; // NOLINT
|
bool has_transition_length{false};
|
||||||
uint32_t transition_length{0}; // NOLINT
|
uint32_t transition_length{0};
|
||||||
bool has_flash_length{false}; // NOLINT
|
bool has_flash_length{false};
|
||||||
uint32_t flash_length{0}; // NOLINT
|
uint32_t flash_length{0};
|
||||||
bool has_effect{false}; // NOLINT
|
bool has_effect{false};
|
||||||
std::string effect{}; // NOLINT
|
std::string effect{};
|
||||||
void encode(ProtoWriteBuffer buffer) const override;
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
void dump_to(std::string &out) const override;
|
void dump_to(std::string &out) const override;
|
||||||
|
|
||||||
@@ -395,14 +415,16 @@ class LightCommandRequest : public ProtoMessage {
|
|||||||
};
|
};
|
||||||
class ListEntitiesSensorResponse : public ProtoMessage {
|
class ListEntitiesSensorResponse : public ProtoMessage {
|
||||||
public:
|
public:
|
||||||
std::string object_id{}; // NOLINT
|
std::string object_id{};
|
||||||
uint32_t key{0}; // NOLINT
|
uint32_t key{0};
|
||||||
std::string name{}; // NOLINT
|
std::string name{};
|
||||||
std::string unique_id{}; // NOLINT
|
std::string unique_id{};
|
||||||
std::string icon{}; // NOLINT
|
std::string icon{};
|
||||||
std::string unit_of_measurement{}; // NOLINT
|
std::string unit_of_measurement{};
|
||||||
int32_t accuracy_decimals{0}; // NOLINT
|
int32_t accuracy_decimals{0};
|
||||||
bool force_update{false}; // NOLINT
|
bool force_update{false};
|
||||||
|
std::string device_class{};
|
||||||
|
enums::SensorStateClass state_class{};
|
||||||
void encode(ProtoWriteBuffer buffer) const override;
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
void dump_to(std::string &out) const override;
|
void dump_to(std::string &out) const override;
|
||||||
|
|
||||||
@@ -413,9 +435,9 @@ class ListEntitiesSensorResponse : public ProtoMessage {
|
|||||||
};
|
};
|
||||||
class SensorStateResponse : public ProtoMessage {
|
class SensorStateResponse : public ProtoMessage {
|
||||||
public:
|
public:
|
||||||
uint32_t key{0}; // NOLINT
|
uint32_t key{0};
|
||||||
float state{0.0f}; // NOLINT
|
float state{0.0f};
|
||||||
bool missing_state{false}; // NOLINT
|
bool missing_state{false};
|
||||||
void encode(ProtoWriteBuffer buffer) const override;
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
void dump_to(std::string &out) const override;
|
void dump_to(std::string &out) const override;
|
||||||
|
|
||||||
@@ -425,12 +447,12 @@ class SensorStateResponse : public ProtoMessage {
|
|||||||
};
|
};
|
||||||
class ListEntitiesSwitchResponse : public ProtoMessage {
|
class ListEntitiesSwitchResponse : public ProtoMessage {
|
||||||
public:
|
public:
|
||||||
std::string object_id{}; // NOLINT
|
std::string object_id{};
|
||||||
uint32_t key{0}; // NOLINT
|
uint32_t key{0};
|
||||||
std::string name{}; // NOLINT
|
std::string name{};
|
||||||
std::string unique_id{}; // NOLINT
|
std::string unique_id{};
|
||||||
std::string icon{}; // NOLINT
|
std::string icon{};
|
||||||
bool assumed_state{false}; // NOLINT
|
bool assumed_state{false};
|
||||||
void encode(ProtoWriteBuffer buffer) const override;
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
void dump_to(std::string &out) const override;
|
void dump_to(std::string &out) const override;
|
||||||
|
|
||||||
@@ -441,8 +463,8 @@ class ListEntitiesSwitchResponse : public ProtoMessage {
|
|||||||
};
|
};
|
||||||
class SwitchStateResponse : public ProtoMessage {
|
class SwitchStateResponse : public ProtoMessage {
|
||||||
public:
|
public:
|
||||||
uint32_t key{0}; // NOLINT
|
uint32_t key{0};
|
||||||
bool state{false}; // NOLINT
|
bool state{false};
|
||||||
void encode(ProtoWriteBuffer buffer) const override;
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
void dump_to(std::string &out) const override;
|
void dump_to(std::string &out) const override;
|
||||||
|
|
||||||
@@ -452,8 +474,8 @@ class SwitchStateResponse : public ProtoMessage {
|
|||||||
};
|
};
|
||||||
class SwitchCommandRequest : public ProtoMessage {
|
class SwitchCommandRequest : public ProtoMessage {
|
||||||
public:
|
public:
|
||||||
uint32_t key{0}; // NOLINT
|
uint32_t key{0};
|
||||||
bool state{false}; // NOLINT
|
bool state{false};
|
||||||
void encode(ProtoWriteBuffer buffer) const override;
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
void dump_to(std::string &out) const override;
|
void dump_to(std::string &out) const override;
|
||||||
|
|
||||||
@@ -463,11 +485,11 @@ class SwitchCommandRequest : public ProtoMessage {
|
|||||||
};
|
};
|
||||||
class ListEntitiesTextSensorResponse : public ProtoMessage {
|
class ListEntitiesTextSensorResponse : public ProtoMessage {
|
||||||
public:
|
public:
|
||||||
std::string object_id{}; // NOLINT
|
std::string object_id{};
|
||||||
uint32_t key{0}; // NOLINT
|
uint32_t key{0};
|
||||||
std::string name{}; // NOLINT
|
std::string name{};
|
||||||
std::string unique_id{}; // NOLINT
|
std::string unique_id{};
|
||||||
std::string icon{}; // NOLINT
|
std::string icon{};
|
||||||
void encode(ProtoWriteBuffer buffer) const override;
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
void dump_to(std::string &out) const override;
|
void dump_to(std::string &out) const override;
|
||||||
|
|
||||||
@@ -477,9 +499,9 @@ class ListEntitiesTextSensorResponse : public ProtoMessage {
|
|||||||
};
|
};
|
||||||
class TextSensorStateResponse : public ProtoMessage {
|
class TextSensorStateResponse : public ProtoMessage {
|
||||||
public:
|
public:
|
||||||
uint32_t key{0}; // NOLINT
|
uint32_t key{0};
|
||||||
std::string state{}; // NOLINT
|
std::string state{};
|
||||||
bool missing_state{false}; // NOLINT
|
bool missing_state{false};
|
||||||
void encode(ProtoWriteBuffer buffer) const override;
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
void dump_to(std::string &out) const override;
|
void dump_to(std::string &out) const override;
|
||||||
|
|
||||||
@@ -490,8 +512,8 @@ class TextSensorStateResponse : public ProtoMessage {
|
|||||||
};
|
};
|
||||||
class SubscribeLogsRequest : public ProtoMessage {
|
class SubscribeLogsRequest : public ProtoMessage {
|
||||||
public:
|
public:
|
||||||
enums::LogLevel level{}; // NOLINT
|
enums::LogLevel level{};
|
||||||
bool dump_config{false}; // NOLINT
|
bool dump_config{false};
|
||||||
void encode(ProtoWriteBuffer buffer) const override;
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
void dump_to(std::string &out) const override;
|
void dump_to(std::string &out) const override;
|
||||||
|
|
||||||
@@ -500,10 +522,10 @@ class SubscribeLogsRequest : public ProtoMessage {
|
|||||||
};
|
};
|
||||||
class SubscribeLogsResponse : public ProtoMessage {
|
class SubscribeLogsResponse : public ProtoMessage {
|
||||||
public:
|
public:
|
||||||
enums::LogLevel level{}; // NOLINT
|
enums::LogLevel level{};
|
||||||
std::string tag{}; // NOLINT
|
std::string tag{};
|
||||||
std::string message{}; // NOLINT
|
std::string message{};
|
||||||
bool send_failed{false}; // NOLINT
|
bool send_failed{false};
|
||||||
void encode(ProtoWriteBuffer buffer) const override;
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
void dump_to(std::string &out) const override;
|
void dump_to(std::string &out) const override;
|
||||||
|
|
||||||
@@ -520,8 +542,8 @@ class SubscribeHomeassistantServicesRequest : public ProtoMessage {
|
|||||||
};
|
};
|
||||||
class HomeassistantServiceMap : public ProtoMessage {
|
class HomeassistantServiceMap : public ProtoMessage {
|
||||||
public:
|
public:
|
||||||
std::string key{}; // NOLINT
|
std::string key{};
|
||||||
std::string value{}; // NOLINT
|
std::string value{};
|
||||||
void encode(ProtoWriteBuffer buffer) const override;
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
void dump_to(std::string &out) const override;
|
void dump_to(std::string &out) const override;
|
||||||
|
|
||||||
@@ -530,11 +552,11 @@ class HomeassistantServiceMap : public ProtoMessage {
|
|||||||
};
|
};
|
||||||
class HomeassistantServiceResponse : public ProtoMessage {
|
class HomeassistantServiceResponse : public ProtoMessage {
|
||||||
public:
|
public:
|
||||||
std::string service{}; // NOLINT
|
std::string service{};
|
||||||
std::vector<HomeassistantServiceMap> data{}; // NOLINT
|
std::vector<HomeassistantServiceMap> data{};
|
||||||
std::vector<HomeassistantServiceMap> data_template{}; // NOLINT
|
std::vector<HomeassistantServiceMap> data_template{};
|
||||||
std::vector<HomeassistantServiceMap> variables{}; // NOLINT
|
std::vector<HomeassistantServiceMap> variables{};
|
||||||
bool is_event{false}; // NOLINT
|
bool is_event{false};
|
||||||
void encode(ProtoWriteBuffer buffer) const override;
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
void dump_to(std::string &out) const override;
|
void dump_to(std::string &out) const override;
|
||||||
|
|
||||||
@@ -551,7 +573,8 @@ class SubscribeHomeAssistantStatesRequest : public ProtoMessage {
|
|||||||
};
|
};
|
||||||
class SubscribeHomeAssistantStateResponse : public ProtoMessage {
|
class SubscribeHomeAssistantStateResponse : public ProtoMessage {
|
||||||
public:
|
public:
|
||||||
std::string entity_id{}; // NOLINT
|
std::string entity_id{};
|
||||||
|
std::string attribute{};
|
||||||
void encode(ProtoWriteBuffer buffer) const override;
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
void dump_to(std::string &out) const override;
|
void dump_to(std::string &out) const override;
|
||||||
|
|
||||||
@@ -560,8 +583,9 @@ class SubscribeHomeAssistantStateResponse : public ProtoMessage {
|
|||||||
};
|
};
|
||||||
class HomeAssistantStateResponse : public ProtoMessage {
|
class HomeAssistantStateResponse : public ProtoMessage {
|
||||||
public:
|
public:
|
||||||
std::string entity_id{}; // NOLINT
|
std::string entity_id{};
|
||||||
std::string state{}; // NOLINT
|
std::string state{};
|
||||||
|
std::string attribute{};
|
||||||
void encode(ProtoWriteBuffer buffer) const override;
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
void dump_to(std::string &out) const override;
|
void dump_to(std::string &out) const override;
|
||||||
|
|
||||||
@@ -577,7 +601,7 @@ class GetTimeRequest : public ProtoMessage {
|
|||||||
};
|
};
|
||||||
class GetTimeResponse : public ProtoMessage {
|
class GetTimeResponse : public ProtoMessage {
|
||||||
public:
|
public:
|
||||||
uint32_t epoch_seconds{0}; // NOLINT
|
uint32_t epoch_seconds{0};
|
||||||
void encode(ProtoWriteBuffer buffer) const override;
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
void dump_to(std::string &out) const override;
|
void dump_to(std::string &out) const override;
|
||||||
|
|
||||||
@@ -586,8 +610,8 @@ class GetTimeResponse : public ProtoMessage {
|
|||||||
};
|
};
|
||||||
class ListEntitiesServicesArgument : public ProtoMessage {
|
class ListEntitiesServicesArgument : public ProtoMessage {
|
||||||
public:
|
public:
|
||||||
std::string name{}; // NOLINT
|
std::string name{};
|
||||||
enums::ServiceArgType type{}; // NOLINT
|
enums::ServiceArgType type{};
|
||||||
void encode(ProtoWriteBuffer buffer) const override;
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
void dump_to(std::string &out) const override;
|
void dump_to(std::string &out) const override;
|
||||||
|
|
||||||
@@ -597,9 +621,9 @@ class ListEntitiesServicesArgument : public ProtoMessage {
|
|||||||
};
|
};
|
||||||
class ListEntitiesServicesResponse : public ProtoMessage {
|
class ListEntitiesServicesResponse : public ProtoMessage {
|
||||||
public:
|
public:
|
||||||
std::string name{}; // NOLINT
|
std::string name{};
|
||||||
uint32_t key{0}; // NOLINT
|
uint32_t key{0};
|
||||||
std::vector<ListEntitiesServicesArgument> args{}; // NOLINT
|
std::vector<ListEntitiesServicesArgument> args{};
|
||||||
void encode(ProtoWriteBuffer buffer) const override;
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
void dump_to(std::string &out) const override;
|
void dump_to(std::string &out) const override;
|
||||||
|
|
||||||
@@ -609,15 +633,15 @@ class ListEntitiesServicesResponse : public ProtoMessage {
|
|||||||
};
|
};
|
||||||
class ExecuteServiceArgument : public ProtoMessage {
|
class ExecuteServiceArgument : public ProtoMessage {
|
||||||
public:
|
public:
|
||||||
bool bool_{false}; // NOLINT
|
bool bool_{false};
|
||||||
int32_t legacy_int{0}; // NOLINT
|
int32_t legacy_int{0};
|
||||||
float float_{0.0f}; // NOLINT
|
float float_{0.0f};
|
||||||
std::string string_{}; // NOLINT
|
std::string string_{};
|
||||||
int32_t int_{0}; // NOLINT
|
int32_t int_{0};
|
||||||
std::vector<bool> bool_array{}; // NOLINT
|
std::vector<bool> bool_array{};
|
||||||
std::vector<int32_t> int_array{}; // NOLINT
|
std::vector<int32_t> int_array{};
|
||||||
std::vector<float> float_array{}; // NOLINT
|
std::vector<float> float_array{};
|
||||||
std::vector<std::string> string_array{}; // NOLINT
|
std::vector<std::string> string_array{};
|
||||||
void encode(ProtoWriteBuffer buffer) const override;
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
void dump_to(std::string &out) const override;
|
void dump_to(std::string &out) const override;
|
||||||
|
|
||||||
@@ -628,8 +652,8 @@ class ExecuteServiceArgument : public ProtoMessage {
|
|||||||
};
|
};
|
||||||
class ExecuteServiceRequest : public ProtoMessage {
|
class ExecuteServiceRequest : public ProtoMessage {
|
||||||
public:
|
public:
|
||||||
uint32_t key{0}; // NOLINT
|
uint32_t key{0};
|
||||||
std::vector<ExecuteServiceArgument> args{}; // NOLINT
|
std::vector<ExecuteServiceArgument> args{};
|
||||||
void encode(ProtoWriteBuffer buffer) const override;
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
void dump_to(std::string &out) const override;
|
void dump_to(std::string &out) const override;
|
||||||
|
|
||||||
@@ -639,10 +663,10 @@ class ExecuteServiceRequest : public ProtoMessage {
|
|||||||
};
|
};
|
||||||
class ListEntitiesCameraResponse : public ProtoMessage {
|
class ListEntitiesCameraResponse : public ProtoMessage {
|
||||||
public:
|
public:
|
||||||
std::string object_id{}; // NOLINT
|
std::string object_id{};
|
||||||
uint32_t key{0}; // NOLINT
|
uint32_t key{0};
|
||||||
std::string name{}; // NOLINT
|
std::string name{};
|
||||||
std::string unique_id{}; // NOLINT
|
std::string unique_id{};
|
||||||
void encode(ProtoWriteBuffer buffer) const override;
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
void dump_to(std::string &out) const override;
|
void dump_to(std::string &out) const override;
|
||||||
|
|
||||||
@@ -652,9 +676,9 @@ class ListEntitiesCameraResponse : public ProtoMessage {
|
|||||||
};
|
};
|
||||||
class CameraImageResponse : public ProtoMessage {
|
class CameraImageResponse : public ProtoMessage {
|
||||||
public:
|
public:
|
||||||
uint32_t key{0}; // NOLINT
|
uint32_t key{0};
|
||||||
std::string data{}; // NOLINT
|
std::string data{};
|
||||||
bool done{false}; // NOLINT
|
bool done{false};
|
||||||
void encode(ProtoWriteBuffer buffer) const override;
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
void dump_to(std::string &out) const override;
|
void dump_to(std::string &out) const override;
|
||||||
|
|
||||||
@@ -665,8 +689,8 @@ class CameraImageResponse : public ProtoMessage {
|
|||||||
};
|
};
|
||||||
class CameraImageRequest : public ProtoMessage {
|
class CameraImageRequest : public ProtoMessage {
|
||||||
public:
|
public:
|
||||||
bool single{false}; // NOLINT
|
bool single{false};
|
||||||
bool stream{false}; // NOLINT
|
bool stream{false};
|
||||||
void encode(ProtoWriteBuffer buffer) const override;
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
void dump_to(std::string &out) const override;
|
void dump_to(std::string &out) const override;
|
||||||
|
|
||||||
@@ -675,20 +699,23 @@ class CameraImageRequest : public ProtoMessage {
|
|||||||
};
|
};
|
||||||
class ListEntitiesClimateResponse : public ProtoMessage {
|
class ListEntitiesClimateResponse : public ProtoMessage {
|
||||||
public:
|
public:
|
||||||
std::string object_id{}; // NOLINT
|
std::string object_id{};
|
||||||
uint32_t key{0}; // NOLINT
|
uint32_t key{0};
|
||||||
std::string name{}; // NOLINT
|
std::string name{};
|
||||||
std::string unique_id{}; // NOLINT
|
std::string unique_id{};
|
||||||
bool supports_current_temperature{false}; // NOLINT
|
bool supports_current_temperature{false};
|
||||||
bool supports_two_point_target_temperature{false}; // NOLINT
|
bool supports_two_point_target_temperature{false};
|
||||||
std::vector<enums::ClimateMode> supported_modes{}; // NOLINT
|
std::vector<enums::ClimateMode> supported_modes{};
|
||||||
float visual_min_temperature{0.0f}; // NOLINT
|
float visual_min_temperature{0.0f};
|
||||||
float visual_max_temperature{0.0f}; // NOLINT
|
float visual_max_temperature{0.0f};
|
||||||
float visual_temperature_step{0.0f}; // NOLINT
|
float visual_temperature_step{0.0f};
|
||||||
bool supports_away{false}; // NOLINT
|
bool supports_away{false};
|
||||||
bool supports_action{false}; // NOLINT
|
bool supports_action{false};
|
||||||
std::vector<enums::ClimateFanMode> supported_fan_modes{}; // NOLINT
|
std::vector<enums::ClimateFanMode> supported_fan_modes{};
|
||||||
std::vector<enums::ClimateSwingMode> supported_swing_modes{}; // NOLINT
|
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 encode(ProtoWriteBuffer buffer) const override;
|
||||||
void dump_to(std::string &out) const override;
|
void dump_to(std::string &out) const override;
|
||||||
|
|
||||||
@@ -699,45 +726,56 @@ class ListEntitiesClimateResponse : public ProtoMessage {
|
|||||||
};
|
};
|
||||||
class ClimateStateResponse : public ProtoMessage {
|
class ClimateStateResponse : public ProtoMessage {
|
||||||
public:
|
public:
|
||||||
uint32_t key{0}; // NOLINT
|
uint32_t key{0};
|
||||||
enums::ClimateMode mode{}; // NOLINT
|
enums::ClimateMode mode{};
|
||||||
float current_temperature{0.0f}; // NOLINT
|
float current_temperature{0.0f};
|
||||||
float target_temperature{0.0f}; // NOLINT
|
float target_temperature{0.0f};
|
||||||
float target_temperature_low{0.0f}; // NOLINT
|
float target_temperature_low{0.0f};
|
||||||
float target_temperature_high{0.0f}; // NOLINT
|
float target_temperature_high{0.0f};
|
||||||
bool away{false}; // NOLINT
|
bool away{false};
|
||||||
enums::ClimateAction action{}; // NOLINT
|
enums::ClimateAction action{};
|
||||||
enums::ClimateFanMode fan_mode{}; // NOLINT
|
enums::ClimateFanMode fan_mode{};
|
||||||
enums::ClimateSwingMode swing_mode{}; // NOLINT
|
enums::ClimateSwingMode swing_mode{};
|
||||||
|
std::string custom_fan_mode{};
|
||||||
|
enums::ClimatePreset preset{};
|
||||||
|
std::string custom_preset{};
|
||||||
void encode(ProtoWriteBuffer buffer) const override;
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
void dump_to(std::string &out) const override;
|
void dump_to(std::string &out) const override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
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;
|
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||||
};
|
};
|
||||||
class ClimateCommandRequest : public ProtoMessage {
|
class ClimateCommandRequest : public ProtoMessage {
|
||||||
public:
|
public:
|
||||||
uint32_t key{0}; // NOLINT
|
uint32_t key{0};
|
||||||
bool has_mode{false}; // NOLINT
|
bool has_mode{false};
|
||||||
enums::ClimateMode mode{}; // NOLINT
|
enums::ClimateMode mode{};
|
||||||
bool has_target_temperature{false}; // NOLINT
|
bool has_target_temperature{false};
|
||||||
float target_temperature{0.0f}; // NOLINT
|
float target_temperature{0.0f};
|
||||||
bool has_target_temperature_low{false}; // NOLINT
|
bool has_target_temperature_low{false};
|
||||||
float target_temperature_low{0.0f}; // NOLINT
|
float target_temperature_low{0.0f};
|
||||||
bool has_target_temperature_high{false}; // NOLINT
|
bool has_target_temperature_high{false};
|
||||||
float target_temperature_high{0.0f}; // NOLINT
|
float target_temperature_high{0.0f};
|
||||||
bool has_away{false}; // NOLINT
|
bool has_away{false};
|
||||||
bool away{false}; // NOLINT
|
bool away{false};
|
||||||
bool has_fan_mode{false}; // NOLINT
|
bool has_fan_mode{false};
|
||||||
enums::ClimateFanMode fan_mode{}; // NOLINT
|
enums::ClimateFanMode fan_mode{};
|
||||||
bool has_swing_mode{false}; // NOLINT
|
bool has_swing_mode{false};
|
||||||
enums::ClimateSwingMode swing_mode{}; // NOLINT
|
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 encode(ProtoWriteBuffer buffer) const override;
|
||||||
void dump_to(std::string &out) const override;
|
void dump_to(std::string &out) const override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
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;
|
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; }
|
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{
|
this->state_subs_.push_back(HomeAssistantStateSubscription{
|
||||||
.entity_id = std::move(entity_id),
|
.entity_id = std::move(entity_id),
|
||||||
|
.attribute = std::move(attribute),
|
||||||
.callback = std::move(f),
|
.callback = std::move(f),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,10 +71,12 @@ class APIServer : public Component, public Controller {
|
|||||||
|
|
||||||
struct HomeAssistantStateSubscription {
|
struct HomeAssistantStateSubscription {
|
||||||
std::string entity_id;
|
std::string entity_id;
|
||||||
|
optional<std::string> attribute;
|
||||||
std::function<void(std::string)> callback;
|
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<HomeAssistantStateSubscription> &get_state_subs() const;
|
||||||
const std::vector<UserServiceDescriptor *> &get_user_services() const { return this->user_services_; }
|
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);
|
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:
|
* Usage:
|
||||||
*
|
*
|
||||||
* ```cpp
|
* ```cpp
|
||||||
* void setup() override {
|
* 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) {
|
* 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.
|
* @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 callback The member function to call when the entity state changes.
|
||||||
* @param entity_id The entity_id to track.
|
* @param entity_id The entity_id to track.
|
||||||
|
* @param attribute The entity state attribute to track.
|
||||||
*/
|
*/
|
||||||
template<typename T>
|
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);
|
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:
|
* Usage:
|
||||||
*
|
*å
|
||||||
* ```cpp
|
* ```cpp
|
||||||
* void setup() override {
|
* void setup() override {
|
||||||
* subscribe_homeassistant_state(&CustomNativeAPI::on_state_changed, "sensor.weather_forecast");
|
* 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.
|
* @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 callback The member function to call when the entity state changes.
|
||||||
* @param entity_id The entity_id to track.
|
* @param entity_id The entity_id to track.
|
||||||
|
* @param attribute The entity state attribute to track.
|
||||||
*/
|
*/
|
||||||
template<typename T>
|
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);
|
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.
|
/** Call a Home Assistant service from ESPHome.
|
||||||
|
|||||||
@@ -1,40 +1,48 @@
|
|||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome import pins
|
from esphome import pins
|
||||||
from esphome.const import CONF_INDOOR, CONF_WATCHDOG_THRESHOLD, \
|
from esphome.const import (
|
||||||
CONF_NOISE_LEVEL, CONF_SPIKE_REJECTION, CONF_LIGHTNING_THRESHOLD, \
|
CONF_INDOOR,
|
||||||
CONF_MASK_DISTURBER, CONF_DIV_RATIO, CONF_CAPACITANCE
|
CONF_WATCHDOG_THRESHOLD,
|
||||||
from esphome.core import coroutine
|
CONF_NOISE_LEVEL,
|
||||||
|
CONF_SPIKE_REJECTION,
|
||||||
|
CONF_LIGHTNING_THRESHOLD,
|
||||||
|
CONF_MASK_DISTURBER,
|
||||||
|
CONF_DIV_RATIO,
|
||||||
|
CONF_CAPACITANCE,
|
||||||
|
)
|
||||||
|
|
||||||
AUTO_LOAD = ['sensor', 'binary_sensor']
|
AUTO_LOAD = ["sensor", "binary_sensor"]
|
||||||
MULTI_CONF = True
|
MULTI_CONF = True
|
||||||
|
|
||||||
CONF_AS3935_ID = 'as3935_id'
|
CONF_AS3935_ID = "as3935_id"
|
||||||
|
|
||||||
as3935_ns = cg.esphome_ns.namespace('as3935')
|
as3935_ns = cg.esphome_ns.namespace("as3935")
|
||||||
AS3935 = as3935_ns.class_('AS3935Component', cg.Component)
|
AS3935 = as3935_ns.class_("AS3935Component", cg.Component)
|
||||||
|
|
||||||
CONF_IRQ_PIN = 'irq_pin'
|
CONF_IRQ_PIN = "irq_pin"
|
||||||
AS3935_SCHEMA = cv.Schema({
|
AS3935_SCHEMA = cv.Schema(
|
||||||
cv.GenerateID(): cv.declare_id(AS3935),
|
{
|
||||||
cv.Required(CONF_IRQ_PIN): pins.gpio_input_pin_schema,
|
cv.GenerateID(): cv.declare_id(AS3935),
|
||||||
|
cv.Required(CONF_IRQ_PIN): pins.gpio_input_pin_schema,
|
||||||
cv.Optional(CONF_INDOOR, default=True): cv.boolean,
|
cv.Optional(CONF_INDOOR, default=True): cv.boolean,
|
||||||
cv.Optional(CONF_NOISE_LEVEL, default=2): cv.int_range(min=1, max=7),
|
cv.Optional(CONF_NOISE_LEVEL, default=2): cv.int_range(min=1, max=7),
|
||||||
cv.Optional(CONF_WATCHDOG_THRESHOLD, default=2): cv.int_range(min=1, max=10),
|
cv.Optional(CONF_WATCHDOG_THRESHOLD, default=2): cv.int_range(min=1, max=10),
|
||||||
cv.Optional(CONF_SPIKE_REJECTION, default=2): cv.int_range(min=1, max=11),
|
cv.Optional(CONF_SPIKE_REJECTION, default=2): cv.int_range(min=1, max=11),
|
||||||
cv.Optional(CONF_LIGHTNING_THRESHOLD, default=1): cv.one_of(1, 5, 9, 16, int=True),
|
cv.Optional(CONF_LIGHTNING_THRESHOLD, default=1): cv.one_of(
|
||||||
cv.Optional(CONF_MASK_DISTURBER, default=False): cv.boolean,
|
1, 5, 9, 16, int=True
|
||||||
cv.Optional(CONF_DIV_RATIO, default=0): cv.one_of(0, 16, 32, 64, 128, int=True),
|
),
|
||||||
cv.Optional(CONF_CAPACITANCE, default=0): cv.int_range(min=0, max=15),
|
cv.Optional(CONF_MASK_DISTURBER, default=False): cv.boolean,
|
||||||
})
|
cv.Optional(CONF_DIV_RATIO, default=0): cv.one_of(0, 16, 32, 64, 128, int=True),
|
||||||
|
cv.Optional(CONF_CAPACITANCE, default=0): cv.int_range(min=0, max=15),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@coroutine
|
async def setup_as3935(var, config):
|
||||||
def setup_as3935(var, config):
|
await cg.register_component(var, config)
|
||||||
yield 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_irq_pin(irq_pin))
|
||||||
cg.add(var.set_indoor(config[CONF_INDOOR]))
|
cg.add(var.set_indoor(config[CONF_INDOOR]))
|
||||||
cg.add(var.set_noise_level(config[CONF_NOISE_LEVEL]))
|
cg.add(var.set_noise_level(config[CONF_NOISE_LEVEL]))
|
||||||
|
|||||||
@@ -3,14 +3,16 @@ import esphome.config_validation as cv
|
|||||||
from esphome.components import binary_sensor
|
from esphome.components import binary_sensor
|
||||||
from . import AS3935, CONF_AS3935_ID
|
from . import AS3935, CONF_AS3935_ID
|
||||||
|
|
||||||
DEPENDENCIES = ['as3935']
|
DEPENDENCIES = ["as3935"]
|
||||||
|
|
||||||
CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend({
|
CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend(
|
||||||
cv.GenerateID(CONF_AS3935_ID): cv.use_id(AS3935),
|
{
|
||||||
})
|
cv.GenerateID(CONF_AS3935_ID): cv.use_id(AS3935),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def to_code(config):
|
async def to_code(config):
|
||||||
hub = yield cg.get_variable(config[CONF_AS3935_ID])
|
hub = await cg.get_variable(config[CONF_AS3935_ID])
|
||||||
var = yield binary_sensor.new_binary_sensor(config)
|
var = await binary_sensor.new_binary_sensor(config)
|
||||||
cg.add(hub.set_thunder_alert_binary_sensor(var))
|
cg.add(hub.set_thunder_alert_binary_sensor(var))
|
||||||
|
|||||||
@@ -1,30 +1,46 @@
|
|||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.components import sensor
|
from esphome.components import sensor
|
||||||
from esphome.const import CONF_DISTANCE, CONF_LIGHTNING_ENERGY, \
|
from esphome.const import (
|
||||||
UNIT_KILOMETER, UNIT_EMPTY, ICON_SIGNAL_DISTANCE_VARIANT, ICON_FLASH
|
CONF_DISTANCE,
|
||||||
|
CONF_LIGHTNING_ENERGY,
|
||||||
|
DEVICE_CLASS_EMPTY,
|
||||||
|
STATE_CLASS_NONE,
|
||||||
|
UNIT_KILOMETER,
|
||||||
|
UNIT_EMPTY,
|
||||||
|
ICON_SIGNAL_DISTANCE_VARIANT,
|
||||||
|
ICON_FLASH,
|
||||||
|
)
|
||||||
from . import AS3935, CONF_AS3935_ID
|
from . import AS3935, CONF_AS3935_ID
|
||||||
|
|
||||||
DEPENDENCIES = ['as3935']
|
DEPENDENCIES = ["as3935"]
|
||||||
|
|
||||||
CONFIG_SCHEMA = cv.Schema({
|
CONFIG_SCHEMA = cv.Schema(
|
||||||
cv.GenerateID(CONF_AS3935_ID): cv.use_id(AS3935),
|
{
|
||||||
cv.Optional(CONF_DISTANCE):
|
cv.GenerateID(CONF_AS3935_ID): cv.use_id(AS3935),
|
||||||
sensor.sensor_schema(UNIT_KILOMETER, ICON_SIGNAL_DISTANCE_VARIANT, 1),
|
cv.Optional(CONF_DISTANCE): sensor.sensor_schema(
|
||||||
cv.Optional(CONF_LIGHTNING_ENERGY):
|
UNIT_KILOMETER,
|
||||||
sensor.sensor_schema(UNIT_EMPTY, ICON_FLASH, 1),
|
ICON_SIGNAL_DISTANCE_VARIANT,
|
||||||
}).extend(cv.COMPONENT_SCHEMA)
|
1,
|
||||||
|
DEVICE_CLASS_EMPTY,
|
||||||
|
STATE_CLASS_NONE,
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_LIGHTNING_ENERGY): sensor.sensor_schema(
|
||||||
|
UNIT_EMPTY, ICON_FLASH, 1, DEVICE_CLASS_EMPTY, STATE_CLASS_NONE
|
||||||
|
),
|
||||||
|
}
|
||||||
|
).extend(cv.COMPONENT_SCHEMA)
|
||||||
|
|
||||||
|
|
||||||
def to_code(config):
|
async def to_code(config):
|
||||||
hub = yield cg.get_variable(config[CONF_AS3935_ID])
|
hub = await cg.get_variable(config[CONF_AS3935_ID])
|
||||||
|
|
||||||
if CONF_DISTANCE in config:
|
if CONF_DISTANCE in config:
|
||||||
conf = config[CONF_DISTANCE]
|
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))
|
cg.add(hub.set_distance_sensor(distance_sensor))
|
||||||
|
|
||||||
if CONF_LIGHTNING_ENERGY in config:
|
if CONF_LIGHTNING_ENERGY in config:
|
||||||
conf = config[CONF_LIGHTNING_ENERGY]
|
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))
|
cg.add(hub.set_energy_sensor(lightning_energy_sensor))
|
||||||
|
|||||||
@@ -3,18 +3,24 @@ import esphome.config_validation as cv
|
|||||||
from esphome.components import as3935, i2c
|
from esphome.components import as3935, i2c
|
||||||
from esphome.const import CONF_ID
|
from esphome.const import CONF_ID
|
||||||
|
|
||||||
AUTO_LOAD = ['as3935']
|
AUTO_LOAD = ["as3935"]
|
||||||
DEPENDENCIES = ['i2c']
|
DEPENDENCIES = ["i2c"]
|
||||||
|
|
||||||
as3935_i2c_ns = cg.esphome_ns.namespace('as3935_i2c')
|
as3935_i2c_ns = cg.esphome_ns.namespace("as3935_i2c")
|
||||||
I2CAS3935 = as3935_i2c_ns.class_('I2CAS3935Component', as3935.AS3935, i2c.I2CDevice)
|
I2CAS3935 = as3935_i2c_ns.class_("I2CAS3935Component", as3935.AS3935, i2c.I2CDevice)
|
||||||
|
|
||||||
CONFIG_SCHEMA = cv.All(as3935.AS3935_SCHEMA.extend({
|
CONFIG_SCHEMA = cv.All(
|
||||||
cv.GenerateID(): cv.declare_id(I2CAS3935),
|
as3935.AS3935_SCHEMA.extend(
|
||||||
}).extend(cv.COMPONENT_SCHEMA).extend(i2c.i2c_device_schema(0x03)))
|
{
|
||||||
|
cv.GenerateID(): cv.declare_id(I2CAS3935),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.extend(cv.COMPONENT_SCHEMA)
|
||||||
|
.extend(i2c.i2c_device_schema(0x03))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def to_code(config):
|
async def to_code(config):
|
||||||
var = cg.new_Pvariable(config[CONF_ID])
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
yield as3935.setup_as3935(var, config)
|
await as3935.setup_as3935(var, config)
|
||||||
yield i2c.register_i2c_device(var, config)
|
await i2c.register_i2c_device(var, config)
|
||||||
|
|||||||
@@ -3,18 +3,24 @@ import esphome.config_validation as cv
|
|||||||
from esphome.components import as3935, spi
|
from esphome.components import as3935, spi
|
||||||
from esphome.const import CONF_ID
|
from esphome.const import CONF_ID
|
||||||
|
|
||||||
AUTO_LOAD = ['as3935']
|
AUTO_LOAD = ["as3935"]
|
||||||
DEPENDENCIES = ['spi']
|
DEPENDENCIES = ["spi"]
|
||||||
|
|
||||||
as3935_spi_ns = cg.esphome_ns.namespace('as3935_spi')
|
as3935_spi_ns = cg.esphome_ns.namespace("as3935_spi")
|
||||||
SPIAS3935 = as3935_spi_ns.class_('SPIAS3935Component', as3935.AS3935, spi.SPIDevice)
|
SPIAS3935 = as3935_spi_ns.class_("SPIAS3935Component", as3935.AS3935, spi.SPIDevice)
|
||||||
|
|
||||||
CONFIG_SCHEMA = cv.All(as3935.AS3935_SCHEMA.extend({
|
CONFIG_SCHEMA = cv.All(
|
||||||
cv.GenerateID(): cv.declare_id(SPIAS3935),
|
as3935.AS3935_SCHEMA.extend(
|
||||||
}).extend(cv.COMPONENT_SCHEMA).extend(spi.spi_device_schema(cs_pin_required=True)))
|
{
|
||||||
|
cv.GenerateID(): cv.declare_id(SPIAS3935),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.extend(cv.COMPONENT_SCHEMA)
|
||||||
|
.extend(spi.spi_device_schema(cs_pin_required=True))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def to_code(config):
|
async def to_code(config):
|
||||||
var = cg.new_Pvariable(config[CONF_ID])
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
yield as3935.setup_as3935(var, config)
|
await as3935.setup_as3935(var, config)
|
||||||
yield spi.register_spi_device(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 SPIAS3935Component::read_register(uint8_t reg) {
|
||||||
uint8_t value = 0;
|
uint8_t value = 0;
|
||||||
this->enable();
|
this->enable();
|
||||||
this->write_byte(reg |= SPI_READ_M);
|
this->write_byte(reg | SPI_READ_M);
|
||||||
value = this->read_byte();
|
value = this->read_byte();
|
||||||
// According to datsheet, the chip select must be written HIGH, LOW, HIGH
|
// According to datsheet, the chip select must be written HIGH, LOW, HIGH
|
||||||
// to correctly end the READ command.
|
// to correctly end the READ command.
|
||||||
|
|||||||
@@ -2,14 +2,14 @@
|
|||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
from esphome.core import CORE, coroutine_with_priority
|
from esphome.core import CORE, coroutine_with_priority
|
||||||
|
|
||||||
CODEOWNERS = ['@OttoWinter']
|
CODEOWNERS = ["@OttoWinter"]
|
||||||
|
|
||||||
|
|
||||||
@coroutine_with_priority(200.0)
|
@coroutine_with_priority(200.0)
|
||||||
def to_code(config):
|
async def to_code(config):
|
||||||
if CORE.is_esp32:
|
if CORE.is_esp32:
|
||||||
# https://github.com/OttoWinter/AsyncTCP/blob/master/library.json
|
# https://github.com/esphome/AsyncTCP/blob/master/library.json
|
||||||
cg.add_library('AsyncTCP-esphome', '1.1.1')
|
cg.add_library("esphome/AsyncTCP-esphome", "1.2.2")
|
||||||
elif CORE.is_esp8266:
|
elif CORE.is_esp8266:
|
||||||
# https://github.com/OttoWinter/ESPAsyncTCP
|
# https://github.com/OttoWinter/ESPAsyncTCP
|
||||||
cg.add_library('ESPAsyncTCP-esphome', '1.2.3')
|
cg.add_library("ESPAsyncTCP-esphome", "1.2.3")
|
||||||
|
|||||||
@@ -1,45 +1,85 @@
|
|||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.components import sensor, esp32_ble_tracker
|
from esphome.components import sensor, esp32_ble_tracker
|
||||||
from esphome.const import CONF_BATTERY_LEVEL, CONF_BATTERY_VOLTAGE, CONF_MAC_ADDRESS, \
|
from esphome.const import (
|
||||||
CONF_HUMIDITY, CONF_TEMPERATURE, CONF_ID, UNIT_CELSIUS, UNIT_PERCENT, UNIT_VOLT, \
|
CONF_BATTERY_LEVEL,
|
||||||
ICON_BATTERY, ICON_THERMOMETER, ICON_WATER_PERCENT
|
CONF_BATTERY_VOLTAGE,
|
||||||
|
CONF_MAC_ADDRESS,
|
||||||
|
CONF_HUMIDITY,
|
||||||
|
CONF_TEMPERATURE,
|
||||||
|
CONF_ID,
|
||||||
|
DEVICE_CLASS_BATTERY,
|
||||||
|
DEVICE_CLASS_HUMIDITY,
|
||||||
|
DEVICE_CLASS_TEMPERATURE,
|
||||||
|
DEVICE_CLASS_VOLTAGE,
|
||||||
|
ICON_EMPTY,
|
||||||
|
STATE_CLASS_MEASUREMENT,
|
||||||
|
UNIT_CELSIUS,
|
||||||
|
UNIT_PERCENT,
|
||||||
|
UNIT_VOLT,
|
||||||
|
)
|
||||||
|
|
||||||
CODEOWNERS = ['@ahpohl']
|
CODEOWNERS = ["@ahpohl"]
|
||||||
|
|
||||||
DEPENDENCIES = ['esp32_ble_tracker']
|
DEPENDENCIES = ["esp32_ble_tracker"]
|
||||||
|
|
||||||
atc_mithermometer_ns = cg.esphome_ns.namespace('atc_mithermometer')
|
atc_mithermometer_ns = cg.esphome_ns.namespace("atc_mithermometer")
|
||||||
ATCMiThermometer = atc_mithermometer_ns.class_('ATCMiThermometer',
|
ATCMiThermometer = atc_mithermometer_ns.class_(
|
||||||
esp32_ble_tracker.ESPBTDeviceListener,
|
"ATCMiThermometer", esp32_ble_tracker.ESPBTDeviceListener, cg.Component
|
||||||
cg.Component)
|
)
|
||||||
|
|
||||||
CONFIG_SCHEMA = cv.Schema({
|
CONFIG_SCHEMA = (
|
||||||
cv.GenerateID(): cv.declare_id(ATCMiThermometer),
|
cv.Schema(
|
||||||
cv.Required(CONF_MAC_ADDRESS): cv.mac_address,
|
{
|
||||||
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 1),
|
cv.GenerateID(): cv.declare_id(ATCMiThermometer),
|
||||||
cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(UNIT_PERCENT, ICON_WATER_PERCENT, 0),
|
cv.Required(CONF_MAC_ADDRESS): cv.mac_address,
|
||||||
cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema(UNIT_PERCENT, ICON_BATTERY, 0),
|
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
|
||||||
cv.Optional(CONF_BATTERY_VOLTAGE): sensor.sensor_schema(UNIT_VOLT, ICON_BATTERY, 3),
|
UNIT_CELSIUS,
|
||||||
}).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA).extend(cv.COMPONENT_SCHEMA)
|
ICON_EMPTY,
|
||||||
|
1,
|
||||||
|
DEVICE_CLASS_TEMPERATURE,
|
||||||
|
STATE_CLASS_MEASUREMENT,
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
|
||||||
|
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,
|
||||||
|
STATE_CLASS_MEASUREMENT,
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_BATTERY_VOLTAGE): sensor.sensor_schema(
|
||||||
|
UNIT_VOLT, ICON_EMPTY, 3, DEVICE_CLASS_VOLTAGE, STATE_CLASS_MEASUREMENT
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA)
|
||||||
|
.extend(cv.COMPONENT_SCHEMA)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def to_code(config):
|
async def to_code(config):
|
||||||
var = cg.new_Pvariable(config[CONF_ID])
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
yield cg.register_component(var, config)
|
await cg.register_component(var, config)
|
||||||
yield esp32_ble_tracker.register_ble_device(var, config)
|
await esp32_ble_tracker.register_ble_device(var, config)
|
||||||
|
|
||||||
cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex))
|
cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex))
|
||||||
|
|
||||||
if CONF_TEMPERATURE in 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(sens))
|
cg.add(var.set_temperature(sens))
|
||||||
if CONF_HUMIDITY in config:
|
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))
|
cg.add(var.set_humidity(sens))
|
||||||
if CONF_BATTERY_LEVEL in config:
|
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))
|
cg.add(var.set_battery_level(sens))
|
||||||
if CONF_BATTERY_VOLTAGE in config:
|
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))
|
cg.add(var.set_battery_voltage(sens))
|
||||||
|
|||||||
@@ -58,6 +58,24 @@ void ATM90E32Component::update() {
|
|||||||
if (this->phase_[2].power_factor_sensor_ != nullptr) {
|
if (this->phase_[2].power_factor_sensor_ != nullptr) {
|
||||||
this->phase_[2].power_factor_sensor_->publish_state(this->get_power_factor_c_());
|
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) {
|
if (this->freq_sensor_ != nullptr) {
|
||||||
this->freq_sensor_->publish_state(this->get_frequency_());
|
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(" ", "Power A", this->phase_[0].power_sensor_);
|
||||||
LOG_SENSOR(" ", "Reactive Power A", this->phase_[0].reactive_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(" ", "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(" ", "Voltage B", this->phase_[1].voltage_sensor_);
|
||||||
LOG_SENSOR(" ", "Current B", this->phase_[1].current_sensor_);
|
LOG_SENSOR(" ", "Current B", this->phase_[1].current_sensor_);
|
||||||
LOG_SENSOR(" ", "Power B", this->phase_[1].power_sensor_);
|
LOG_SENSOR(" ", "Power B", this->phase_[1].power_sensor_);
|
||||||
LOG_SENSOR(" ", "Reactive Power B", this->phase_[1].reactive_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(" ", "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(" ", "Voltage C", this->phase_[2].voltage_sensor_);
|
||||||
LOG_SENSOR(" ", "Current C", this->phase_[2].current_sensor_);
|
LOG_SENSOR(" ", "Current C", this->phase_[2].current_sensor_);
|
||||||
LOG_SENSOR(" ", "Power C", this->phase_[2].power_sensor_);
|
LOG_SENSOR(" ", "Power C", this->phase_[2].power_sensor_);
|
||||||
LOG_SENSOR(" ", "Reactive Power C", this->phase_[2].reactive_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(" ", "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(" ", "Frequency", this->freq_sensor_);
|
||||||
LOG_SENSOR(" ", "Chip Temp", this->chip_temperature_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);
|
int16_t pf = this->read16_(ATM90E32_REGISTER_PFMEANC);
|
||||||
return (float) pf / 1000;
|
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_() {
|
float ATM90E32Component::get_frequency_() {
|
||||||
uint16_t freq = this->read16_(ATM90E32_REGISTER_FREQ);
|
uint16_t freq = this->read16_(ATM90E32_REGISTER_FREQ);
|
||||||
return (float) freq / 100;
|
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_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_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_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_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_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; }
|
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_a_();
|
||||||
float get_power_factor_b_();
|
float get_power_factor_b_();
|
||||||
float get_power_factor_c_();
|
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_frequency_();
|
||||||
float get_chip_temperature_();
|
float get_chip_temperature_();
|
||||||
|
|
||||||
@@ -63,6 +75,8 @@ class ATM90E32Component : public PollingComponent,
|
|||||||
sensor::Sensor *power_sensor_{nullptr};
|
sensor::Sensor *power_sensor_{nullptr};
|
||||||
sensor::Sensor *reactive_power_sensor_{nullptr};
|
sensor::Sensor *reactive_power_sensor_{nullptr};
|
||||||
sensor::Sensor *power_factor_sensor_{nullptr};
|
sensor::Sensor *power_factor_sensor_{nullptr};
|
||||||
|
sensor::Sensor *forward_active_energy_sensor_{nullptr};
|
||||||
|
sensor::Sensor *reverse_active_energy_sensor_{nullptr};
|
||||||
} phase_[3];
|
} phase_[3];
|
||||||
sensor::Sensor *freq_sensor_{nullptr};
|
sensor::Sensor *freq_sensor_{nullptr};
|
||||||
sensor::Sensor *chip_temperature_sensor_{nullptr};
|
sensor::Sensor *chip_temperature_sensor_{nullptr};
|
||||||
|
|||||||
@@ -1,67 +1,143 @@
|
|||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.components import sensor, spi
|
from esphome.components import sensor, spi
|
||||||
from esphome.const import \
|
from esphome.const import (
|
||||||
CONF_ID, CONF_VOLTAGE, CONF_CURRENT, CONF_POWER, CONF_POWER_FACTOR, CONF_FREQUENCY, \
|
CONF_ID,
|
||||||
ICON_FLASH, ICON_LIGHTBULB, ICON_CURRENT_AC, ICON_THERMOMETER, \
|
CONF_REACTIVE_POWER,
|
||||||
UNIT_HERTZ, UNIT_VOLT, UNIT_AMPERE, UNIT_WATT, UNIT_EMPTY, UNIT_CELSIUS, UNIT_VOLT_AMPS_REACTIVE
|
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,
|
||||||
|
DEVICE_CLASS_VOLTAGE,
|
||||||
|
ICON_EMPTY,
|
||||||
|
ICON_LIGHTBULB,
|
||||||
|
ICON_CURRENT_AC,
|
||||||
|
STATE_CLASS_MEASUREMENT,
|
||||||
|
UNIT_HERTZ,
|
||||||
|
UNIT_VOLT,
|
||||||
|
UNIT_AMPERE,
|
||||||
|
UNIT_WATT,
|
||||||
|
UNIT_EMPTY,
|
||||||
|
UNIT_CELSIUS,
|
||||||
|
UNIT_VOLT_AMPS_REACTIVE,
|
||||||
|
UNIT_WATT_HOURS,
|
||||||
|
)
|
||||||
|
|
||||||
CONF_PHASE_A = 'phase_a'
|
CONF_PHASE_A = "phase_a"
|
||||||
CONF_PHASE_B = 'phase_b'
|
CONF_PHASE_B = "phase_b"
|
||||||
CONF_PHASE_C = 'phase_c'
|
CONF_PHASE_C = "phase_c"
|
||||||
|
|
||||||
CONF_REACTIVE_POWER = 'reactive_power'
|
CONF_LINE_FREQUENCY = "line_frequency"
|
||||||
CONF_LINE_FREQUENCY = 'line_frequency'
|
CONF_CHIP_TEMPERATURE = "chip_temperature"
|
||||||
CONF_CHIP_TEMPERATURE = 'chip_temperature'
|
CONF_GAIN_PGA = "gain_pga"
|
||||||
CONF_GAIN_PGA = 'gain_pga'
|
CONF_CURRENT_PHASES = "current_phases"
|
||||||
CONF_CURRENT_PHASES = 'current_phases'
|
CONF_GAIN_VOLTAGE = "gain_voltage"
|
||||||
CONF_GAIN_VOLTAGE = 'gain_voltage'
|
CONF_GAIN_CT = "gain_ct"
|
||||||
CONF_GAIN_CT = 'gain_ct'
|
|
||||||
LINE_FREQS = {
|
LINE_FREQS = {
|
||||||
'50HZ': 50,
|
"50HZ": 50,
|
||||||
'60HZ': 60,
|
"60HZ": 60,
|
||||||
}
|
}
|
||||||
CURRENT_PHASES = {
|
CURRENT_PHASES = {
|
||||||
'2': 2,
|
"2": 2,
|
||||||
'3': 3,
|
"3": 3,
|
||||||
}
|
}
|
||||||
PGA_GAINS = {
|
PGA_GAINS = {
|
||||||
'1X': 0x0,
|
"1X": 0x0,
|
||||||
'2X': 0x15,
|
"2X": 0x15,
|
||||||
'4X': 0x2A,
|
"4X": 0x2A,
|
||||||
}
|
}
|
||||||
|
|
||||||
atm90e32_ns = cg.esphome_ns.namespace('atm90e32')
|
atm90e32_ns = cg.esphome_ns.namespace("atm90e32")
|
||||||
ATM90E32Component = atm90e32_ns.class_('ATM90E32Component', cg.PollingComponent, spi.SPIDevice)
|
ATM90E32Component = atm90e32_ns.class_(
|
||||||
|
"ATM90E32Component", cg.PollingComponent, spi.SPIDevice
|
||||||
|
)
|
||||||
|
|
||||||
ATM90E32_PHASE_SCHEMA = cv.Schema({
|
ATM90E32_PHASE_SCHEMA = cv.Schema(
|
||||||
cv.Optional(CONF_VOLTAGE): sensor.sensor_schema(UNIT_VOLT, ICON_FLASH, 2),
|
{
|
||||||
cv.Optional(CONF_CURRENT): sensor.sensor_schema(UNIT_AMPERE, ICON_CURRENT_AC, 2),
|
cv.Optional(CONF_VOLTAGE): sensor.sensor_schema(
|
||||||
cv.Optional(CONF_POWER): sensor.sensor_schema(UNIT_WATT, ICON_FLASH, 2),
|
UNIT_VOLT,
|
||||||
cv.Optional(CONF_REACTIVE_POWER): sensor.sensor_schema(UNIT_VOLT_AMPS_REACTIVE,
|
ICON_EMPTY,
|
||||||
ICON_LIGHTBULB, 2),
|
2,
|
||||||
cv.Optional(CONF_POWER_FACTOR): sensor.sensor_schema(UNIT_EMPTY, ICON_FLASH, 2),
|
DEVICE_CLASS_VOLTAGE,
|
||||||
cv.Optional(CONF_GAIN_VOLTAGE, default=7305): cv.uint16_t,
|
STATE_CLASS_MEASUREMENT,
|
||||||
cv.Optional(CONF_GAIN_CT, default=27961): cv.uint16_t,
|
),
|
||||||
})
|
cv.Optional(CONF_CURRENT): sensor.sensor_schema(
|
||||||
|
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, STATE_CLASS_MEASUREMENT
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_REACTIVE_POWER): sensor.sensor_schema(
|
||||||
|
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,
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
CONFIG_SCHEMA = cv.Schema({
|
CONFIG_SCHEMA = (
|
||||||
cv.GenerateID(): cv.declare_id(ATM90E32Component),
|
cv.Schema(
|
||||||
cv.Optional(CONF_PHASE_A): ATM90E32_PHASE_SCHEMA,
|
{
|
||||||
cv.Optional(CONF_PHASE_B): ATM90E32_PHASE_SCHEMA,
|
cv.GenerateID(): cv.declare_id(ATM90E32Component),
|
||||||
cv.Optional(CONF_PHASE_C): ATM90E32_PHASE_SCHEMA,
|
cv.Optional(CONF_PHASE_A): ATM90E32_PHASE_SCHEMA,
|
||||||
cv.Optional(CONF_FREQUENCY): sensor.sensor_schema(UNIT_HERTZ, ICON_CURRENT_AC, 1),
|
cv.Optional(CONF_PHASE_B): ATM90E32_PHASE_SCHEMA,
|
||||||
cv.Optional(CONF_CHIP_TEMPERATURE): sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 1),
|
cv.Optional(CONF_PHASE_C): ATM90E32_PHASE_SCHEMA,
|
||||||
cv.Required(CONF_LINE_FREQUENCY): cv.enum(LINE_FREQS, upper=True),
|
cv.Optional(CONF_FREQUENCY): sensor.sensor_schema(
|
||||||
cv.Optional(CONF_CURRENT_PHASES, default='3'): cv.enum(CURRENT_PHASES, upper=True),
|
UNIT_HERTZ,
|
||||||
cv.Optional(CONF_GAIN_PGA, default='2X'): cv.enum(PGA_GAINS, upper=True),
|
ICON_CURRENT_AC,
|
||||||
}).extend(cv.polling_component_schema('60s')).extend(spi.spi_device_schema())
|
1,
|
||||||
|
DEVICE_CLASS_EMPTY,
|
||||||
|
STATE_CLASS_MEASUREMENT,
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_CHIP_TEMPERATURE): sensor.sensor_schema(
|
||||||
|
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(
|
||||||
|
CURRENT_PHASES, upper=True
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_GAIN_PGA, default="2X"): cv.enum(PGA_GAINS, upper=True),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.extend(cv.polling_component_schema("60s"))
|
||||||
|
.extend(spi.spi_device_schema())
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def to_code(config):
|
async def to_code(config):
|
||||||
var = cg.new_Pvariable(config[CONF_ID])
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
yield cg.register_component(var, config)
|
await cg.register_component(var, config)
|
||||||
yield spi.register_spi_device(var, config)
|
await spi.register_spi_device(var, config)
|
||||||
|
|
||||||
for i, phase in enumerate([CONF_PHASE_A, CONF_PHASE_B, CONF_PHASE_C]):
|
for i, phase in enumerate([CONF_PHASE_A, CONF_PHASE_B, CONF_PHASE_C]):
|
||||||
if phase not in config:
|
if phase not in config:
|
||||||
@@ -70,25 +146,31 @@ def to_code(config):
|
|||||||
cg.add(var.set_volt_gain(i, conf[CONF_GAIN_VOLTAGE]))
|
cg.add(var.set_volt_gain(i, conf[CONF_GAIN_VOLTAGE]))
|
||||||
cg.add(var.set_ct_gain(i, conf[CONF_GAIN_CT]))
|
cg.add(var.set_ct_gain(i, conf[CONF_GAIN_CT]))
|
||||||
if CONF_VOLTAGE in conf:
|
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))
|
cg.add(var.set_voltage_sensor(i, sens))
|
||||||
if CONF_CURRENT in conf:
|
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))
|
cg.add(var.set_current_sensor(i, sens))
|
||||||
if CONF_POWER in conf:
|
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))
|
cg.add(var.set_power_sensor(i, sens))
|
||||||
if CONF_REACTIVE_POWER in conf:
|
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))
|
cg.add(var.set_reactive_power_sensor(i, sens))
|
||||||
if CONF_POWER_FACTOR in conf:
|
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))
|
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:
|
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))
|
cg.add(var.set_freq_sensor(sens))
|
||||||
if CONF_CHIP_TEMPERATURE in config:
|
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_chip_temperature_sensor(sens))
|
||||||
cg.add(var.set_line_freq(config[CONF_LINE_FREQUENCY]))
|
cg.add(var.set_line_freq(config[CONF_LINE_FREQUENCY]))
|
||||||
cg.add(var.set_current_phases(config[CONF_CURRENT_PHASES]))
|
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))
|
||||||
@@ -1 +1 @@
|
|||||||
CODEOWNERS = ['@OttoWinter']
|
CODEOWNERS = ["@OttoWinter"]
|
||||||
|
|||||||
@@ -2,56 +2,76 @@ import esphome.codegen as cg
|
|||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome import automation
|
from esphome import automation
|
||||||
from esphome.components import climate, sensor
|
from esphome.components import climate, sensor
|
||||||
from esphome.const import CONF_AWAY_CONFIG, CONF_COOL_ACTION, \
|
from esphome.const import (
|
||||||
CONF_DEFAULT_TARGET_TEMPERATURE_HIGH, CONF_DEFAULT_TARGET_TEMPERATURE_LOW, CONF_HEAT_ACTION, \
|
CONF_AWAY_CONFIG,
|
||||||
CONF_ID, CONF_IDLE_ACTION, CONF_SENSOR
|
CONF_COOL_ACTION,
|
||||||
|
CONF_DEFAULT_TARGET_TEMPERATURE_HIGH,
|
||||||
|
CONF_DEFAULT_TARGET_TEMPERATURE_LOW,
|
||||||
|
CONF_HEAT_ACTION,
|
||||||
|
CONF_ID,
|
||||||
|
CONF_IDLE_ACTION,
|
||||||
|
CONF_SENSOR,
|
||||||
|
)
|
||||||
|
|
||||||
bang_bang_ns = cg.esphome_ns.namespace('bang_bang')
|
bang_bang_ns = cg.esphome_ns.namespace("bang_bang")
|
||||||
BangBangClimate = bang_bang_ns.class_('BangBangClimate', climate.Climate, cg.Component)
|
BangBangClimate = bang_bang_ns.class_("BangBangClimate", climate.Climate, cg.Component)
|
||||||
BangBangClimateTargetTempConfig = bang_bang_ns.struct('BangBangClimateTargetTempConfig')
|
BangBangClimateTargetTempConfig = bang_bang_ns.struct("BangBangClimateTargetTempConfig")
|
||||||
|
|
||||||
CONFIG_SCHEMA = cv.All(climate.CLIMATE_SCHEMA.extend({
|
CONFIG_SCHEMA = cv.All(
|
||||||
cv.GenerateID(): cv.declare_id(BangBangClimate),
|
climate.CLIMATE_SCHEMA.extend(
|
||||||
cv.Required(CONF_SENSOR): cv.use_id(sensor.Sensor),
|
{
|
||||||
cv.Required(CONF_DEFAULT_TARGET_TEMPERATURE_LOW): cv.temperature,
|
cv.GenerateID(): cv.declare_id(BangBangClimate),
|
||||||
cv.Required(CONF_DEFAULT_TARGET_TEMPERATURE_HIGH): cv.temperature,
|
cv.Required(CONF_SENSOR): cv.use_id(sensor.Sensor),
|
||||||
cv.Required(CONF_IDLE_ACTION): automation.validate_automation(single=True),
|
cv.Required(CONF_DEFAULT_TARGET_TEMPERATURE_LOW): cv.temperature,
|
||||||
cv.Optional(CONF_COOL_ACTION): automation.validate_automation(single=True),
|
cv.Required(CONF_DEFAULT_TARGET_TEMPERATURE_HIGH): cv.temperature,
|
||||||
cv.Optional(CONF_HEAT_ACTION): automation.validate_automation(single=True),
|
cv.Required(CONF_IDLE_ACTION): automation.validate_automation(single=True),
|
||||||
cv.Optional(CONF_AWAY_CONFIG): cv.Schema({
|
cv.Optional(CONF_COOL_ACTION): automation.validate_automation(single=True),
|
||||||
cv.Required(CONF_DEFAULT_TARGET_TEMPERATURE_LOW): cv.temperature,
|
cv.Optional(CONF_HEAT_ACTION): automation.validate_automation(single=True),
|
||||||
cv.Required(CONF_DEFAULT_TARGET_TEMPERATURE_HIGH): cv.temperature,
|
cv.Optional(CONF_AWAY_CONFIG): cv.Schema(
|
||||||
}),
|
{
|
||||||
}).extend(cv.COMPONENT_SCHEMA), cv.has_at_least_one_key(CONF_COOL_ACTION, CONF_HEAT_ACTION))
|
cv.Required(CONF_DEFAULT_TARGET_TEMPERATURE_LOW): cv.temperature,
|
||||||
|
cv.Required(CONF_DEFAULT_TARGET_TEMPERATURE_HIGH): cv.temperature,
|
||||||
|
}
|
||||||
|
),
|
||||||
|
}
|
||||||
|
).extend(cv.COMPONENT_SCHEMA),
|
||||||
|
cv.has_at_least_one_key(CONF_COOL_ACTION, CONF_HEAT_ACTION),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def to_code(config):
|
async def to_code(config):
|
||||||
var = cg.new_Pvariable(config[CONF_ID])
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
yield cg.register_component(var, config)
|
await cg.register_component(var, config)
|
||||||
yield climate.register_climate(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))
|
cg.add(var.set_sensor(sens))
|
||||||
|
|
||||||
normal_config = BangBangClimateTargetTempConfig(
|
normal_config = BangBangClimateTargetTempConfig(
|
||||||
config[CONF_DEFAULT_TARGET_TEMPERATURE_LOW],
|
config[CONF_DEFAULT_TARGET_TEMPERATURE_LOW],
|
||||||
config[CONF_DEFAULT_TARGET_TEMPERATURE_HIGH]
|
config[CONF_DEFAULT_TARGET_TEMPERATURE_HIGH],
|
||||||
)
|
)
|
||||||
cg.add(var.set_normal_config(normal_config))
|
cg.add(var.set_normal_config(normal_config))
|
||||||
|
|
||||||
yield automation.build_automation(var.get_idle_trigger(), [], config[CONF_IDLE_ACTION])
|
await automation.build_automation(
|
||||||
|
var.get_idle_trigger(), [], config[CONF_IDLE_ACTION]
|
||||||
|
)
|
||||||
|
|
||||||
if CONF_COOL_ACTION in config:
|
if CONF_COOL_ACTION in config:
|
||||||
yield automation.build_automation(var.get_cool_trigger(), [], config[CONF_COOL_ACTION])
|
await automation.build_automation(
|
||||||
|
var.get_cool_trigger(), [], config[CONF_COOL_ACTION]
|
||||||
|
)
|
||||||
cg.add(var.set_supports_cool(True))
|
cg.add(var.set_supports_cool(True))
|
||||||
if CONF_HEAT_ACTION in config:
|
if CONF_HEAT_ACTION in config:
|
||||||
yield automation.build_automation(var.get_heat_trigger(), [], config[CONF_HEAT_ACTION])
|
await automation.build_automation(
|
||||||
|
var.get_heat_trigger(), [], config[CONF_HEAT_ACTION]
|
||||||
|
)
|
||||||
cg.add(var.set_supports_heat(True))
|
cg.add(var.set_supports_heat(True))
|
||||||
|
|
||||||
if CONF_AWAY_CONFIG in config:
|
if CONF_AWAY_CONFIG in config:
|
||||||
away = config[CONF_AWAY_CONFIG]
|
away = config[CONF_AWAY_CONFIG]
|
||||||
away_config = BangBangClimateTargetTempConfig(
|
away_config = BangBangClimateTargetTempConfig(
|
||||||
away[CONF_DEFAULT_TARGET_TEMPERATURE_LOW],
|
away[CONF_DEFAULT_TARGET_TEMPERATURE_LOW],
|
||||||
away[CONF_DEFAULT_TARGET_TEMPERATURE_HIGH]
|
away[CONF_DEFAULT_TARGET_TEMPERATURE_HIGH],
|
||||||
)
|
)
|
||||||
cg.add(var.set_away_config(away_config))
|
cg.add(var.set_away_config(away_config))
|
||||||
|
|||||||
@@ -17,8 +17,8 @@ void BH1750Sensor::setup() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t mtreg_hi = (this->measurement_time_ >> 5) & 0b111;
|
uint8_t mtreg_hi = (this->measurement_duration_ >> 5) & 0b111;
|
||||||
uint8_t mtreg_lo = (this->measurement_time_ >> 0) & 0b11111;
|
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_HI | mtreg_hi, nullptr, 0);
|
||||||
this->write_bytes(BH1750_COMMAND_MT_REG_LO | mtreg_lo, 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;
|
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);
|
ESP_LOGD(TAG, "'%s': Got illuminance=%.1flx", this->get_name().c_str(), lx);
|
||||||
this->publish_state(lx);
|
this->publish_state(lx);
|
||||||
this->status_clear_warning();
|
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.
|
* @param resolution The new resolution of the sensor.
|
||||||
*/
|
*/
|
||||||
void set_resolution(BH1750Resolution resolution);
|
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 ==========
|
// ========== INTERNAL METHODS ==========
|
||||||
// (In most use cases you won't need these)
|
// (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_();
|
void read_data_();
|
||||||
|
|
||||||
BH1750Resolution resolution_{BH1750_RESOLUTION_0P5_LX};
|
BH1750Resolution resolution_{BH1750_RESOLUTION_0P5_LX};
|
||||||
uint8_t measurement_time_;
|
uint8_t measurement_duration_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace bh1750
|
} // namespace bh1750
|
||||||
|
|||||||
@@ -1,33 +1,59 @@
|
|||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.components import i2c, sensor
|
from esphome.components import i2c, sensor
|
||||||
from esphome.const import CONF_ID, CONF_RESOLUTION, UNIT_LUX, ICON_BRIGHTNESS_5
|
from esphome.const import (
|
||||||
|
CONF_ID,
|
||||||
|
CONF_RESOLUTION,
|
||||||
|
DEVICE_CLASS_ILLUMINANCE,
|
||||||
|
ICON_EMPTY,
|
||||||
|
STATE_CLASS_MEASUREMENT,
|
||||||
|
UNIT_LUX,
|
||||||
|
CONF_MEASUREMENT_DURATION,
|
||||||
|
)
|
||||||
|
|
||||||
DEPENDENCIES = ['i2c']
|
DEPENDENCIES = ["i2c"]
|
||||||
|
|
||||||
bh1750_ns = cg.esphome_ns.namespace('bh1750')
|
bh1750_ns = cg.esphome_ns.namespace("bh1750")
|
||||||
BH1750Resolution = bh1750_ns.enum('BH1750Resolution')
|
BH1750Resolution = bh1750_ns.enum("BH1750Resolution")
|
||||||
BH1750_RESOLUTIONS = {
|
BH1750_RESOLUTIONS = {
|
||||||
4.0: BH1750Resolution.BH1750_RESOLUTION_4P0_LX,
|
4.0: BH1750Resolution.BH1750_RESOLUTION_4P0_LX,
|
||||||
1.0: BH1750Resolution.BH1750_RESOLUTION_1P0_LX,
|
1.0: BH1750Resolution.BH1750_RESOLUTION_1P0_LX,
|
||||||
0.5: BH1750Resolution.BH1750_RESOLUTION_0P5_LX,
|
0.5: BH1750Resolution.BH1750_RESOLUTION_0P5_LX,
|
||||||
}
|
}
|
||||||
|
|
||||||
BH1750Sensor = bh1750_ns.class_('BH1750Sensor', sensor.Sensor, cg.PollingComponent, i2c.I2CDevice)
|
BH1750Sensor = bh1750_ns.class_(
|
||||||
|
"BH1750Sensor", sensor.Sensor, cg.PollingComponent, i2c.I2CDevice
|
||||||
|
)
|
||||||
|
|
||||||
CONF_MEASUREMENT_TIME = 'measurement_time'
|
CONF_MEASUREMENT_TIME = "measurement_time"
|
||||||
CONFIG_SCHEMA = sensor.sensor_schema(UNIT_LUX, ICON_BRIGHTNESS_5, 1).extend({
|
CONFIG_SCHEMA = (
|
||||||
cv.GenerateID(): cv.declare_id(BH1750Sensor),
|
sensor.sensor_schema(
|
||||||
cv.Optional(CONF_RESOLUTION, default=0.5): cv.enum(BH1750_RESOLUTIONS, float=True),
|
UNIT_LUX, ICON_EMPTY, 1, DEVICE_CLASS_ILLUMINANCE, STATE_CLASS_MEASUREMENT
|
||||||
cv.Optional(CONF_MEASUREMENT_TIME, default=69): cv.int_range(min=31, max=254),
|
)
|
||||||
}).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x23))
|
.extend(
|
||||||
|
{
|
||||||
|
cv.GenerateID(): cv.declare_id(BH1750Sensor),
|
||||||
|
cv.Optional(CONF_RESOLUTION, default=0.5): cv.enum(
|
||||||
|
BH1750_RESOLUTIONS, float=True
|
||||||
|
),
|
||||||
|
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"))
|
||||||
|
.extend(i2c.i2c_device_schema(0x23))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def to_code(config):
|
async def to_code(config):
|
||||||
var = cg.new_Pvariable(config[CONF_ID])
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
yield cg.register_component(var, config)
|
await cg.register_component(var, config)
|
||||||
yield sensor.register_sensor(var, config)
|
await sensor.register_sensor(var, config)
|
||||||
yield i2c.register_i2c_device(var, config)
|
await i2c.register_i2c_device(var, config)
|
||||||
|
|
||||||
cg.add(var.set_resolution(config[CONF_RESOLUTION]))
|
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]))
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
|
|
||||||
binary_ns = cg.esphome_ns.namespace('binary')
|
binary_ns = cg.esphome_ns.namespace("binary")
|
||||||
|
|||||||
@@ -1,33 +1,39 @@
|
|||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.components import fan, output
|
from esphome.components import fan, output
|
||||||
from esphome.const import CONF_DIRECTION_OUTPUT, CONF_OSCILLATION_OUTPUT, \
|
from esphome.const import (
|
||||||
CONF_OUTPUT, CONF_OUTPUT_ID
|
CONF_DIRECTION_OUTPUT,
|
||||||
|
CONF_OSCILLATION_OUTPUT,
|
||||||
|
CONF_OUTPUT,
|
||||||
|
CONF_OUTPUT_ID,
|
||||||
|
)
|
||||||
from .. import binary_ns
|
from .. import binary_ns
|
||||||
|
|
||||||
BinaryFan = binary_ns.class_('BinaryFan', cg.Component)
|
BinaryFan = binary_ns.class_("BinaryFan", cg.Component)
|
||||||
|
|
||||||
CONFIG_SCHEMA = fan.FAN_SCHEMA.extend({
|
CONFIG_SCHEMA = fan.FAN_SCHEMA.extend(
|
||||||
cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(BinaryFan),
|
{
|
||||||
cv.Required(CONF_OUTPUT): cv.use_id(output.BinaryOutput),
|
cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(BinaryFan),
|
||||||
cv.Optional(CONF_DIRECTION_OUTPUT): cv.use_id(output.BinaryOutput),
|
cv.Required(CONF_OUTPUT): cv.use_id(output.BinaryOutput),
|
||||||
cv.Optional(CONF_OSCILLATION_OUTPUT): cv.use_id(output.BinaryOutput),
|
cv.Optional(CONF_DIRECTION_OUTPUT): cv.use_id(output.BinaryOutput),
|
||||||
}).extend(cv.COMPONENT_SCHEMA)
|
cv.Optional(CONF_OSCILLATION_OUTPUT): cv.use_id(output.BinaryOutput),
|
||||||
|
}
|
||||||
|
).extend(cv.COMPONENT_SCHEMA)
|
||||||
|
|
||||||
|
|
||||||
def to_code(config):
|
async def to_code(config):
|
||||||
var = cg.new_Pvariable(config[CONF_OUTPUT_ID])
|
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_))
|
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_))
|
cg.add(var.set_output(output_))
|
||||||
|
|
||||||
if CONF_OSCILLATION_OUTPUT in config:
|
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))
|
cg.add(var.set_oscillating(oscillation_output))
|
||||||
|
|
||||||
if CONF_DIRECTION_OUTPUT in config:
|
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))
|
cg.add(var.set_direction(direction_output))
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ void binary::BinaryFan::dump_config() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
void BinaryFan::setup() {
|
void BinaryFan::setup() {
|
||||||
auto traits = fan::FanTraits(this->oscillating_ != nullptr, false, this->direction_ != nullptr);
|
auto traits = fan::FanTraits(this->oscillating_ != nullptr, false, this->direction_ != nullptr, 0);
|
||||||
this->fan_->set_traits(traits);
|
this->fan_->set_traits(traits);
|
||||||
this->fan_->add_on_state_callback([this]() { this->next_update_ = true; });
|
this->fan_->add_on_state_callback([this]() { this->next_update_ = true; });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,17 +4,19 @@ from esphome.components import light, output
|
|||||||
from esphome.const import CONF_OUTPUT_ID, CONF_OUTPUT
|
from esphome.const import CONF_OUTPUT_ID, CONF_OUTPUT
|
||||||
from .. import binary_ns
|
from .. import binary_ns
|
||||||
|
|
||||||
BinaryLightOutput = binary_ns.class_('BinaryLightOutput', light.LightOutput)
|
BinaryLightOutput = binary_ns.class_("BinaryLightOutput", light.LightOutput)
|
||||||
|
|
||||||
CONFIG_SCHEMA = light.BINARY_LIGHT_SCHEMA.extend({
|
CONFIG_SCHEMA = light.BINARY_LIGHT_SCHEMA.extend(
|
||||||
cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(BinaryLightOutput),
|
{
|
||||||
cv.Required(CONF_OUTPUT): cv.use_id(output.BinaryOutput),
|
cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(BinaryLightOutput),
|
||||||
})
|
cv.Required(CONF_OUTPUT): cv.use_id(output.BinaryOutput),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def to_code(config):
|
async def to_code(config):
|
||||||
var = cg.new_Pvariable(config[CONF_OUTPUT_ID])
|
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))
|
cg.add(var.set_output(out))
|
||||||
|
|||||||
@@ -3,131 +3,261 @@ import esphome.config_validation as cv
|
|||||||
from esphome import automation, core
|
from esphome import automation, core
|
||||||
from esphome.automation import Condition, maybe_simple_id
|
from esphome.automation import Condition, maybe_simple_id
|
||||||
from esphome.components import mqtt
|
from esphome.components import mqtt
|
||||||
from esphome.const import CONF_DEVICE_CLASS, CONF_FILTERS, \
|
from esphome.const import (
|
||||||
CONF_ID, CONF_INTERNAL, CONF_INVALID_COOLDOWN, CONF_INVERTED, \
|
CONF_DELAY,
|
||||||
CONF_MAX_LENGTH, CONF_MIN_LENGTH, CONF_ON_CLICK, \
|
CONF_DEVICE_CLASS,
|
||||||
CONF_ON_DOUBLE_CLICK, CONF_ON_MULTI_CLICK, CONF_ON_PRESS, CONF_ON_RELEASE, CONF_ON_STATE, \
|
CONF_FILTERS,
|
||||||
CONF_STATE, CONF_TIMING, CONF_TRIGGER_ID, CONF_FOR, CONF_NAME, CONF_MQTT_ID
|
CONF_ID,
|
||||||
from esphome.core import CORE, coroutine, coroutine_with_priority
|
CONF_INTERNAL,
|
||||||
|
CONF_INVALID_COOLDOWN,
|
||||||
|
CONF_INVERTED,
|
||||||
|
CONF_MAX_LENGTH,
|
||||||
|
CONF_MIN_LENGTH,
|
||||||
|
CONF_ON_CLICK,
|
||||||
|
CONF_ON_DOUBLE_CLICK,
|
||||||
|
CONF_ON_MULTI_CLICK,
|
||||||
|
CONF_ON_PRESS,
|
||||||
|
CONF_ON_RELEASE,
|
||||||
|
CONF_ON_STATE,
|
||||||
|
CONF_STATE,
|
||||||
|
CONF_TIMING,
|
||||||
|
CONF_TRIGGER_ID,
|
||||||
|
CONF_FOR,
|
||||||
|
CONF_NAME,
|
||||||
|
CONF_MQTT_ID,
|
||||||
|
DEVICE_CLASS_EMPTY,
|
||||||
|
DEVICE_CLASS_BATTERY,
|
||||||
|
DEVICE_CLASS_BATTERY_CHARGING,
|
||||||
|
DEVICE_CLASS_COLD,
|
||||||
|
DEVICE_CLASS_CONNECTIVITY,
|
||||||
|
DEVICE_CLASS_DOOR,
|
||||||
|
DEVICE_CLASS_GARAGE_DOOR,
|
||||||
|
DEVICE_CLASS_GAS,
|
||||||
|
DEVICE_CLASS_HEAT,
|
||||||
|
DEVICE_CLASS_LIGHT,
|
||||||
|
DEVICE_CLASS_LOCK,
|
||||||
|
DEVICE_CLASS_MOISTURE,
|
||||||
|
DEVICE_CLASS_MOTION,
|
||||||
|
DEVICE_CLASS_MOVING,
|
||||||
|
DEVICE_CLASS_OCCUPANCY,
|
||||||
|
DEVICE_CLASS_OPENING,
|
||||||
|
DEVICE_CLASS_PLUG,
|
||||||
|
DEVICE_CLASS_POWER,
|
||||||
|
DEVICE_CLASS_PRESENCE,
|
||||||
|
DEVICE_CLASS_PROBLEM,
|
||||||
|
DEVICE_CLASS_SAFETY,
|
||||||
|
DEVICE_CLASS_SMOKE,
|
||||||
|
DEVICE_CLASS_SOUND,
|
||||||
|
DEVICE_CLASS_VIBRATION,
|
||||||
|
DEVICE_CLASS_WINDOW,
|
||||||
|
)
|
||||||
|
from esphome.core import CORE, coroutine_with_priority
|
||||||
from esphome.util import Registry
|
from esphome.util import Registry
|
||||||
|
|
||||||
CODEOWNERS = ['@esphome/core']
|
CODEOWNERS = ["@esphome/core"]
|
||||||
DEVICE_CLASSES = [
|
DEVICE_CLASSES = [
|
||||||
'', 'battery', 'cold', 'connectivity', 'door', 'garage_door', 'gas',
|
DEVICE_CLASS_EMPTY,
|
||||||
'heat', 'light', 'lock', 'moisture', 'motion', 'moving', 'occupancy',
|
DEVICE_CLASS_BATTERY,
|
||||||
'opening', 'plug', 'power', 'presence', 'problem', 'safety', 'smoke',
|
DEVICE_CLASS_BATTERY_CHARGING,
|
||||||
'sound', 'vibration', 'window'
|
DEVICE_CLASS_COLD,
|
||||||
|
DEVICE_CLASS_CONNECTIVITY,
|
||||||
|
DEVICE_CLASS_DOOR,
|
||||||
|
DEVICE_CLASS_GARAGE_DOOR,
|
||||||
|
DEVICE_CLASS_GAS,
|
||||||
|
DEVICE_CLASS_HEAT,
|
||||||
|
DEVICE_CLASS_LIGHT,
|
||||||
|
DEVICE_CLASS_LOCK,
|
||||||
|
DEVICE_CLASS_MOISTURE,
|
||||||
|
DEVICE_CLASS_MOTION,
|
||||||
|
DEVICE_CLASS_MOVING,
|
||||||
|
DEVICE_CLASS_OCCUPANCY,
|
||||||
|
DEVICE_CLASS_OPENING,
|
||||||
|
DEVICE_CLASS_PLUG,
|
||||||
|
DEVICE_CLASS_POWER,
|
||||||
|
DEVICE_CLASS_PRESENCE,
|
||||||
|
DEVICE_CLASS_PROBLEM,
|
||||||
|
DEVICE_CLASS_SAFETY,
|
||||||
|
DEVICE_CLASS_SMOKE,
|
||||||
|
DEVICE_CLASS_SOUND,
|
||||||
|
DEVICE_CLASS_VIBRATION,
|
||||||
|
DEVICE_CLASS_WINDOW,
|
||||||
]
|
]
|
||||||
|
|
||||||
IS_PLATFORM_COMPONENT = True
|
IS_PLATFORM_COMPONENT = True
|
||||||
|
|
||||||
binary_sensor_ns = cg.esphome_ns.namespace('binary_sensor')
|
binary_sensor_ns = cg.esphome_ns.namespace("binary_sensor")
|
||||||
BinarySensor = binary_sensor_ns.class_('BinarySensor', cg.Nameable)
|
BinarySensor = binary_sensor_ns.class_("BinarySensor", cg.Nameable)
|
||||||
BinarySensorInitiallyOff = binary_sensor_ns.class_('BinarySensorInitiallyOff', BinarySensor)
|
BinarySensorInitiallyOff = binary_sensor_ns.class_(
|
||||||
BinarySensorPtr = BinarySensor.operator('ptr')
|
"BinarySensorInitiallyOff", BinarySensor
|
||||||
|
)
|
||||||
|
BinarySensorPtr = BinarySensor.operator("ptr")
|
||||||
|
|
||||||
# Triggers
|
# Triggers
|
||||||
PressTrigger = binary_sensor_ns.class_('PressTrigger', automation.Trigger.template())
|
PressTrigger = binary_sensor_ns.class_("PressTrigger", automation.Trigger.template())
|
||||||
ReleaseTrigger = binary_sensor_ns.class_('ReleaseTrigger', automation.Trigger.template())
|
ReleaseTrigger = binary_sensor_ns.class_(
|
||||||
ClickTrigger = binary_sensor_ns.class_('ClickTrigger', automation.Trigger.template())
|
"ReleaseTrigger", automation.Trigger.template()
|
||||||
DoubleClickTrigger = binary_sensor_ns.class_('DoubleClickTrigger', automation.Trigger.template())
|
)
|
||||||
MultiClickTrigger = binary_sensor_ns.class_('MultiClickTrigger', automation.Trigger.template(),
|
ClickTrigger = binary_sensor_ns.class_("ClickTrigger", automation.Trigger.template())
|
||||||
cg.Component)
|
DoubleClickTrigger = binary_sensor_ns.class_(
|
||||||
MultiClickTriggerEvent = binary_sensor_ns.struct('MultiClickTriggerEvent')
|
"DoubleClickTrigger", automation.Trigger.template()
|
||||||
StateTrigger = binary_sensor_ns.class_('StateTrigger', automation.Trigger.template(bool))
|
)
|
||||||
BinarySensorPublishAction = binary_sensor_ns.class_('BinarySensorPublishAction', automation.Action)
|
MultiClickTrigger = binary_sensor_ns.class_(
|
||||||
|
"MultiClickTrigger", automation.Trigger.template(), cg.Component
|
||||||
|
)
|
||||||
|
MultiClickTriggerEvent = binary_sensor_ns.struct("MultiClickTriggerEvent")
|
||||||
|
StateTrigger = binary_sensor_ns.class_(
|
||||||
|
"StateTrigger", automation.Trigger.template(bool)
|
||||||
|
)
|
||||||
|
BinarySensorPublishAction = binary_sensor_ns.class_(
|
||||||
|
"BinarySensorPublishAction", automation.Action
|
||||||
|
)
|
||||||
|
|
||||||
# Condition
|
# Condition
|
||||||
BinarySensorCondition = binary_sensor_ns.class_('BinarySensorCondition', Condition)
|
BinarySensorCondition = binary_sensor_ns.class_("BinarySensorCondition", Condition)
|
||||||
|
|
||||||
# Filters
|
# Filters
|
||||||
Filter = binary_sensor_ns.class_('Filter')
|
Filter = binary_sensor_ns.class_("Filter")
|
||||||
DelayedOnOffFilter = binary_sensor_ns.class_('DelayedOnOffFilter', Filter, cg.Component)
|
DelayedOnOffFilter = binary_sensor_ns.class_("DelayedOnOffFilter", Filter, cg.Component)
|
||||||
DelayedOnFilter = binary_sensor_ns.class_('DelayedOnFilter', Filter, cg.Component)
|
DelayedOnFilter = binary_sensor_ns.class_("DelayedOnFilter", Filter, cg.Component)
|
||||||
DelayedOffFilter = binary_sensor_ns.class_('DelayedOffFilter', Filter, cg.Component)
|
DelayedOffFilter = binary_sensor_ns.class_("DelayedOffFilter", Filter, cg.Component)
|
||||||
InvertFilter = binary_sensor_ns.class_('InvertFilter', Filter)
|
InvertFilter = binary_sensor_ns.class_("InvertFilter", Filter)
|
||||||
LambdaFilter = binary_sensor_ns.class_('LambdaFilter', Filter)
|
AutorepeatFilter = binary_sensor_ns.class_("AutorepeatFilter", Filter, cg.Component)
|
||||||
|
LambdaFilter = binary_sensor_ns.class_("LambdaFilter", Filter)
|
||||||
|
|
||||||
FILTER_REGISTRY = Registry()
|
FILTER_REGISTRY = Registry()
|
||||||
validate_filters = cv.validate_registry('filter', FILTER_REGISTRY)
|
validate_filters = cv.validate_registry("filter", FILTER_REGISTRY)
|
||||||
|
|
||||||
|
|
||||||
@FILTER_REGISTRY.register('invert', InvertFilter, {})
|
@FILTER_REGISTRY.register("invert", InvertFilter, {})
|
||||||
def invert_filter_to_code(config, filter_id):
|
async def invert_filter_to_code(config, filter_id):
|
||||||
yield cg.new_Pvariable(filter_id)
|
return cg.new_Pvariable(filter_id)
|
||||||
|
|
||||||
|
|
||||||
@FILTER_REGISTRY.register('delayed_on_off', DelayedOnOffFilter,
|
@FILTER_REGISTRY.register(
|
||||||
cv.positive_time_period_milliseconds)
|
"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)
|
var = cg.new_Pvariable(filter_id, config)
|
||||||
yield cg.register_component(var, {})
|
await cg.register_component(var, {})
|
||||||
yield var
|
return var
|
||||||
|
|
||||||
|
|
||||||
@FILTER_REGISTRY.register('delayed_on', DelayedOnFilter,
|
@FILTER_REGISTRY.register(
|
||||||
cv.positive_time_period_milliseconds)
|
"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)
|
var = cg.new_Pvariable(filter_id, config)
|
||||||
yield cg.register_component(var, {})
|
await cg.register_component(var, {})
|
||||||
yield var
|
return var
|
||||||
|
|
||||||
|
|
||||||
@FILTER_REGISTRY.register('delayed_off', DelayedOffFilter, cv.positive_time_period_milliseconds)
|
@FILTER_REGISTRY.register(
|
||||||
def delayed_off_filter_to_code(config, filter_id):
|
"delayed_off", DelayedOffFilter, cv.positive_time_period_milliseconds
|
||||||
|
)
|
||||||
|
async def delayed_off_filter_to_code(config, filter_id):
|
||||||
var = cg.new_Pvariable(filter_id, config)
|
var = cg.new_Pvariable(filter_id, config)
|
||||||
yield cg.register_component(var, {})
|
await cg.register_component(var, {})
|
||||||
yield var
|
return var
|
||||||
|
|
||||||
|
|
||||||
@FILTER_REGISTRY.register('lambda', LambdaFilter, cv.returning_lambda)
|
CONF_TIME_OFF = "time_off"
|
||||||
def lambda_filter_to_code(config, filter_id):
|
CONF_TIME_ON = "time_on"
|
||||||
lambda_ = yield cg.process_lambda(config, [(bool, 'x')], return_type=cg.optional.template(bool))
|
|
||||||
yield cg.new_Pvariable(filter_id, lambda_)
|
DEFAULT_DELAY = "1s"
|
||||||
|
DEFAULT_TIME_OFF = "100ms"
|
||||||
|
DEFAULT_TIME_ON = "900ms"
|
||||||
|
|
||||||
|
|
||||||
MULTI_CLICK_TIMING_SCHEMA = cv.Schema({
|
@FILTER_REGISTRY.register(
|
||||||
cv.Optional(CONF_STATE): cv.boolean,
|
"autorepeat",
|
||||||
cv.Optional(CONF_MIN_LENGTH): cv.positive_time_period_milliseconds,
|
AutorepeatFilter,
|
||||||
cv.Optional(CONF_MAX_LENGTH): cv.positive_time_period_milliseconds,
|
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)
|
||||||
|
async def lambda_filter_to_code(config, filter_id):
|
||||||
|
lambda_ = await cg.process_lambda(
|
||||||
|
config, [(bool, "x")], return_type=cg.optional.template(bool)
|
||||||
|
)
|
||||||
|
return cg.new_Pvariable(filter_id, lambda_)
|
||||||
|
|
||||||
|
|
||||||
|
MULTI_CLICK_TIMING_SCHEMA = cv.Schema(
|
||||||
|
{
|
||||||
|
cv.Optional(CONF_STATE): cv.boolean,
|
||||||
|
cv.Optional(CONF_MIN_LENGTH): cv.positive_time_period_milliseconds,
|
||||||
|
cv.Optional(CONF_MAX_LENGTH): cv.positive_time_period_milliseconds,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def parse_multi_click_timing_str(value):
|
def parse_multi_click_timing_str(value):
|
||||||
if not isinstance(value, str):
|
if not isinstance(value, str):
|
||||||
return value
|
return value
|
||||||
|
|
||||||
parts = value.lower().split(' ')
|
parts = value.lower().split(" ")
|
||||||
if len(parts) != 5:
|
if len(parts) != 5:
|
||||||
raise cv.Invalid("Multi click timing grammar consists of exactly 5 words, not {}"
|
raise cv.Invalid(
|
||||||
"".format(len(parts)))
|
"Multi click timing grammar consists of exactly 5 words, not {}"
|
||||||
|
"".format(len(parts))
|
||||||
|
)
|
||||||
try:
|
try:
|
||||||
state = cv.boolean(parts[0])
|
state = cv.boolean(parts[0])
|
||||||
except cv.Invalid:
|
except cv.Invalid:
|
||||||
# pylint: disable=raise-missing-from
|
# pylint: disable=raise-missing-from
|
||||||
raise cv.Invalid("First word must either be ON or OFF, not {}".format(parts[0]))
|
raise cv.Invalid("First word must either be ON or OFF, not {}".format(parts[0]))
|
||||||
|
|
||||||
if parts[1] != 'for':
|
if parts[1] != "for":
|
||||||
raise cv.Invalid("Second word must be 'for', got {}".format(parts[1]))
|
raise cv.Invalid("Second word must be 'for', got {}".format(parts[1]))
|
||||||
|
|
||||||
if parts[2] == 'at':
|
if parts[2] == "at":
|
||||||
if parts[3] == 'least':
|
if parts[3] == "least":
|
||||||
key = CONF_MIN_LENGTH
|
key = CONF_MIN_LENGTH
|
||||||
elif parts[3] == 'most':
|
elif parts[3] == "most":
|
||||||
key = CONF_MAX_LENGTH
|
key = CONF_MAX_LENGTH
|
||||||
else:
|
else:
|
||||||
raise cv.Invalid("Third word after at must either be 'least' or 'most', got {}"
|
raise cv.Invalid(
|
||||||
"".format(parts[3]))
|
"Third word after at must either be 'least' or 'most', got {}"
|
||||||
|
"".format(parts[3])
|
||||||
|
)
|
||||||
try:
|
try:
|
||||||
length = cv.positive_time_period_milliseconds(parts[4])
|
length = cv.positive_time_period_milliseconds(parts[4])
|
||||||
except cv.Invalid as err:
|
except cv.Invalid as err:
|
||||||
raise cv.Invalid(f"Multi Click Grammar Parsing length failed: {err}")
|
raise cv.Invalid(f"Multi Click Grammar Parsing length failed: {err}")
|
||||||
return {
|
return {CONF_STATE: state, key: str(length)}
|
||||||
CONF_STATE: state,
|
|
||||||
key: str(length)
|
|
||||||
}
|
|
||||||
|
|
||||||
if parts[3] != 'to':
|
if parts[3] != "to":
|
||||||
raise cv.Invalid("Multi click grammar: 4th word must be 'to'")
|
raise cv.Invalid("Multi click grammar: 4th word must be 'to'")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -143,7 +273,7 @@ def parse_multi_click_timing_str(value):
|
|||||||
return {
|
return {
|
||||||
CONF_STATE: state,
|
CONF_STATE: state,
|
||||||
CONF_MIN_LENGTH: str(min_length),
|
CONF_MIN_LENGTH: str(min_length),
|
||||||
CONF_MAX_LENGTH: str(max_length)
|
CONF_MAX_LENGTH: str(max_length),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -163,11 +293,15 @@ def validate_multi_click_timing(value):
|
|||||||
|
|
||||||
new_state = v_.get(CONF_STATE, not state)
|
new_state = v_.get(CONF_STATE, not state)
|
||||||
if new_state == state:
|
if new_state == state:
|
||||||
raise cv.Invalid("Timings must have alternating state. Indices {} and {} have "
|
raise cv.Invalid(
|
||||||
"the same state {}".format(i, i + 1, state))
|
"Timings must have alternating state. Indices {} and {} have "
|
||||||
|
"the same state {}".format(i, i + 1, state)
|
||||||
|
)
|
||||||
if max_length is not None and max_length < min_length:
|
if max_length is not None and max_length < min_length:
|
||||||
raise cv.Invalid("Max length ({}) must be larger than min length ({})."
|
raise cv.Invalid(
|
||||||
"".format(max_length, min_length))
|
"Max length ({}) must be larger than min length ({})."
|
||||||
|
"".format(max_length, min_length)
|
||||||
|
)
|
||||||
|
|
||||||
state = new_state
|
state = new_state
|
||||||
tim = {
|
tim = {
|
||||||
@@ -180,50 +314,74 @@ def validate_multi_click_timing(value):
|
|||||||
return timings
|
return timings
|
||||||
|
|
||||||
|
|
||||||
device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space='_')
|
device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space="_")
|
||||||
|
|
||||||
BINARY_SENSOR_SCHEMA = cv.MQTT_COMPONENT_SCHEMA.extend({
|
BINARY_SENSOR_SCHEMA = cv.MQTT_COMPONENT_SCHEMA.extend(
|
||||||
cv.GenerateID(): cv.declare_id(BinarySensor),
|
{
|
||||||
cv.OnlyWith(CONF_MQTT_ID, 'mqtt'): cv.declare_id(mqtt.MQTTBinarySensorComponent),
|
cv.GenerateID(): cv.declare_id(BinarySensor),
|
||||||
|
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(
|
||||||
cv.Optional(CONF_DEVICE_CLASS): device_class,
|
mqtt.MQTTBinarySensorComponent
|
||||||
cv.Optional(CONF_FILTERS): validate_filters,
|
),
|
||||||
cv.Optional(CONF_ON_PRESS): automation.validate_automation({
|
cv.Optional(CONF_DEVICE_CLASS): device_class,
|
||||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(PressTrigger),
|
cv.Optional(CONF_FILTERS): validate_filters,
|
||||||
}),
|
cv.Optional(CONF_ON_PRESS): automation.validate_automation(
|
||||||
cv.Optional(CONF_ON_RELEASE): automation.validate_automation({
|
{
|
||||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ReleaseTrigger),
|
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(PressTrigger),
|
||||||
}),
|
}
|
||||||
cv.Optional(CONF_ON_CLICK): automation.validate_automation({
|
),
|
||||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ClickTrigger),
|
cv.Optional(CONF_ON_RELEASE): automation.validate_automation(
|
||||||
cv.Optional(CONF_MIN_LENGTH, default='50ms'): cv.positive_time_period_milliseconds,
|
{
|
||||||
cv.Optional(CONF_MAX_LENGTH, default='350ms'): cv.positive_time_period_milliseconds,
|
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ReleaseTrigger),
|
||||||
}),
|
}
|
||||||
cv.Optional(CONF_ON_DOUBLE_CLICK): automation.validate_automation({
|
),
|
||||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(DoubleClickTrigger),
|
cv.Optional(CONF_ON_CLICK): automation.validate_automation(
|
||||||
cv.Optional(CONF_MIN_LENGTH, default='50ms'): cv.positive_time_period_milliseconds,
|
{
|
||||||
cv.Optional(CONF_MAX_LENGTH, default='350ms'): cv.positive_time_period_milliseconds,
|
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ClickTrigger),
|
||||||
}),
|
cv.Optional(
|
||||||
cv.Optional(CONF_ON_MULTI_CLICK): automation.validate_automation({
|
CONF_MIN_LENGTH, default="50ms"
|
||||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(MultiClickTrigger),
|
): cv.positive_time_period_milliseconds,
|
||||||
cv.Required(CONF_TIMING): cv.All([parse_multi_click_timing_str],
|
cv.Optional(
|
||||||
validate_multi_click_timing),
|
CONF_MAX_LENGTH, default="350ms"
|
||||||
cv.Optional(CONF_INVALID_COOLDOWN, default='1s'): cv.positive_time_period_milliseconds,
|
): cv.positive_time_period_milliseconds,
|
||||||
}),
|
}
|
||||||
cv.Optional(CONF_ON_STATE): automation.validate_automation({
|
),
|
||||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(StateTrigger),
|
cv.Optional(CONF_ON_DOUBLE_CLICK): automation.validate_automation(
|
||||||
}),
|
{
|
||||||
|
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(DoubleClickTrigger),
|
||||||
cv.Optional(CONF_INVERTED): cv.invalid(
|
cv.Optional(
|
||||||
"The inverted binary_sensor property has been replaced by the "
|
CONF_MIN_LENGTH, default="50ms"
|
||||||
"new 'invert' binary sensor filter. Please see "
|
): cv.positive_time_period_milliseconds,
|
||||||
"https://esphome.io/components/binary_sensor/index.html."
|
cv.Optional(
|
||||||
),
|
CONF_MAX_LENGTH, default="350ms"
|
||||||
})
|
): cv.positive_time_period_milliseconds,
|
||||||
|
}
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_ON_MULTI_CLICK): automation.validate_automation(
|
||||||
|
{
|
||||||
|
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(MultiClickTrigger),
|
||||||
|
cv.Required(CONF_TIMING): cv.All(
|
||||||
|
[parse_multi_click_timing_str], validate_multi_click_timing
|
||||||
|
),
|
||||||
|
cv.Optional(
|
||||||
|
CONF_INVALID_COOLDOWN, default="1s"
|
||||||
|
): cv.positive_time_period_milliseconds,
|
||||||
|
}
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_ON_STATE): automation.validate_automation(
|
||||||
|
{
|
||||||
|
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(StateTrigger),
|
||||||
|
}
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_INVERTED): cv.invalid(
|
||||||
|
"The inverted binary_sensor property has been replaced by the "
|
||||||
|
"new 'invert' binary sensor filter. Please see "
|
||||||
|
"https://esphome.io/components/binary_sensor/index.html."
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@coroutine
|
async def setup_binary_sensor_core_(var, config):
|
||||||
def setup_binary_sensor_core_(var, config):
|
|
||||||
cg.add(var.set_name(config[CONF_NAME]))
|
cg.add(var.set_name(config[CONF_NAME]))
|
||||||
if CONF_INTERNAL in config:
|
if CONF_INTERNAL in config:
|
||||||
cg.add(var.set_internal(config[CONF_INTERNAL]))
|
cg.add(var.set_internal(config[CONF_INTERNAL]))
|
||||||
@@ -232,88 +390,96 @@ def setup_binary_sensor_core_(var, config):
|
|||||||
if CONF_INVERTED in config:
|
if CONF_INVERTED in config:
|
||||||
cg.add(var.set_inverted(config[CONF_INVERTED]))
|
cg.add(var.set_inverted(config[CONF_INVERTED]))
|
||||||
if CONF_FILTERS in config:
|
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))
|
cg.add(var.add_filters(filters))
|
||||||
|
|
||||||
for conf in config.get(CONF_ON_PRESS, []):
|
for conf in config.get(CONF_ON_PRESS, []):
|
||||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
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, []):
|
for conf in config.get(CONF_ON_RELEASE, []):
|
||||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
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, []):
|
for conf in config.get(CONF_ON_CLICK, []):
|
||||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var,
|
trigger = cg.new_Pvariable(
|
||||||
conf[CONF_MIN_LENGTH], conf[CONF_MAX_LENGTH])
|
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, []):
|
for conf in config.get(CONF_ON_DOUBLE_CLICK, []):
|
||||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var,
|
trigger = cg.new_Pvariable(
|
||||||
conf[CONF_MIN_LENGTH], conf[CONF_MAX_LENGTH])
|
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, []):
|
for conf in config.get(CONF_ON_MULTI_CLICK, []):
|
||||||
timings = []
|
timings = []
|
||||||
for tim in conf[CONF_TIMING]:
|
for tim in conf[CONF_TIMING]:
|
||||||
timings.append(cg.StructInitializer(
|
timings.append(
|
||||||
MultiClickTriggerEvent,
|
cg.StructInitializer(
|
||||||
('state', tim[CONF_STATE]),
|
MultiClickTriggerEvent,
|
||||||
('min_length', tim[CONF_MIN_LENGTH]),
|
("state", tim[CONF_STATE]),
|
||||||
('max_length', tim.get(CONF_MAX_LENGTH, 4294967294)),
|
("min_length", tim[CONF_MIN_LENGTH]),
|
||||||
))
|
("max_length", tim.get(CONF_MAX_LENGTH, 4294967294)),
|
||||||
|
)
|
||||||
|
)
|
||||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var, timings)
|
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var, timings)
|
||||||
if CONF_INVALID_COOLDOWN in conf:
|
if CONF_INVALID_COOLDOWN in conf:
|
||||||
cg.add(trigger.set_invalid_cooldown(conf[CONF_INVALID_COOLDOWN]))
|
cg.add(trigger.set_invalid_cooldown(conf[CONF_INVALID_COOLDOWN]))
|
||||||
yield cg.register_component(trigger, conf)
|
await cg.register_component(trigger, conf)
|
||||||
yield automation.build_automation(trigger, [], conf)
|
await automation.build_automation(trigger, [], conf)
|
||||||
|
|
||||||
for conf in config.get(CONF_ON_STATE, []):
|
for conf in config.get(CONF_ON_STATE, []):
|
||||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
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:
|
if CONF_MQTT_ID in config:
|
||||||
mqtt_ = cg.new_Pvariable(config[CONF_MQTT_ID], var)
|
mqtt_ = cg.new_Pvariable(config[CONF_MQTT_ID], var)
|
||||||
yield mqtt.register_mqtt_component(mqtt_, config)
|
await mqtt.register_mqtt_component(mqtt_, config)
|
||||||
|
|
||||||
|
|
||||||
@coroutine
|
async def register_binary_sensor(var, config):
|
||||||
def register_binary_sensor(var, config):
|
|
||||||
if not CORE.has_id(config[CONF_ID]):
|
if not CORE.has_id(config[CONF_ID]):
|
||||||
var = cg.Pvariable(config[CONF_ID], var)
|
var = cg.Pvariable(config[CONF_ID], var)
|
||||||
cg.add(cg.App.register_binary_sensor(var))
|
cg.add(cg.App.register_binary_sensor(var))
|
||||||
yield setup_binary_sensor_core_(var, config)
|
await setup_binary_sensor_core_(var, config)
|
||||||
|
|
||||||
|
|
||||||
@coroutine
|
async def new_binary_sensor(config):
|
||||||
def new_binary_sensor(config):
|
|
||||||
var = cg.new_Pvariable(config[CONF_ID], config[CONF_NAME])
|
var = cg.new_Pvariable(config[CONF_ID], config[CONF_NAME])
|
||||||
yield register_binary_sensor(var, config)
|
await register_binary_sensor(var, config)
|
||||||
yield var
|
return var
|
||||||
|
|
||||||
|
|
||||||
BINARY_SENSOR_CONDITION_SCHEMA = maybe_simple_id({
|
BINARY_SENSOR_CONDITION_SCHEMA = maybe_simple_id(
|
||||||
cv.Required(CONF_ID): cv.use_id(BinarySensor),
|
{
|
||||||
cv.Optional(CONF_FOR): cv.invalid("This option has been removed in 1.13, please use the "
|
cv.Required(CONF_ID): cv.use_id(BinarySensor),
|
||||||
"'for' condition instead."),
|
cv.Optional(CONF_FOR): cv.invalid(
|
||||||
})
|
"This option has been removed in 1.13, please use the "
|
||||||
|
"'for' condition instead."
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@automation.register_condition('binary_sensor.is_on', BinarySensorCondition,
|
@automation.register_condition(
|
||||||
BINARY_SENSOR_CONDITION_SCHEMA)
|
"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])
|
async def binary_sensor_is_on_to_code(config, condition_id, template_arg, args):
|
||||||
yield cg.new_Pvariable(condition_id, template_arg, paren, True)
|
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,
|
@automation.register_condition(
|
||||||
BINARY_SENSOR_CONDITION_SCHEMA)
|
"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])
|
async def binary_sensor_is_off_to_code(config, condition_id, template_arg, args):
|
||||||
yield cg.new_Pvariable(condition_id, template_arg, paren, False)
|
paren = await cg.get_variable(config[CONF_ID])
|
||||||
|
return cg.new_Pvariable(condition_id, template_arg, paren, False)
|
||||||
|
|
||||||
|
|
||||||
@coroutine_with_priority(100.0)
|
@coroutine_with_priority(100.0)
|
||||||
def to_code(config):
|
async def to_code(config):
|
||||||
cg.add_define('USE_BINARY_SENSOR')
|
cg.add_define("USE_BINARY_SENSOR")
|
||||||
cg.add_global(binary_sensor_ns.using)
|
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; }
|
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) {}
|
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); }
|
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;
|
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 {
|
class LambdaFilter : public Filter {
|
||||||
public:
|
public:
|
||||||
explicit LambdaFilter(const std::function<optional<bool>(bool)> &f);
|
explicit LambdaFilter(const std::function<optional<bool>(bool)> &f);
|
||||||
|
|||||||
@@ -2,14 +2,26 @@ import esphome.codegen as cg
|
|||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
|
|
||||||
from esphome.components import sensor, binary_sensor
|
from esphome.components import sensor, binary_sensor
|
||||||
from esphome.const import CONF_ID, CONF_CHANNELS, CONF_VALUE, CONF_TYPE, UNIT_EMPTY, \
|
from esphome.const import (
|
||||||
ICON_CHECK_CIRCLE_OUTLINE, CONF_BINARY_SENSOR, CONF_GROUP
|
CONF_ID,
|
||||||
|
CONF_CHANNELS,
|
||||||
|
CONF_VALUE,
|
||||||
|
CONF_TYPE,
|
||||||
|
DEVICE_CLASS_EMPTY,
|
||||||
|
UNIT_EMPTY,
|
||||||
|
ICON_CHECK_CIRCLE_OUTLINE,
|
||||||
|
CONF_BINARY_SENSOR,
|
||||||
|
CONF_GROUP,
|
||||||
|
STATE_CLASS_NONE,
|
||||||
|
)
|
||||||
|
|
||||||
DEPENDENCIES = ['binary_sensor']
|
DEPENDENCIES = ["binary_sensor"]
|
||||||
|
|
||||||
binary_sensor_map_ns = cg.esphome_ns.namespace('binary_sensor_map')
|
binary_sensor_map_ns = cg.esphome_ns.namespace("binary_sensor_map")
|
||||||
BinarySensorMap = binary_sensor_map_ns.class_('BinarySensorMap', cg.Component, sensor.Sensor)
|
BinarySensorMap = binary_sensor_map_ns.class_(
|
||||||
SensorMapType = binary_sensor_map_ns.enum('SensorMapType')
|
"BinarySensorMap", cg.Component, sensor.Sensor
|
||||||
|
)
|
||||||
|
SensorMapType = binary_sensor_map_ns.enum("SensorMapType")
|
||||||
|
|
||||||
SENSOR_MAP_TYPES = {
|
SENSOR_MAP_TYPES = {
|
||||||
CONF_GROUP: SensorMapType.BINARY_SENSOR_MAP_TYPE_GROUP,
|
CONF_GROUP: SensorMapType.BINARY_SENSOR_MAP_TYPE_GROUP,
|
||||||
@@ -20,22 +32,35 @@ entry = {
|
|||||||
cv.Required(CONF_VALUE): cv.float_,
|
cv.Required(CONF_VALUE): cv.float_,
|
||||||
}
|
}
|
||||||
|
|
||||||
CONFIG_SCHEMA = cv.typed_schema({
|
CONFIG_SCHEMA = cv.typed_schema(
|
||||||
CONF_GROUP: sensor.sensor_schema(UNIT_EMPTY, ICON_CHECK_CIRCLE_OUTLINE, 0).extend({
|
{
|
||||||
cv.GenerateID(): cv.declare_id(BinarySensorMap),
|
CONF_GROUP: sensor.sensor_schema(
|
||||||
cv.Required(CONF_CHANNELS): cv.All(cv.ensure_list(entry), cv.Length(min=1)),
|
UNIT_EMPTY,
|
||||||
}),
|
ICON_CHECK_CIRCLE_OUTLINE,
|
||||||
}, lower=True)
|
0,
|
||||||
|
DEVICE_CLASS_EMPTY,
|
||||||
|
STATE_CLASS_NONE,
|
||||||
|
).extend(
|
||||||
|
{
|
||||||
|
cv.GenerateID(): cv.declare_id(BinarySensorMap),
|
||||||
|
cv.Required(CONF_CHANNELS): cv.All(
|
||||||
|
cv.ensure_list(entry), cv.Length(min=1)
|
||||||
|
),
|
||||||
|
}
|
||||||
|
),
|
||||||
|
},
|
||||||
|
lower=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def to_code(config):
|
async def to_code(config):
|
||||||
var = cg.new_Pvariable(config[CONF_ID])
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
yield cg.register_component(var, config)
|
await cg.register_component(var, config)
|
||||||
yield sensor.register_sensor(var, config)
|
await sensor.register_sensor(var, config)
|
||||||
|
|
||||||
constant = SENSOR_MAP_TYPES[config[CONF_TYPE]]
|
constant = SENSOR_MAP_TYPES[config[CONF_TYPE]]
|
||||||
cg.add(var.set_sensor_type(constant))
|
cg.add(var.set_sensor_type(constant))
|
||||||
|
|
||||||
for ch in config[CONF_CHANNELS]:
|
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]))
|
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
|
||||||
@@ -3,34 +3,52 @@ import esphome.config_validation as cv
|
|||||||
from esphome.components import binary_sensor, esp32_ble_tracker
|
from esphome.components import binary_sensor, esp32_ble_tracker
|
||||||
from esphome.const import CONF_MAC_ADDRESS, CONF_SERVICE_UUID, CONF_ID
|
from esphome.const import CONF_MAC_ADDRESS, CONF_SERVICE_UUID, CONF_ID
|
||||||
|
|
||||||
DEPENDENCIES = ['esp32_ble_tracker']
|
DEPENDENCIES = ["esp32_ble_tracker"]
|
||||||
|
|
||||||
ble_presence_ns = cg.esphome_ns.namespace('ble_presence')
|
ble_presence_ns = cg.esphome_ns.namespace("ble_presence")
|
||||||
BLEPresenceDevice = ble_presence_ns.class_('BLEPresenceDevice', binary_sensor.BinarySensor,
|
BLEPresenceDevice = ble_presence_ns.class_(
|
||||||
cg.Component, esp32_ble_tracker.ESPBTDeviceListener)
|
"BLEPresenceDevice",
|
||||||
|
binary_sensor.BinarySensor,
|
||||||
|
cg.Component,
|
||||||
|
esp32_ble_tracker.ESPBTDeviceListener,
|
||||||
|
)
|
||||||
|
|
||||||
CONFIG_SCHEMA = cv.All(binary_sensor.BINARY_SENSOR_SCHEMA.extend({
|
CONFIG_SCHEMA = cv.All(
|
||||||
cv.GenerateID(): cv.declare_id(BLEPresenceDevice),
|
binary_sensor.BINARY_SENSOR_SCHEMA.extend(
|
||||||
cv.Optional(CONF_MAC_ADDRESS): cv.mac_address,
|
{
|
||||||
cv.Optional(CONF_SERVICE_UUID): esp32_ble_tracker.bt_uuid,
|
cv.GenerateID(): cv.declare_id(BLEPresenceDevice),
|
||||||
}).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA).extend(
|
cv.Optional(CONF_MAC_ADDRESS): cv.mac_address,
|
||||||
cv.COMPONENT_SCHEMA), cv.has_exactly_one_key(CONF_MAC_ADDRESS, CONF_SERVICE_UUID))
|
cv.Optional(CONF_SERVICE_UUID): esp32_ble_tracker.bt_uuid,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA)
|
||||||
|
.extend(cv.COMPONENT_SCHEMA),
|
||||||
|
cv.has_exactly_one_key(CONF_MAC_ADDRESS, CONF_SERVICE_UUID),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def to_code(config):
|
async def to_code(config):
|
||||||
var = cg.new_Pvariable(config[CONF_ID])
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
yield cg.register_component(var, config)
|
await cg.register_component(var, config)
|
||||||
yield esp32_ble_tracker.register_ble_device(var, config)
|
await esp32_ble_tracker.register_ble_device(var, config)
|
||||||
yield binary_sensor.register_binary_sensor(var, config)
|
await binary_sensor.register_binary_sensor(var, config)
|
||||||
|
|
||||||
if CONF_MAC_ADDRESS in config:
|
if CONF_MAC_ADDRESS in config:
|
||||||
cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex))
|
cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex))
|
||||||
|
|
||||||
if CONF_SERVICE_UUID in config:
|
if CONF_SERVICE_UUID in config:
|
||||||
if len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid16_format):
|
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])))
|
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):
|
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])))
|
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):
|
elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid128_format):
|
||||||
uuid128 = esp32_ble_tracker.as_hex_array(config[CONF_SERVICE_UUID])
|
uuid128 = esp32_ble_tracker.as_hex_array(config[CONF_SERVICE_UUID])
|
||||||
cg.add(var.set_service_uuid128(uuid128))
|
cg.add(var.set_service_uuid128(uuid128))
|
||||||
|
|||||||
@@ -1,36 +1,66 @@
|
|||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.components import sensor, esp32_ble_tracker
|
from esphome.components import sensor, esp32_ble_tracker
|
||||||
from esphome.const import CONF_SERVICE_UUID, CONF_MAC_ADDRESS, CONF_ID, UNIT_DECIBEL, ICON_SIGNAL
|
from esphome.const import (
|
||||||
|
CONF_SERVICE_UUID,
|
||||||
|
CONF_MAC_ADDRESS,
|
||||||
|
CONF_ID,
|
||||||
|
DEVICE_CLASS_SIGNAL_STRENGTH,
|
||||||
|
STATE_CLASS_MEASUREMENT,
|
||||||
|
UNIT_DECIBEL,
|
||||||
|
ICON_EMPTY,
|
||||||
|
)
|
||||||
|
|
||||||
DEPENDENCIES = ['esp32_ble_tracker']
|
DEPENDENCIES = ["esp32_ble_tracker"]
|
||||||
|
|
||||||
ble_rssi_ns = cg.esphome_ns.namespace('ble_rssi')
|
ble_rssi_ns = cg.esphome_ns.namespace("ble_rssi")
|
||||||
BLERSSISensor = ble_rssi_ns.class_('BLERSSISensor', sensor.Sensor, cg.Component,
|
BLERSSISensor = ble_rssi_ns.class_(
|
||||||
esp32_ble_tracker.ESPBTDeviceListener)
|
"BLERSSISensor", sensor.Sensor, cg.Component, esp32_ble_tracker.ESPBTDeviceListener
|
||||||
|
)
|
||||||
|
|
||||||
CONFIG_SCHEMA = cv.All(sensor.sensor_schema(UNIT_DECIBEL, ICON_SIGNAL, 0).extend({
|
CONFIG_SCHEMA = cv.All(
|
||||||
cv.GenerateID(): cv.declare_id(BLERSSISensor),
|
sensor.sensor_schema(
|
||||||
cv.Optional(CONF_MAC_ADDRESS): cv.mac_address,
|
UNIT_DECIBEL,
|
||||||
cv.Optional(CONF_SERVICE_UUID): esp32_ble_tracker.bt_uuid,
|
ICON_EMPTY,
|
||||||
}).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA).extend(
|
0,
|
||||||
cv.COMPONENT_SCHEMA), cv.has_exactly_one_key(CONF_MAC_ADDRESS, CONF_SERVICE_UUID))
|
DEVICE_CLASS_SIGNAL_STRENGTH,
|
||||||
|
STATE_CLASS_MEASUREMENT,
|
||||||
|
)
|
||||||
|
.extend(
|
||||||
|
{
|
||||||
|
cv.GenerateID(): cv.declare_id(BLERSSISensor),
|
||||||
|
cv.Optional(CONF_MAC_ADDRESS): cv.mac_address,
|
||||||
|
cv.Optional(CONF_SERVICE_UUID): esp32_ble_tracker.bt_uuid,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA)
|
||||||
|
.extend(cv.COMPONENT_SCHEMA),
|
||||||
|
cv.has_exactly_one_key(CONF_MAC_ADDRESS, CONF_SERVICE_UUID),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def to_code(config):
|
async def to_code(config):
|
||||||
var = cg.new_Pvariable(config[CONF_ID])
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
yield cg.register_component(var, config)
|
await cg.register_component(var, config)
|
||||||
yield esp32_ble_tracker.register_ble_device(var, config)
|
await esp32_ble_tracker.register_ble_device(var, config)
|
||||||
yield sensor.register_sensor(var, config)
|
await sensor.register_sensor(var, config)
|
||||||
|
|
||||||
if CONF_MAC_ADDRESS in config:
|
if CONF_MAC_ADDRESS in config:
|
||||||
cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex))
|
cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex))
|
||||||
|
|
||||||
if CONF_SERVICE_UUID in config:
|
if CONF_SERVICE_UUID in config:
|
||||||
if len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid16_format):
|
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])))
|
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):
|
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])))
|
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):
|
elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid128_format):
|
||||||
uuid128 = esp32_ble_tracker.as_hex_array(config[CONF_SERVICE_UUID])
|
uuid128 = esp32_ble_tracker.as_hex_array(config[CONF_SERVICE_UUID])
|
||||||
cg.add(var.set_service_uuid128(uuid128))
|
cg.add(var.set_service_uuid128(uuid128))
|
||||||
|
|||||||
@@ -3,20 +3,29 @@ import esphome.config_validation as cv
|
|||||||
from esphome.components import text_sensor, esp32_ble_tracker
|
from esphome.components import text_sensor, esp32_ble_tracker
|
||||||
from esphome.const import CONF_ID
|
from esphome.const import CONF_ID
|
||||||
|
|
||||||
DEPENDENCIES = ['esp32_ble_tracker']
|
DEPENDENCIES = ["esp32_ble_tracker"]
|
||||||
|
|
||||||
ble_scanner_ns = cg.esphome_ns.namespace('ble_scanner')
|
ble_scanner_ns = cg.esphome_ns.namespace("ble_scanner")
|
||||||
BLEScanner = ble_scanner_ns.class_('BLEScanner', text_sensor.TextSensor, cg.Component,
|
BLEScanner = ble_scanner_ns.class_(
|
||||||
esp32_ble_tracker.ESPBTDeviceListener)
|
"BLEScanner",
|
||||||
|
text_sensor.TextSensor,
|
||||||
|
cg.Component,
|
||||||
|
esp32_ble_tracker.ESPBTDeviceListener,
|
||||||
|
)
|
||||||
|
|
||||||
CONFIG_SCHEMA = cv.All(text_sensor.TEXT_SENSOR_SCHEMA.extend({
|
CONFIG_SCHEMA = cv.All(
|
||||||
cv.GenerateID(): cv.declare_id(BLEScanner),
|
text_sensor.TEXT_SENSOR_SCHEMA.extend(
|
||||||
}).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA).extend(
|
{
|
||||||
cv.COMPONENT_SCHEMA))
|
cv.GenerateID(): cv.declare_id(BLEScanner),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA)
|
||||||
|
.extend(cv.COMPONENT_SCHEMA)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def to_code(config):
|
async def to_code(config):
|
||||||
var = cg.new_Pvariable(config[CONF_ID])
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
yield cg.register_component(var, config)
|
await cg.register_component(var, config)
|
||||||
yield esp32_ble_tracker.register_ble_device(var, config)
|
await esp32_ble_tracker.register_ble_device(var, config)
|
||||||
yield text_sensor.register_text_sensor(var, config)
|
await text_sensor.register_text_sensor(var, config)
|
||||||
|
|||||||
@@ -1,75 +1,122 @@
|
|||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.components import i2c, sensor
|
from esphome.components import i2c, sensor
|
||||||
from esphome.const import CONF_HUMIDITY, CONF_ID, CONF_IIR_FILTER, CONF_OVERSAMPLING, \
|
from esphome.const import (
|
||||||
CONF_PRESSURE, CONF_TEMPERATURE, ICON_THERMOMETER, \
|
CONF_HUMIDITY,
|
||||||
UNIT_CELSIUS, UNIT_HECTOPASCAL, ICON_GAUGE, ICON_WATER_PERCENT, UNIT_PERCENT
|
CONF_ID,
|
||||||
|
CONF_IIR_FILTER,
|
||||||
|
CONF_OVERSAMPLING,
|
||||||
|
CONF_PRESSURE,
|
||||||
|
CONF_TEMPERATURE,
|
||||||
|
DEVICE_CLASS_HUMIDITY,
|
||||||
|
DEVICE_CLASS_PRESSURE,
|
||||||
|
DEVICE_CLASS_TEMPERATURE,
|
||||||
|
ICON_EMPTY,
|
||||||
|
STATE_CLASS_MEASUREMENT,
|
||||||
|
UNIT_CELSIUS,
|
||||||
|
UNIT_HECTOPASCAL,
|
||||||
|
UNIT_PERCENT,
|
||||||
|
)
|
||||||
|
|
||||||
DEPENDENCIES = ['i2c']
|
DEPENDENCIES = ["i2c"]
|
||||||
|
|
||||||
bme280_ns = cg.esphome_ns.namespace('bme280')
|
bme280_ns = cg.esphome_ns.namespace("bme280")
|
||||||
BME280Oversampling = bme280_ns.enum('BME280Oversampling')
|
BME280Oversampling = bme280_ns.enum("BME280Oversampling")
|
||||||
OVERSAMPLING_OPTIONS = {
|
OVERSAMPLING_OPTIONS = {
|
||||||
'NONE': BME280Oversampling.BME280_OVERSAMPLING_NONE,
|
"NONE": BME280Oversampling.BME280_OVERSAMPLING_NONE,
|
||||||
'1X': BME280Oversampling.BME280_OVERSAMPLING_1X,
|
"1X": BME280Oversampling.BME280_OVERSAMPLING_1X,
|
||||||
'2X': BME280Oversampling.BME280_OVERSAMPLING_2X,
|
"2X": BME280Oversampling.BME280_OVERSAMPLING_2X,
|
||||||
'4X': BME280Oversampling.BME280_OVERSAMPLING_4X,
|
"4X": BME280Oversampling.BME280_OVERSAMPLING_4X,
|
||||||
'8X': BME280Oversampling.BME280_OVERSAMPLING_8X,
|
"8X": BME280Oversampling.BME280_OVERSAMPLING_8X,
|
||||||
'16X': BME280Oversampling.BME280_OVERSAMPLING_16X,
|
"16X": BME280Oversampling.BME280_OVERSAMPLING_16X,
|
||||||
}
|
}
|
||||||
|
|
||||||
BME280IIRFilter = bme280_ns.enum('BME280IIRFilter')
|
BME280IIRFilter = bme280_ns.enum("BME280IIRFilter")
|
||||||
IIR_FILTER_OPTIONS = {
|
IIR_FILTER_OPTIONS = {
|
||||||
'OFF': BME280IIRFilter.BME280_IIR_FILTER_OFF,
|
"OFF": BME280IIRFilter.BME280_IIR_FILTER_OFF,
|
||||||
'2X': BME280IIRFilter.BME280_IIR_FILTER_2X,
|
"2X": BME280IIRFilter.BME280_IIR_FILTER_2X,
|
||||||
'4X': BME280IIRFilter.BME280_IIR_FILTER_4X,
|
"4X": BME280IIRFilter.BME280_IIR_FILTER_4X,
|
||||||
'8X': BME280IIRFilter.BME280_IIR_FILTER_8X,
|
"8X": BME280IIRFilter.BME280_IIR_FILTER_8X,
|
||||||
'16X': BME280IIRFilter.BME280_IIR_FILTER_16X,
|
"16X": BME280IIRFilter.BME280_IIR_FILTER_16X,
|
||||||
}
|
}
|
||||||
|
|
||||||
BME280Component = bme280_ns.class_('BME280Component', cg.PollingComponent, i2c.I2CDevice)
|
BME280Component = bme280_ns.class_(
|
||||||
|
"BME280Component", cg.PollingComponent, i2c.I2CDevice
|
||||||
|
)
|
||||||
|
|
||||||
CONFIG_SCHEMA = cv.Schema({
|
CONFIG_SCHEMA = (
|
||||||
cv.GenerateID(): cv.declare_id(BME280Component),
|
cv.Schema(
|
||||||
cv.Optional(CONF_TEMPERATURE):
|
{
|
||||||
sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 1).extend({
|
cv.GenerateID(): cv.declare_id(BME280Component),
|
||||||
cv.Optional(CONF_OVERSAMPLING, default='16X'):
|
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
|
||||||
cv.enum(OVERSAMPLING_OPTIONS, upper=True),
|
UNIT_CELSIUS,
|
||||||
}),
|
ICON_EMPTY,
|
||||||
cv.Optional(CONF_PRESSURE):
|
1,
|
||||||
sensor.sensor_schema(UNIT_HECTOPASCAL, ICON_GAUGE, 1).extend({
|
DEVICE_CLASS_TEMPERATURE,
|
||||||
cv.Optional(CONF_OVERSAMPLING, default='16X'):
|
STATE_CLASS_MEASUREMENT,
|
||||||
cv.enum(OVERSAMPLING_OPTIONS, upper=True),
|
).extend(
|
||||||
}),
|
{
|
||||||
cv.Optional(CONF_HUMIDITY):
|
cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum(
|
||||||
sensor.sensor_schema(UNIT_PERCENT, ICON_WATER_PERCENT, 1).extend({
|
OVERSAMPLING_OPTIONS, upper=True
|
||||||
cv.Optional(CONF_OVERSAMPLING, default='16X'):
|
),
|
||||||
cv.enum(OVERSAMPLING_OPTIONS, upper=True),
|
}
|
||||||
}),
|
),
|
||||||
cv.Optional(CONF_IIR_FILTER, default='OFF'): cv.enum(IIR_FILTER_OPTIONS, upper=True),
|
cv.Optional(CONF_PRESSURE): sensor.sensor_schema(
|
||||||
}).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x77))
|
UNIT_HECTOPASCAL,
|
||||||
|
ICON_EMPTY,
|
||||||
|
1,
|
||||||
|
DEVICE_CLASS_PRESSURE,
|
||||||
|
STATE_CLASS_MEASUREMENT,
|
||||||
|
).extend(
|
||||||
|
{
|
||||||
|
cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum(
|
||||||
|
OVERSAMPLING_OPTIONS, upper=True
|
||||||
|
),
|
||||||
|
}
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
|
||||||
|
UNIT_PERCENT,
|
||||||
|
ICON_EMPTY,
|
||||||
|
1,
|
||||||
|
DEVICE_CLASS_HUMIDITY,
|
||||||
|
STATE_CLASS_MEASUREMENT,
|
||||||
|
).extend(
|
||||||
|
{
|
||||||
|
cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum(
|
||||||
|
OVERSAMPLING_OPTIONS, upper=True
|
||||||
|
),
|
||||||
|
}
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_IIR_FILTER, default="OFF"): cv.enum(
|
||||||
|
IIR_FILTER_OPTIONS, upper=True
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.extend(cv.polling_component_schema("60s"))
|
||||||
|
.extend(i2c.i2c_device_schema(0x77))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def to_code(config):
|
async def to_code(config):
|
||||||
var = cg.new_Pvariable(config[CONF_ID])
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
yield cg.register_component(var, config)
|
await cg.register_component(var, config)
|
||||||
yield i2c.register_i2c_device(var, config)
|
await i2c.register_i2c_device(var, config)
|
||||||
|
|
||||||
if CONF_TEMPERATURE in config:
|
if CONF_TEMPERATURE in config:
|
||||||
conf = config[CONF_TEMPERATURE]
|
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_sensor(sens))
|
||||||
cg.add(var.set_temperature_oversampling(conf[CONF_OVERSAMPLING]))
|
cg.add(var.set_temperature_oversampling(conf[CONF_OVERSAMPLING]))
|
||||||
|
|
||||||
if CONF_PRESSURE in config:
|
if CONF_PRESSURE in config:
|
||||||
conf = config[CONF_PRESSURE]
|
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_sensor(sens))
|
||||||
cg.add(var.set_pressure_oversampling(conf[CONF_OVERSAMPLING]))
|
cg.add(var.set_pressure_oversampling(conf[CONF_OVERSAMPLING]))
|
||||||
|
|
||||||
if CONF_HUMIDITY in config:
|
if CONF_HUMIDITY in config:
|
||||||
conf = config[CONF_HUMIDITY]
|
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_sensor(sens))
|
||||||
cg.add(var.set_humidity_oversampling(conf[CONF_OVERSAMPLING]))
|
cg.add(var.set_humidity_oversampling(conf[CONF_OVERSAMPLING]))
|
||||||
|
|
||||||
|
|||||||
@@ -2,92 +2,161 @@ import esphome.codegen as cg
|
|||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome import core
|
from esphome import core
|
||||||
from esphome.components import i2c, sensor
|
from esphome.components import i2c, sensor
|
||||||
from esphome.const import CONF_DURATION, CONF_GAS_RESISTANCE, CONF_HEATER, \
|
from esphome.const import (
|
||||||
CONF_HUMIDITY, CONF_ID, CONF_IIR_FILTER, CONF_OVERSAMPLING, CONF_PRESSURE, \
|
CONF_DURATION,
|
||||||
CONF_TEMPERATURE, UNIT_OHM, ICON_GAS_CYLINDER, UNIT_CELSIUS, \
|
CONF_GAS_RESISTANCE,
|
||||||
ICON_THERMOMETER, UNIT_HECTOPASCAL, ICON_GAUGE, ICON_WATER_PERCENT, UNIT_PERCENT
|
CONF_HEATER,
|
||||||
|
CONF_HUMIDITY,
|
||||||
|
CONF_ID,
|
||||||
|
CONF_IIR_FILTER,
|
||||||
|
CONF_OVERSAMPLING,
|
||||||
|
CONF_PRESSURE,
|
||||||
|
CONF_TEMPERATURE,
|
||||||
|
DEVICE_CLASS_EMPTY,
|
||||||
|
DEVICE_CLASS_HUMIDITY,
|
||||||
|
DEVICE_CLASS_PRESSURE,
|
||||||
|
DEVICE_CLASS_TEMPERATURE,
|
||||||
|
STATE_CLASS_MEASUREMENT,
|
||||||
|
UNIT_OHM,
|
||||||
|
ICON_GAS_CYLINDER,
|
||||||
|
UNIT_CELSIUS,
|
||||||
|
ICON_EMPTY,
|
||||||
|
UNIT_HECTOPASCAL,
|
||||||
|
UNIT_PERCENT,
|
||||||
|
)
|
||||||
|
|
||||||
DEPENDENCIES = ['i2c']
|
DEPENDENCIES = ["i2c"]
|
||||||
|
|
||||||
bme680_ns = cg.esphome_ns.namespace('bme680')
|
bme680_ns = cg.esphome_ns.namespace("bme680")
|
||||||
BME680Oversampling = bme680_ns.enum('BME680Oversampling')
|
BME680Oversampling = bme680_ns.enum("BME680Oversampling")
|
||||||
OVERSAMPLING_OPTIONS = {
|
OVERSAMPLING_OPTIONS = {
|
||||||
'NONE': BME680Oversampling.BME680_OVERSAMPLING_NONE,
|
"NONE": BME680Oversampling.BME680_OVERSAMPLING_NONE,
|
||||||
'1X': BME680Oversampling.BME680_OVERSAMPLING_1X,
|
"1X": BME680Oversampling.BME680_OVERSAMPLING_1X,
|
||||||
'2X': BME680Oversampling.BME680_OVERSAMPLING_2X,
|
"2X": BME680Oversampling.BME680_OVERSAMPLING_2X,
|
||||||
'4X': BME680Oversampling.BME680_OVERSAMPLING_4X,
|
"4X": BME680Oversampling.BME680_OVERSAMPLING_4X,
|
||||||
'8X': BME680Oversampling.BME680_OVERSAMPLING_8X,
|
"8X": BME680Oversampling.BME680_OVERSAMPLING_8X,
|
||||||
'16X': BME680Oversampling.BME680_OVERSAMPLING_16X,
|
"16X": BME680Oversampling.BME680_OVERSAMPLING_16X,
|
||||||
}
|
}
|
||||||
|
|
||||||
BME680IIRFilter = bme680_ns.enum('BME680IIRFilter')
|
BME680IIRFilter = bme680_ns.enum("BME680IIRFilter")
|
||||||
IIR_FILTER_OPTIONS = {
|
IIR_FILTER_OPTIONS = {
|
||||||
'OFF': BME680IIRFilter.BME680_IIR_FILTER_OFF,
|
"OFF": BME680IIRFilter.BME680_IIR_FILTER_OFF,
|
||||||
'1X': BME680IIRFilter.BME680_IIR_FILTER_1X,
|
"1X": BME680IIRFilter.BME680_IIR_FILTER_1X,
|
||||||
'3X': BME680IIRFilter.BME680_IIR_FILTER_3X,
|
"3X": BME680IIRFilter.BME680_IIR_FILTER_3X,
|
||||||
'7X': BME680IIRFilter.BME680_IIR_FILTER_7X,
|
"7X": BME680IIRFilter.BME680_IIR_FILTER_7X,
|
||||||
'15X': BME680IIRFilter.BME680_IIR_FILTER_15X,
|
"15X": BME680IIRFilter.BME680_IIR_FILTER_15X,
|
||||||
'31X': BME680IIRFilter.BME680_IIR_FILTER_31X,
|
"31X": BME680IIRFilter.BME680_IIR_FILTER_31X,
|
||||||
'63X': BME680IIRFilter.BME680_IIR_FILTER_63X,
|
"63X": BME680IIRFilter.BME680_IIR_FILTER_63X,
|
||||||
'127X': BME680IIRFilter.BME680_IIR_FILTER_127X,
|
"127X": BME680IIRFilter.BME680_IIR_FILTER_127X,
|
||||||
}
|
}
|
||||||
|
|
||||||
BME680Component = bme680_ns.class_('BME680Component', cg.PollingComponent, i2c.I2CDevice)
|
BME680Component = bme680_ns.class_(
|
||||||
|
"BME680Component", cg.PollingComponent, i2c.I2CDevice
|
||||||
|
)
|
||||||
|
|
||||||
CONFIG_SCHEMA = cv.Schema({
|
CONFIG_SCHEMA = (
|
||||||
cv.GenerateID(): cv.declare_id(BME680Component),
|
cv.Schema(
|
||||||
cv.Optional(CONF_TEMPERATURE):
|
{
|
||||||
sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 1).extend({
|
cv.GenerateID(): cv.declare_id(BME680Component),
|
||||||
cv.Optional(CONF_OVERSAMPLING, default='16X'):
|
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
|
||||||
cv.enum(OVERSAMPLING_OPTIONS, upper=True),
|
UNIT_CELSIUS,
|
||||||
}),
|
ICON_EMPTY,
|
||||||
cv.Optional(CONF_PRESSURE):
|
1,
|
||||||
sensor.sensor_schema(UNIT_HECTOPASCAL, ICON_GAUGE, 1).extend({
|
DEVICE_CLASS_TEMPERATURE,
|
||||||
cv.Optional(CONF_OVERSAMPLING, default='16X'):
|
STATE_CLASS_MEASUREMENT,
|
||||||
cv.enum(OVERSAMPLING_OPTIONS, upper=True),
|
).extend(
|
||||||
}),
|
{
|
||||||
cv.Optional(CONF_HUMIDITY):
|
cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum(
|
||||||
sensor.sensor_schema(UNIT_PERCENT, ICON_WATER_PERCENT, 1).extend({
|
OVERSAMPLING_OPTIONS, upper=True
|
||||||
cv.Optional(CONF_OVERSAMPLING, default='16X'):
|
),
|
||||||
cv.enum(OVERSAMPLING_OPTIONS, upper=True),
|
}
|
||||||
}),
|
),
|
||||||
cv.Optional(CONF_GAS_RESISTANCE):
|
cv.Optional(CONF_PRESSURE): sensor.sensor_schema(
|
||||||
sensor.sensor_schema(UNIT_OHM, ICON_GAS_CYLINDER, 1),
|
UNIT_HECTOPASCAL,
|
||||||
cv.Optional(CONF_IIR_FILTER, default='OFF'): cv.enum(IIR_FILTER_OPTIONS, upper=True),
|
ICON_EMPTY,
|
||||||
cv.Optional(CONF_HEATER): cv.Any(None, cv.All(cv.Schema({
|
1,
|
||||||
cv.Optional(CONF_TEMPERATURE, default=320): cv.int_range(min=200, max=400),
|
DEVICE_CLASS_PRESSURE,
|
||||||
cv.Optional(CONF_DURATION, default='150ms'): cv.All(
|
STATE_CLASS_MEASUREMENT,
|
||||||
cv.positive_time_period_milliseconds, cv.Range(max=core.TimePeriod(milliseconds=4032)))
|
).extend(
|
||||||
}), cv.has_at_least_one_key(CONF_TEMPERATURE, CONF_DURATION))),
|
{
|
||||||
}).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x76))
|
cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum(
|
||||||
|
OVERSAMPLING_OPTIONS, upper=True
|
||||||
|
),
|
||||||
|
}
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
|
||||||
|
UNIT_PERCENT,
|
||||||
|
ICON_EMPTY,
|
||||||
|
1,
|
||||||
|
DEVICE_CLASS_HUMIDITY,
|
||||||
|
STATE_CLASS_MEASUREMENT,
|
||||||
|
).extend(
|
||||||
|
{
|
||||||
|
cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum(
|
||||||
|
OVERSAMPLING_OPTIONS, upper=True
|
||||||
|
),
|
||||||
|
}
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_GAS_RESISTANCE): sensor.sensor_schema(
|
||||||
|
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
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_HEATER): cv.Any(
|
||||||
|
None,
|
||||||
|
cv.All(
|
||||||
|
cv.Schema(
|
||||||
|
{
|
||||||
|
cv.Optional(CONF_TEMPERATURE, default=320): cv.int_range(
|
||||||
|
min=200, max=400
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_DURATION, default="150ms"): cv.All(
|
||||||
|
cv.positive_time_period_milliseconds,
|
||||||
|
cv.Range(max=core.TimePeriod(milliseconds=4032)),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
),
|
||||||
|
cv.has_at_least_one_key(CONF_TEMPERATURE, CONF_DURATION),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.extend(cv.polling_component_schema("60s"))
|
||||||
|
.extend(i2c.i2c_device_schema(0x76))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def to_code(config):
|
async def to_code(config):
|
||||||
var = cg.new_Pvariable(config[CONF_ID])
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
yield cg.register_component(var, config)
|
await cg.register_component(var, config)
|
||||||
yield i2c.register_i2c_device(var, config)
|
await i2c.register_i2c_device(var, config)
|
||||||
|
|
||||||
if CONF_TEMPERATURE in config:
|
if CONF_TEMPERATURE in config:
|
||||||
conf = config[CONF_TEMPERATURE]
|
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_sensor(sens))
|
||||||
cg.add(var.set_temperature_oversampling(conf[CONF_OVERSAMPLING]))
|
cg.add(var.set_temperature_oversampling(conf[CONF_OVERSAMPLING]))
|
||||||
|
|
||||||
if CONF_PRESSURE in config:
|
if CONF_PRESSURE in config:
|
||||||
conf = config[CONF_PRESSURE]
|
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_sensor(sens))
|
||||||
cg.add(var.set_pressure_oversampling(conf[CONF_OVERSAMPLING]))
|
cg.add(var.set_pressure_oversampling(conf[CONF_OVERSAMPLING]))
|
||||||
|
|
||||||
if CONF_HUMIDITY in config:
|
if CONF_HUMIDITY in config:
|
||||||
conf = config[CONF_HUMIDITY]
|
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_sensor(sens))
|
||||||
cg.add(var.set_humidity_oversampling(conf[CONF_OVERSAMPLING]))
|
cg.add(var.set_humidity_oversampling(conf[CONF_OVERSAMPLING]))
|
||||||
|
|
||||||
if CONF_GAS_RESISTANCE in config:
|
if CONF_GAS_RESISTANCE in config:
|
||||||
conf = config[CONF_GAS_RESISTANCE]
|
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_gas_resistance_sensor(sens))
|
||||||
|
|
||||||
cg.add(var.set_iir_filter(IIR_FILTER_OPTIONS[config[CONF_IIR_FILTER]]))
|
cg.add(var.set_iir_filter(IIR_FILTER_OPTIONS[config[CONF_IIR_FILTER]]))
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user