mirror of
https://github.com/esphome/esphome.git
synced 2025-08-10 20:29:24 +00:00
Compare commits
219 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
8eaffee160 | ||
![]() |
557a622f71 | ||
![]() |
e9c6556296 | ||
![]() |
ae9b247f47 | ||
![]() |
a59761d292 | ||
![]() |
030c87d142 | ||
![]() |
fddb05c845 | ||
![]() |
3b13e62d3f | ||
![]() |
9e7acd6f75 | ||
![]() |
7d9c043f1d | ||
![]() |
bafbeefb37 | ||
![]() |
07f2931841 | ||
![]() |
616ad04131 | ||
![]() |
b966e58f9e | ||
![]() |
f33c2a48eb | ||
![]() |
44d5be6e7b | ||
![]() |
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 | ||
![]() |
d238e06f86 | ||
![]() |
2fc59ecc30 | ||
![]() |
0047d24698 | ||
![]() |
89a89e1785 | ||
![]() |
1952d275f7 | ||
![]() |
043095b605 | ||
![]() |
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 |
7
.github/FUNDING.yml
vendored
7
.github/FUNDING.yml
vendored
@@ -1,8 +1,3 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github:
|
||||
patreon: ottowinter
|
||||
open_collective:
|
||||
ko_fi:
|
||||
tidelift:
|
||||
custom: https://esphome.io/guides/supporters.html
|
||||
custom: https://www.nabucasa.com
|
||||
|
40
.github/PULL_REQUEST_TEMPLATE.md
vendored
40
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -1,13 +1,47 @@
|
||||
## Description:
|
||||
# What does this implement/fix?
|
||||
|
||||
Quick description
|
||||
|
||||
**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)
|
||||
- [ ] Configuration change (this will require users to update their yaml configuration files to keep working)
|
||||
|
||||
**Related issue or feature (if applicable):** fixes <link to issue>
|
||||
|
||||
**Pull request in [esphome-docs](https://github.com/esphome/esphome-docs) with documentation (if applicable):** esphome/esphome-docs#<esphome-docs PR number goes here>
|
||||
|
||||
# Test Environment
|
||||
|
||||
- [ ] ESP32
|
||||
- [ ] ESP8266
|
||||
- [ ] Windows
|
||||
- [ ] Mac OS
|
||||
- [ ] Linux
|
||||
|
||||
## 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
|
||||
|
||||
```
|
||||
|
||||
# Explain your changes
|
||||
|
||||
Describe your changes here to communicate to the maintainers **why we should accept this pull request**.
|
||||
Very important to fill if no issue linked
|
||||
|
||||
## Checklist:
|
||||
- [ ] The code change is tested and works locally.
|
||||
- [ ] Tests have been added to verify that the new code works (under `tests/` folder).
|
||||
|
||||
|
||||
If user exposed functionality or configuration variables are added/changed:
|
||||
- [ ] 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
|
||||
- name: Set up env variables
|
||||
run: |
|
||||
base_version="2.6.0"
|
||||
base_version="3.0.0"
|
||||
|
||||
if [[ "${{ matrix.build_type }}" == "hassio" ]]; then
|
||||
build_from="esphome/esphome-hassio-base-${{ matrix.arch }}:${base_version}"
|
||||
@@ -45,7 +45,7 @@ jobs:
|
||||
run: |
|
||||
docker pull "${BUILD_TO}:dev" || true
|
||||
- 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: |
|
||||
docker build \
|
||||
--build-arg "BUILD_FROM=${BUILD_FROM}" \
|
||||
|
20
.github/workflows/ci.yml
vendored
20
.github/workflows/ci.yml
vendored
@@ -18,15 +18,6 @@ jobs:
|
||||
container: esphome/esphome-lint:latest
|
||||
steps:
|
||||
- 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
|
||||
# (build flags, libraries etc)
|
||||
- name: Set up platformio environment
|
||||
@@ -49,15 +40,6 @@ jobs:
|
||||
split: [1, 2, 3, 4]
|
||||
steps:
|
||||
- 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
|
||||
# (build flags, libraries etc)
|
||||
- name: Set up platformio environment
|
||||
@@ -133,7 +115,7 @@ jobs:
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ~/.platformio
|
||||
key: test-home-platformio-${{ matrix.test }}-${{ hashFiles('esphome/core_config.py') }}
|
||||
key: test-home-platformio-${{ matrix.test }}-${{ hashFiles('esphome/core/config.py') }}
|
||||
restore-keys: |
|
||||
test-home-platformio-${{ matrix.test }}-
|
||||
- name: Set up environment
|
||||
|
36
.github/workflows/docker-lint-build.yml
vendored
Normal file
36
.github/workflows/docker-lint-build.yml
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
name: Build and publish lint docker image
|
||||
|
||||
# Only run when docker paths change
|
||||
on:
|
||||
push:
|
||||
branches: [dev]
|
||||
paths:
|
||||
- 'docker/Dockerfile.lint'
|
||||
- 'requirements.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: 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" \
|
||||
.
|
||||
- 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:latest"
|
24
.github/workflows/release-dev.yml
vendored
24
.github/workflows/release-dev.yml
vendored
@@ -15,15 +15,6 @@ jobs:
|
||||
container: esphome/esphome-lint:latest
|
||||
steps:
|
||||
- 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
|
||||
# (build flags, libraries etc)
|
||||
- name: Set up platformio environment
|
||||
@@ -46,15 +37,6 @@ jobs:
|
||||
split: [1, 2, 3, 4]
|
||||
steps:
|
||||
- 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
|
||||
# (build flags, libraries etc)
|
||||
- name: Set up platformio environment
|
||||
@@ -130,7 +112,7 @@ jobs:
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ~/.platformio
|
||||
key: test-home-platformio-${{ matrix.test }}-${{ hashFiles('esphome/core_config.py') }}
|
||||
key: test-home-platformio-${{ matrix.test }}-${{ hashFiles('esphome/core/config.py') }}
|
||||
restore-keys: |
|
||||
test-home-platformio-${{ matrix.test }}-
|
||||
- name: Set up environment
|
||||
@@ -192,7 +174,7 @@ jobs:
|
||||
echo "TAG=${TAG}" >> $GITHUB_ENV
|
||||
- name: Set up env variables
|
||||
run: |
|
||||
base_version="2.6.0"
|
||||
base_version="3.0.0"
|
||||
|
||||
if [[ "${{ matrix.build_type }}" == "hassio" ]]; then
|
||||
build_from="esphome/esphome-hassio-base-${{ matrix.arch }}:${base_version}"
|
||||
@@ -211,7 +193,7 @@ jobs:
|
||||
run: |
|
||||
docker pull "${BUILD_TO}:dev" || true
|
||||
- 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: |
|
||||
docker build \
|
||||
--build-arg "BUILD_FROM=${BUILD_FROM}" \
|
||||
|
24
.github/workflows/release.yml
vendored
24
.github/workflows/release.yml
vendored
@@ -14,15 +14,6 @@ jobs:
|
||||
container: esphome/esphome-lint:latest
|
||||
steps:
|
||||
- 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
|
||||
# (build flags, libraries etc)
|
||||
- name: Set up platformio environment
|
||||
@@ -45,15 +36,6 @@ jobs:
|
||||
split: [1, 2, 3, 4]
|
||||
steps:
|
||||
- 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
|
||||
# (build flags, libraries etc)
|
||||
- name: Set up platformio environment
|
||||
@@ -129,7 +111,7 @@ jobs:
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ~/.platformio
|
||||
key: test-home-platformio-${{ matrix.test }}-${{ hashFiles('esphome/core_config.py') }}
|
||||
key: test-home-platformio-${{ matrix.test }}-${{ hashFiles('esphome/core/config.py') }}
|
||||
restore-keys: |
|
||||
test-home-platformio-${{ matrix.test }}-
|
||||
- name: Set up environment
|
||||
@@ -212,7 +194,7 @@ jobs:
|
||||
echo "TAG=${TAG}" >> $GITHUB_ENV
|
||||
- name: Set up env variables
|
||||
run: |
|
||||
base_version="2.6.0"
|
||||
base_version="3.0.0"
|
||||
|
||||
if [[ "${{ matrix.build_type }}" == "hassio" ]]; then
|
||||
build_from="esphome/esphome-hassio-base-${{ matrix.arch }}:${base_version}"
|
||||
@@ -239,7 +221,7 @@ jobs:
|
||||
run: |
|
||||
docker pull "${BUILD_TO}:${CACHE_TAG}" || true
|
||||
- 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: |
|
||||
docker build \
|
||||
--build-arg "BUILD_FROM=${BUILD_FROM}" \
|
||||
|
@@ -1,11 +1,27 @@
|
||||
# See https://pre-commit.com for more information
|
||||
# See https://pre-commit.com/hooks.html for more hooks
|
||||
repos:
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v2.4.0
|
||||
- repo: https://github.com/ambv/black
|
||||
rev: 20.8b1
|
||||
hooks:
|
||||
- id: trailing-whitespace
|
||||
- id: end-of-file-fixer
|
||||
- id: check-yaml
|
||||
- id: check-added-large-files
|
||||
- id: flake8
|
||||
- id: black
|
||||
args:
|
||||
- --safe
|
||||
- --quiet
|
||||
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
|
||||
|
25
CODEOWNERS
25
CODEOWNERS
@@ -13,12 +13,16 @@ esphome/core/* @esphome/core
|
||||
# Integrations
|
||||
esphome/components/ac_dimmer/* @glmnet
|
||||
esphome/components/adc/* @esphome/core
|
||||
esphome/components/addressable_light/* @justfalter
|
||||
esphome/components/animation/* @syndlex
|
||||
esphome/components/api/* @OttoWinter
|
||||
esphome/components/async_tcp/* @OttoWinter
|
||||
esphome/components/atc_mithermometer/* @ahpohl
|
||||
esphome/components/b_parasite/* @rbaron
|
||||
esphome/components/bang_bang/* @OttoWinter
|
||||
esphome/components/binary_sensor/* @esphome/core
|
||||
esphome/components/ble_client/* @buxtronix
|
||||
esphome/components/bme680_bsec/* @trvrnrth
|
||||
esphome/components/canbus/* @danielschramm @mvturnho
|
||||
esphome/components/captive_portal/* @OttoWinter
|
||||
esphome/components/climate/* @esphome/core
|
||||
@@ -33,10 +37,13 @@ esphome/components/ds1307/* @badbadc0ffee
|
||||
esphome/components/exposure_notifications/* @OttoWinter
|
||||
esphome/components/ezo/* @ssieb
|
||||
esphome/components/fastled_base/* @OttoWinter
|
||||
esphome/components/fingerprint_grow/* @OnFreund @loongyh
|
||||
esphome/components/globals/* @esphome/core
|
||||
esphome/components/gpio/* @esphome/core
|
||||
esphome/components/gps/* @coogle
|
||||
esphome/components/homeassistant/* @OttoWinter
|
||||
esphome/components/i2c/* @esphome/core
|
||||
esphome/components/inkbird_ibsth1_mini/* @fkirill
|
||||
esphome/components/inkplate6/* @jesserockz
|
||||
esphome/components/integration/* @OttoWinter
|
||||
esphome/components/interval/* @esphome/core
|
||||
@@ -44,10 +51,18 @@ esphome/components/json/* @OttoWinter
|
||||
esphome/components/ledc/* @OttoWinter
|
||||
esphome/components/light/* @esphome/core
|
||||
esphome/components/logger/* @esphome/core
|
||||
esphome/components/mcp23s08/* @SenexCrenshaw
|
||||
esphome/components/mcp23s17/* @SenexCrenshaw
|
||||
esphome/components/max7219digit/* @rspaargaren
|
||||
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/mcp9808/* @k7hpn
|
||||
esphome/components/midea_ac/* @dudanov
|
||||
esphome/components/midea_dongle/* @dudanov
|
||||
esphome/components/network/* @esphome/core
|
||||
esphome/components/nfc/* @jesserockz
|
||||
esphome/components/ota/* @esphome/core
|
||||
@@ -57,6 +72,7 @@ esphome/components/pn532/* @OttoWinter @jesserockz
|
||||
esphome/components/pn532_i2c/* @OttoWinter @jesserockz
|
||||
esphome/components/pn532_spi/* @OttoWinter @jesserockz
|
||||
esphome/components/power_supply/* @esphome/core
|
||||
esphome/components/pulse_meter/* @stevebaxter
|
||||
esphome/components/rc522/* @glmnet
|
||||
esphome/components/rc522_i2c/* @glmnet
|
||||
esphome/components/rc522_spi/* @glmnet
|
||||
@@ -65,8 +81,11 @@ esphome/components/rf_bridge/* @jesserockz
|
||||
esphome/components/rtttl/* @glmnet
|
||||
esphome/components/script/* @esphome/core
|
||||
esphome/components/sensor/* @esphome/core
|
||||
esphome/components/sgp40/* @SenexCrenshaw
|
||||
esphome/components/sht4x/* @sjtrny
|
||||
esphome/components/shutdown/* @esphome/core
|
||||
esphome/components/sim800l/* @glmnet
|
||||
esphome/components/sm2135/* @BoukeHaarsma23
|
||||
esphome/components/spi/* @esphome/core
|
||||
esphome/components/ssd1322_base/* @kbx81
|
||||
esphome/components/ssd1322_spi/* @kbx81
|
||||
@@ -84,12 +103,14 @@ esphome/components/st7789v/* @kbx81
|
||||
esphome/components/substitutions/* @esphome/core
|
||||
esphome/components/sun/* @OttoWinter
|
||||
esphome/components/switch/* @esphome/core
|
||||
esphome/components/tca9548a/* @andreashergert1984
|
||||
esphome/components/tcl112/* @glmnet
|
||||
esphome/components/teleinfo/* @0hax
|
||||
esphome/components/thermostat/* @kbx81
|
||||
esphome/components/time/* @OttoWinter
|
||||
esphome/components/tm1637/* @glmnet
|
||||
esphome/components/tmp102/* @timsavage
|
||||
esphome/components/tof10120/* @wstrzalka
|
||||
esphome/components/tuya/binary_sensor/* @jesserockz
|
||||
esphome/components/tuya/climate/* @jesserockz
|
||||
esphome/components/tuya/sensor/* @jesserockz
|
||||
|
@@ -8,19 +8,19 @@ In the interest of fostering an open and welcoming environment, we as contributo
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
- Using welcoming and inclusive language
|
||||
- Being respectful of differing viewpoints and experiences
|
||||
- Gracefully accepting constructive criticism
|
||||
- Focusing on what is best for the community
|
||||
- Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a professional setting
|
||||
- The use of sexualized language or imagery and unwelcome sexual attention or advances
|
||||
- Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
- Public or private harassment
|
||||
- Publishing others' private information, such as a physical or electronic address, without explicit permission
|
||||
- Other conduct which could reasonably be considered inappropriate in a professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
@@ -34,7 +34,7 @@ This Code of Conduct applies both within project spaces and in public spaces whe
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at contact@otto-winter.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at esphome@nabucasa.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
|
||||
|
||||
|
@@ -1,9 +1,11 @@
|
||||
ARG BUILD_FROM=esphome/esphome-base-amd64:2.6.0
|
||||
ARG BUILD_FROM=esphome/esphome-base-amd64:3.0.0
|
||||
FROM ${BUILD_FROM}
|
||||
|
||||
# First install requirements to leverage caching when requirements don't change
|
||||
COPY requirements.txt /
|
||||
RUN pip3 install --no-cache-dir -r /requirements.txt
|
||||
COPY requirements.txt docker/platformio_install_deps.py platformio.ini /
|
||||
RUN \
|
||||
pip3 install --no-cache-dir -r /requirements.txt \
|
||||
&& /platformio_install_deps.py /platformio.ini
|
||||
|
||||
# Then copy esphome and install
|
||||
COPY . .
|
||||
@@ -16,7 +18,7 @@ ENV USERNAME="" PASSWORD=""
|
||||
EXPOSE 6052
|
||||
|
||||
# Run healthcheck (heartbeat)
|
||||
HEALTHCHECK --interval=5m --timeout=3s \
|
||||
HEALTHCHECK --interval=30s --timeout=30s \
|
||||
CMD curl --fail http://localhost:6052 || exit 1
|
||||
|
||||
# The directory the user should mount their configuration files to
|
||||
|
@@ -1,4 +1,4 @@
|
||||
FROM esphome/esphome-base-amd64:2.6.0
|
||||
FROM esphome/esphome-base-amd64:3.0.0
|
||||
|
||||
COPY . .
|
||||
|
||||
|
@@ -2,8 +2,10 @@ ARG BUILD_FROM
|
||||
FROM ${BUILD_FROM}
|
||||
|
||||
# First install requirements to leverage caching when requirements don't change
|
||||
COPY requirements.txt /
|
||||
RUN pip3 install --no-cache-dir -r /requirements.txt
|
||||
COPY requirements.txt docker/platformio_install_deps.py platformio.ini /
|
||||
RUN \
|
||||
pip3 install --no-cache-dir -r /requirements.txt \
|
||||
&& /platformio_install_deps.py /platformio.ini
|
||||
|
||||
# Copy root filesystem
|
||||
COPY docker/rootfs/ /
|
||||
|
@@ -1,7 +1,9 @@
|
||||
FROM esphome/esphome-lint-base:2.6.0
|
||||
FROM esphome/esphome-lint-base:3.0.0
|
||||
|
||||
COPY requirements.txt requirements_test.txt /
|
||||
RUN pip3 install --no-cache-dir -r /requirements.txt -r /requirements_test.txt
|
||||
COPY requirements.txt requirements_test.txt docker/platformio_install_deps.py platformio.ini /
|
||||
RUN \
|
||||
pip3 install --no-cache-dir -r /requirements.txt -r /requirements_test.txt \
|
||||
&& /platformio_install_deps.py /platformio.ini
|
||||
|
||||
VOLUME ["/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])
|
@@ -8,21 +8,37 @@ from datetime import datetime
|
||||
from esphome import const, writer, yaml_util
|
||||
import esphome.codegen as cg
|
||||
from esphome.config import iter_components, read_config, strip_default_ids
|
||||
from esphome.const import CONF_BAUD_RATE, CONF_BROKER, CONF_LOGGER, CONF_OTA, \
|
||||
CONF_PASSWORD, CONF_PORT, CONF_ESPHOME, CONF_PLATFORMIO_OPTIONS
|
||||
from esphome.const import (
|
||||
CONF_BAUD_RATE,
|
||||
CONF_BROKER,
|
||||
CONF_LOGGER,
|
||||
CONF_OTA,
|
||||
CONF_PASSWORD,
|
||||
CONF_PORT,
|
||||
CONF_ESPHOME,
|
||||
CONF_PLATFORMIO_OPTIONS,
|
||||
)
|
||||
from esphome.core import CORE, EsphomeError, coroutine, coroutine_with_priority
|
||||
from esphome.helpers import color, indent
|
||||
from esphome.util import run_external_command, run_external_process, safe_print, list_yaml_files, \
|
||||
get_serial_ports
|
||||
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__)
|
||||
|
||||
|
||||
def choose_prompt(options):
|
||||
if not options:
|
||||
raise EsphomeError("Found no valid options for upload/logging, please make sure relevant "
|
||||
"sections (ota, api, mqtt, ...) are in your configuration and/or the "
|
||||
"device is plugged in.")
|
||||
raise EsphomeError(
|
||||
"Found no valid options for upload/logging, please make sure relevant "
|
||||
"sections (ota, api, mqtt, ...) are in your configuration and/or the "
|
||||
"device is plugged in."
|
||||
)
|
||||
|
||||
if len(options) == 1:
|
||||
return options[0][1]
|
||||
@@ -32,7 +48,7 @@ def choose_prompt(options):
|
||||
safe_print(f" [{i+1}] {desc}")
|
||||
|
||||
while True:
|
||||
opt = input('(number): ')
|
||||
opt = input("(number): ")
|
||||
if opt in options:
|
||||
opt = options.index(opt)
|
||||
break
|
||||
@@ -42,7 +58,7 @@ def choose_prompt(options):
|
||||
raise ValueError
|
||||
break
|
||||
except ValueError:
|
||||
safe_print(color('red', f"Invalid option: '{opt}'"))
|
||||
safe_print(color(Fore.RED, f"Invalid option: '{opt}'"))
|
||||
return options[opt - 1][1]
|
||||
|
||||
|
||||
@@ -50,14 +66,14 @@ def choose_upload_log_host(default, check_default, show_ota, show_mqtt, show_api
|
||||
options = []
|
||||
for port in get_serial_ports():
|
||||
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))
|
||||
if default == 'OTA':
|
||||
if default == "OTA":
|
||||
return CORE.address
|
||||
if show_mqtt and 'mqtt' in CORE.config:
|
||||
options.append(("MQTT ({})".format(CORE.config['mqtt'][CONF_BROKER]), 'MQTT'))
|
||||
if default == 'OTA':
|
||||
return 'MQTT'
|
||||
if show_mqtt and "mqtt" in CORE.config:
|
||||
options.append(("MQTT ({})".format(CORE.config["mqtt"][CONF_BROKER]), "MQTT"))
|
||||
if default == "OTA":
|
||||
return "MQTT"
|
||||
if default is not None:
|
||||
return default
|
||||
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):
|
||||
if port.startswith('/') or port.startswith('COM'):
|
||||
return 'SERIAL'
|
||||
if port == 'MQTT':
|
||||
return 'MQTT'
|
||||
return 'NETWORK'
|
||||
if port.startswith("/") or port.startswith("COM"):
|
||||
return "SERIAL"
|
||||
if port == "MQTT":
|
||||
return "MQTT"
|
||||
return "NETWORK"
|
||||
|
||||
|
||||
def run_miniterm(config, port):
|
||||
@@ -80,7 +96,7 @@ def run_miniterm(config, port):
|
||||
if CONF_LOGGER not in config:
|
||||
_LOGGER.info("Logger is not enabled. Not starting UART logs.")
|
||||
return
|
||||
baud_rate = config['logger'][CONF_BAUD_RATE]
|
||||
baud_rate = config["logger"][CONF_BAUD_RATE]
|
||||
if baud_rate == 0:
|
||||
_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)
|
||||
@@ -93,13 +109,18 @@ def run_miniterm(config, port):
|
||||
except serial.SerialException:
|
||||
_LOGGER.error("Serial port closed!")
|
||||
return
|
||||
line = raw.replace(b'\r', b'').replace(b'\n', b'').decode('utf8', 'backslashreplace')
|
||||
time = datetime.now().time().strftime('[%H:%M:%S]')
|
||||
line = (
|
||||
raw.replace(b"\r", b"")
|
||||
.replace(b"\n", b"")
|
||||
.decode("utf8", "backslashreplace")
|
||||
)
|
||||
time = datetime.now().time().strftime("[%H:%M:%S]")
|
||||
message = time + line
|
||||
safe_print(message)
|
||||
|
||||
backtrace_state = platformio_api.process_stacktrace(
|
||||
config, line, backtrace_state=backtrace_state)
|
||||
config, line, backtrace_state=backtrace_state
|
||||
)
|
||||
|
||||
|
||||
def wrap_to_code(name, comp):
|
||||
@@ -111,7 +132,7 @@ def wrap_to_code(name, comp):
|
||||
cg.add(cg.LineComment(f"{name}:"))
|
||||
if comp.config_schema is not None:
|
||||
conf_str = yaml_util.dump(conf)
|
||||
conf_str = conf_str.replace('//', '')
|
||||
conf_str = conf_str.replace("//", "")
|
||||
cg.add(cg.LineComment(indent(conf_str)))
|
||||
yield coro(conf)
|
||||
|
||||
@@ -151,15 +172,31 @@ def compile_program(args, config):
|
||||
|
||||
def upload_using_esptool(config, port):
|
||||
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):
|
||||
cmd = ['esptool.py', '--before', 'default_reset', '--after', 'hard_reset',
|
||||
'--baud', str(baud_rate),
|
||||
'--chip', 'esp8266', '--port', port, 'write_flash', '0x0', path]
|
||||
cmd = [
|
||||
"esptool.py",
|
||||
"--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
|
||||
|
||||
# pylint: disable=protected-access
|
||||
return run_external_command(esptool._main, *cmd)
|
||||
|
||||
@@ -169,14 +206,16 @@ def upload_using_esptool(config, port):
|
||||
if rc == 0 or first_baudrate == 115200:
|
||||
return rc
|
||||
# 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.",
|
||||
first_baudrate)
|
||||
_LOGGER.info(
|
||||
"Upload with baud rate %s failed. Trying again with baud rate 115200.",
|
||||
first_baudrate,
|
||||
)
|
||||
return run_esptool(115200)
|
||||
|
||||
|
||||
def upload_program(config, args, host):
|
||||
# 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
|
||||
|
||||
if CORE.is_esp8266:
|
||||
@@ -186,8 +225,10 @@ def upload_program(config, args, host):
|
||||
from esphome import espota2
|
||||
|
||||
if CONF_OTA not in config:
|
||||
raise EsphomeError("Cannot upload Over the Air as the config does not include the ota: "
|
||||
"component")
|
||||
raise EsphomeError(
|
||||
"Cannot upload Over the Air as the config does not include the ota: "
|
||||
"component"
|
||||
)
|
||||
|
||||
ota_conf = config[CONF_OTA]
|
||||
remote_port = ota_conf[CONF_PORT]
|
||||
@@ -196,19 +237,21 @@ def upload_program(config, args, host):
|
||||
|
||||
|
||||
def show_logs(config, args, port):
|
||||
if 'logger' not in config:
|
||||
if "logger" not in config:
|
||||
raise EsphomeError("Logger is not configured!")
|
||||
if get_port_type(port) == 'SERIAL':
|
||||
if get_port_type(port) == "SERIAL":
|
||||
run_miniterm(config, port)
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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)")
|
||||
|
||||
@@ -216,43 +259,9 @@ def show_logs(config, args, port):
|
||||
def clean_mqtt(config, args):
|
||||
from esphome import mqtt
|
||||
|
||||
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
|
||||
return mqtt.clear_topic(
|
||||
config, args.topic, args.username, args.password, args.client_id
|
||||
)
|
||||
|
||||
|
||||
def command_wizard(args):
|
||||
@@ -272,6 +281,8 @@ def command_config(args, config):
|
||||
def command_vscode(args):
|
||||
from esphome import vscode
|
||||
|
||||
logging.disable(logging.INFO)
|
||||
logging.disable(logging.WARNING)
|
||||
CORE.config_path = args.configuration[0]
|
||||
vscode.read_config(args)
|
||||
|
||||
@@ -291,8 +302,13 @@ def command_compile(args, config):
|
||||
|
||||
|
||||
def command_upload(args, config):
|
||||
port = choose_upload_log_host(default=args.upload_port, check_default=None,
|
||||
show_ota=True, show_mqtt=False, show_api=False)
|
||||
port = choose_upload_log_host(
|
||||
default=args.upload_port,
|
||||
check_default=None,
|
||||
show_ota=True,
|
||||
show_mqtt=False,
|
||||
show_api=False,
|
||||
)
|
||||
exit_code = upload_program(config, args, port)
|
||||
if exit_code != 0:
|
||||
return exit_code
|
||||
@@ -301,8 +317,13 @@ def command_upload(args, config):
|
||||
|
||||
|
||||
def command_logs(args, config):
|
||||
port = choose_upload_log_host(default=args.serial_port, check_default=None,
|
||||
show_ota=False, show_mqtt=True, show_api=True)
|
||||
port = choose_upload_log_host(
|
||||
default=args.serial_port,
|
||||
check_default=None,
|
||||
show_ota=False,
|
||||
show_mqtt=True,
|
||||
show_api=True,
|
||||
)
|
||||
return show_logs(config, args, port)
|
||||
|
||||
|
||||
@@ -314,16 +335,26 @@ def command_run(args, config):
|
||||
if exit_code != 0:
|
||||
return exit_code
|
||||
_LOGGER.info("Successfully compiled program.")
|
||||
port = choose_upload_log_host(default=args.upload_port, check_default=None,
|
||||
show_ota=True, show_mqtt=False, show_api=True)
|
||||
port = choose_upload_log_host(
|
||||
default=args.upload_port,
|
||||
check_default=None,
|
||||
show_ota=True,
|
||||
show_mqtt=False,
|
||||
show_api=True,
|
||||
)
|
||||
exit_code = upload_program(config, args, port)
|
||||
if exit_code != 0:
|
||||
return exit_code
|
||||
_LOGGER.info("Successfully uploaded program.")
|
||||
if args.no_logs:
|
||||
return 0
|
||||
port = choose_upload_log_host(default=args.upload_port, check_default=port,
|
||||
show_ota=False, show_mqtt=True, show_api=True)
|
||||
port = choose_upload_log_host(
|
||||
default=args.upload_port,
|
||||
check_default=port,
|
||||
show_ota=False,
|
||||
show_mqtt=True,
|
||||
show_api=True,
|
||||
)
|
||||
return show_logs(config, args, port)
|
||||
|
||||
|
||||
@@ -372,137 +403,189 @@ def command_update_all(args):
|
||||
click.echo(f"{half_line}{middle_text}{half_line}")
|
||||
|
||||
for f in files:
|
||||
print("Updating {}".format(color('cyan', f)))
|
||||
print('-' * twidth)
|
||||
print("Updating {}".format(color(Fore.CYAN, f)))
|
||||
print("-" * twidth)
|
||||
print()
|
||||
rc = run_external_process('esphome', '--dashboard', f, 'run', '--no-logs', '--upload-port',
|
||||
'OTA')
|
||||
rc = run_external_process(
|
||||
"esphome", "--dashboard", f, "run", "--no-logs", "--upload-port", "OTA"
|
||||
)
|
||||
if rc == 0:
|
||||
print_bar("[{}] {}".format(color('bold_green', 'SUCCESS'), f))
|
||||
print_bar("[{}] {}".format(color(Fore.BOLD_GREEN, "SUCCESS"), f))
|
||||
success[f] = True
|
||||
else:
|
||||
print_bar("[{}] {}".format(color('bold_red', 'ERROR'), f))
|
||||
print_bar("[{}] {}".format(color(Fore.BOLD_RED, "ERROR"), f))
|
||||
success[f] = False
|
||||
|
||||
print()
|
||||
print()
|
||||
print()
|
||||
|
||||
print_bar('[{}]'.format(color('bold_white', 'SUMMARY')))
|
||||
print_bar("[{}]".format(color(Fore.BOLD_WHITE, "SUMMARY")))
|
||||
failed = 0
|
||||
for f in files:
|
||||
if success[f]:
|
||||
print(" - {}: {}".format(f, color('green', 'SUCCESS')))
|
||||
print(" - {}: {}".format(f, color(Fore.GREEN, "SUCCESS")))
|
||||
else:
|
||||
print(" - {}: {}".format(f, color('bold_red', 'FAILED')))
|
||||
print(" - {}: {}".format(f, color(Fore.BOLD_RED, "FAILED")))
|
||||
failed += 1
|
||||
return failed
|
||||
|
||||
|
||||
PRE_CONFIG_ACTIONS = {
|
||||
'wizard': command_wizard,
|
||||
'version': command_version,
|
||||
'dashboard': command_dashboard,
|
||||
'vscode': command_vscode,
|
||||
'update-all': command_update_all,
|
||||
"wizard": command_wizard,
|
||||
"version": command_version,
|
||||
"dashboard": command_dashboard,
|
||||
"vscode": command_vscode,
|
||||
"update-all": command_update_all,
|
||||
}
|
||||
|
||||
POST_CONFIG_ACTIONS = {
|
||||
'config': command_config,
|
||||
'compile': command_compile,
|
||||
'upload': command_upload,
|
||||
'logs': command_logs,
|
||||
'run': command_run,
|
||||
'clean-mqtt': command_clean_mqtt,
|
||||
'mqtt-fingerprint': command_mqtt_fingerprint,
|
||||
'clean': command_clean,
|
||||
"config": command_config,
|
||||
"compile": command_compile,
|
||||
"upload": command_upload,
|
||||
"logs": command_logs,
|
||||
"run": command_run,
|
||||
"clean-mqtt": command_clean_mqtt,
|
||||
"mqtt-fingerprint": command_mqtt_fingerprint,
|
||||
"clean": command_clean,
|
||||
}
|
||||
|
||||
|
||||
def parse_args(argv):
|
||||
parser = argparse.ArgumentParser(description=f'ESPHome v{const.__version__}')
|
||||
parser.add_argument('-v', '--verbose', help="Enable verbose esphome logs.",
|
||||
action='store_true')
|
||||
parser.add_argument('-q', '--quiet', help="Disable all esphome logs.",
|
||||
action='store_true')
|
||||
parser.add_argument('--dashboard', help=argparse.SUPPRESS, action='store_true')
|
||||
parser.add_argument('-s', '--substitution', nargs=2, action='append',
|
||||
help='Add a substitution', metavar=('key', 'value'))
|
||||
parser.add_argument('configuration', help='Your YAML configuration file.', nargs='*')
|
||||
parser = argparse.ArgumentParser(description=f"ESPHome v{const.__version__}")
|
||||
parser.add_argument(
|
||||
"-v", "--verbose", help="Enable verbose esphome logs.", action="store_true"
|
||||
)
|
||||
parser.add_argument(
|
||||
"-q", "--quiet", help="Disable all esphome logs.", action="store_true"
|
||||
)
|
||||
parser.add_argument("--dashboard", help=argparse.SUPPRESS, action="store_true")
|
||||
parser.add_argument(
|
||||
"-s",
|
||||
"--substitution",
|
||||
nargs=2,
|
||||
action="append",
|
||||
help="Add a substitution",
|
||||
metavar=("key", "value"),
|
||||
)
|
||||
parser.add_argument(
|
||||
"configuration", help="Your YAML configuration file.", nargs="*"
|
||||
)
|
||||
|
||||
subparsers = parser.add_subparsers(help='Commands', dest='command')
|
||||
subparsers = parser.add_subparsers(help="Commands", dest="command")
|
||||
subparsers.required = True
|
||||
subparsers.add_parser('config', help='Validate the configuration and spit it out.')
|
||||
subparsers.add_parser("config", help="Validate the configuration and spit it out.")
|
||||
|
||||
parser_compile = subparsers.add_parser('compile',
|
||||
help='Read the configuration and compile a program.')
|
||||
parser_compile.add_argument('--only-generate',
|
||||
help="Only generate source code, do not compile.",
|
||||
action='store_true')
|
||||
parser_compile = subparsers.add_parser(
|
||||
"compile", help="Read the configuration and compile a program."
|
||||
)
|
||||
parser_compile.add_argument(
|
||||
"--only-generate",
|
||||
help="Only generate source code, do not compile.",
|
||||
action="store_true",
|
||||
)
|
||||
|
||||
parser_upload = subparsers.add_parser('upload', help='Validate the configuration '
|
||||
'and upload the latest binary.')
|
||||
parser_upload.add_argument('--upload-port', help="Manually specify the upload port to use. "
|
||||
"For example /dev/cu.SLAB_USBtoUART.")
|
||||
parser_upload = subparsers.add_parser(
|
||||
"upload", help="Validate the configuration " "and upload the latest binary."
|
||||
)
|
||||
parser_upload.add_argument(
|
||||
"--upload-port",
|
||||
help="Manually specify the upload port to use. "
|
||||
"For example /dev/cu.SLAB_USBtoUART.",
|
||||
)
|
||||
|
||||
parser_logs = subparsers.add_parser('logs', help='Validate the configuration '
|
||||
'and show all MQTT logs.')
|
||||
parser_logs.add_argument('--topic', help='Manually set the topic to subscribe to.')
|
||||
parser_logs.add_argument('--username', help='Manually set the username.')
|
||||
parser_logs.add_argument('--password', help='Manually set the password.')
|
||||
parser_logs.add_argument('--client-id', help='Manually set the client id.')
|
||||
parser_logs.add_argument('--serial-port', help="Manually specify a serial port to use"
|
||||
"For example /dev/cu.SLAB_USBtoUART.")
|
||||
parser_logs = subparsers.add_parser(
|
||||
"logs", help="Validate the configuration " "and show all MQTT logs."
|
||||
)
|
||||
parser_logs.add_argument("--topic", help="Manually set the topic to subscribe to.")
|
||||
parser_logs.add_argument("--username", help="Manually set the username.")
|
||||
parser_logs.add_argument("--password", help="Manually set the password.")
|
||||
parser_logs.add_argument("--client-id", help="Manually set the client id.")
|
||||
parser_logs.add_argument(
|
||||
"--serial-port",
|
||||
help="Manually specify a serial port to use"
|
||||
"For example /dev/cu.SLAB_USBtoUART.",
|
||||
)
|
||||
|
||||
parser_run = subparsers.add_parser('run', help='Validate the configuration, create a binary, '
|
||||
'upload it, and start MQTT logs.')
|
||||
parser_run.add_argument('--upload-port', help="Manually specify the upload port/ip to use. "
|
||||
"For example /dev/cu.SLAB_USBtoUART.")
|
||||
parser_run.add_argument('--no-logs', help='Disable starting MQTT logs.',
|
||||
action='store_true')
|
||||
parser_run.add_argument('--topic', help='Manually set the topic to subscribe to for logs.')
|
||||
parser_run.add_argument('--username', help='Manually set the MQTT username for logs.')
|
||||
parser_run.add_argument('--password', help='Manually set the MQTT password for logs.')
|
||||
parser_run.add_argument('--client-id', help='Manually set the client id for logs.')
|
||||
parser_run = subparsers.add_parser(
|
||||
"run",
|
||||
help="Validate the configuration, create a binary, "
|
||||
"upload it, and start MQTT logs.",
|
||||
)
|
||||
parser_run.add_argument(
|
||||
"--upload-port",
|
||||
help="Manually specify the upload port/ip to use. "
|
||||
"For example /dev/cu.SLAB_USBtoUART.",
|
||||
)
|
||||
parser_run.add_argument(
|
||||
"--no-logs", help="Disable starting MQTT logs.", action="store_true"
|
||||
)
|
||||
parser_run.add_argument(
|
||||
"--topic", help="Manually set the topic to subscribe to for logs."
|
||||
)
|
||||
parser_run.add_argument(
|
||||
"--username", help="Manually set the MQTT username for logs."
|
||||
)
|
||||
parser_run.add_argument(
|
||||
"--password", help="Manually set the MQTT password for logs."
|
||||
)
|
||||
parser_run.add_argument("--client-id", help="Manually set the client id for logs.")
|
||||
|
||||
parser_clean = subparsers.add_parser('clean-mqtt', help="Helper to clear an MQTT topic from "
|
||||
"retain messages.")
|
||||
parser_clean.add_argument('--topic', help='Manually set the topic to subscribe to.')
|
||||
parser_clean.add_argument('--username', help='Manually set the username.')
|
||||
parser_clean.add_argument('--password', help='Manually set the password.')
|
||||
parser_clean.add_argument('--client-id', help='Manually set the client id.')
|
||||
parser_clean = subparsers.add_parser(
|
||||
"clean-mqtt", help="Helper to clear an MQTT topic from " "retain messages."
|
||||
)
|
||||
parser_clean.add_argument("--topic", help="Manually set the topic to subscribe to.")
|
||||
parser_clean.add_argument("--username", help="Manually set the username.")
|
||||
parser_clean.add_argument("--password", help="Manually set the password.")
|
||||
parser_clean.add_argument("--client-id", help="Manually set the client id.")
|
||||
|
||||
subparsers.add_parser('wizard', help="A helpful setup wizard that will guide "
|
||||
"you through setting up esphome.")
|
||||
subparsers.add_parser(
|
||||
"wizard",
|
||||
help="A helpful setup wizard that will guide "
|
||||
"you through setting up esphome.",
|
||||
)
|
||||
|
||||
subparsers.add_parser('mqtt-fingerprint', help="Get the SSL fingerprint from a MQTT broker.")
|
||||
subparsers.add_parser(
|
||||
"mqtt-fingerprint", help="Get the SSL fingerprint from a MQTT broker."
|
||||
)
|
||||
|
||||
subparsers.add_parser('version', help="Print the esphome version and exit.")
|
||||
subparsers.add_parser("version", help="Print the esphome version and exit.")
|
||||
|
||||
subparsers.add_parser('clean', help="Delete all temporary build files.")
|
||||
subparsers.add_parser("clean", help="Delete all temporary build files.")
|
||||
|
||||
dashboard = subparsers.add_parser('dashboard',
|
||||
help="Create a simple web server for a dashboard.")
|
||||
dashboard.add_argument("--port", help="The HTTP port to open connections on. Defaults to 6052.",
|
||||
type=int, default=6052)
|
||||
dashboard.add_argument("--username", help="The optional username to require "
|
||||
"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)
|
||||
dashboard = subparsers.add_parser(
|
||||
"dashboard", help="Create a simple web server for a dashboard."
|
||||
)
|
||||
dashboard.add_argument(
|
||||
"--port",
|
||||
help="The HTTP port to open connections on. Defaults to 6052.",
|
||||
type=int,
|
||||
default=6052,
|
||||
)
|
||||
dashboard.add_argument(
|
||||
"--username",
|
||||
help="The optional username to require " "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)
|
||||
vscode.add_argument('--ace', action='store_true')
|
||||
vscode = subparsers.add_parser("vscode", help=argparse.SUPPRESS)
|
||||
vscode.add_argument("--ace", action="store_true")
|
||||
|
||||
subparsers.add_parser('update-all', help=argparse.SUPPRESS)
|
||||
subparsers.add_parser("update-all", help=argparse.SUPPRESS)
|
||||
|
||||
return parser.parse_args(argv[1:])
|
||||
|
||||
@@ -512,13 +595,15 @@ def run_esphome(argv):
|
||||
CORE.dashboard = args.dashboard
|
||||
|
||||
setup_log(args.verbose, args.quiet)
|
||||
if args.command != 'version' and not args.configuration:
|
||||
if args.command != "version" and not args.configuration:
|
||||
_LOGGER.error("Missing configuration parameter, see esphome --help.")
|
||||
return 1
|
||||
|
||||
if sys.version_info < (3, 6, 0):
|
||||
_LOGGER.error("You're running ESPHome with Python <3.6. ESPHome is no longer compatible "
|
||||
"with this Python version. Please reinstall ESPHome with Python 3.6+")
|
||||
if sys.version_info < (3, 7, 0):
|
||||
_LOGGER.error(
|
||||
"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
|
||||
|
||||
if args.command in PRE_CONFIG_ACTIONS:
|
||||
|
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
|
||||
from esphome.const import CONF_PASSWORD, CONF_PORT
|
||||
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
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
@@ -177,10 +178,14 @@ class APIClient(threading.Thread):
|
||||
try:
|
||||
ip = resolve_ip_address(self._address)
|
||||
except EsphomeError as err:
|
||||
_LOGGER.warning("Error resolving IP address of %s. Is it connected to WiFi?",
|
||||
self._address)
|
||||
_LOGGER.warning("(If this error persists, please set a static IP address: "
|
||||
"https://esphome.io/components/wifi.html#manual-ips)")
|
||||
_LOGGER.warning(
|
||||
"Error resolving IP address of %s. Is it connected to WiFi?",
|
||||
self._address,
|
||||
)
|
||||
_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
|
||||
|
||||
_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()
|
||||
|
||||
hello = pb.HelloRequest()
|
||||
hello.client_info = f'ESPHome v{const.__version__}'
|
||||
hello.client_info = f"ESPHome v{const.__version__}"
|
||||
try:
|
||||
resp = self._send_message_await_response(hello, pb.HelloResponse)
|
||||
except APIConnectionError as err:
|
||||
self._fatal_error(err)
|
||||
raise err
|
||||
_LOGGER.debug("Successfully connected to %s ('%s' API=%s.%s)", self._address,
|
||||
resp.server_info, resp.api_version_major, resp.api_version_minor)
|
||||
_LOGGER.debug(
|
||||
"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._refresh_ping()
|
||||
if self.on_connect is not None:
|
||||
@@ -270,7 +280,9 @@ class APIClient(threading.Thread):
|
||||
req += encoded
|
||||
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()
|
||||
responses = []
|
||||
|
||||
@@ -295,12 +307,15 @@ class APIClient(threading.Thread):
|
||||
def is_response(msg):
|
||||
return isinstance(msg, response_type)
|
||||
|
||||
return self._send_message_await_response_complex(send_msg, is_response, is_response,
|
||||
timeout)[0]
|
||||
return self._send_message_await_response_complex(
|
||||
send_msg, is_response, is_response, timeout
|
||||
)[0]
|
||||
|
||||
def device_info(self):
|
||||
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):
|
||||
self._check_connected()
|
||||
@@ -310,7 +325,9 @@ class APIClient(threading.Thread):
|
||||
self._check_connected()
|
||||
|
||||
try:
|
||||
self._send_message_await_response(pb.DisconnectRequest(), pb.DisconnectResponse)
|
||||
self._send_message_await_response(
|
||||
pb.DisconnectRequest(), pb.DisconnectResponse
|
||||
)
|
||||
except APIConnectionError:
|
||||
pass
|
||||
self._close_socket()
|
||||
@@ -415,7 +432,7 @@ class APIClient(threading.Thread):
|
||||
|
||||
|
||||
def run_logs(config, address):
|
||||
conf = config['api']
|
||||
conf = config["api"]
|
||||
port = conf[CONF_PORT]
|
||||
password = conf[CONF_PASSWORD]
|
||||
_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)
|
||||
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:
|
||||
_LOGGER.warning("Initial connection failed. The ESP might not be connected "
|
||||
"to WiFi yet (%s). Re-Trying in %s seconds",
|
||||
error, wait_time)
|
||||
_LOGGER.warning(
|
||||
"Initial connection failed. The ESP might not be connected "
|
||||
"to WiFi yet (%s). Re-Trying in %s seconds",
|
||||
error,
|
||||
wait_time,
|
||||
)
|
||||
else:
|
||||
_LOGGER.warning("Couldn't connect to API (%s). Trying to reconnect in %s seconds",
|
||||
error, wait_time)
|
||||
timer = threading.Timer(wait_time, functools.partial(try_connect, None, tries + 1))
|
||||
_LOGGER.warning(
|
||||
"Couldn't connect to API (%s). Trying to reconnect in %s seconds",
|
||||
error,
|
||||
wait_time,
|
||||
)
|
||||
timer = threading.Timer(
|
||||
wait_time, functools.partial(try_connect, None, tries + 1)
|
||||
)
|
||||
timer.start()
|
||||
retry_timer.append(timer)
|
||||
|
||||
def on_log(msg):
|
||||
time_ = datetime.now().time().strftime('[%H:%M:%S]')
|
||||
time_ = datetime.now().time().strftime("[%H:%M:%S]")
|
||||
text = msg.message
|
||||
if msg.send_failed:
|
||||
text = color('white', '(Message skipped because it was too big to fit in '
|
||||
'TCP buffer - This is only cosmetic)')
|
||||
text = color(
|
||||
Fore.WHITE,
|
||||
"(Message skipped because it was too big to fit in "
|
||||
"TCP buffer - This is only cosmetic)",
|
||||
)
|
||||
safe_print(time_ + text)
|
||||
|
||||
def on_login():
|
||||
|
@@ -1,8 +1,17 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_AUTOMATION_ID, CONF_CONDITION, CONF_ELSE, CONF_ID, CONF_THEN, \
|
||||
CONF_TRIGGER_ID, CONF_TYPE_ID, CONF_TIME
|
||||
from esphome.const import (
|
||||
CONF_AUTOMATION_ID,
|
||||
CONF_CONDITION,
|
||||
CONF_ELSE,
|
||||
CONF_ID,
|
||||
CONF_THEN,
|
||||
CONF_TRIGGER_ID,
|
||||
CONF_TYPE_ID,
|
||||
CONF_TIME,
|
||||
)
|
||||
from esphome.core import coroutine
|
||||
from esphome.jsonschema import jschema_extractor
|
||||
from esphome.util import Registry
|
||||
|
||||
|
||||
@@ -13,7 +22,12 @@ def maybe_simple_id(*validators):
|
||||
def maybe_conf(conf, *validators):
|
||||
validator = cv.All(*validators)
|
||||
|
||||
@jschema_extractor("maybe")
|
||||
def validate(value):
|
||||
# pylint: disable=comparison-with-callable
|
||||
if value == jschema_extractor:
|
||||
return validator
|
||||
|
||||
if isinstance(value, dict):
|
||||
return validator(value)
|
||||
with cv.remove_prepend_path([conf]):
|
||||
@@ -30,36 +44,34 @@ def register_condition(name, condition_type, schema):
|
||||
return CONDITION_REGISTRY.register(name, condition_type, schema)
|
||||
|
||||
|
||||
Action = cg.esphome_ns.class_('Action')
|
||||
Trigger = cg.esphome_ns.class_('Trigger')
|
||||
Action = cg.esphome_ns.class_("Action")
|
||||
Trigger = cg.esphome_ns.class_("Trigger")
|
||||
ACTION_REGISTRY = Registry()
|
||||
Condition = cg.esphome_ns.class_('Condition')
|
||||
Condition = cg.esphome_ns.class_("Condition")
|
||||
CONDITION_REGISTRY = Registry()
|
||||
validate_action = cv.validate_registry_entry('action', ACTION_REGISTRY)
|
||||
validate_action_list = cv.validate_registry('action', ACTION_REGISTRY)
|
||||
validate_condition = cv.validate_registry_entry('condition', CONDITION_REGISTRY)
|
||||
validate_condition_list = cv.validate_registry('condition', CONDITION_REGISTRY)
|
||||
validate_action = cv.validate_registry_entry("action", ACTION_REGISTRY)
|
||||
validate_action_list = cv.validate_registry("action", ACTION_REGISTRY)
|
||||
validate_condition = cv.validate_registry_entry("condition", CONDITION_REGISTRY)
|
||||
validate_condition_list = cv.validate_registry("condition", CONDITION_REGISTRY)
|
||||
|
||||
|
||||
def validate_potentially_and_condition(value):
|
||||
if isinstance(value, list):
|
||||
with cv.remove_prepend_path(['and']):
|
||||
return validate_condition({
|
||||
'and': value
|
||||
})
|
||||
with cv.remove_prepend_path(["and"]):
|
||||
return validate_condition({"and": value})
|
||||
return validate_condition(value)
|
||||
|
||||
|
||||
DelayAction = cg.esphome_ns.class_('DelayAction', Action, cg.Component)
|
||||
LambdaAction = cg.esphome_ns.class_('LambdaAction', Action)
|
||||
IfAction = cg.esphome_ns.class_('IfAction', Action)
|
||||
WhileAction = cg.esphome_ns.class_('WhileAction', Action)
|
||||
WaitUntilAction = cg.esphome_ns.class_('WaitUntilAction', Action, cg.Component)
|
||||
UpdateComponentAction = cg.esphome_ns.class_('UpdateComponentAction', Action)
|
||||
Automation = cg.esphome_ns.class_('Automation')
|
||||
DelayAction = cg.esphome_ns.class_("DelayAction", Action, cg.Component)
|
||||
LambdaAction = cg.esphome_ns.class_("LambdaAction", Action)
|
||||
IfAction = cg.esphome_ns.class_("IfAction", Action)
|
||||
WhileAction = cg.esphome_ns.class_("WhileAction", Action)
|
||||
WaitUntilAction = cg.esphome_ns.class_("WaitUntilAction", Action, cg.Component)
|
||||
UpdateComponentAction = cg.esphome_ns.class_("UpdateComponentAction", Action)
|
||||
Automation = cg.esphome_ns.class_("Automation")
|
||||
|
||||
LambdaCondition = cg.esphome_ns.class_('LambdaCondition', Condition)
|
||||
ForCondition = cg.esphome_ns.class_('ForCondition', Condition, cg.Component)
|
||||
LambdaCondition = cg.esphome_ns.class_("LambdaCondition", Condition)
|
||||
ForCondition = cg.esphome_ns.class_("ForCondition", Condition, cg.Component)
|
||||
|
||||
|
||||
def validate_automation(extra_schema=None, extra_validators=None, single=False):
|
||||
@@ -83,10 +95,10 @@ def validate_automation(extra_schema=None, extra_validators=None, single=False):
|
||||
try:
|
||||
return cv.Schema([schema])(value)
|
||||
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
|
||||
raise err
|
||||
if 'Unable to find action' in str(err):
|
||||
if "Unable to find action" in str(err):
|
||||
raise err2
|
||||
raise cv.MultipleInvalid([err, err2])
|
||||
elif isinstance(value, dict):
|
||||
@@ -97,7 +109,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.
|
||||
return [schema(value)]
|
||||
|
||||
@jschema_extractor("automation")
|
||||
def validator(value):
|
||||
# hack to get the schema
|
||||
# pylint: disable=comparison-with-callable
|
||||
if value == jschema_extractor:
|
||||
return schema
|
||||
|
||||
value = validator_(value)
|
||||
if extra_validators is not None:
|
||||
value = cv.Schema([extra_validators])(value)
|
||||
@@ -110,47 +128,59 @@ def validate_automation(extra_schema=None, extra_validators=None, single=False):
|
||||
return validator
|
||||
|
||||
|
||||
AUTOMATION_SCHEMA = cv.Schema({
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(Trigger),
|
||||
cv.GenerateID(CONF_AUTOMATION_ID): cv.declare_id(Automation),
|
||||
cv.Required(CONF_THEN): validate_action_list,
|
||||
})
|
||||
AUTOMATION_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(Trigger),
|
||||
cv.GenerateID(CONF_AUTOMATION_ID): cv.declare_id(Automation),
|
||||
cv.Required(CONF_THEN): validate_action_list,
|
||||
}
|
||||
)
|
||||
|
||||
AndCondition = cg.esphome_ns.class_('AndCondition', Condition)
|
||||
OrCondition = cg.esphome_ns.class_('OrCondition', Condition)
|
||||
NotCondition = cg.esphome_ns.class_('NotCondition', Condition)
|
||||
AndCondition = cg.esphome_ns.class_("AndCondition", Condition)
|
||||
OrCondition = cg.esphome_ns.class_("OrCondition", 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):
|
||||
conditions = yield build_condition_list(config, template_arg, args)
|
||||
yield 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):
|
||||
conditions = yield build_condition_list(config, template_arg, args)
|
||||
yield 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):
|
||||
condition = yield build_condition(config, template_arg, args)
|
||||
yield cg.new_Pvariable(condition_id, template_arg, condition)
|
||||
|
||||
|
||||
@register_condition('lambda', LambdaCondition, cv.lambda_)
|
||||
@register_condition("lambda", LambdaCondition, cv.lambda_)
|
||||
def lambda_condition_to_code(config, condition_id, template_arg, args):
|
||||
lambda_ = yield cg.process_lambda(config, args, return_type=bool)
|
||||
yield cg.new_Pvariable(condition_id, template_arg, lambda_)
|
||||
|
||||
|
||||
@register_condition('for', ForCondition, cv.Schema({
|
||||
cv.Required(CONF_TIME): cv.templatable(cv.positive_time_period_milliseconds),
|
||||
cv.Required(CONF_CONDITION): validate_potentially_and_condition,
|
||||
}).extend(cv.COMPONENT_SCHEMA))
|
||||
@register_condition(
|
||||
"for",
|
||||
ForCondition,
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_TIME): cv.templatable(
|
||||
cv.positive_time_period_milliseconds
|
||||
),
|
||||
cv.Required(CONF_CONDITION): validate_potentially_and_condition,
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA),
|
||||
)
|
||||
def for_condition_to_code(config, condition_id, template_arg, args):
|
||||
condition = yield build_condition(config[CONF_CONDITION], cg.TemplateArguments(), [])
|
||||
condition = yield build_condition(
|
||||
config[CONF_CONDITION], cg.TemplateArguments(), []
|
||||
)
|
||||
var = cg.new_Pvariable(condition_id, template_arg, condition)
|
||||
yield cg.register_component(var, config)
|
||||
templ = yield cg.templatable(config[CONF_TIME], args, cg.uint32)
|
||||
@@ -158,7 +188,9 @@ def for_condition_to_code(config, condition_id, template_arg, args):
|
||||
yield var
|
||||
|
||||
|
||||
@register_action('delay', DelayAction, cv.templatable(cv.positive_time_period_milliseconds))
|
||||
@register_action(
|
||||
"delay", DelayAction, cv.templatable(cv.positive_time_period_milliseconds)
|
||||
)
|
||||
def delay_action_to_code(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
yield cg.register_component(var, {})
|
||||
@@ -167,11 +199,18 @@ def delay_action_to_code(config, action_id, template_arg, args):
|
||||
yield var
|
||||
|
||||
|
||||
@register_action('if', IfAction, cv.All({
|
||||
cv.Required(CONF_CONDITION): validate_potentially_and_condition,
|
||||
cv.Optional(CONF_THEN): validate_action_list,
|
||||
cv.Optional(CONF_ELSE): validate_action_list,
|
||||
}, cv.has_at_least_one_key(CONF_THEN, CONF_ELSE)))
|
||||
@register_action(
|
||||
"if",
|
||||
IfAction,
|
||||
cv.All(
|
||||
{
|
||||
cv.Required(CONF_CONDITION): validate_potentially_and_condition,
|
||||
cv.Optional(CONF_THEN): validate_action_list,
|
||||
cv.Optional(CONF_ELSE): validate_action_list,
|
||||
},
|
||||
cv.has_at_least_one_key(CONF_THEN, CONF_ELSE),
|
||||
),
|
||||
)
|
||||
def if_action_to_code(config, action_id, template_arg, args):
|
||||
conditions = yield build_condition(config[CONF_CONDITION], template_arg, args)
|
||||
var = cg.new_Pvariable(action_id, template_arg, conditions)
|
||||
@@ -184,10 +223,16 @@ def if_action_to_code(config, action_id, template_arg, args):
|
||||
yield var
|
||||
|
||||
|
||||
@register_action('while', WhileAction, cv.Schema({
|
||||
cv.Required(CONF_CONDITION): validate_potentially_and_condition,
|
||||
cv.Required(CONF_THEN): validate_action_list,
|
||||
}))
|
||||
@register_action(
|
||||
"while",
|
||||
WhileAction,
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_CONDITION): validate_potentially_and_condition,
|
||||
cv.Required(CONF_THEN): validate_action_list,
|
||||
}
|
||||
),
|
||||
)
|
||||
def while_action_to_code(config, action_id, template_arg, args):
|
||||
conditions = yield build_condition(config[CONF_CONDITION], template_arg, args)
|
||||
var = cg.new_Pvariable(action_id, template_arg, conditions)
|
||||
@@ -197,15 +242,17 @@ def while_action_to_code(config, action_id, template_arg, args):
|
||||
|
||||
|
||||
def validate_wait_until(value):
|
||||
schema = cv.Schema({
|
||||
cv.Required(CONF_CONDITION): validate_potentially_and_condition,
|
||||
})
|
||||
schema = cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_CONDITION): validate_potentially_and_condition,
|
||||
}
|
||||
)
|
||||
if isinstance(value, dict) and CONF_CONDITION in value:
|
||||
return schema(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):
|
||||
conditions = yield build_condition(config[CONF_CONDITION], template_arg, args)
|
||||
var = cg.new_Pvariable(action_id, template_arg, conditions)
|
||||
@@ -213,15 +260,21 @@ def wait_until_action_to_code(config, action_id, template_arg, args):
|
||||
yield var
|
||||
|
||||
|
||||
@register_action('lambda', LambdaAction, cv.lambda_)
|
||||
@register_action("lambda", LambdaAction, cv.lambda_)
|
||||
def lambda_action_to_code(config, action_id, template_arg, args):
|
||||
lambda_ = yield cg.process_lambda(config, args, return_type=cg.void)
|
||||
yield cg.new_Pvariable(action_id, template_arg, lambda_)
|
||||
|
||||
|
||||
@register_action('component.update', UpdateComponentAction, maybe_simple_id({
|
||||
cv.Required(CONF_ID): cv.use_id(cg.PollingComponent),
|
||||
}))
|
||||
@register_action(
|
||||
"component.update",
|
||||
UpdateComponentAction,
|
||||
maybe_simple_id(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.use_id(cg.PollingComponent),
|
||||
}
|
||||
),
|
||||
)
|
||||
def component_update_action_to_code(config, action_id, template_arg, args):
|
||||
comp = yield cg.get_variable(config[CONF_ID])
|
||||
yield cg.new_Pvariable(action_id, template_arg, comp)
|
||||
@@ -229,7 +282,9 @@ def component_update_action_to_code(config, action_id, template_arg, args):
|
||||
|
||||
@coroutine
|
||||
def build_action(full_config, template_arg, args):
|
||||
registry_entry, config = cg.extract_registry_entry_config(ACTION_REGISTRY, full_config)
|
||||
registry_entry, config = cg.extract_registry_entry_config(
|
||||
ACTION_REGISTRY, full_config
|
||||
)
|
||||
action_id = full_config[CONF_TYPE_ID]
|
||||
builder = registry_entry.coroutine_fun
|
||||
yield builder(config, action_id, template_arg, args)
|
||||
@@ -246,7 +301,9 @@ def build_action_list(config, templ, arg_type):
|
||||
|
||||
@coroutine
|
||||
def build_condition(full_config, template_arg, args):
|
||||
registry_entry, config = cg.extract_registry_entry_config(CONDITION_REGISTRY, full_config)
|
||||
registry_entry, config = cg.extract_registry_entry_config(
|
||||
CONDITION_REGISTRY, full_config
|
||||
)
|
||||
action_id = full_config[CONF_TYPE_ID]
|
||||
builder = registry_entry.coroutine_fun
|
||||
yield builder(config, action_id, template_arg, args)
|
||||
|
@@ -9,18 +9,71 @@
|
||||
|
||||
# pylint: disable=unused-import
|
||||
from esphome.cpp_generator import ( # noqa
|
||||
Expression, RawExpression, RawStatement, TemplateArguments,
|
||||
StructInitializer, ArrayInitializer, safe_exp, Statement, LineComment,
|
||||
progmem_array, statement, 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)
|
||||
Expression,
|
||||
RawExpression,
|
||||
RawStatement,
|
||||
TemplateArguments,
|
||||
StructInitializer,
|
||||
ArrayInitializer,
|
||||
safe_exp,
|
||||
Statement,
|
||||
LineComment,
|
||||
progmem_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
|
||||
gpio_pin_expression, register_component, build_registry_entry,
|
||||
build_registry_list, extract_registry_entry_config, register_parented)
|
||||
gpio_pin_expression,
|
||||
register_component,
|
||||
build_registry_entry,
|
||||
build_registry_list,
|
||||
extract_registry_entry_config,
|
||||
register_parented,
|
||||
)
|
||||
from esphome.cpp_types import ( # noqa
|
||||
global_ns, void, nullptr, float_, 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)
|
||||
global_ns,
|
||||
void,
|
||||
nullptr,
|
||||
float_,
|
||||
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) {
|
||||
this->sleep_pin_->setup();
|
||||
this->sleep_pin_->digital_write(false);
|
||||
this->sleep_pin_state_ = false;
|
||||
}
|
||||
this->step_pin_->setup();
|
||||
this->step_pin_->digital_write(false);
|
||||
@@ -27,7 +28,12 @@ void A4988::dump_config() {
|
||||
void A4988::loop() {
|
||||
bool at_target = this->has_reached_target();
|
||||
if (this->sleep_pin_ != nullptr) {
|
||||
bool sleep_rising_edge = !sleep_pin_state_ & !at_target;
|
||||
this->sleep_pin_->digital_write(!at_target);
|
||||
this->sleep_pin_state_ = !at_target;
|
||||
if (sleep_rising_edge) {
|
||||
delayMicroseconds(1000);
|
||||
}
|
||||
}
|
||||
if (at_target) {
|
||||
this->high_freq_.stop();
|
||||
|
@@ -21,6 +21,7 @@ class A4988 : public stepper::Stepper, public Component {
|
||||
GPIOPin *step_pin_;
|
||||
GPIOPin *dir_pin_;
|
||||
GPIOPin *sleep_pin_{nullptr};
|
||||
bool sleep_pin_state_;
|
||||
HighFrequencyLoopRequester high_freq_;
|
||||
};
|
||||
|
||||
|
@@ -5,15 +5,17 @@ import esphome.codegen as cg
|
||||
from esphome.const import CONF_DIR_PIN, CONF_ID, CONF_SLEEP_PIN, CONF_STEP_PIN
|
||||
|
||||
|
||||
a4988_ns = cg.esphome_ns.namespace('a4988')
|
||||
A4988 = a4988_ns.class_('A4988', stepper.Stepper, cg.Component)
|
||||
a4988_ns = cg.esphome_ns.namespace("a4988")
|
||||
A4988 = a4988_ns.class_("A4988", stepper.Stepper, cg.Component)
|
||||
|
||||
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_DIR_PIN): pins.gpio_output_pin_schema,
|
||||
cv.Optional(CONF_SLEEP_PIN): pins.gpio_output_pin_schema,
|
||||
}).extend(cv.COMPONENT_SCHEMA)
|
||||
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_DIR_PIN): pins.gpio_output_pin_schema,
|
||||
cv.Optional(CONF_SLEEP_PIN): pins.gpio_output_pin_schema,
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
|
@@ -4,28 +4,32 @@ from esphome import pins
|
||||
from esphome.components import output
|
||||
from esphome.const import CONF_ID, CONF_MIN_POWER, CONF_METHOD
|
||||
|
||||
CODEOWNERS = ['@glmnet']
|
||||
CODEOWNERS = ["@glmnet"]
|
||||
|
||||
ac_dimmer_ns = cg.esphome_ns.namespace('ac_dimmer')
|
||||
AcDimmer = ac_dimmer_ns.class_('AcDimmer', output.FloatOutput, cg.Component)
|
||||
ac_dimmer_ns = cg.esphome_ns.namespace("ac_dimmer")
|
||||
AcDimmer = ac_dimmer_ns.class_("AcDimmer", output.FloatOutput, cg.Component)
|
||||
|
||||
DimMethod = ac_dimmer_ns.enum('DimMethod')
|
||||
DimMethod = ac_dimmer_ns.enum("DimMethod")
|
||||
DIM_METHODS = {
|
||||
'LEADING_PULSE': DimMethod.DIM_METHOD_LEADING_PULSE,
|
||||
'LEADING': DimMethod.DIM_METHOD_LEADING,
|
||||
'TRAILING': DimMethod.DIM_METHOD_TRAILING,
|
||||
"LEADING_PULSE": DimMethod.DIM_METHOD_LEADING_PULSE,
|
||||
"LEADING": DimMethod.DIM_METHOD_LEADING,
|
||||
"TRAILING": DimMethod.DIM_METHOD_TRAILING,
|
||||
}
|
||||
|
||||
CONF_GATE_PIN = 'gate_pin'
|
||||
CONF_ZERO_CROSS_PIN = 'zero_cross_pin'
|
||||
CONF_INIT_WITH_HALF_CYCLE = 'init_with_half_cycle'
|
||||
CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend({
|
||||
cv.Required(CONF_ID): cv.declare_id(AcDimmer),
|
||||
cv.Required(CONF_GATE_PIN): pins.internal_gpio_output_pin_schema,
|
||||
cv.Required(CONF_ZERO_CROSS_PIN): pins.internal_gpio_input_pin_schema,
|
||||
cv.Optional(CONF_INIT_WITH_HALF_CYCLE, default=True): cv.boolean,
|
||||
cv.Optional(CONF_METHOD, default='leading pulse'): cv.enum(DIM_METHODS, upper=True, space='_'),
|
||||
}).extend(cv.COMPONENT_SCHEMA)
|
||||
CONF_GATE_PIN = "gate_pin"
|
||||
CONF_ZERO_CROSS_PIN = "zero_cross_pin"
|
||||
CONF_INIT_WITH_HALF_CYCLE = "init_with_half_cycle"
|
||||
CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.declare_id(AcDimmer),
|
||||
cv.Required(CONF_GATE_PIN): pins.internal_gpio_output_pin_schema,
|
||||
cv.Required(CONF_ZERO_CROSS_PIN): pins.internal_gpio_input_pin_schema,
|
||||
cv.Optional(CONF_INIT_WITH_HALF_CYCLE, default=True): cv.boolean,
|
||||
cv.Optional(CONF_METHOD, default="leading pulse"): cv.enum(
|
||||
DIM_METHODS, upper=True, space="_"
|
||||
),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
|
@@ -5,18 +5,22 @@ from esphome.components.light.types import AddressableLightEffect
|
||||
from esphome.components.light.effects import register_addressable_effect
|
||||
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', uart.UARTDevice, AddressableLightEffect)
|
||||
"AdalightLightEffect", uart.UARTDevice, AddressableLightEffect
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema({})
|
||||
|
||||
|
||||
@register_addressable_effect('adalight', AdalightLightEffect, "Adalight", {
|
||||
cv.GenerateID(CONF_UART_ID): cv.use_id(uart.UARTComponent)
|
||||
})
|
||||
@register_addressable_effect(
|
||||
"adalight",
|
||||
AdalightLightEffect,
|
||||
"Adalight",
|
||||
{cv.GenerateID(CONF_UART_ID): cv.use_id(uart.UARTComponent)},
|
||||
)
|
||||
def adalight_light_effect_to_code(config, effect_id):
|
||||
effect = cg.new_Pvariable(effect_id, config[CONF_NAME])
|
||||
yield uart.register_uart_device(effect, config)
|
||||
|
@@ -42,11 +42,11 @@ void AdalightLightEffect::reset_frame_(light::AddressableLight &it) {
|
||||
|
||||
void AdalightLightEffect::blank_all_leds_(light::AddressableLight &it) {
|
||||
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();
|
||||
|
||||
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) {
|
||||
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;
|
||||
|
@@ -16,7 +16,7 @@ class AdalightLightEffect : public light::AddressableLightEffect, public uart::U
|
||||
public:
|
||||
void start() 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:
|
||||
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() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up ADC '%s'...", this->get_name().c_str());
|
||||
#ifndef USE_ADC_SENSOR_VCC
|
||||
GPIOPin(this->pin_, INPUT).setup();
|
||||
#endif
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
analogSetPinAttenuation(this->pin_, this->attenuation_);
|
||||
|
@@ -2,36 +2,51 @@ import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import pins
|
||||
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,
|
||||
UNIT_VOLT,
|
||||
)
|
||||
|
||||
|
||||
AUTO_LOAD = ['voltage_sampler']
|
||||
AUTO_LOAD = ["voltage_sampler"]
|
||||
|
||||
ATTENUATION_MODES = {
|
||||
'0db': cg.global_ns.ADC_0db,
|
||||
'2.5db': cg.global_ns.ADC_2_5db,
|
||||
'6db': cg.global_ns.ADC_6db,
|
||||
'11db': cg.global_ns.ADC_11db,
|
||||
"0db": cg.global_ns.ADC_0db,
|
||||
"2.5db": cg.global_ns.ADC_2_5db,
|
||||
"6db": cg.global_ns.ADC_6db,
|
||||
"11db": cg.global_ns.ADC_11db,
|
||||
}
|
||||
|
||||
|
||||
def validate_adc_pin(value):
|
||||
vcc = str(value).upper()
|
||||
if vcc == 'VCC':
|
||||
if vcc == "VCC":
|
||||
return cv.only_on_esp8266(vcc)
|
||||
return pins.analog_pin(value)
|
||||
|
||||
|
||||
adc_ns = cg.esphome_ns.namespace('adc')
|
||||
ADCSensor = adc_ns.class_('ADCSensor', sensor.Sensor, cg.PollingComponent,
|
||||
voltage_sampler.VoltageSampler)
|
||||
adc_ns = cg.esphome_ns.namespace("adc")
|
||||
ADCSensor = adc_ns.class_(
|
||||
"ADCSensor", sensor.Sensor, cg.PollingComponent, voltage_sampler.VoltageSampler
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = sensor.sensor_schema(UNIT_VOLT, ICON_FLASH, 2).extend({
|
||||
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'))
|
||||
CONFIG_SCHEMA = (
|
||||
sensor.sensor_schema(UNIT_VOLT, ICON_EMPTY, 2, DEVICE_CLASS_VOLTAGE)
|
||||
.extend(
|
||||
{
|
||||
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):
|
||||
@@ -39,8 +54,8 @@ def to_code(config):
|
||||
yield cg.register_component(var, config)
|
||||
yield sensor.register_sensor(var, config)
|
||||
|
||||
if config[CONF_PIN] == 'VCC':
|
||||
cg.add_define('USE_ADC_SENSOR_VCC')
|
||||
if config[CONF_PIN] == "VCC":
|
||||
cg.add_define("USE_ADC_SENSOR_VCC")
|
||||
else:
|
||||
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),
|
||||
)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
wrapped_light = yield cg.get_variable(config[CONF_ADDRESSABLE_LIGHT_ID])
|
||||
cg.add(var.set_width(config[CONF_WIDTH]))
|
||||
cg.add(var.set_height(config[CONF_HEIGHT]))
|
||||
cg.add(var.set_light(wrapped_light))
|
||||
|
||||
yield cg.register_component(var, config)
|
||||
yield display.register_display(var, config)
|
||||
|
||||
if CONF_PIXEL_MAPPER in config:
|
||||
pixel_mapper_template_ = yield 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_ = yield cg.process_lambda(
|
||||
config[CONF_LAMBDA], [(display.DisplayBufferRef, "it")], return_type=cg.void
|
||||
)
|
||||
cg.add(var.set_writer(lambda_))
|
@@ -2,29 +2,54 @@ import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import sensor, i2c
|
||||
from esphome import pins
|
||||
from esphome.const import CONF_ID, CONF_VOLTAGE, \
|
||||
UNIT_VOLT, ICON_FLASH, UNIT_AMPERE, UNIT_WATT
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_VOLTAGE,
|
||||
DEVICE_CLASS_CURRENT,
|
||||
DEVICE_CLASS_POWER,
|
||||
DEVICE_CLASS_VOLTAGE,
|
||||
ICON_EMPTY,
|
||||
UNIT_VOLT,
|
||||
UNIT_AMPERE,
|
||||
UNIT_WATT,
|
||||
)
|
||||
|
||||
DEPENDENCIES = ['i2c']
|
||||
DEPENDENCIES = ["i2c"]
|
||||
|
||||
ade7953_ns = cg.esphome_ns.namespace('ade7953')
|
||||
ADE7953 = ade7953_ns.class_('ADE7953', cg.PollingComponent, i2c.I2CDevice)
|
||||
ade7953_ns = cg.esphome_ns.namespace("ade7953")
|
||||
ADE7953 = ade7953_ns.class_("ADE7953", cg.PollingComponent, i2c.I2CDevice)
|
||||
|
||||
CONF_IRQ_PIN = 'irq_pin'
|
||||
CONF_CURRENT_A = 'current_a'
|
||||
CONF_CURRENT_B = 'current_b'
|
||||
CONF_ACTIVE_POWER_A = 'active_power_a'
|
||||
CONF_ACTIVE_POWER_B = 'active_power_b'
|
||||
CONF_IRQ_PIN = "irq_pin"
|
||||
CONF_CURRENT_A = "current_a"
|
||||
CONF_CURRENT_B = "current_b"
|
||||
CONF_ACTIVE_POWER_A = "active_power_a"
|
||||
CONF_ACTIVE_POWER_B = "active_power_b"
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema({
|
||||
cv.GenerateID(): cv.declare_id(ADE7953),
|
||||
cv.Optional(CONF_IRQ_PIN): pins.input_pin,
|
||||
cv.Optional(CONF_VOLTAGE): sensor.sensor_schema(UNIT_VOLT, ICON_FLASH, 1),
|
||||
cv.Optional(CONF_CURRENT_A): sensor.sensor_schema(UNIT_AMPERE, ICON_FLASH, 2),
|
||||
cv.Optional(CONF_CURRENT_B): sensor.sensor_schema(UNIT_AMPERE, ICON_FLASH, 2),
|
||||
cv.Optional(CONF_ACTIVE_POWER_A): sensor.sensor_schema(UNIT_WATT, ICON_FLASH, 1),
|
||||
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))
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(ADE7953),
|
||||
cv.Optional(CONF_IRQ_PIN): pins.input_pin,
|
||||
cv.Optional(CONF_VOLTAGE): sensor.sensor_schema(
|
||||
UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE
|
||||
),
|
||||
cv.Optional(CONF_CURRENT_A): sensor.sensor_schema(
|
||||
UNIT_AMPERE, ICON_EMPTY, 2, DEVICE_CLASS_CURRENT
|
||||
),
|
||||
cv.Optional(CONF_CURRENT_B): sensor.sensor_schema(
|
||||
UNIT_AMPERE, ICON_EMPTY, 2, DEVICE_CLASS_CURRENT
|
||||
),
|
||||
cv.Optional(CONF_ACTIVE_POWER_A): sensor.sensor_schema(
|
||||
UNIT_WATT, ICON_EMPTY, 1, DEVICE_CLASS_POWER
|
||||
),
|
||||
cv.Optional(CONF_ACTIVE_POWER_B): sensor.sensor_schema(
|
||||
UNIT_WATT, ICON_EMPTY, 1, DEVICE_CLASS_POWER
|
||||
),
|
||||
}
|
||||
)
|
||||
.extend(cv.polling_component_schema("60s"))
|
||||
.extend(i2c.i2c_device_schema(0x38))
|
||||
)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
@@ -35,10 +60,15 @@ def to_code(config):
|
||||
if CONF_IRQ_PIN in config:
|
||||
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,
|
||||
CONF_ACTIVE_POWER_B]:
|
||||
for key in [
|
||||
CONF_VOLTAGE,
|
||||
CONF_CURRENT_A,
|
||||
CONF_CURRENT_B,
|
||||
CONF_ACTIVE_POWER_A,
|
||||
CONF_ACTIVE_POWER_B,
|
||||
]:
|
||||
if key not in config:
|
||||
continue
|
||||
conf = config[key]
|
||||
sens = yield sensor.new_sensor(conf)
|
||||
cg.add(getattr(var, f'set_{key}_sensor')(sens))
|
||||
cg.add(getattr(var, f"set_{key}_sensor")(sens))
|
||||
|
@@ -3,18 +3,24 @@ import esphome.config_validation as cv
|
||||
from esphome.components import i2c
|
||||
from esphome.const import CONF_ID
|
||||
|
||||
DEPENDENCIES = ['i2c']
|
||||
AUTO_LOAD = ['sensor', 'voltage_sampler']
|
||||
DEPENDENCIES = ["i2c"]
|
||||
AUTO_LOAD = ["sensor", "voltage_sampler"]
|
||||
MULTI_CONF = True
|
||||
|
||||
ads1115_ns = cg.esphome_ns.namespace('ads1115')
|
||||
ADS1115Component = ads1115_ns.class_('ADS1115Component', cg.Component, i2c.I2CDevice)
|
||||
ads1115_ns = cg.esphome_ns.namespace("ads1115")
|
||||
ADS1115Component = ads1115_ns.class_("ADS1115Component", cg.Component, i2c.I2CDevice)
|
||||
|
||||
CONF_CONTINUOUS_MODE = 'continuous_mode'
|
||||
CONFIG_SCHEMA = cv.Schema({
|
||||
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))
|
||||
CONF_CONTINUOUS_MODE = "continuous_mode"
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema(
|
||||
{
|
||||
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):
|
||||
|
@@ -1,53 +1,67 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
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,
|
||||
UNIT_VOLT,
|
||||
CONF_ID,
|
||||
)
|
||||
from . import ads1115_ns, ADS1115Component
|
||||
|
||||
DEPENDENCIES = ['ads1115']
|
||||
DEPENDENCIES = ["ads1115"]
|
||||
|
||||
ADS1115Multiplexer = ads1115_ns.enum('ADS1115Multiplexer')
|
||||
ADS1115Multiplexer = ads1115_ns.enum("ADS1115Multiplexer")
|
||||
MUX = {
|
||||
'A0_A1': ADS1115Multiplexer.ADS1115_MULTIPLEXER_P0_N1,
|
||||
'A0_A3': ADS1115Multiplexer.ADS1115_MULTIPLEXER_P0_N3,
|
||||
'A1_A3': ADS1115Multiplexer.ADS1115_MULTIPLEXER_P1_N3,
|
||||
'A2_A3': ADS1115Multiplexer.ADS1115_MULTIPLEXER_P2_N3,
|
||||
'A0_GND': ADS1115Multiplexer.ADS1115_MULTIPLEXER_P0_NG,
|
||||
'A1_GND': ADS1115Multiplexer.ADS1115_MULTIPLEXER_P1_NG,
|
||||
'A2_GND': ADS1115Multiplexer.ADS1115_MULTIPLEXER_P2_NG,
|
||||
'A3_GND': ADS1115Multiplexer.ADS1115_MULTIPLEXER_P3_NG,
|
||||
"A0_A1": ADS1115Multiplexer.ADS1115_MULTIPLEXER_P0_N1,
|
||||
"A0_A3": ADS1115Multiplexer.ADS1115_MULTIPLEXER_P0_N3,
|
||||
"A1_A3": ADS1115Multiplexer.ADS1115_MULTIPLEXER_P1_N3,
|
||||
"A2_A3": ADS1115Multiplexer.ADS1115_MULTIPLEXER_P2_N3,
|
||||
"A0_GND": ADS1115Multiplexer.ADS1115_MULTIPLEXER_P0_NG,
|
||||
"A1_GND": ADS1115Multiplexer.ADS1115_MULTIPLEXER_P1_NG,
|
||||
"A2_GND": ADS1115Multiplexer.ADS1115_MULTIPLEXER_P2_NG,
|
||||
"A3_GND": ADS1115Multiplexer.ADS1115_MULTIPLEXER_P3_NG,
|
||||
}
|
||||
|
||||
ADS1115Gain = ads1115_ns.enum('ADS1115Gain')
|
||||
ADS1115Gain = ads1115_ns.enum("ADS1115Gain")
|
||||
GAIN = {
|
||||
'6.144': ADS1115Gain.ADS1115_GAIN_6P144,
|
||||
'4.096': ADS1115Gain.ADS1115_GAIN_4P096,
|
||||
'2.048': ADS1115Gain.ADS1115_GAIN_2P048,
|
||||
'1.024': ADS1115Gain.ADS1115_GAIN_1P024,
|
||||
'0.512': ADS1115Gain.ADS1115_GAIN_0P512,
|
||||
'0.256': ADS1115Gain.ADS1115_GAIN_0P256,
|
||||
"6.144": ADS1115Gain.ADS1115_GAIN_6P144,
|
||||
"4.096": ADS1115Gain.ADS1115_GAIN_4P096,
|
||||
"2.048": ADS1115Gain.ADS1115_GAIN_2P048,
|
||||
"1.024": ADS1115Gain.ADS1115_GAIN_1P024,
|
||||
"0.512": ADS1115Gain.ADS1115_GAIN_0P512,
|
||||
"0.256": ADS1115Gain.ADS1115_GAIN_0P256,
|
||||
}
|
||||
|
||||
|
||||
def validate_gain(value):
|
||||
if isinstance(value, float):
|
||||
value = f'{value:0.03f}'
|
||||
value = f"{value:0.03f}"
|
||||
elif not isinstance(value, str):
|
||||
raise cv.Invalid(f'invalid gain "{value}"')
|
||||
|
||||
return cv.enum(GAIN)(value)
|
||||
|
||||
|
||||
ADS1115Sensor = ads1115_ns.class_('ADS1115Sensor', sensor.Sensor, cg.PollingComponent,
|
||||
voltage_sampler.VoltageSampler)
|
||||
ADS1115Sensor = ads1115_ns.class_(
|
||||
"ADS1115Sensor", sensor.Sensor, cg.PollingComponent, voltage_sampler.VoltageSampler
|
||||
)
|
||||
|
||||
CONF_ADS1115_ID = 'ads1115_id'
|
||||
CONFIG_SCHEMA = sensor.sensor_schema(UNIT_VOLT, ICON_FLASH, 3).extend({
|
||||
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'))
|
||||
CONF_ADS1115_ID = "ads1115_id"
|
||||
CONFIG_SCHEMA = (
|
||||
sensor.sensor_schema(UNIT_VOLT, ICON_EMPTY, 3, DEVICE_CLASS_VOLTAGE)
|
||||
.extend(
|
||||
{
|
||||
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):
|
||||
|
@@ -1,19 +1,37 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import i2c, sensor
|
||||
from esphome.const import CONF_HUMIDITY, CONF_ID, CONF_TEMPERATURE, \
|
||||
UNIT_CELSIUS, ICON_THERMOMETER, ICON_WATER_PERCENT, UNIT_PERCENT
|
||||
from esphome.const import (
|
||||
CONF_HUMIDITY,
|
||||
CONF_ID,
|
||||
CONF_TEMPERATURE,
|
||||
DEVICE_CLASS_HUMIDITY,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
ICON_EMPTY,
|
||||
UNIT_CELSIUS,
|
||||
UNIT_PERCENT,
|
||||
)
|
||||
|
||||
DEPENDENCIES = ['i2c']
|
||||
DEPENDENCIES = ["i2c"]
|
||||
|
||||
aht10_ns = cg.esphome_ns.namespace('aht10')
|
||||
AHT10Component = aht10_ns.class_('AHT10Component', cg.PollingComponent, i2c.I2CDevice)
|
||||
aht10_ns = cg.esphome_ns.namespace("aht10")
|
||||
AHT10Component = aht10_ns.class_("AHT10Component", cg.PollingComponent, i2c.I2CDevice)
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema({
|
||||
cv.GenerateID(): cv.declare_id(AHT10Component),
|
||||
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),
|
||||
}).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x38))
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(AHT10Component),
|
||||
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
|
||||
UNIT_CELSIUS, ICON_EMPTY, 2, DEVICE_CLASS_TEMPERATURE
|
||||
),
|
||||
cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
|
||||
UNIT_PERCENT, ICON_EMPTY, 2, DEVICE_CLASS_HUMIDITY
|
||||
),
|
||||
}
|
||||
)
|
||||
.extend(cv.polling_component_schema("60s"))
|
||||
.extend(i2c.i2c_device_schema(0x38))
|
||||
)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
|
@@ -1,19 +1,39 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import i2c, sensor
|
||||
from esphome.const import CONF_HUMIDITY, CONF_ID, CONF_TEMPERATURE, \
|
||||
UNIT_CELSIUS, ICON_THERMOMETER, ICON_WATER_PERCENT, UNIT_PERCENT
|
||||
from esphome.const import (
|
||||
CONF_HUMIDITY,
|
||||
CONF_ID,
|
||||
CONF_TEMPERATURE,
|
||||
DEVICE_CLASS_HUMIDITY,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
UNIT_CELSIUS,
|
||||
ICON_EMPTY,
|
||||
UNIT_PERCENT,
|
||||
)
|
||||
|
||||
DEPENDENCIES = ['i2c']
|
||||
DEPENDENCIES = ["i2c"]
|
||||
|
||||
am2320_ns = cg.esphome_ns.namespace('am2320')
|
||||
AM2320Component = am2320_ns.class_('AM2320Component', cg.PollingComponent, i2c.I2CDevice)
|
||||
am2320_ns = cg.esphome_ns.namespace("am2320")
|
||||
AM2320Component = am2320_ns.class_(
|
||||
"AM2320Component", cg.PollingComponent, i2c.I2CDevice
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema({
|
||||
cv.GenerateID(): cv.declare_id(AM2320Component),
|
||||
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),
|
||||
}).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x5C))
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(AM2320Component),
|
||||
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
|
||||
UNIT_CELSIUS, ICON_EMPTY, 1, DEVICE_CLASS_TEMPERATURE
|
||||
),
|
||||
cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
|
||||
UNIT_PERCENT, ICON_EMPTY, 1, DEVICE_CLASS_HUMIDITY
|
||||
),
|
||||
}
|
||||
)
|
||||
.extend(cv.polling_component_schema("60s"))
|
||||
.extend(i2c.i2c_device_schema(0x5C))
|
||||
)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
|
@@ -10,24 +10,28 @@ from esphome.core import CORE, HexInt
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DEPENDENCIES = ['display']
|
||||
DEPENDENCIES = ["display"]
|
||||
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({
|
||||
cv.Required(CONF_ID): cv.declare_id(Animation_),
|
||||
cv.Required(CONF_FILE): cv.file_,
|
||||
cv.Optional(CONF_RESIZE): cv.dimensions,
|
||||
cv.Optional(CONF_TYPE, default='BINARY'): cv.enum(espImage.IMAGE_TYPE, upper=True),
|
||||
cv.GenerateID(CONF_RAW_DATA_ID): cv.declare_id(cg.uint8),
|
||||
})
|
||||
ANIMATION_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.declare_id(Animation_),
|
||||
cv.Required(CONF_FILE): cv.file_,
|
||||
cv.Optional(CONF_RESIZE): cv.dimensions,
|
||||
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)
|
||||
|
||||
CODEOWNERS = ['@syndlex']
|
||||
CODEOWNERS = ["@syndlex"]
|
||||
|
||||
|
||||
def to_code(config):
|
||||
@@ -46,26 +50,28 @@ def to_code(config):
|
||||
width, height = image.size
|
||||
else:
|
||||
if width > 500 or height > 500:
|
||||
_LOGGER.warning("The image you requested is very big. Please consider using"
|
||||
" the resize parameter.")
|
||||
_LOGGER.warning(
|
||||
"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)]
|
||||
pos = 0
|
||||
for frameIndex in range(frames):
|
||||
image.seek(frameIndex)
|
||||
frame = image.convert('L', dither=Image.NONE)
|
||||
frame = image.convert("L", dither=Image.NONE)
|
||||
pixels = list(frame.getdata())
|
||||
for pix in pixels:
|
||||
data[pos] = pix
|
||||
pos += 1
|
||||
|
||||
elif config[CONF_TYPE] == 'RGB24':
|
||||
elif config[CONF_TYPE] == "RGB24":
|
||||
data = [0 for _ in range(height * width * 3 * frames)]
|
||||
pos = 0
|
||||
for frameIndex in range(frames):
|
||||
image.seek(frameIndex)
|
||||
frame = image.convert('RGB')
|
||||
frame = image.convert("RGB")
|
||||
pixels = list(frame.getdata())
|
||||
for pix in pixels:
|
||||
data[pos] = pix[0]
|
||||
@@ -75,12 +81,12 @@ def to_code(config):
|
||||
data[pos] = pix[2]
|
||||
pos += 1
|
||||
|
||||
elif config[CONF_TYPE] == 'BINARY':
|
||||
elif config[CONF_TYPE] == "BINARY":
|
||||
width8 = ((width + 7) // 8) * 8
|
||||
data = [0 for _ in range((height * width8 // 8) * frames)]
|
||||
for frameIndex in range(frames):
|
||||
image.seek(frameIndex)
|
||||
frame = image.convert('1', dither=Image.NONE)
|
||||
frame = image.convert("1", dither=Image.NONE)
|
||||
for y in range(height):
|
||||
for x in range(width):
|
||||
if frame.getpixel((x, y)):
|
||||
@@ -90,5 +96,11 @@ def to_code(config):
|
||||
|
||||
rhs = [HexInt(x) for x in data]
|
||||
prog_arr = cg.progmem_array(config[CONF_RAW_DATA_ID], rhs)
|
||||
cg.new_Pvariable(config[CONF_ID], prog_arr, width, height, frames,
|
||||
espImage.IMAGE_TYPE[config[CONF_TYPE]])
|
||||
cg.new_Pvariable(
|
||||
config[CONF_ID],
|
||||
prog_arr,
|
||||
width,
|
||||
height,
|
||||
frames,
|
||||
espImage.IMAGE_TYPE[config[CONF_TYPE]],
|
||||
)
|
||||
|
@@ -3,18 +3,24 @@ import esphome.config_validation as cv
|
||||
from esphome.components import i2c
|
||||
from esphome.const import CONF_ID
|
||||
|
||||
DEPENDENCIES = ['i2c']
|
||||
AUTO_LOAD = ['sensor', 'binary_sensor']
|
||||
DEPENDENCIES = ["i2c"]
|
||||
AUTO_LOAD = ["sensor", "binary_sensor"]
|
||||
MULTI_CONF = True
|
||||
|
||||
CONF_APDS9960_ID = 'apds9960_id'
|
||||
CONF_APDS9960_ID = "apds9960_id"
|
||||
|
||||
apds9960_nds = cg.esphome_ns.namespace('apds9960')
|
||||
APDS9960 = apds9960_nds.class_('APDS9960', cg.PollingComponent, i2c.I2CDevice)
|
||||
apds9960_nds = cg.esphome_ns.namespace("apds9960")
|
||||
APDS9960 = apds9960_nds.class_("APDS9960", cg.PollingComponent, i2c.I2CDevice)
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema({
|
||||
cv.GenerateID(): cv.declare_id(APDS9960),
|
||||
}).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x39))
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(APDS9960),
|
||||
}
|
||||
)
|
||||
.extend(cv.polling_component_schema("60s"))
|
||||
.extend(i2c.i2c_device_schema(0x39))
|
||||
)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
|
@@ -4,20 +4,24 @@ from esphome.components import binary_sensor
|
||||
from esphome.const import CONF_DIRECTION, CONF_DEVICE_CLASS, DEVICE_CLASS_MOVING
|
||||
from . import APDS9960, CONF_APDS9960_ID
|
||||
|
||||
DEPENDENCIES = ['apds9960']
|
||||
DEPENDENCIES = ["apds9960"]
|
||||
|
||||
DIRECTIONS = {
|
||||
'UP': 'set_up_direction',
|
||||
'DOWN': 'set_down_direction',
|
||||
'LEFT': 'set_left_direction',
|
||||
'RIGHT': 'set_right_direction',
|
||||
"UP": "set_up_direction",
|
||||
"DOWN": "set_down_direction",
|
||||
"LEFT": "set_left_direction",
|
||||
"RIGHT": "set_right_direction",
|
||||
}
|
||||
|
||||
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.Optional(CONF_DEVICE_CLASS, default=DEVICE_CLASS_MOVING): binary_sensor.device_class,
|
||||
})
|
||||
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.Optional(
|
||||
CONF_DEVICE_CLASS, default=DEVICE_CLASS_MOVING
|
||||
): binary_sensor.device_class,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
|
@@ -1,23 +1,27 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import sensor
|
||||
from esphome.const import CONF_TYPE, UNIT_PERCENT, ICON_LIGHTBULB
|
||||
from esphome.const import CONF_TYPE, DEVICE_CLASS_EMPTY, UNIT_PERCENT, ICON_LIGHTBULB
|
||||
from . import APDS9960, CONF_APDS9960_ID
|
||||
|
||||
DEPENDENCIES = ['apds9960']
|
||||
DEPENDENCIES = ["apds9960"]
|
||||
|
||||
TYPES = {
|
||||
'CLEAR': 'set_clear_channel',
|
||||
'RED': 'set_red_channel',
|
||||
'GREEN': 'set_green_channel',
|
||||
'BLUE': 'set_blue_channel',
|
||||
'PROXIMITY': 'set_proximity',
|
||||
"CLEAR": "set_clear_channel",
|
||||
"RED": "set_red_channel",
|
||||
"GREEN": "set_green_channel",
|
||||
"BLUE": "set_blue_channel",
|
||||
"PROXIMITY": "set_proximity",
|
||||
}
|
||||
|
||||
CONFIG_SCHEMA = sensor.sensor_schema(UNIT_PERCENT, ICON_LIGHTBULB, 1).extend({
|
||||
cv.Required(CONF_TYPE): cv.one_of(*TYPES, upper=True),
|
||||
cv.GenerateID(CONF_APDS9960_ID): cv.use_id(APDS9960),
|
||||
})
|
||||
CONFIG_SCHEMA = sensor.sensor_schema(
|
||||
UNIT_PERCENT, ICON_LIGHTBULB, 1, DEVICE_CLASS_EMPTY
|
||||
).extend(
|
||||
{
|
||||
cv.Required(CONF_TYPE): cv.one_of(*TYPES, upper=True),
|
||||
cv.GenerateID(CONF_APDS9960_ID): cv.use_id(APDS9960),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
|
@@ -2,46 +2,69 @@ import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import automation
|
||||
from esphome.automation import Condition
|
||||
from esphome.const import CONF_DATA, 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.const import (
|
||||
CONF_DATA,
|
||||
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
|
||||
|
||||
DEPENDENCIES = ['network']
|
||||
AUTO_LOAD = ['async_tcp']
|
||||
CODEOWNERS = ['@OttoWinter']
|
||||
DEPENDENCIES = ["network"]
|
||||
AUTO_LOAD = ["async_tcp"]
|
||||
CODEOWNERS = ["@OttoWinter"]
|
||||
|
||||
api_ns = cg.esphome_ns.namespace('api')
|
||||
APIServer = api_ns.class_('APIServer', cg.Component, cg.Controller)
|
||||
HomeAssistantServiceCallAction = api_ns.class_('HomeAssistantServiceCallAction', automation.Action)
|
||||
APIConnectedCondition = api_ns.class_('APIConnectedCondition', Condition)
|
||||
api_ns = cg.esphome_ns.namespace("api")
|
||||
APIServer = api_ns.class_("APIServer", cg.Component, cg.Controller)
|
||||
HomeAssistantServiceCallAction = api_ns.class_(
|
||||
"HomeAssistantServiceCallAction", automation.Action
|
||||
)
|
||||
APIConnectedCondition = api_ns.class_("APIConnectedCondition", Condition)
|
||||
|
||||
UserServiceTrigger = api_ns.class_('UserServiceTrigger', automation.Trigger)
|
||||
ListEntitiesServicesArgument = api_ns.class_('ListEntitiesServicesArgument')
|
||||
UserServiceTrigger = api_ns.class_("UserServiceTrigger", automation.Trigger)
|
||||
ListEntitiesServicesArgument = api_ns.class_("ListEntitiesServicesArgument")
|
||||
SERVICE_ARG_NATIVE_TYPES = {
|
||||
'bool': bool,
|
||||
'int': cg.int32,
|
||||
'float': float,
|
||||
'string': cg.std_string,
|
||||
'bool[]': cg.std_vector.template(bool),
|
||||
'int[]': cg.std_vector.template(cg.int32),
|
||||
'float[]': cg.std_vector.template(float),
|
||||
'string[]': cg.std_vector.template(cg.std_string),
|
||||
"bool": bool,
|
||||
"int": cg.int32,
|
||||
"float": float,
|
||||
"string": cg.std_string,
|
||||
"bool[]": cg.std_vector.template(bool),
|
||||
"int[]": cg.std_vector.template(cg.int32),
|
||||
"float[]": cg.std_vector.template(float),
|
||||
"string[]": cg.std_vector.template(cg.std_string),
|
||||
}
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema({
|
||||
cv.GenerateID(): cv.declare_id(APIServer),
|
||||
cv.Optional(CONF_PORT, default=6053): cv.port,
|
||||
cv.Optional(CONF_PASSWORD, default=''): cv.string_strict,
|
||||
cv.Optional(CONF_REBOOT_TIMEOUT, default='15min'): cv.positive_time_period_milliseconds,
|
||||
cv.Optional(CONF_SERVICES): automation.validate_automation({
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(UserServiceTrigger),
|
||||
cv.Required(CONF_SERVICE): cv.valid_name,
|
||||
cv.Optional(CONF_VARIABLES, default={}): cv.Schema({
|
||||
cv.validate_id_name: cv.one_of(*SERVICE_ARG_NATIVE_TYPES, lower=True),
|
||||
}),
|
||||
}),
|
||||
}).extend(cv.COMPONENT_SCHEMA)
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(APIServer),
|
||||
cv.Optional(CONF_PORT, default=6053): cv.port,
|
||||
cv.Optional(CONF_PASSWORD, default=""): cv.string_strict,
|
||||
cv.Optional(
|
||||
CONF_REBOOT_TIMEOUT, default="15min"
|
||||
): cv.positive_time_period_milliseconds,
|
||||
cv.Optional(CONF_SERVICES): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(UserServiceTrigger),
|
||||
cv.Required(CONF_SERVICE): cv.valid_name,
|
||||
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)
|
||||
@@ -63,28 +86,36 @@ def to_code(config):
|
||||
func_args.append((native, name))
|
||||
service_arg_names.append(name)
|
||||
templ = cg.TemplateArguments(*template_args)
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], templ,
|
||||
conf[CONF_SERVICE], service_arg_names)
|
||||
trigger = cg.new_Pvariable(
|
||||
conf[CONF_TRIGGER_ID], templ, conf[CONF_SERVICE], service_arg_names
|
||||
)
|
||||
cg.add(var.register_user_service(trigger))
|
||||
yield automation.build_automation(trigger, func_args, conf)
|
||||
|
||||
cg.add_define('USE_API')
|
||||
cg.add_define("USE_API")
|
||||
cg.add_global(api_ns.using)
|
||||
|
||||
|
||||
KEY_VALUE_SCHEMA = cv.Schema({cv.string: cv.templatable(cv.string_strict)})
|
||||
|
||||
HOMEASSISTANT_SERVICE_ACTION_SCHEMA = cv.Schema({
|
||||
cv.GenerateID(): cv.use_id(APIServer),
|
||||
cv.Required(CONF_SERVICE): cv.templatable(cv.string),
|
||||
cv.Optional(CONF_DATA, default={}): KEY_VALUE_SCHEMA,
|
||||
cv.Optional(CONF_DATA_TEMPLATE, default={}): KEY_VALUE_SCHEMA,
|
||||
cv.Optional(CONF_VARIABLES, default={}): cv.Schema({cv.string: cv.returning_lambda}),
|
||||
})
|
||||
HOMEASSISTANT_SERVICE_ACTION_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(APIServer),
|
||||
cv.Required(CONF_SERVICE): cv.templatable(cv.string),
|
||||
cv.Optional(CONF_DATA, default={}): KEY_VALUE_SCHEMA,
|
||||
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,
|
||||
HOMEASSISTANT_SERVICE_ACTION_SCHEMA)
|
||||
@automation.register_action(
|
||||
"homeassistant.service",
|
||||
HomeAssistantServiceCallAction,
|
||||
HOMEASSISTANT_SERVICE_ACTION_SCHEMA,
|
||||
)
|
||||
def homeassistant_service_to_code(config, action_id, template_arg, args):
|
||||
serv = yield cg.get_variable(config[CONF_ID])
|
||||
var = cg.new_Pvariable(action_id, template_arg, serv, False)
|
||||
@@ -104,23 +135,30 @@ def homeassistant_service_to_code(config, action_id, template_arg, args):
|
||||
|
||||
def validate_homeassistant_event(value):
|
||||
value = cv.string(value)
|
||||
if not value.startswith('esphome.'):
|
||||
raise cv.Invalid("ESPHome can only generate Home Assistant events that begin with "
|
||||
"esphome. For example 'esphome.xyz'")
|
||||
if not value.startswith("esphome."):
|
||||
raise cv.Invalid(
|
||||
"ESPHome can only generate Home Assistant events that begin with "
|
||||
"esphome. For example 'esphome.xyz'"
|
||||
)
|
||||
return value
|
||||
|
||||
|
||||
HOMEASSISTANT_EVENT_ACTION_SCHEMA = cv.Schema({
|
||||
cv.GenerateID(): cv.use_id(APIServer),
|
||||
cv.Required(CONF_EVENT): validate_homeassistant_event,
|
||||
cv.Optional(CONF_DATA, default={}): KEY_VALUE_SCHEMA,
|
||||
cv.Optional(CONF_DATA_TEMPLATE, default={}): KEY_VALUE_SCHEMA,
|
||||
cv.Optional(CONF_VARIABLES, default={}): KEY_VALUE_SCHEMA,
|
||||
})
|
||||
HOMEASSISTANT_EVENT_ACTION_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(APIServer),
|
||||
cv.Required(CONF_EVENT): validate_homeassistant_event,
|
||||
cv.Optional(CONF_DATA, 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,
|
||||
HOMEASSISTANT_EVENT_ACTION_SCHEMA)
|
||||
@automation.register_action(
|
||||
"homeassistant.event",
|
||||
HomeAssistantServiceCallAction,
|
||||
HOMEASSISTANT_EVENT_ACTION_SCHEMA,
|
||||
)
|
||||
def homeassistant_event_to_code(config, action_id, template_arg, args):
|
||||
serv = yield cg.get_variable(config[CONF_ID])
|
||||
var = cg.new_Pvariable(action_id, template_arg, serv, True)
|
||||
@@ -138,23 +176,29 @@ def homeassistant_event_to_code(config, action_id, template_arg, args):
|
||||
yield var
|
||||
|
||||
|
||||
HOMEASSISTANT_TAG_SCANNED_ACTION_SCHEMA = cv.maybe_simple_value({
|
||||
cv.GenerateID(): cv.use_id(APIServer),
|
||||
cv.Required(CONF_TAG): cv.templatable(cv.string_strict),
|
||||
}, key=CONF_TAG)
|
||||
HOMEASSISTANT_TAG_SCANNED_ACTION_SCHEMA = cv.maybe_simple_value(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(APIServer),
|
||||
cv.Required(CONF_TAG): cv.templatable(cv.string_strict),
|
||||
},
|
||||
key=CONF_TAG,
|
||||
)
|
||||
|
||||
|
||||
@automation.register_action('homeassistant.tag_scanned', HomeAssistantServiceCallAction,
|
||||
HOMEASSISTANT_TAG_SCANNED_ACTION_SCHEMA)
|
||||
@automation.register_action(
|
||||
"homeassistant.tag_scanned",
|
||||
HomeAssistantServiceCallAction,
|
||||
HOMEASSISTANT_TAG_SCANNED_ACTION_SCHEMA,
|
||||
)
|
||||
def homeassistant_tag_scanned_to_code(config, action_id, template_arg, args):
|
||||
serv = yield cg.get_variable(config[CONF_ID])
|
||||
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)
|
||||
cg.add(var.add_data('tag_id', templ))
|
||||
cg.add(var.add_data("tag_id", templ))
|
||||
yield var
|
||||
|
||||
|
||||
@automation.register_condition('api.connected', APIConnectedCondition, {})
|
||||
@automation.register_condition("api.connected", APIConnectedCondition, {})
|
||||
def api_connected_to_code(config, condition_id, template_arg, args):
|
||||
yield cg.new_Pvariable(condition_id, template_arg)
|
||||
|
@@ -46,6 +46,7 @@ service APIConnection {
|
||||
// The Home Assistant protocol is structured as a simple
|
||||
// TCP socket with short binary messages encoded in the protocol buffers 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 type of message.
|
||||
// * The message object encoded as a ProtoBuf message
|
||||
@@ -302,6 +303,7 @@ message ListEntitiesFanResponse {
|
||||
bool supports_oscillation = 5;
|
||||
bool supports_speed = 6;
|
||||
bool supports_direction = 7;
|
||||
int32 supported_speed_count = 8;
|
||||
}
|
||||
enum FanSpeed {
|
||||
FAN_SPEED_LOW = 0;
|
||||
@@ -321,8 +323,9 @@ message FanStateResponse {
|
||||
fixed32 key = 1;
|
||||
bool state = 2;
|
||||
bool oscillating = 3;
|
||||
FanSpeed speed = 4;
|
||||
FanSpeed speed = 4 [deprecated = true];
|
||||
FanDirection direction = 5;
|
||||
int32 speed_level = 6;
|
||||
}
|
||||
message FanCommandRequest {
|
||||
option (id) = 31;
|
||||
@@ -333,12 +336,14 @@ message FanCommandRequest {
|
||||
fixed32 key = 1;
|
||||
bool has_state = 2;
|
||||
bool state = 3;
|
||||
bool has_speed = 4;
|
||||
FanSpeed speed = 5;
|
||||
bool has_speed = 4 [deprecated = true];
|
||||
FanSpeed speed = 5 [deprecated = true];
|
||||
bool has_oscillating = 6;
|
||||
bool oscillating = 7;
|
||||
bool has_direction = 8;
|
||||
FanDirection direction = 9;
|
||||
bool has_speed_level = 10;
|
||||
int32 speed_level = 11;
|
||||
}
|
||||
|
||||
// ==================== LIGHT ====================
|
||||
@@ -418,6 +423,7 @@ message ListEntitiesSensorResponse {
|
||||
string unit_of_measurement = 6;
|
||||
int32 accuracy_decimals = 7;
|
||||
bool force_update = 8;
|
||||
string device_class = 9;
|
||||
}
|
||||
message SensorStateResponse {
|
||||
option (id) = 25;
|
||||
|
@@ -9,6 +9,9 @@
|
||||
#ifdef USE_HOMEASSISTANT_TIME
|
||||
#include "esphome/components/homeassistant/time/homeassistant_time.h"
|
||||
#endif
|
||||
#ifdef USE_FAN
|
||||
#include "esphome/components/fan/fan_helpers.h"
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
namespace api {
|
||||
@@ -246,8 +249,10 @@ bool APIConnection::send_fan_state(fan::FanState *fan) {
|
||||
resp.state = fan->state;
|
||||
if (traits.supports_oscillation())
|
||||
resp.oscillating = fan->oscillating;
|
||||
if (traits.supports_speed())
|
||||
resp.speed = static_cast<enums::FanSpeed>(fan->speed);
|
||||
if (traits.supports_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())
|
||||
resp.direction = static_cast<enums::FanDirection>(fan->direction);
|
||||
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_speed = traits.supports_speed();
|
||||
msg.supports_direction = traits.supports_direction();
|
||||
msg.supported_speed_count = traits.supported_speed_count();
|
||||
return this->send_list_entities_fan_response(msg);
|
||||
}
|
||||
void APIConnection::fan_command(const FanCommandRequest &msg) {
|
||||
@@ -269,13 +275,19 @@ void APIConnection::fan_command(const FanCommandRequest &msg) {
|
||||
if (fan == nullptr)
|
||||
return;
|
||||
|
||||
auto traits = fan->get_traits();
|
||||
|
||||
auto call = fan->make_call();
|
||||
if (msg.has_state)
|
||||
call.set_state(msg.state);
|
||||
if (msg.has_oscillating)
|
||||
call.set_oscillating(msg.oscillating);
|
||||
if (msg.has_speed)
|
||||
call.set_speed(static_cast<fan::FanSpeed>(msg.speed));
|
||||
if (msg.has_speed_level) {
|
||||
// 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)
|
||||
call.set_direction(static_cast<fan::FanDirection>(msg.direction));
|
||||
call.perform();
|
||||
@@ -382,6 +394,7 @@ bool APIConnection::send_sensor_info(sensor::Sensor *sensor) {
|
||||
msg.unit_of_measurement = sensor->get_unit_of_measurement();
|
||||
msg.accuracy_decimals = sensor->get_accuracy_decimals();
|
||||
msg.force_update = sensor->get_force_update();
|
||||
msg.device_class = sensor->get_device_class();
|
||||
return this->send_list_entities_sensor_response(msg);
|
||||
}
|
||||
#endif
|
||||
@@ -589,7 +602,7 @@ HelloResponse APIConnection::hello(const HelloRequest &msg) {
|
||||
|
||||
HelloResponse resp;
|
||||
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 ")";
|
||||
this->connection_state_ = ConnectionState::CONNECTED;
|
||||
return resp;
|
||||
|
@@ -774,6 +774,10 @@ bool ListEntitiesFanResponse::decode_varint(uint32_t field_id, ProtoVarInt value
|
||||
this->supports_direction = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
case 8: {
|
||||
this->supported_speed_count = value.as_int32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -814,6 +818,7 @@ void ListEntitiesFanResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_bool(5, this->supports_oscillation);
|
||||
buffer.encode_bool(6, this->supports_speed);
|
||||
buffer.encode_bool(7, this->supports_direction);
|
||||
buffer.encode_int32(8, this->supported_speed_count);
|
||||
}
|
||||
void ListEntitiesFanResponse::dump_to(std::string &out) const {
|
||||
char buffer[64];
|
||||
@@ -846,6 +851,11 @@ void ListEntitiesFanResponse::dump_to(std::string &out) const {
|
||||
out.append(" supports_direction: ");
|
||||
out.append(YESNO(this->supports_direction));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" supported_speed_count: ");
|
||||
sprintf(buffer, "%d", this->supported_speed_count);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
bool FanStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
@@ -866,6 +876,10 @@ bool FanStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
this->direction = value.as_enum<enums::FanDirection>();
|
||||
return true;
|
||||
}
|
||||
case 6: {
|
||||
this->speed_level = value.as_int32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -886,6 +900,7 @@ void FanStateResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_bool(3, this->oscillating);
|
||||
buffer.encode_enum<enums::FanSpeed>(4, this->speed);
|
||||
buffer.encode_enum<enums::FanDirection>(5, this->direction);
|
||||
buffer.encode_int32(6, this->speed_level);
|
||||
}
|
||||
void FanStateResponse::dump_to(std::string &out) const {
|
||||
char buffer[64];
|
||||
@@ -910,6 +925,11 @@ void FanStateResponse::dump_to(std::string &out) const {
|
||||
out.append(" direction: ");
|
||||
out.append(proto_enum_to_string<enums::FanDirection>(this->direction));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" speed_level: ");
|
||||
sprintf(buffer, "%d", this->speed_level);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
bool FanCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
@@ -946,6 +966,14 @@ bool FanCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
this->direction = value.as_enum<enums::FanDirection>();
|
||||
return true;
|
||||
}
|
||||
case 10: {
|
||||
this->has_speed_level = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
case 11: {
|
||||
this->speed_level = value.as_int32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -970,6 +998,8 @@ void FanCommandRequest::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_bool(7, this->oscillating);
|
||||
buffer.encode_bool(8, this->has_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 {
|
||||
char buffer[64];
|
||||
@@ -1010,6 +1040,15 @@ void FanCommandRequest::dump_to(std::string &out) const {
|
||||
out.append(" direction: ");
|
||||
out.append(proto_enum_to_string<enums::FanDirection>(this->direction));
|
||||
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("}");
|
||||
}
|
||||
bool ListEntitiesLightResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
@@ -1494,6 +1533,10 @@ bool ListEntitiesSensorResponse::decode_length(uint32_t field_id, ProtoLengthDel
|
||||
this->unit_of_measurement = value.as_string();
|
||||
return true;
|
||||
}
|
||||
case 9: {
|
||||
this->device_class = value.as_string();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -1517,6 +1560,7 @@ void ListEntitiesSensorResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_string(6, this->unit_of_measurement);
|
||||
buffer.encode_int32(7, this->accuracy_decimals);
|
||||
buffer.encode_bool(8, this->force_update);
|
||||
buffer.encode_string(9, this->device_class);
|
||||
}
|
||||
void ListEntitiesSensorResponse::dump_to(std::string &out) const {
|
||||
char buffer[64];
|
||||
@@ -1554,6 +1598,10 @@ void ListEntitiesSensorResponse::dump_to(std::string &out) const {
|
||||
out.append(" force_update: ");
|
||||
out.append(YESNO(this->force_update));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" device_class: ");
|
||||
out.append("'").append(this->device_class).append("'");
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
bool SensorStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
|
@@ -284,6 +284,7 @@ class ListEntitiesFanResponse : public ProtoMessage {
|
||||
bool supports_oscillation{false}; // NOLINT
|
||||
bool supports_speed{false}; // NOLINT
|
||||
bool supports_direction{false}; // NOLINT
|
||||
int32_t supported_speed_count{0}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
@@ -299,6 +300,7 @@ class FanStateResponse : public ProtoMessage {
|
||||
bool oscillating{false}; // NOLINT
|
||||
enums::FanSpeed speed{}; // NOLINT
|
||||
enums::FanDirection direction{}; // NOLINT
|
||||
int32_t speed_level{0}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
@@ -317,6 +319,8 @@ class FanCommandRequest : public ProtoMessage {
|
||||
bool oscillating{false}; // NOLINT
|
||||
bool has_direction{false}; // NOLINT
|
||||
enums::FanDirection direction{}; // NOLINT
|
||||
bool has_speed_level{false}; // NOLINT
|
||||
int32_t speed_level{0}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
@@ -403,6 +407,7 @@ class ListEntitiesSensorResponse : public ProtoMessage {
|
||||
std::string unit_of_measurement{}; // NOLINT
|
||||
int32_t accuracy_decimals{0}; // NOLINT
|
||||
bool force_update{false}; // NOLINT
|
||||
std::string device_class{}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
|
@@ -1,33 +1,43 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import pins
|
||||
from esphome.const import CONF_INDOOR, CONF_WATCHDOG_THRESHOLD, \
|
||||
CONF_NOISE_LEVEL, CONF_SPIKE_REJECTION, CONF_LIGHTNING_THRESHOLD, \
|
||||
CONF_MASK_DISTURBER, CONF_DIV_RATIO, CONF_CAPACITANCE
|
||||
from esphome.const import (
|
||||
CONF_INDOOR,
|
||||
CONF_WATCHDOG_THRESHOLD,
|
||||
CONF_NOISE_LEVEL,
|
||||
CONF_SPIKE_REJECTION,
|
||||
CONF_LIGHTNING_THRESHOLD,
|
||||
CONF_MASK_DISTURBER,
|
||||
CONF_DIV_RATIO,
|
||||
CONF_CAPACITANCE,
|
||||
)
|
||||
from esphome.core import coroutine
|
||||
|
||||
AUTO_LOAD = ['sensor', 'binary_sensor']
|
||||
AUTO_LOAD = ["sensor", "binary_sensor"]
|
||||
MULTI_CONF = True
|
||||
|
||||
CONF_AS3935_ID = 'as3935_id'
|
||||
CONF_AS3935_ID = "as3935_id"
|
||||
|
||||
as3935_ns = cg.esphome_ns.namespace('as3935')
|
||||
AS3935 = as3935_ns.class_('AS3935Component', cg.Component)
|
||||
as3935_ns = cg.esphome_ns.namespace("as3935")
|
||||
AS3935 = as3935_ns.class_("AS3935Component", cg.Component)
|
||||
|
||||
CONF_IRQ_PIN = 'irq_pin'
|
||||
AS3935_SCHEMA = cv.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_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_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_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),
|
||||
})
|
||||
CONF_IRQ_PIN = "irq_pin"
|
||||
AS3935_SCHEMA = cv.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_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_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_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
|
||||
|
@@ -3,11 +3,13 @@ import esphome.config_validation as cv
|
||||
from esphome.components import binary_sensor
|
||||
from . import AS3935, CONF_AS3935_ID
|
||||
|
||||
DEPENDENCIES = ['as3935']
|
||||
DEPENDENCIES = ["as3935"]
|
||||
|
||||
CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend({
|
||||
cv.GenerateID(CONF_AS3935_ID): cv.use_id(AS3935),
|
||||
})
|
||||
CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(CONF_AS3935_ID): cv.use_id(AS3935),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
|
@@ -1,19 +1,30 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import sensor
|
||||
from esphome.const import CONF_DISTANCE, CONF_LIGHTNING_ENERGY, \
|
||||
UNIT_KILOMETER, UNIT_EMPTY, ICON_SIGNAL_DISTANCE_VARIANT, ICON_FLASH
|
||||
from esphome.const import (
|
||||
CONF_DISTANCE,
|
||||
CONF_LIGHTNING_ENERGY,
|
||||
DEVICE_CLASS_EMPTY,
|
||||
UNIT_KILOMETER,
|
||||
UNIT_EMPTY,
|
||||
ICON_SIGNAL_DISTANCE_VARIANT,
|
||||
ICON_FLASH,
|
||||
)
|
||||
from . import AS3935, CONF_AS3935_ID
|
||||
|
||||
DEPENDENCIES = ['as3935']
|
||||
DEPENDENCIES = ["as3935"]
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema({
|
||||
cv.GenerateID(CONF_AS3935_ID): cv.use_id(AS3935),
|
||||
cv.Optional(CONF_DISTANCE):
|
||||
sensor.sensor_schema(UNIT_KILOMETER, ICON_SIGNAL_DISTANCE_VARIANT, 1),
|
||||
cv.Optional(CONF_LIGHTNING_ENERGY):
|
||||
sensor.sensor_schema(UNIT_EMPTY, ICON_FLASH, 1),
|
||||
}).extend(cv.COMPONENT_SCHEMA)
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(CONF_AS3935_ID): cv.use_id(AS3935),
|
||||
cv.Optional(CONF_DISTANCE): sensor.sensor_schema(
|
||||
UNIT_KILOMETER, ICON_SIGNAL_DISTANCE_VARIANT, 1, DEVICE_CLASS_EMPTY
|
||||
),
|
||||
cv.Optional(CONF_LIGHTNING_ENERGY): sensor.sensor_schema(
|
||||
UNIT_EMPTY, ICON_FLASH, 1, DEVICE_CLASS_EMPTY
|
||||
),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
|
@@ -3,15 +3,21 @@ import esphome.config_validation as cv
|
||||
from esphome.components import as3935, i2c
|
||||
from esphome.const import CONF_ID
|
||||
|
||||
AUTO_LOAD = ['as3935']
|
||||
DEPENDENCIES = ['i2c']
|
||||
AUTO_LOAD = ["as3935"]
|
||||
DEPENDENCIES = ["i2c"]
|
||||
|
||||
as3935_i2c_ns = cg.esphome_ns.namespace('as3935_i2c')
|
||||
I2CAS3935 = as3935_i2c_ns.class_('I2CAS3935Component', as3935.AS3935, i2c.I2CDevice)
|
||||
as3935_i2c_ns = cg.esphome_ns.namespace("as3935_i2c")
|
||||
I2CAS3935 = as3935_i2c_ns.class_("I2CAS3935Component", as3935.AS3935, i2c.I2CDevice)
|
||||
|
||||
CONFIG_SCHEMA = cv.All(as3935.AS3935_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_id(I2CAS3935),
|
||||
}).extend(cv.COMPONENT_SCHEMA).extend(i2c.i2c_device_schema(0x03)))
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
as3935.AS3935_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(I2CAS3935),
|
||||
}
|
||||
)
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
.extend(i2c.i2c_device_schema(0x03))
|
||||
)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
|
@@ -3,15 +3,21 @@ import esphome.config_validation as cv
|
||||
from esphome.components import as3935, spi
|
||||
from esphome.const import CONF_ID
|
||||
|
||||
AUTO_LOAD = ['as3935']
|
||||
DEPENDENCIES = ['spi']
|
||||
AUTO_LOAD = ["as3935"]
|
||||
DEPENDENCIES = ["spi"]
|
||||
|
||||
as3935_spi_ns = cg.esphome_ns.namespace('as3935_spi')
|
||||
SPIAS3935 = as3935_spi_ns.class_('SPIAS3935Component', as3935.AS3935, spi.SPIDevice)
|
||||
as3935_spi_ns = cg.esphome_ns.namespace("as3935_spi")
|
||||
SPIAS3935 = as3935_spi_ns.class_("SPIAS3935Component", as3935.AS3935, spi.SPIDevice)
|
||||
|
||||
CONFIG_SCHEMA = cv.All(as3935.AS3935_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_id(SPIAS3935),
|
||||
}).extend(cv.COMPONENT_SCHEMA).extend(spi.spi_device_schema(cs_pin_required=True)))
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
as3935.AS3935_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(SPIAS3935),
|
||||
}
|
||||
)
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
.extend(spi.spi_device_schema(cs_pin_required=True))
|
||||
)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
|
@@ -2,14 +2,14 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.core import CORE, coroutine_with_priority
|
||||
|
||||
CODEOWNERS = ['@OttoWinter']
|
||||
CODEOWNERS = ["@OttoWinter"]
|
||||
|
||||
|
||||
@coroutine_with_priority(200.0)
|
||||
def to_code(config):
|
||||
if CORE.is_esp32:
|
||||
# https://github.com/OttoWinter/AsyncTCP/blob/master/library.json
|
||||
cg.add_library('AsyncTCP-esphome', '1.1.1')
|
||||
# https://github.com/esphome/AsyncTCP/blob/master/library.json
|
||||
cg.add_library("esphome/AsyncTCP-esphome", "1.2.2")
|
||||
elif CORE.is_esp8266:
|
||||
# https://github.com/OttoWinter/ESPAsyncTCP
|
||||
cg.add_library('ESPAsyncTCP-esphome', '1.2.3')
|
||||
cg.add_library("ESPAsyncTCP-esphome", "1.2.3")
|
||||
|
@@ -1,27 +1,54 @@
|
||||
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_LEVEL, CONF_BATTERY_VOLTAGE, CONF_MAC_ADDRESS, \
|
||||
CONF_HUMIDITY, CONF_TEMPERATURE, CONF_ID, UNIT_CELSIUS, UNIT_PERCENT, UNIT_VOLT, \
|
||||
ICON_BATTERY, ICON_THERMOMETER, ICON_WATER_PERCENT
|
||||
from esphome.const import (
|
||||
CONF_BATTERY_LEVEL,
|
||||
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,
|
||||
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')
|
||||
ATCMiThermometer = atc_mithermometer_ns.class_('ATCMiThermometer',
|
||||
esp32_ble_tracker.ESPBTDeviceListener,
|
||||
cg.Component)
|
||||
atc_mithermometer_ns = cg.esphome_ns.namespace("atc_mithermometer")
|
||||
ATCMiThermometer = atc_mithermometer_ns.class_(
|
||||
"ATCMiThermometer", esp32_ble_tracker.ESPBTDeviceListener, cg.Component
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema({
|
||||
cv.GenerateID(): cv.declare_id(ATCMiThermometer),
|
||||
cv.Required(CONF_MAC_ADDRESS): cv.mac_address,
|
||||
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 1),
|
||||
cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(UNIT_PERCENT, ICON_WATER_PERCENT, 0),
|
||||
cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema(UNIT_PERCENT, ICON_BATTERY, 0),
|
||||
cv.Optional(CONF_BATTERY_VOLTAGE): sensor.sensor_schema(UNIT_VOLT, ICON_BATTERY, 3),
|
||||
}).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA).extend(cv.COMPONENT_SCHEMA)
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(ATCMiThermometer),
|
||||
cv.Required(CONF_MAC_ADDRESS): cv.mac_address,
|
||||
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
|
||||
UNIT_CELSIUS, ICON_EMPTY, 1, DEVICE_CLASS_TEMPERATURE
|
||||
),
|
||||
cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
|
||||
UNIT_PERCENT, ICON_EMPTY, 0, DEVICE_CLASS_HUMIDITY
|
||||
),
|
||||
cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema(
|
||||
UNIT_PERCENT, ICON_EMPTY, 0, DEVICE_CLASS_BATTERY
|
||||
),
|
||||
cv.Optional(CONF_BATTERY_VOLTAGE): sensor.sensor_schema(
|
||||
UNIT_VOLT, ICON_EMPTY, 3, DEVICE_CLASS_VOLTAGE
|
||||
),
|
||||
}
|
||||
)
|
||||
.extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA)
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
|
@@ -58,6 +58,24 @@ void ATM90E32Component::update() {
|
||||
if (this->phase_[2].power_factor_sensor_ != nullptr) {
|
||||
this->phase_[2].power_factor_sensor_->publish_state(this->get_power_factor_c_());
|
||||
}
|
||||
if (this->phase_[0].forward_active_energy_sensor_ != nullptr) {
|
||||
this->phase_[0].forward_active_energy_sensor_->publish_state(this->get_forward_active_energy_a_());
|
||||
}
|
||||
if (this->phase_[1].forward_active_energy_sensor_ != nullptr) {
|
||||
this->phase_[1].forward_active_energy_sensor_->publish_state(this->get_forward_active_energy_b_());
|
||||
}
|
||||
if (this->phase_[2].forward_active_energy_sensor_ != nullptr) {
|
||||
this->phase_[2].forward_active_energy_sensor_->publish_state(this->get_forward_active_energy_c_());
|
||||
}
|
||||
if (this->phase_[0].reverse_active_energy_sensor_ != nullptr) {
|
||||
this->phase_[0].reverse_active_energy_sensor_->publish_state(this->get_reverse_active_energy_a_());
|
||||
}
|
||||
if (this->phase_[1].reverse_active_energy_sensor_ != nullptr) {
|
||||
this->phase_[1].reverse_active_energy_sensor_->publish_state(this->get_reverse_active_energy_b_());
|
||||
}
|
||||
if (this->phase_[2].reverse_active_energy_sensor_ != nullptr) {
|
||||
this->phase_[2].reverse_active_energy_sensor_->publish_state(this->get_reverse_active_energy_c_());
|
||||
}
|
||||
if (this->freq_sensor_ != nullptr) {
|
||||
this->freq_sensor_->publish_state(this->get_frequency_());
|
||||
}
|
||||
@@ -119,16 +137,22 @@ void ATM90E32Component::dump_config() {
|
||||
LOG_SENSOR(" ", "Power A", this->phase_[0].power_sensor_);
|
||||
LOG_SENSOR(" ", "Reactive Power A", this->phase_[0].reactive_power_sensor_);
|
||||
LOG_SENSOR(" ", "PF A", this->phase_[0].power_factor_sensor_);
|
||||
LOG_SENSOR(" ", "Active Forward Energy A", this->phase_[0].forward_active_energy_sensor_);
|
||||
LOG_SENSOR(" ", "Active Reverse Energy A", this->phase_[0].reverse_active_energy_sensor_);
|
||||
LOG_SENSOR(" ", "Voltage B", this->phase_[1].voltage_sensor_);
|
||||
LOG_SENSOR(" ", "Current B", this->phase_[1].current_sensor_);
|
||||
LOG_SENSOR(" ", "Power B", this->phase_[1].power_sensor_);
|
||||
LOG_SENSOR(" ", "Reactive Power B", this->phase_[1].reactive_power_sensor_);
|
||||
LOG_SENSOR(" ", "PF B", this->phase_[1].power_factor_sensor_);
|
||||
LOG_SENSOR(" ", "Active Forward Energy B", this->phase_[1].forward_active_energy_sensor_);
|
||||
LOG_SENSOR(" ", "Active Reverse Energy B", this->phase_[1].reverse_active_energy_sensor_);
|
||||
LOG_SENSOR(" ", "Voltage C", this->phase_[2].voltage_sensor_);
|
||||
LOG_SENSOR(" ", "Current C", this->phase_[2].current_sensor_);
|
||||
LOG_SENSOR(" ", "Power C", this->phase_[2].power_sensor_);
|
||||
LOG_SENSOR(" ", "Reactive Power C", this->phase_[2].reactive_power_sensor_);
|
||||
LOG_SENSOR(" ", "PF C", this->phase_[2].power_factor_sensor_);
|
||||
LOG_SENSOR(" ", "Active Forward Energy C", this->phase_[2].forward_active_energy_sensor_);
|
||||
LOG_SENSOR(" ", "Active Reverse Energy C", this->phase_[2].reverse_active_energy_sensor_);
|
||||
LOG_SENSOR(" ", "Frequency", this->freq_sensor_);
|
||||
LOG_SENSOR(" ", "Chip Temp", this->chip_temperature_sensor_);
|
||||
}
|
||||
@@ -239,6 +263,30 @@ float ATM90E32Component::get_power_factor_c_() {
|
||||
int16_t pf = this->read16_(ATM90E32_REGISTER_PFMEANC);
|
||||
return (float) pf / 1000;
|
||||
}
|
||||
float ATM90E32Component::get_forward_active_energy_a_() {
|
||||
uint16_t val = this->read16_(ATM90E32_REGISTER_APENERGYA);
|
||||
return (float) val * 10 / 3200; // convert register value to WattHours
|
||||
}
|
||||
float ATM90E32Component::get_forward_active_energy_b_() {
|
||||
uint16_t val = this->read16_(ATM90E32_REGISTER_APENERGYB);
|
||||
return (float) val * 10 / 3200;
|
||||
}
|
||||
float ATM90E32Component::get_forward_active_energy_c_() {
|
||||
uint16_t val = this->read16_(ATM90E32_REGISTER_APENERGYC);
|
||||
return (float) val * 10 / 3200;
|
||||
}
|
||||
float ATM90E32Component::get_reverse_active_energy_a_() {
|
||||
uint16_t val = this->read16_(ATM90E32_REGISTER_ANENERGYA);
|
||||
return (float) val * 10 / 3200;
|
||||
}
|
||||
float ATM90E32Component::get_reverse_active_energy_b_() {
|
||||
uint16_t val = this->read16_(ATM90E32_REGISTER_ANENERGYB);
|
||||
return (float) val * 10 / 3200;
|
||||
}
|
||||
float ATM90E32Component::get_reverse_active_energy_c_() {
|
||||
uint16_t val = this->read16_(ATM90E32_REGISTER_ANENERGYC);
|
||||
return (float) val * 10 / 3200;
|
||||
}
|
||||
float ATM90E32Component::get_frequency_() {
|
||||
uint16_t freq = this->read16_(ATM90E32_REGISTER_FREQ);
|
||||
return (float) freq / 100;
|
||||
|
@@ -20,6 +20,12 @@ class ATM90E32Component : public PollingComponent,
|
||||
void set_current_sensor(int phase, sensor::Sensor *obj) { this->phase_[phase].current_sensor_ = obj; }
|
||||
void set_power_sensor(int phase, sensor::Sensor *obj) { this->phase_[phase].power_sensor_ = obj; }
|
||||
void set_reactive_power_sensor(int phase, sensor::Sensor *obj) { this->phase_[phase].reactive_power_sensor_ = obj; }
|
||||
void set_forward_active_energy_sensor(int phase, sensor::Sensor *obj) {
|
||||
this->phase_[phase].forward_active_energy_sensor_ = obj;
|
||||
}
|
||||
void set_reverse_active_energy_sensor(int phase, sensor::Sensor *obj) {
|
||||
this->phase_[phase].reverse_active_energy_sensor_ = obj;
|
||||
}
|
||||
void set_power_factor_sensor(int phase, sensor::Sensor *obj) { this->phase_[phase].power_factor_sensor_ = obj; }
|
||||
void set_volt_gain(int phase, uint16_t gain) { this->phase_[phase].volt_gain_ = gain; }
|
||||
void set_ct_gain(int phase, uint16_t gain) { this->phase_[phase].ct_gain_ = gain; }
|
||||
@@ -52,6 +58,12 @@ class ATM90E32Component : public PollingComponent,
|
||||
float get_power_factor_a_();
|
||||
float get_power_factor_b_();
|
||||
float get_power_factor_c_();
|
||||
float get_forward_active_energy_a_();
|
||||
float get_forward_active_energy_b_();
|
||||
float get_forward_active_energy_c_();
|
||||
float get_reverse_active_energy_a_();
|
||||
float get_reverse_active_energy_b_();
|
||||
float get_reverse_active_energy_c_();
|
||||
float get_frequency_();
|
||||
float get_chip_temperature_();
|
||||
|
||||
@@ -63,6 +75,8 @@ class ATM90E32Component : public PollingComponent,
|
||||
sensor::Sensor *power_sensor_{nullptr};
|
||||
sensor::Sensor *reactive_power_sensor_{nullptr};
|
||||
sensor::Sensor *power_factor_sensor_{nullptr};
|
||||
sensor::Sensor *forward_active_energy_sensor_{nullptr};
|
||||
sensor::Sensor *reverse_active_energy_sensor_{nullptr};
|
||||
} phase_[3];
|
||||
sensor::Sensor *freq_sensor_{nullptr};
|
||||
sensor::Sensor *chip_temperature_sensor_{nullptr};
|
||||
|
@@ -1,61 +1,116 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import sensor, spi
|
||||
from esphome.const import \
|
||||
CONF_ID, CONF_VOLTAGE, CONF_CURRENT, CONF_POWER, CONF_POWER_FACTOR, CONF_FREQUENCY, \
|
||||
ICON_FLASH, ICON_LIGHTBULB, ICON_CURRENT_AC, ICON_THERMOMETER, \
|
||||
UNIT_HERTZ, UNIT_VOLT, UNIT_AMPERE, UNIT_WATT, UNIT_EMPTY, UNIT_CELSIUS, UNIT_VOLT_AMPS_REACTIVE
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
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,
|
||||
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_B = 'phase_b'
|
||||
CONF_PHASE_C = 'phase_c'
|
||||
CONF_PHASE_A = "phase_a"
|
||||
CONF_PHASE_B = "phase_b"
|
||||
CONF_PHASE_C = "phase_c"
|
||||
|
||||
CONF_REACTIVE_POWER = 'reactive_power'
|
||||
CONF_LINE_FREQUENCY = 'line_frequency'
|
||||
CONF_CHIP_TEMPERATURE = 'chip_temperature'
|
||||
CONF_GAIN_PGA = 'gain_pga'
|
||||
CONF_CURRENT_PHASES = 'current_phases'
|
||||
CONF_GAIN_VOLTAGE = 'gain_voltage'
|
||||
CONF_GAIN_CT = 'gain_ct'
|
||||
CONF_REACTIVE_POWER = "reactive_power"
|
||||
CONF_LINE_FREQUENCY = "line_frequency"
|
||||
CONF_CHIP_TEMPERATURE = "chip_temperature"
|
||||
CONF_GAIN_PGA = "gain_pga"
|
||||
CONF_CURRENT_PHASES = "current_phases"
|
||||
CONF_GAIN_VOLTAGE = "gain_voltage"
|
||||
CONF_GAIN_CT = "gain_ct"
|
||||
LINE_FREQS = {
|
||||
'50HZ': 50,
|
||||
'60HZ': 60,
|
||||
"50HZ": 50,
|
||||
"60HZ": 60,
|
||||
}
|
||||
CURRENT_PHASES = {
|
||||
'2': 2,
|
||||
'3': 3,
|
||||
"2": 2,
|
||||
"3": 3,
|
||||
}
|
||||
PGA_GAINS = {
|
||||
'1X': 0x0,
|
||||
'2X': 0x15,
|
||||
'4X': 0x2A,
|
||||
"1X": 0x0,
|
||||
"2X": 0x15,
|
||||
"4X": 0x2A,
|
||||
}
|
||||
|
||||
atm90e32_ns = cg.esphome_ns.namespace('atm90e32')
|
||||
ATM90E32Component = atm90e32_ns.class_('ATM90E32Component', cg.PollingComponent, spi.SPIDevice)
|
||||
atm90e32_ns = cg.esphome_ns.namespace("atm90e32")
|
||||
ATM90E32Component = atm90e32_ns.class_(
|
||||
"ATM90E32Component", cg.PollingComponent, spi.SPIDevice
|
||||
)
|
||||
|
||||
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_POWER): sensor.sensor_schema(UNIT_WATT, ICON_FLASH, 2),
|
||||
cv.Optional(CONF_REACTIVE_POWER): sensor.sensor_schema(UNIT_VOLT_AMPS_REACTIVE,
|
||||
ICON_LIGHTBULB, 2),
|
||||
cv.Optional(CONF_POWER_FACTOR): sensor.sensor_schema(UNIT_EMPTY, ICON_FLASH, 2),
|
||||
cv.Optional(CONF_GAIN_VOLTAGE, default=7305): cv.uint16_t,
|
||||
cv.Optional(CONF_GAIN_CT, default=27961): cv.uint16_t,
|
||||
})
|
||||
ATM90E32_PHASE_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_VOLTAGE): sensor.sensor_schema(
|
||||
UNIT_VOLT, ICON_EMPTY, 2, DEVICE_CLASS_VOLTAGE
|
||||
),
|
||||
cv.Optional(CONF_CURRENT): sensor.sensor_schema(
|
||||
UNIT_AMPERE, ICON_EMPTY, 2, DEVICE_CLASS_CURRENT
|
||||
),
|
||||
cv.Optional(CONF_POWER): sensor.sensor_schema(
|
||||
UNIT_WATT, ICON_EMPTY, 2, DEVICE_CLASS_POWER
|
||||
),
|
||||
cv.Optional(CONF_REACTIVE_POWER): sensor.sensor_schema(
|
||||
UNIT_VOLT_AMPS_REACTIVE, ICON_LIGHTBULB, 2, DEVICE_CLASS_EMPTY
|
||||
),
|
||||
cv.Optional(CONF_POWER_FACTOR): sensor.sensor_schema(
|
||||
UNIT_EMPTY, ICON_EMPTY, 2, DEVICE_CLASS_POWER_FACTOR
|
||||
),
|
||||
cv.Optional(CONF_FORWARD_ACTIVE_ENERGY): sensor.sensor_schema(
|
||||
UNIT_WATT_HOURS, ICON_EMPTY, 2, DEVICE_CLASS_ENERGY
|
||||
),
|
||||
cv.Optional(CONF_REVERSE_ACTIVE_ENERGY): sensor.sensor_schema(
|
||||
UNIT_WATT_HOURS, ICON_EMPTY, 2, DEVICE_CLASS_ENERGY
|
||||
),
|
||||
cv.Optional(CONF_GAIN_VOLTAGE, default=7305): cv.uint16_t,
|
||||
cv.Optional(CONF_GAIN_CT, default=27961): cv.uint16_t,
|
||||
}
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema({
|
||||
cv.GenerateID(): cv.declare_id(ATM90E32Component),
|
||||
cv.Optional(CONF_PHASE_A): ATM90E32_PHASE_SCHEMA,
|
||||
cv.Optional(CONF_PHASE_B): ATM90E32_PHASE_SCHEMA,
|
||||
cv.Optional(CONF_PHASE_C): ATM90E32_PHASE_SCHEMA,
|
||||
cv.Optional(CONF_FREQUENCY): sensor.sensor_schema(UNIT_HERTZ, ICON_CURRENT_AC, 1),
|
||||
cv.Optional(CONF_CHIP_TEMPERATURE): sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 1),
|
||||
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())
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(ATM90E32Component),
|
||||
cv.Optional(CONF_PHASE_A): ATM90E32_PHASE_SCHEMA,
|
||||
cv.Optional(CONF_PHASE_B): ATM90E32_PHASE_SCHEMA,
|
||||
cv.Optional(CONF_PHASE_C): ATM90E32_PHASE_SCHEMA,
|
||||
cv.Optional(CONF_FREQUENCY): sensor.sensor_schema(
|
||||
UNIT_HERTZ, ICON_CURRENT_AC, 1, DEVICE_CLASS_EMPTY
|
||||
),
|
||||
cv.Optional(CONF_CHIP_TEMPERATURE): sensor.sensor_schema(
|
||||
UNIT_CELSIUS, ICON_EMPTY, 1, DEVICE_CLASS_TEMPERATURE
|
||||
),
|
||||
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):
|
||||
@@ -84,6 +139,12 @@ def to_code(config):
|
||||
if CONF_POWER_FACTOR in conf:
|
||||
sens = yield sensor.new_sensor(conf[CONF_POWER_FACTOR])
|
||||
cg.add(var.set_power_factor_sensor(i, sens))
|
||||
if CONF_FORWARD_ACTIVE_ENERGY in conf:
|
||||
sens = yield 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 = yield sensor.new_sensor(conf[CONF_REVERSE_ACTIVE_ENERGY])
|
||||
cg.add(var.set_reverse_active_energy_sensor(i, sens))
|
||||
if CONF_FREQUENCY in config:
|
||||
sens = yield sensor.new_sensor(config[CONF_FREQUENCY])
|
||||
cg.add(var.set_freq_sensor(sens))
|
||||
|
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
|
68
esphome/components/b_parasite/sensor.py
Normal file
68
esphome/components/b_parasite/sensor.py
Normal file
@@ -0,0 +1,68 @@
|
||||
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,
|
||||
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
|
||||
),
|
||||
cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
|
||||
UNIT_PERCENT, ICON_EMPTY, 1, DEVICE_CLASS_HUMIDITY
|
||||
),
|
||||
cv.Optional(CONF_BATTERY_VOLTAGE): sensor.sensor_schema(
|
||||
UNIT_VOLT, ICON_EMPTY, 3, DEVICE_CLASS_VOLTAGE
|
||||
),
|
||||
cv.Optional(CONF_MOISTURE): sensor.sensor_schema(
|
||||
UNIT_PERCENT, ICON_EMPTY, 1, DEVICE_CLASS_HUMIDITY
|
||||
),
|
||||
}
|
||||
)
|
||||
.extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA)
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
yield cg.register_component(var, config)
|
||||
yield esp32_ble_tracker.register_ble_device(var, config)
|
||||
|
||||
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 = yield sensor.new_sensor(config[config_key])
|
||||
cg.add(setter(sens))
|
@@ -1 +1 @@
|
||||
CODEOWNERS = ['@OttoWinter']
|
||||
CODEOWNERS = ["@OttoWinter"]
|
||||
|
@@ -2,27 +2,41 @@ import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import automation
|
||||
from esphome.components import climate, sensor
|
||||
from esphome.const import CONF_AWAY_CONFIG, CONF_COOL_ACTION, \
|
||||
CONF_DEFAULT_TARGET_TEMPERATURE_HIGH, CONF_DEFAULT_TARGET_TEMPERATURE_LOW, CONF_HEAT_ACTION, \
|
||||
CONF_ID, CONF_IDLE_ACTION, CONF_SENSOR
|
||||
from esphome.const import (
|
||||
CONF_AWAY_CONFIG,
|
||||
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')
|
||||
BangBangClimate = bang_bang_ns.class_('BangBangClimate', climate.Climate, cg.Component)
|
||||
BangBangClimateTargetTempConfig = bang_bang_ns.struct('BangBangClimateTargetTempConfig')
|
||||
bang_bang_ns = cg.esphome_ns.namespace("bang_bang")
|
||||
BangBangClimate = bang_bang_ns.class_("BangBangClimate", climate.Climate, cg.Component)
|
||||
BangBangClimateTargetTempConfig = bang_bang_ns.struct("BangBangClimateTargetTempConfig")
|
||||
|
||||
CONFIG_SCHEMA = cv.All(climate.CLIMATE_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_id(BangBangClimate),
|
||||
cv.Required(CONF_SENSOR): cv.use_id(sensor.Sensor),
|
||||
cv.Required(CONF_DEFAULT_TARGET_TEMPERATURE_LOW): cv.temperature,
|
||||
cv.Required(CONF_DEFAULT_TARGET_TEMPERATURE_HIGH): cv.temperature,
|
||||
cv.Required(CONF_IDLE_ACTION): automation.validate_automation(single=True),
|
||||
cv.Optional(CONF_COOL_ACTION): automation.validate_automation(single=True),
|
||||
cv.Optional(CONF_HEAT_ACTION): automation.validate_automation(single=True),
|
||||
cv.Optional(CONF_AWAY_CONFIG): cv.Schema({
|
||||
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))
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
climate.CLIMATE_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(BangBangClimate),
|
||||
cv.Required(CONF_SENSOR): cv.use_id(sensor.Sensor),
|
||||
cv.Required(CONF_DEFAULT_TARGET_TEMPERATURE_LOW): cv.temperature,
|
||||
cv.Required(CONF_DEFAULT_TARGET_TEMPERATURE_HIGH): cv.temperature,
|
||||
cv.Required(CONF_IDLE_ACTION): automation.validate_automation(single=True),
|
||||
cv.Optional(CONF_COOL_ACTION): automation.validate_automation(single=True),
|
||||
cv.Optional(CONF_HEAT_ACTION): automation.validate_automation(single=True),
|
||||
cv.Optional(CONF_AWAY_CONFIG): cv.Schema(
|
||||
{
|
||||
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):
|
||||
@@ -35,23 +49,29 @@ def to_code(config):
|
||||
|
||||
normal_config = BangBangClimateTargetTempConfig(
|
||||
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))
|
||||
|
||||
yield automation.build_automation(var.get_idle_trigger(), [], config[CONF_IDLE_ACTION])
|
||||
yield automation.build_automation(
|
||||
var.get_idle_trigger(), [], config[CONF_IDLE_ACTION]
|
||||
)
|
||||
|
||||
if CONF_COOL_ACTION in config:
|
||||
yield automation.build_automation(var.get_cool_trigger(), [], config[CONF_COOL_ACTION])
|
||||
yield automation.build_automation(
|
||||
var.get_cool_trigger(), [], config[CONF_COOL_ACTION]
|
||||
)
|
||||
cg.add(var.set_supports_cool(True))
|
||||
if CONF_HEAT_ACTION in config:
|
||||
yield automation.build_automation(var.get_heat_trigger(), [], config[CONF_HEAT_ACTION])
|
||||
yield automation.build_automation(
|
||||
var.get_heat_trigger(), [], config[CONF_HEAT_ACTION]
|
||||
)
|
||||
cg.add(var.set_supports_heat(True))
|
||||
|
||||
if CONF_AWAY_CONFIG in config:
|
||||
away = config[CONF_AWAY_CONFIG]
|
||||
away_config = BangBangClimateTargetTempConfig(
|
||||
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))
|
||||
|
@@ -17,8 +17,8 @@ void BH1750Sensor::setup() {
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t mtreg_hi = (this->measurement_time_ >> 5) & 0b111;
|
||||
uint8_t mtreg_lo = (this->measurement_time_ >> 0) & 0b11111;
|
||||
uint8_t mtreg_hi = (this->measurement_duration_ >> 5) & 0b111;
|
||||
uint8_t mtreg_lo = (this->measurement_duration_ >> 0) & 0b11111;
|
||||
this->write_bytes(BH1750_COMMAND_MT_REG_HI | mtreg_hi, nullptr, 0);
|
||||
this->write_bytes(BH1750_COMMAND_MT_REG_LO | mtreg_lo, nullptr, 0);
|
||||
}
|
||||
@@ -77,7 +77,7 @@ void BH1750Sensor::read_data_() {
|
||||
}
|
||||
|
||||
float lx = float(raw_value) / 1.2f;
|
||||
lx *= 69.0f / this->measurement_time_;
|
||||
lx *= 69.0f / this->measurement_duration_;
|
||||
ESP_LOGD(TAG, "'%s': Got illuminance=%.1flx", this->get_name().c_str(), lx);
|
||||
this->publish_state(lx);
|
||||
this->status_clear_warning();
|
||||
|
@@ -28,7 +28,7 @@ class BH1750Sensor : public sensor::Sensor, public PollingComponent, public i2c:
|
||||
* @param resolution The new resolution of the sensor.
|
||||
*/
|
||||
void set_resolution(BH1750Resolution resolution);
|
||||
void set_measurement_time(uint8_t measurement_time) { measurement_time_ = measurement_time; }
|
||||
void set_measurement_duration(uint8_t measurement_duration) { measurement_duration_ = measurement_duration; }
|
||||
|
||||
// ========== INTERNAL METHODS ==========
|
||||
// (In most use cases you won't need these)
|
||||
@@ -41,7 +41,7 @@ class BH1750Sensor : public sensor::Sensor, public PollingComponent, public i2c:
|
||||
void read_data_();
|
||||
|
||||
BH1750Resolution resolution_{BH1750_RESOLUTION_0P5_LX};
|
||||
uint8_t measurement_time_;
|
||||
uint8_t measurement_duration_;
|
||||
};
|
||||
|
||||
} // namespace bh1750
|
||||
|
@@ -1,26 +1,49 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
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,
|
||||
UNIT_LUX,
|
||||
CONF_MEASUREMENT_DURATION,
|
||||
)
|
||||
|
||||
DEPENDENCIES = ['i2c']
|
||||
DEPENDENCIES = ["i2c"]
|
||||
|
||||
bh1750_ns = cg.esphome_ns.namespace('bh1750')
|
||||
BH1750Resolution = bh1750_ns.enum('BH1750Resolution')
|
||||
bh1750_ns = cg.esphome_ns.namespace("bh1750")
|
||||
BH1750Resolution = bh1750_ns.enum("BH1750Resolution")
|
||||
BH1750_RESOLUTIONS = {
|
||||
4.0: BH1750Resolution.BH1750_RESOLUTION_4P0_LX,
|
||||
1.0: BH1750Resolution.BH1750_RESOLUTION_1P0_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'
|
||||
CONFIG_SCHEMA = sensor.sensor_schema(UNIT_LUX, ICON_BRIGHTNESS_5, 1).extend({
|
||||
cv.GenerateID(): cv.declare_id(BH1750Sensor),
|
||||
cv.Optional(CONF_RESOLUTION, default=0.5): cv.enum(BH1750_RESOLUTIONS, float=True),
|
||||
cv.Optional(CONF_MEASUREMENT_TIME, default=69): cv.int_range(min=31, max=254),
|
||||
}).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x23))
|
||||
CONF_MEASUREMENT_TIME = "measurement_time"
|
||||
CONFIG_SCHEMA = (
|
||||
sensor.sensor_schema(UNIT_LUX, ICON_EMPTY, 1, DEVICE_CLASS_ILLUMINANCE)
|
||||
.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):
|
||||
@@ -30,4 +53,4 @@ def to_code(config):
|
||||
yield i2c.register_i2c_device(var, config)
|
||||
|
||||
cg.add(var.set_resolution(config[CONF_RESOLUTION]))
|
||||
cg.add(var.set_measurement_time(config[CONF_MEASUREMENT_TIME]))
|
||||
cg.add(var.set_measurement_duration(config[CONF_MEASUREMENT_DURATION]))
|
||||
|
@@ -1,3 +1,3 @@
|
||||
import esphome.codegen as cg
|
||||
|
||||
binary_ns = cg.esphome_ns.namespace('binary')
|
||||
binary_ns = cg.esphome_ns.namespace("binary")
|
||||
|
@@ -1,18 +1,24 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import fan, output
|
||||
from esphome.const import CONF_DIRECTION_OUTPUT, CONF_OSCILLATION_OUTPUT, \
|
||||
CONF_OUTPUT, CONF_OUTPUT_ID
|
||||
from esphome.const import (
|
||||
CONF_DIRECTION_OUTPUT,
|
||||
CONF_OSCILLATION_OUTPUT,
|
||||
CONF_OUTPUT,
|
||||
CONF_OUTPUT_ID,
|
||||
)
|
||||
from .. import binary_ns
|
||||
|
||||
BinaryFan = binary_ns.class_('BinaryFan', cg.Component)
|
||||
BinaryFan = binary_ns.class_("BinaryFan", cg.Component)
|
||||
|
||||
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.Optional(CONF_DIRECTION_OUTPUT): cv.use_id(output.BinaryOutput),
|
||||
cv.Optional(CONF_OSCILLATION_OUTPUT): cv.use_id(output.BinaryOutput),
|
||||
}).extend(cv.COMPONENT_SCHEMA)
|
||||
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.Optional(CONF_DIRECTION_OUTPUT): cv.use_id(output.BinaryOutput),
|
||||
cv.Optional(CONF_OSCILLATION_OUTPUT): cv.use_id(output.BinaryOutput),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
|
@@ -16,7 +16,7 @@ void binary::BinaryFan::dump_config() {
|
||||
}
|
||||
}
|
||||
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_->add_on_state_callback([this]() { this->next_update_ = true; });
|
||||
}
|
||||
|
@@ -4,12 +4,14 @@ from esphome.components import light, output
|
||||
from esphome.const import CONF_OUTPUT_ID, CONF_OUTPUT
|
||||
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({
|
||||
cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(BinaryLightOutput),
|
||||
cv.Required(CONF_OUTPUT): cv.use_id(output.BinaryOutput),
|
||||
})
|
||||
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),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
|
@@ -3,131 +3,214 @@ import esphome.config_validation as cv
|
||||
from esphome import automation, core
|
||||
from esphome.automation import Condition, maybe_simple_id
|
||||
from esphome.components import mqtt
|
||||
from esphome.const import CONF_DEVICE_CLASS, CONF_FILTERS, \
|
||||
CONF_ID, 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
|
||||
from esphome.const import (
|
||||
CONF_DEVICE_CLASS,
|
||||
CONF_FILTERS,
|
||||
CONF_ID,
|
||||
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, coroutine_with_priority
|
||||
from esphome.util import Registry
|
||||
|
||||
CODEOWNERS = ['@esphome/core']
|
||||
CODEOWNERS = ["@esphome/core"]
|
||||
DEVICE_CLASSES = [
|
||||
'', 'battery', 'cold', 'connectivity', 'door', 'garage_door', 'gas',
|
||||
'heat', 'light', 'lock', 'moisture', 'motion', 'moving', 'occupancy',
|
||||
'opening', 'plug', 'power', 'presence', 'problem', 'safety', 'smoke',
|
||||
'sound', 'vibration', 'window'
|
||||
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,
|
||||
]
|
||||
|
||||
IS_PLATFORM_COMPONENT = True
|
||||
|
||||
binary_sensor_ns = cg.esphome_ns.namespace('binary_sensor')
|
||||
BinarySensor = binary_sensor_ns.class_('BinarySensor', cg.Nameable)
|
||||
BinarySensorInitiallyOff = binary_sensor_ns.class_('BinarySensorInitiallyOff', BinarySensor)
|
||||
BinarySensorPtr = BinarySensor.operator('ptr')
|
||||
binary_sensor_ns = cg.esphome_ns.namespace("binary_sensor")
|
||||
BinarySensor = binary_sensor_ns.class_("BinarySensor", cg.Nameable)
|
||||
BinarySensorInitiallyOff = binary_sensor_ns.class_(
|
||||
"BinarySensorInitiallyOff", BinarySensor
|
||||
)
|
||||
BinarySensorPtr = BinarySensor.operator("ptr")
|
||||
|
||||
# Triggers
|
||||
PressTrigger = binary_sensor_ns.class_('PressTrigger', automation.Trigger.template())
|
||||
ReleaseTrigger = binary_sensor_ns.class_('ReleaseTrigger', automation.Trigger.template())
|
||||
ClickTrigger = binary_sensor_ns.class_('ClickTrigger', automation.Trigger.template())
|
||||
DoubleClickTrigger = binary_sensor_ns.class_('DoubleClickTrigger', automation.Trigger.template())
|
||||
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)
|
||||
PressTrigger = binary_sensor_ns.class_("PressTrigger", automation.Trigger.template())
|
||||
ReleaseTrigger = binary_sensor_ns.class_(
|
||||
"ReleaseTrigger", automation.Trigger.template()
|
||||
)
|
||||
ClickTrigger = binary_sensor_ns.class_("ClickTrigger", automation.Trigger.template())
|
||||
DoubleClickTrigger = binary_sensor_ns.class_(
|
||||
"DoubleClickTrigger", automation.Trigger.template()
|
||||
)
|
||||
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
|
||||
BinarySensorCondition = binary_sensor_ns.class_('BinarySensorCondition', Condition)
|
||||
BinarySensorCondition = binary_sensor_ns.class_("BinarySensorCondition", Condition)
|
||||
|
||||
# Filters
|
||||
Filter = binary_sensor_ns.class_('Filter')
|
||||
DelayedOnOffFilter = binary_sensor_ns.class_('DelayedOnOffFilter', Filter, cg.Component)
|
||||
DelayedOnFilter = binary_sensor_ns.class_('DelayedOnFilter', Filter, cg.Component)
|
||||
DelayedOffFilter = binary_sensor_ns.class_('DelayedOffFilter', Filter, cg.Component)
|
||||
InvertFilter = binary_sensor_ns.class_('InvertFilter', Filter)
|
||||
LambdaFilter = binary_sensor_ns.class_('LambdaFilter', Filter)
|
||||
Filter = binary_sensor_ns.class_("Filter")
|
||||
DelayedOnOffFilter = binary_sensor_ns.class_("DelayedOnOffFilter", Filter, cg.Component)
|
||||
DelayedOnFilter = binary_sensor_ns.class_("DelayedOnFilter", Filter, cg.Component)
|
||||
DelayedOffFilter = binary_sensor_ns.class_("DelayedOffFilter", Filter, cg.Component)
|
||||
InvertFilter = binary_sensor_ns.class_("InvertFilter", Filter)
|
||||
LambdaFilter = binary_sensor_ns.class_("LambdaFilter", Filter)
|
||||
|
||||
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):
|
||||
yield cg.new_Pvariable(filter_id)
|
||||
|
||||
|
||||
@FILTER_REGISTRY.register('delayed_on_off', DelayedOnOffFilter,
|
||||
cv.positive_time_period_milliseconds)
|
||||
@FILTER_REGISTRY.register(
|
||||
"delayed_on_off", DelayedOnOffFilter, cv.positive_time_period_milliseconds
|
||||
)
|
||||
def delayed_on_off_filter_to_code(config, filter_id):
|
||||
var = cg.new_Pvariable(filter_id, config)
|
||||
yield cg.register_component(var, {})
|
||||
yield var
|
||||
|
||||
|
||||
@FILTER_REGISTRY.register('delayed_on', DelayedOnFilter,
|
||||
cv.positive_time_period_milliseconds)
|
||||
@FILTER_REGISTRY.register(
|
||||
"delayed_on", DelayedOnFilter, cv.positive_time_period_milliseconds
|
||||
)
|
||||
def delayed_on_filter_to_code(config, filter_id):
|
||||
var = cg.new_Pvariable(filter_id, config)
|
||||
yield cg.register_component(var, {})
|
||||
yield var
|
||||
|
||||
|
||||
@FILTER_REGISTRY.register('delayed_off', DelayedOffFilter, cv.positive_time_period_milliseconds)
|
||||
@FILTER_REGISTRY.register(
|
||||
"delayed_off", DelayedOffFilter, cv.positive_time_period_milliseconds
|
||||
)
|
||||
def delayed_off_filter_to_code(config, filter_id):
|
||||
var = cg.new_Pvariable(filter_id, config)
|
||||
yield cg.register_component(var, {})
|
||||
yield var
|
||||
|
||||
|
||||
@FILTER_REGISTRY.register('lambda', LambdaFilter, cv.returning_lambda)
|
||||
@FILTER_REGISTRY.register("lambda", LambdaFilter, cv.returning_lambda)
|
||||
def lambda_filter_to_code(config, filter_id):
|
||||
lambda_ = yield cg.process_lambda(config, [(bool, 'x')], return_type=cg.optional.template(bool))
|
||||
lambda_ = yield cg.process_lambda(
|
||||
config, [(bool, "x")], return_type=cg.optional.template(bool)
|
||||
)
|
||||
yield 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,
|
||||
})
|
||||
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):
|
||||
if not isinstance(value, str):
|
||||
return value
|
||||
|
||||
parts = value.lower().split(' ')
|
||||
parts = value.lower().split(" ")
|
||||
if len(parts) != 5:
|
||||
raise cv.Invalid("Multi click timing grammar consists of exactly 5 words, not {}"
|
||||
"".format(len(parts)))
|
||||
raise cv.Invalid(
|
||||
"Multi click timing grammar consists of exactly 5 words, not {}"
|
||||
"".format(len(parts))
|
||||
)
|
||||
try:
|
||||
state = cv.boolean(parts[0])
|
||||
except cv.Invalid:
|
||||
# pylint: disable=raise-missing-from
|
||||
raise cv.Invalid("First word must either be ON or OFF, not {}".format(parts[0]))
|
||||
|
||||
if parts[1] != 'for':
|
||||
if parts[1] != "for":
|
||||
raise cv.Invalid("Second word must be 'for', got {}".format(parts[1]))
|
||||
|
||||
if parts[2] == 'at':
|
||||
if parts[3] == 'least':
|
||||
if parts[2] == "at":
|
||||
if parts[3] == "least":
|
||||
key = CONF_MIN_LENGTH
|
||||
elif parts[3] == 'most':
|
||||
elif parts[3] == "most":
|
||||
key = CONF_MAX_LENGTH
|
||||
else:
|
||||
raise cv.Invalid("Third word after at must either be 'least' or 'most', got {}"
|
||||
"".format(parts[3]))
|
||||
raise cv.Invalid(
|
||||
"Third word after at must either be 'least' or 'most', got {}"
|
||||
"".format(parts[3])
|
||||
)
|
||||
try:
|
||||
length = cv.positive_time_period_milliseconds(parts[4])
|
||||
except cv.Invalid as err:
|
||||
raise cv.Invalid(f"Multi Click Grammar Parsing length failed: {err}")
|
||||
return {
|
||||
CONF_STATE: state,
|
||||
key: str(length)
|
||||
}
|
||||
return {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'")
|
||||
|
||||
try:
|
||||
@@ -143,7 +226,7 @@ def parse_multi_click_timing_str(value):
|
||||
return {
|
||||
CONF_STATE: state,
|
||||
CONF_MIN_LENGTH: str(min_length),
|
||||
CONF_MAX_LENGTH: str(max_length)
|
||||
CONF_MAX_LENGTH: str(max_length),
|
||||
}
|
||||
|
||||
|
||||
@@ -163,11 +246,15 @@ def validate_multi_click_timing(value):
|
||||
|
||||
new_state = v_.get(CONF_STATE, not state)
|
||||
if new_state == state:
|
||||
raise cv.Invalid("Timings must have alternating state. Indices {} and {} have "
|
||||
"the same state {}".format(i, i + 1, state))
|
||||
raise cv.Invalid(
|
||||
"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:
|
||||
raise cv.Invalid("Max length ({}) must be larger than min length ({})."
|
||||
"".format(max_length, min_length))
|
||||
raise cv.Invalid(
|
||||
"Max length ({}) must be larger than min length ({})."
|
||||
"".format(max_length, min_length)
|
||||
)
|
||||
|
||||
state = new_state
|
||||
tim = {
|
||||
@@ -180,46 +267,71 @@ def validate_multi_click_timing(value):
|
||||
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({
|
||||
cv.GenerateID(): cv.declare_id(BinarySensor),
|
||||
cv.OnlyWith(CONF_MQTT_ID, 'mqtt'): cv.declare_id(mqtt.MQTTBinarySensorComponent),
|
||||
|
||||
cv.Optional(CONF_DEVICE_CLASS): device_class,
|
||||
cv.Optional(CONF_FILTERS): validate_filters,
|
||||
cv.Optional(CONF_ON_PRESS): automation.validate_automation({
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(PressTrigger),
|
||||
}),
|
||||
cv.Optional(CONF_ON_RELEASE): automation.validate_automation({
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ReleaseTrigger),
|
||||
}),
|
||||
cv.Optional(CONF_ON_CLICK): automation.validate_automation({
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ClickTrigger),
|
||||
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.Optional(CONF_ON_DOUBLE_CLICK): automation.validate_automation({
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(DoubleClickTrigger),
|
||||
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.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."
|
||||
),
|
||||
})
|
||||
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.Optional(CONF_DEVICE_CLASS): device_class,
|
||||
cv.Optional(CONF_FILTERS): validate_filters,
|
||||
cv.Optional(CONF_ON_PRESS): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(PressTrigger),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_RELEASE): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ReleaseTrigger),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_CLICK): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ClickTrigger),
|
||||
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.Optional(CONF_ON_DOUBLE_CLICK): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(DoubleClickTrigger),
|
||||
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.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
|
||||
@@ -244,24 +356,28 @@ def setup_binary_sensor_core_(var, config):
|
||||
yield automation.build_automation(trigger, [], conf)
|
||||
|
||||
for conf in config.get(CONF_ON_CLICK, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var,
|
||||
conf[CONF_MIN_LENGTH], conf[CONF_MAX_LENGTH])
|
||||
trigger = cg.new_Pvariable(
|
||||
conf[CONF_TRIGGER_ID], var, conf[CONF_MIN_LENGTH], conf[CONF_MAX_LENGTH]
|
||||
)
|
||||
yield automation.build_automation(trigger, [], conf)
|
||||
|
||||
for conf in config.get(CONF_ON_DOUBLE_CLICK, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var,
|
||||
conf[CONF_MIN_LENGTH], conf[CONF_MAX_LENGTH])
|
||||
trigger = cg.new_Pvariable(
|
||||
conf[CONF_TRIGGER_ID], var, conf[CONF_MIN_LENGTH], conf[CONF_MAX_LENGTH]
|
||||
)
|
||||
yield automation.build_automation(trigger, [], conf)
|
||||
|
||||
for conf in config.get(CONF_ON_MULTI_CLICK, []):
|
||||
timings = []
|
||||
for tim in conf[CONF_TIMING]:
|
||||
timings.append(cg.StructInitializer(
|
||||
MultiClickTriggerEvent,
|
||||
('state', tim[CONF_STATE]),
|
||||
('min_length', tim[CONF_MIN_LENGTH]),
|
||||
('max_length', tim.get(CONF_MAX_LENGTH, 4294967294)),
|
||||
))
|
||||
timings.append(
|
||||
cg.StructInitializer(
|
||||
MultiClickTriggerEvent,
|
||||
("state", tim[CONF_STATE]),
|
||||
("min_length", tim[CONF_MIN_LENGTH]),
|
||||
("max_length", tim.get(CONF_MAX_LENGTH, 4294967294)),
|
||||
)
|
||||
)
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var, timings)
|
||||
if CONF_INVALID_COOLDOWN in conf:
|
||||
cg.add(trigger.set_invalid_cooldown(conf[CONF_INVALID_COOLDOWN]))
|
||||
@@ -270,7 +386,7 @@ def setup_binary_sensor_core_(var, config):
|
||||
|
||||
for conf in config.get(CONF_ON_STATE, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
yield automation.build_automation(trigger, [(bool, 'x')], conf)
|
||||
yield automation.build_automation(trigger, [(bool, "x")], conf)
|
||||
|
||||
if CONF_MQTT_ID in config:
|
||||
mqtt_ = cg.new_Pvariable(config[CONF_MQTT_ID], var)
|
||||
@@ -292,22 +408,28 @@ def new_binary_sensor(config):
|
||||
yield var
|
||||
|
||||
|
||||
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 "
|
||||
"'for' condition instead."),
|
||||
})
|
||||
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 "
|
||||
"'for' condition instead."
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@automation.register_condition('binary_sensor.is_on', BinarySensorCondition,
|
||||
BINARY_SENSOR_CONDITION_SCHEMA)
|
||||
@automation.register_condition(
|
||||
"binary_sensor.is_on", BinarySensorCondition, BINARY_SENSOR_CONDITION_SCHEMA
|
||||
)
|
||||
def binary_sensor_is_on_to_code(config, condition_id, template_arg, args):
|
||||
paren = yield cg.get_variable(config[CONF_ID])
|
||||
yield cg.new_Pvariable(condition_id, template_arg, paren, True)
|
||||
|
||||
|
||||
@automation.register_condition('binary_sensor.is_off', BinarySensorCondition,
|
||||
BINARY_SENSOR_CONDITION_SCHEMA)
|
||||
@automation.register_condition(
|
||||
"binary_sensor.is_off", BinarySensorCondition, BINARY_SENSOR_CONDITION_SCHEMA
|
||||
)
|
||||
def binary_sensor_is_off_to_code(config, condition_id, template_arg, args):
|
||||
paren = yield cg.get_variable(config[CONF_ID])
|
||||
yield cg.new_Pvariable(condition_id, template_arg, paren, False)
|
||||
@@ -315,5 +437,5 @@ def binary_sensor_is_off_to_code(config, condition_id, template_arg, args):
|
||||
|
||||
@coroutine_with_priority(100.0)
|
||||
def to_code(config):
|
||||
cg.add_define('USE_BINARY_SENSOR')
|
||||
cg.add_define("USE_BINARY_SENSOR")
|
||||
cg.add_global(binary_sensor_ns.using)
|
||||
|
@@ -2,14 +2,25 @@ import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
|
||||
from esphome.components import sensor, binary_sensor
|
||||
from esphome.const import CONF_ID, CONF_CHANNELS, CONF_VALUE, CONF_TYPE, UNIT_EMPTY, \
|
||||
ICON_CHECK_CIRCLE_OUTLINE, CONF_BINARY_SENSOR, CONF_GROUP
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_CHANNELS,
|
||||
CONF_VALUE,
|
||||
CONF_TYPE,
|
||||
DEVICE_CLASS_EMPTY,
|
||||
UNIT_EMPTY,
|
||||
ICON_CHECK_CIRCLE_OUTLINE,
|
||||
CONF_BINARY_SENSOR,
|
||||
CONF_GROUP,
|
||||
)
|
||||
|
||||
DEPENDENCIES = ['binary_sensor']
|
||||
DEPENDENCIES = ["binary_sensor"]
|
||||
|
||||
binary_sensor_map_ns = cg.esphome_ns.namespace('binary_sensor_map')
|
||||
BinarySensorMap = binary_sensor_map_ns.class_('BinarySensorMap', cg.Component, sensor.Sensor)
|
||||
SensorMapType = binary_sensor_map_ns.enum('SensorMapType')
|
||||
binary_sensor_map_ns = cg.esphome_ns.namespace("binary_sensor_map")
|
||||
BinarySensorMap = binary_sensor_map_ns.class_(
|
||||
"BinarySensorMap", cg.Component, sensor.Sensor
|
||||
)
|
||||
SensorMapType = binary_sensor_map_ns.enum("SensorMapType")
|
||||
|
||||
SENSOR_MAP_TYPES = {
|
||||
CONF_GROUP: SensorMapType.BINARY_SENSOR_MAP_TYPE_GROUP,
|
||||
@@ -20,12 +31,21 @@ entry = {
|
||||
cv.Required(CONF_VALUE): cv.float_,
|
||||
}
|
||||
|
||||
CONFIG_SCHEMA = cv.typed_schema({
|
||||
CONF_GROUP: sensor.sensor_schema(UNIT_EMPTY, ICON_CHECK_CIRCLE_OUTLINE, 0).extend({
|
||||
cv.GenerateID(): cv.declare_id(BinarySensorMap),
|
||||
cv.Required(CONF_CHANNELS): cv.All(cv.ensure_list(entry), cv.Length(min=1)),
|
||||
}),
|
||||
}, lower=True)
|
||||
CONFIG_SCHEMA = cv.typed_schema(
|
||||
{
|
||||
CONF_GROUP: sensor.sensor_schema(
|
||||
UNIT_EMPTY, ICON_CHECK_CIRCLE_OUTLINE, 0, DEVICE_CLASS_EMPTY
|
||||
).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):
|
||||
|
87
esphome/components/ble_client/__init__.py
Normal file
87
esphome/components/ble_client/__init__.py
Normal file
@@ -0,0 +1,87 @@
|
||||
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.core import coroutine
|
||||
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),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@coroutine
|
||||
def register_ble_node(var, config):
|
||||
parent = yield cg.get_variable(config[CONF_BLE_CLIENT_ID])
|
||||
cg.add(parent.register_ble_node(var))
|
||||
|
||||
|
||||
def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
yield cg.register_component(var, config)
|
||||
yield 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)
|
||||
yield automation.build_automation(trigger, [], conf)
|
||||
for conf in config.get(CONF_ON_DISCONNECT, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
yield 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 && 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
|
115
esphome/components/ble_client/sensor/__init__.py
Normal file
115
esphome/components/ble_client/sensor/__init__.py
Normal file
@@ -0,0 +1,115 @@
|
||||
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,
|
||||
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"
|
||||
|
||||
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)
|
||||
.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_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)
|
||||
)
|
||||
|
||||
|
||||
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))
|
||||
|
||||
yield cg.register_component(var, config)
|
||||
yield ble_client.register_ble_node(var, config)
|
||||
cg.add(var.set_enable_notify(config[CONF_NOTIFY]))
|
||||
yield sensor.register_sensor(var, config)
|
||||
for conf in config.get(CONF_ON_NOTIFY, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
yield ble_client.register_ble_node(trigger, config)
|
||||
yield 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
|
129
esphome/components/ble_client/sensor/ble_sensor.cpp
Normal file
129
esphome/components/ble_client/sensor/ble_sensor.cpp
Normal file
@@ -0,0 +1,129 @@
|
||||
#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((float) param->read.value[0]);
|
||||
}
|
||||
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((float) param->notify.value[0]);
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
|
||||
this->node_state = espbt::ClientState::Established;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
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
|
46
esphome/components/ble_client/sensor/ble_sensor.h
Normal file
46
esphome/components/ble_client/sensor/ble_sensor.h
Normal file
@@ -0,0 +1,46 @@
|
||||
#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;
|
||||
|
||||
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_enable_notify(bool notify) { this->notify_ = notify; }
|
||||
uint16_t handle;
|
||||
|
||||
protected:
|
||||
uint32_t hash_base() override;
|
||||
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)
|
||||
)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
yield cg.register_component(var, config)
|
||||
yield switch.register_switch(var, config)
|
||||
yield 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,18 +3,28 @@ import esphome.config_validation as cv
|
||||
from esphome.components import binary_sensor, esp32_ble_tracker
|
||||
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')
|
||||
BLEPresenceDevice = ble_presence_ns.class_('BLEPresenceDevice', binary_sensor.BinarySensor,
|
||||
cg.Component, esp32_ble_tracker.ESPBTDeviceListener)
|
||||
ble_presence_ns = cg.esphome_ns.namespace("ble_presence")
|
||||
BLEPresenceDevice = ble_presence_ns.class_(
|
||||
"BLEPresenceDevice",
|
||||
binary_sensor.BinarySensor,
|
||||
cg.Component,
|
||||
esp32_ble_tracker.ESPBTDeviceListener,
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = cv.All(binary_sensor.BINARY_SENSOR_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_id(BLEPresenceDevice),
|
||||
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))
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
binary_sensor.BINARY_SENSOR_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(BLEPresenceDevice),
|
||||
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):
|
||||
@@ -28,9 +38,17 @@ def to_code(config):
|
||||
|
||||
if CONF_SERVICE_UUID in config:
|
||||
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):
|
||||
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):
|
||||
uuid128 = esp32_ble_tracker.as_hex_array(config[CONF_SERVICE_UUID])
|
||||
cg.add(var.set_service_uuid128(uuid128))
|
||||
|
@@ -1,20 +1,35 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
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,
|
||||
UNIT_DECIBEL,
|
||||
ICON_EMPTY,
|
||||
)
|
||||
|
||||
DEPENDENCIES = ['esp32_ble_tracker']
|
||||
DEPENDENCIES = ["esp32_ble_tracker"]
|
||||
|
||||
ble_rssi_ns = cg.esphome_ns.namespace('ble_rssi')
|
||||
BLERSSISensor = ble_rssi_ns.class_('BLERSSISensor', sensor.Sensor, cg.Component,
|
||||
esp32_ble_tracker.ESPBTDeviceListener)
|
||||
ble_rssi_ns = cg.esphome_ns.namespace("ble_rssi")
|
||||
BLERSSISensor = ble_rssi_ns.class_(
|
||||
"BLERSSISensor", sensor.Sensor, cg.Component, esp32_ble_tracker.ESPBTDeviceListener
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = cv.All(sensor.sensor_schema(UNIT_DECIBEL, ICON_SIGNAL, 0).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))
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
sensor.sensor_schema(UNIT_DECIBEL, ICON_EMPTY, 0, DEVICE_CLASS_SIGNAL_STRENGTH)
|
||||
.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):
|
||||
@@ -28,9 +43,17 @@ def to_code(config):
|
||||
|
||||
if CONF_SERVICE_UUID in config:
|
||||
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):
|
||||
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):
|
||||
uuid128 = esp32_ble_tracker.as_hex_array(config[CONF_SERVICE_UUID])
|
||||
cg.add(var.set_service_uuid128(uuid128))
|
||||
|
@@ -3,16 +3,25 @@ import esphome.config_validation as cv
|
||||
from esphome.components import text_sensor, esp32_ble_tracker
|
||||
from esphome.const import CONF_ID
|
||||
|
||||
DEPENDENCIES = ['esp32_ble_tracker']
|
||||
DEPENDENCIES = ["esp32_ble_tracker"]
|
||||
|
||||
ble_scanner_ns = cg.esphome_ns.namespace('ble_scanner')
|
||||
BLEScanner = ble_scanner_ns.class_('BLEScanner', text_sensor.TextSensor, cg.Component,
|
||||
esp32_ble_tracker.ESPBTDeviceListener)
|
||||
ble_scanner_ns = cg.esphome_ns.namespace("ble_scanner")
|
||||
BLEScanner = ble_scanner_ns.class_(
|
||||
"BLEScanner",
|
||||
text_sensor.TextSensor,
|
||||
cg.Component,
|
||||
esp32_ble_tracker.ESPBTDeviceListener,
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = cv.All(text_sensor.TEXT_SENSOR_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_id(BLEScanner),
|
||||
}).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA).extend(
|
||||
cv.COMPONENT_SCHEMA))
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
text_sensor.TEXT_SENSOR_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(BLEScanner),
|
||||
}
|
||||
)
|
||||
.extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA)
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
|
@@ -1,53 +1,87 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import i2c, sensor
|
||||
from esphome.const import CONF_HUMIDITY, CONF_ID, CONF_IIR_FILTER, CONF_OVERSAMPLING, \
|
||||
CONF_PRESSURE, CONF_TEMPERATURE, ICON_THERMOMETER, \
|
||||
UNIT_CELSIUS, UNIT_HECTOPASCAL, ICON_GAUGE, ICON_WATER_PERCENT, UNIT_PERCENT
|
||||
from esphome.const import (
|
||||
CONF_HUMIDITY,
|
||||
CONF_ID,
|
||||
CONF_IIR_FILTER,
|
||||
CONF_OVERSAMPLING,
|
||||
CONF_PRESSURE,
|
||||
CONF_TEMPERATURE,
|
||||
DEVICE_CLASS_HUMIDITY,
|
||||
DEVICE_CLASS_PRESSURE,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
ICON_EMPTY,
|
||||
UNIT_CELSIUS,
|
||||
UNIT_HECTOPASCAL,
|
||||
UNIT_PERCENT,
|
||||
)
|
||||
|
||||
DEPENDENCIES = ['i2c']
|
||||
DEPENDENCIES = ["i2c"]
|
||||
|
||||
bme280_ns = cg.esphome_ns.namespace('bme280')
|
||||
BME280Oversampling = bme280_ns.enum('BME280Oversampling')
|
||||
bme280_ns = cg.esphome_ns.namespace("bme280")
|
||||
BME280Oversampling = bme280_ns.enum("BME280Oversampling")
|
||||
OVERSAMPLING_OPTIONS = {
|
||||
'NONE': BME280Oversampling.BME280_OVERSAMPLING_NONE,
|
||||
'1X': BME280Oversampling.BME280_OVERSAMPLING_1X,
|
||||
'2X': BME280Oversampling.BME280_OVERSAMPLING_2X,
|
||||
'4X': BME280Oversampling.BME280_OVERSAMPLING_4X,
|
||||
'8X': BME280Oversampling.BME280_OVERSAMPLING_8X,
|
||||
'16X': BME280Oversampling.BME280_OVERSAMPLING_16X,
|
||||
"NONE": BME280Oversampling.BME280_OVERSAMPLING_NONE,
|
||||
"1X": BME280Oversampling.BME280_OVERSAMPLING_1X,
|
||||
"2X": BME280Oversampling.BME280_OVERSAMPLING_2X,
|
||||
"4X": BME280Oversampling.BME280_OVERSAMPLING_4X,
|
||||
"8X": BME280Oversampling.BME280_OVERSAMPLING_8X,
|
||||
"16X": BME280Oversampling.BME280_OVERSAMPLING_16X,
|
||||
}
|
||||
|
||||
BME280IIRFilter = bme280_ns.enum('BME280IIRFilter')
|
||||
BME280IIRFilter = bme280_ns.enum("BME280IIRFilter")
|
||||
IIR_FILTER_OPTIONS = {
|
||||
'OFF': BME280IIRFilter.BME280_IIR_FILTER_OFF,
|
||||
'2X': BME280IIRFilter.BME280_IIR_FILTER_2X,
|
||||
'4X': BME280IIRFilter.BME280_IIR_FILTER_4X,
|
||||
'8X': BME280IIRFilter.BME280_IIR_FILTER_8X,
|
||||
'16X': BME280IIRFilter.BME280_IIR_FILTER_16X,
|
||||
"OFF": BME280IIRFilter.BME280_IIR_FILTER_OFF,
|
||||
"2X": BME280IIRFilter.BME280_IIR_FILTER_2X,
|
||||
"4X": BME280IIRFilter.BME280_IIR_FILTER_4X,
|
||||
"8X": BME280IIRFilter.BME280_IIR_FILTER_8X,
|
||||
"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({
|
||||
cv.GenerateID(): cv.declare_id(BME280Component),
|
||||
cv.Optional(CONF_TEMPERATURE):
|
||||
sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 1).extend({
|
||||
cv.Optional(CONF_OVERSAMPLING, default='16X'):
|
||||
cv.enum(OVERSAMPLING_OPTIONS, upper=True),
|
||||
}),
|
||||
cv.Optional(CONF_PRESSURE):
|
||||
sensor.sensor_schema(UNIT_HECTOPASCAL, ICON_GAUGE, 1).extend({
|
||||
cv.Optional(CONF_OVERSAMPLING, default='16X'):
|
||||
cv.enum(OVERSAMPLING_OPTIONS, upper=True),
|
||||
}),
|
||||
cv.Optional(CONF_HUMIDITY):
|
||||
sensor.sensor_schema(UNIT_PERCENT, ICON_WATER_PERCENT, 1).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))
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(BME280Component),
|
||||
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
|
||||
UNIT_CELSIUS, ICON_EMPTY, 1, DEVICE_CLASS_TEMPERATURE
|
||||
).extend(
|
||||
{
|
||||
cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum(
|
||||
OVERSAMPLING_OPTIONS, upper=True
|
||||
),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_PRESSURE): sensor.sensor_schema(
|
||||
UNIT_HECTOPASCAL, ICON_EMPTY, 1, DEVICE_CLASS_PRESSURE
|
||||
).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
|
||||
).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):
|
||||
|
@@ -2,64 +2,116 @@ import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import core
|
||||
from esphome.components import i2c, sensor
|
||||
from esphome.const import CONF_DURATION, CONF_GAS_RESISTANCE, CONF_HEATER, \
|
||||
CONF_HUMIDITY, CONF_ID, CONF_IIR_FILTER, CONF_OVERSAMPLING, CONF_PRESSURE, \
|
||||
CONF_TEMPERATURE, UNIT_OHM, ICON_GAS_CYLINDER, UNIT_CELSIUS, \
|
||||
ICON_THERMOMETER, UNIT_HECTOPASCAL, ICON_GAUGE, ICON_WATER_PERCENT, UNIT_PERCENT
|
||||
from esphome.const import (
|
||||
CONF_DURATION,
|
||||
CONF_GAS_RESISTANCE,
|
||||
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,
|
||||
UNIT_OHM,
|
||||
ICON_GAS_CYLINDER,
|
||||
UNIT_CELSIUS,
|
||||
ICON_EMPTY,
|
||||
UNIT_HECTOPASCAL,
|
||||
UNIT_PERCENT,
|
||||
)
|
||||
|
||||
DEPENDENCIES = ['i2c']
|
||||
DEPENDENCIES = ["i2c"]
|
||||
|
||||
bme680_ns = cg.esphome_ns.namespace('bme680')
|
||||
BME680Oversampling = bme680_ns.enum('BME680Oversampling')
|
||||
bme680_ns = cg.esphome_ns.namespace("bme680")
|
||||
BME680Oversampling = bme680_ns.enum("BME680Oversampling")
|
||||
OVERSAMPLING_OPTIONS = {
|
||||
'NONE': BME680Oversampling.BME680_OVERSAMPLING_NONE,
|
||||
'1X': BME680Oversampling.BME680_OVERSAMPLING_1X,
|
||||
'2X': BME680Oversampling.BME680_OVERSAMPLING_2X,
|
||||
'4X': BME680Oversampling.BME680_OVERSAMPLING_4X,
|
||||
'8X': BME680Oversampling.BME680_OVERSAMPLING_8X,
|
||||
'16X': BME680Oversampling.BME680_OVERSAMPLING_16X,
|
||||
"NONE": BME680Oversampling.BME680_OVERSAMPLING_NONE,
|
||||
"1X": BME680Oversampling.BME680_OVERSAMPLING_1X,
|
||||
"2X": BME680Oversampling.BME680_OVERSAMPLING_2X,
|
||||
"4X": BME680Oversampling.BME680_OVERSAMPLING_4X,
|
||||
"8X": BME680Oversampling.BME680_OVERSAMPLING_8X,
|
||||
"16X": BME680Oversampling.BME680_OVERSAMPLING_16X,
|
||||
}
|
||||
|
||||
BME680IIRFilter = bme680_ns.enum('BME680IIRFilter')
|
||||
BME680IIRFilter = bme680_ns.enum("BME680IIRFilter")
|
||||
IIR_FILTER_OPTIONS = {
|
||||
'OFF': BME680IIRFilter.BME680_IIR_FILTER_OFF,
|
||||
'1X': BME680IIRFilter.BME680_IIR_FILTER_1X,
|
||||
'3X': BME680IIRFilter.BME680_IIR_FILTER_3X,
|
||||
'7X': BME680IIRFilter.BME680_IIR_FILTER_7X,
|
||||
'15X': BME680IIRFilter.BME680_IIR_FILTER_15X,
|
||||
'31X': BME680IIRFilter.BME680_IIR_FILTER_31X,
|
||||
'63X': BME680IIRFilter.BME680_IIR_FILTER_63X,
|
||||
'127X': BME680IIRFilter.BME680_IIR_FILTER_127X,
|
||||
"OFF": BME680IIRFilter.BME680_IIR_FILTER_OFF,
|
||||
"1X": BME680IIRFilter.BME680_IIR_FILTER_1X,
|
||||
"3X": BME680IIRFilter.BME680_IIR_FILTER_3X,
|
||||
"7X": BME680IIRFilter.BME680_IIR_FILTER_7X,
|
||||
"15X": BME680IIRFilter.BME680_IIR_FILTER_15X,
|
||||
"31X": BME680IIRFilter.BME680_IIR_FILTER_31X,
|
||||
"63X": BME680IIRFilter.BME680_IIR_FILTER_63X,
|
||||
"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({
|
||||
cv.GenerateID(): cv.declare_id(BME680Component),
|
||||
cv.Optional(CONF_TEMPERATURE):
|
||||
sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 1).extend({
|
||||
cv.Optional(CONF_OVERSAMPLING, default='16X'):
|
||||
cv.enum(OVERSAMPLING_OPTIONS, upper=True),
|
||||
}),
|
||||
cv.Optional(CONF_PRESSURE):
|
||||
sensor.sensor_schema(UNIT_HECTOPASCAL, ICON_GAUGE, 1).extend({
|
||||
cv.Optional(CONF_OVERSAMPLING, default='16X'):
|
||||
cv.enum(OVERSAMPLING_OPTIONS, upper=True),
|
||||
}),
|
||||
cv.Optional(CONF_HUMIDITY):
|
||||
sensor.sensor_schema(UNIT_PERCENT, ICON_WATER_PERCENT, 1).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),
|
||||
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))
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(BME680Component),
|
||||
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
|
||||
UNIT_CELSIUS, ICON_EMPTY, 1, DEVICE_CLASS_TEMPERATURE
|
||||
).extend(
|
||||
{
|
||||
cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum(
|
||||
OVERSAMPLING_OPTIONS, upper=True
|
||||
),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_PRESSURE): sensor.sensor_schema(
|
||||
UNIT_HECTOPASCAL, ICON_EMPTY, 1, DEVICE_CLASS_PRESSURE
|
||||
).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
|
||||
).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
|
||||
),
|
||||
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):
|
||||
|
64
esphome/components/bme680_bsec/__init__.py
Normal file
64
esphome/components/bme680_bsec/__init__.py
Normal file
@@ -0,0 +1,64 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import i2c
|
||||
from esphome.const import CONF_ID
|
||||
|
||||
CODEOWNERS = ["@trvrnrth"]
|
||||
DEPENDENCIES = ["i2c"]
|
||||
AUTO_LOAD = ["sensor", "text_sensor"]
|
||||
|
||||
CONF_BME680_BSEC_ID = "bme680_bsec_id"
|
||||
CONF_TEMPERATURE_OFFSET = "temperature_offset"
|
||||
CONF_IAQ_MODE = "iaq_mode"
|
||||
CONF_SAMPLE_RATE = "sample_rate"
|
||||
CONF_STATE_SAVE_INTERVAL = "state_save_interval"
|
||||
|
||||
bme680_bsec_ns = cg.esphome_ns.namespace("bme680_bsec")
|
||||
|
||||
IAQMode = bme680_bsec_ns.enum("IAQMode")
|
||||
IAQ_MODE_OPTIONS = {
|
||||
"STATIC": IAQMode.IAQ_MODE_STATIC,
|
||||
"MOBILE": IAQMode.IAQ_MODE_MOBILE,
|
||||
}
|
||||
|
||||
SampleRate = bme680_bsec_ns.enum("SampleRate")
|
||||
SAMPLE_RATE_OPTIONS = {
|
||||
"LP": SampleRate.SAMPLE_RATE_LP,
|
||||
"ULP": SampleRate.SAMPLE_RATE_ULP,
|
||||
}
|
||||
|
||||
BME680BSECComponent = bme680_bsec_ns.class_(
|
||||
"BME680BSECComponent", cg.Component, i2c.I2CDevice
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(BME680BSECComponent),
|
||||
cv.Optional(CONF_TEMPERATURE_OFFSET, default=0): cv.temperature,
|
||||
cv.Optional(CONF_IAQ_MODE, default="STATIC"): cv.enum(
|
||||
IAQ_MODE_OPTIONS, upper=True
|
||||
),
|
||||
cv.Optional(CONF_SAMPLE_RATE, default="LP"): cv.enum(
|
||||
SAMPLE_RATE_OPTIONS, upper=True
|
||||
),
|
||||
cv.Optional(
|
||||
CONF_STATE_SAVE_INTERVAL, default="6hours"
|
||||
): cv.positive_time_period_minutes,
|
||||
}
|
||||
).extend(i2c.i2c_device_schema(0x76))
|
||||
|
||||
|
||||
def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
yield cg.register_component(var, config)
|
||||
yield i2c.register_i2c_device(var, config)
|
||||
|
||||
cg.add(var.set_temperature_offset(config[CONF_TEMPERATURE_OFFSET]))
|
||||
cg.add(var.set_iaq_mode(config[CONF_IAQ_MODE]))
|
||||
cg.add(var.set_sample_rate(config[CONF_SAMPLE_RATE]))
|
||||
cg.add(
|
||||
var.set_state_save_interval(config[CONF_STATE_SAVE_INTERVAL].total_milliseconds)
|
||||
)
|
||||
|
||||
cg.add_define("USING_BSEC")
|
||||
cg.add_library("BSEC Software Library", "1.6.1480")
|
396
esphome/components/bme680_bsec/bme680_bsec.cpp
Normal file
396
esphome/components/bme680_bsec/bme680_bsec.cpp
Normal file
@@ -0,0 +1,396 @@
|
||||
|
||||
|
||||
#include "bme680_bsec.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include <string>
|
||||
|
||||
namespace esphome {
|
||||
namespace bme680_bsec {
|
||||
#ifdef USING_BSEC
|
||||
static const char *TAG = "bme680_bsec.sensor";
|
||||
|
||||
static const std::string IAQ_ACCURACY_STATES[4] = {"Stabilizing", "Uncertain", "Calibrating", "Calibrated"};
|
||||
|
||||
BME680BSECComponent *BME680BSECComponent::instance;
|
||||
|
||||
void BME680BSECComponent::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up BME680 via BSEC...");
|
||||
BME680BSECComponent::instance = this;
|
||||
|
||||
this->bsec_status_ = bsec_init();
|
||||
if (this->bsec_status_ != BSEC_OK) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
this->bme680_.dev_id = this->address_;
|
||||
this->bme680_.intf = BME680_I2C_INTF;
|
||||
this->bme680_.read = BME680BSECComponent::read_bytes_wrapper;
|
||||
this->bme680_.write = BME680BSECComponent::write_bytes_wrapper;
|
||||
this->bme680_.delay_ms = BME680BSECComponent::delay_ms;
|
||||
this->bme680_.amb_temp = 25;
|
||||
this->bme680_.power_mode = BME680_FORCED_MODE;
|
||||
|
||||
this->bme680_status_ = bme680_init(&this->bme680_);
|
||||
if (this->bme680_status_ != BME680_OK) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
if (this->sample_rate_ == SAMPLE_RATE_ULP) {
|
||||
const uint8_t bsec_config[] = {
|
||||
#include "config/generic_33v_300s_28d/bsec_iaq.txt"
|
||||
};
|
||||
this->set_config_(bsec_config);
|
||||
this->update_subscription_(BSEC_SAMPLE_RATE_ULP);
|
||||
} else {
|
||||
const uint8_t bsec_config[] = {
|
||||
#include "config/generic_33v_3s_28d/bsec_iaq.txt"
|
||||
};
|
||||
this->set_config_(bsec_config);
|
||||
this->update_subscription_(BSEC_SAMPLE_RATE_LP);
|
||||
}
|
||||
if (this->bsec_status_ != BSEC_OK) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
this->load_state_();
|
||||
}
|
||||
|
||||
void BME680BSECComponent::set_config_(const uint8_t *config) {
|
||||
uint8_t work_buffer[BSEC_MAX_WORKBUFFER_SIZE];
|
||||
this->bsec_status_ = bsec_set_configuration(config, BSEC_MAX_PROPERTY_BLOB_SIZE, work_buffer, sizeof(work_buffer));
|
||||
}
|
||||
|
||||
void BME680BSECComponent::update_subscription_(float sample_rate) {
|
||||
bsec_sensor_configuration_t virtual_sensors[BSEC_NUMBER_OUTPUTS];
|
||||
int num_virtual_sensors = 0;
|
||||
|
||||
if (this->iaq_sensor_) {
|
||||
virtual_sensors[num_virtual_sensors].sensor_id =
|
||||
this->iaq_mode_ == IAQ_MODE_STATIC ? BSEC_OUTPUT_STATIC_IAQ : BSEC_OUTPUT_IAQ;
|
||||
virtual_sensors[num_virtual_sensors].sample_rate = sample_rate;
|
||||
num_virtual_sensors++;
|
||||
}
|
||||
|
||||
if (this->co2_equivalent_sensor_) {
|
||||
virtual_sensors[num_virtual_sensors].sensor_id = BSEC_OUTPUT_CO2_EQUIVALENT;
|
||||
virtual_sensors[num_virtual_sensors].sample_rate = sample_rate;
|
||||
num_virtual_sensors++;
|
||||
}
|
||||
|
||||
if (this->breath_voc_equivalent_sensor_) {
|
||||
virtual_sensors[num_virtual_sensors].sensor_id = BSEC_OUTPUT_BREATH_VOC_EQUIVALENT;
|
||||
virtual_sensors[num_virtual_sensors].sample_rate = sample_rate;
|
||||
num_virtual_sensors++;
|
||||
}
|
||||
|
||||
if (this->pressure_sensor_) {
|
||||
virtual_sensors[num_virtual_sensors].sensor_id = BSEC_OUTPUT_RAW_PRESSURE;
|
||||
virtual_sensors[num_virtual_sensors].sample_rate = sample_rate;
|
||||
num_virtual_sensors++;
|
||||
}
|
||||
|
||||
if (this->gas_resistance_sensor_) {
|
||||
virtual_sensors[num_virtual_sensors].sensor_id = BSEC_OUTPUT_RAW_GAS;
|
||||
virtual_sensors[num_virtual_sensors].sample_rate = sample_rate;
|
||||
num_virtual_sensors++;
|
||||
}
|
||||
|
||||
if (this->temperature_sensor_) {
|
||||
virtual_sensors[num_virtual_sensors].sensor_id = BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_TEMPERATURE;
|
||||
virtual_sensors[num_virtual_sensors].sample_rate = sample_rate;
|
||||
num_virtual_sensors++;
|
||||
}
|
||||
|
||||
if (this->humidity_sensor_) {
|
||||
virtual_sensors[num_virtual_sensors].sensor_id = BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_HUMIDITY;
|
||||
virtual_sensors[num_virtual_sensors].sample_rate = sample_rate;
|
||||
num_virtual_sensors++;
|
||||
}
|
||||
|
||||
bsec_sensor_configuration_t sensor_settings[BSEC_MAX_PHYSICAL_SENSOR];
|
||||
uint8_t num_sensor_settings = BSEC_MAX_PHYSICAL_SENSOR;
|
||||
this->bsec_status_ =
|
||||
bsec_update_subscription(virtual_sensors, num_virtual_sensors, sensor_settings, &num_sensor_settings);
|
||||
}
|
||||
|
||||
void BME680BSECComponent::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "BME680 via BSEC:");
|
||||
|
||||
bsec_version_t version;
|
||||
bsec_get_version(&version);
|
||||
ESP_LOGCONFIG(TAG, " BSEC Version: %d.%d.%d.%d", version.major, version.minor, version.major_bugfix,
|
||||
version.minor_bugfix);
|
||||
|
||||
LOG_I2C_DEVICE(this);
|
||||
|
||||
if (this->is_failed()) {
|
||||
ESP_LOGE(TAG, "Communication failed (BSEC Status: %d, BME680 Status: %d)", this->bsec_status_,
|
||||
this->bme680_status_);
|
||||
}
|
||||
|
||||
ESP_LOGCONFIG(TAG, " Temperature Offset: %.2f", this->temperature_offset_);
|
||||
ESP_LOGCONFIG(TAG, " IAQ Mode: %s", this->iaq_mode_ == IAQ_MODE_STATIC ? "Static" : "Mobile");
|
||||
ESP_LOGCONFIG(TAG, " Sample Rate: %s", this->sample_rate_ == SAMPLE_RATE_ULP ? "ULP" : "LP");
|
||||
ESP_LOGCONFIG(TAG, " State Save Interval: %ims", this->state_save_interval_ms_);
|
||||
|
||||
LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
|
||||
LOG_SENSOR(" ", "Pressure", this->pressure_sensor_);
|
||||
LOG_SENSOR(" ", "Humidity", this->humidity_sensor_);
|
||||
LOG_SENSOR(" ", "Gas Resistance", this->gas_resistance_sensor_);
|
||||
LOG_SENSOR(" ", "IAQ", this->iaq_sensor_);
|
||||
LOG_SENSOR(" ", "Numeric IAQ Accuracy", this->iaq_accuracy_sensor_);
|
||||
LOG_TEXT_SENSOR(" ", "IAQ Accuracy", this->iaq_accuracy_text_sensor_);
|
||||
LOG_SENSOR(" ", "CO2 Equivalent", this->co2_equivalent_sensor_);
|
||||
LOG_SENSOR(" ", "Breath VOC Equivalent", this->breath_voc_equivalent_sensor_);
|
||||
}
|
||||
|
||||
float BME680BSECComponent::get_setup_priority() const { return setup_priority::DATA; }
|
||||
|
||||
void BME680BSECComponent::loop() {
|
||||
this->run_();
|
||||
|
||||
if (this->bsec_status_ < BSEC_OK || this->bme680_status_ < BME680_OK) {
|
||||
this->status_set_error();
|
||||
} else {
|
||||
this->status_clear_error();
|
||||
}
|
||||
if (this->bsec_status_ > BSEC_OK || this->bme680_status_ > BME680_OK) {
|
||||
this->status_set_warning();
|
||||
} else {
|
||||
this->status_clear_warning();
|
||||
}
|
||||
}
|
||||
|
||||
void BME680BSECComponent::run_() {
|
||||
int64_t curr_time_ns = this->get_time_ns_();
|
||||
if (curr_time_ns < this->next_call_ns_) {
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGV(TAG, "Performing sensor run");
|
||||
|
||||
bsec_bme_settings_t bme680_settings;
|
||||
this->bsec_status_ = bsec_sensor_control(curr_time_ns, &bme680_settings);
|
||||
if (this->bsec_status_ < BSEC_OK) {
|
||||
ESP_LOGW(TAG, "Failed to fetch sensor control settings (BSEC Error Code %d)", this->bsec_status_);
|
||||
return;
|
||||
}
|
||||
this->next_call_ns_ = bme680_settings.next_call;
|
||||
|
||||
this->bme680_.gas_sett.run_gas = bme680_settings.run_gas;
|
||||
this->bme680_.tph_sett.os_hum = bme680_settings.humidity_oversampling;
|
||||
this->bme680_.tph_sett.os_temp = bme680_settings.temperature_oversampling;
|
||||
this->bme680_.tph_sett.os_pres = bme680_settings.pressure_oversampling;
|
||||
this->bme680_.gas_sett.heatr_temp = bme680_settings.heater_temperature;
|
||||
this->bme680_.gas_sett.heatr_dur = bme680_settings.heating_duration;
|
||||
uint16_t desired_settings =
|
||||
BME680_OST_SEL | BME680_OSP_SEL | BME680_OSH_SEL | BME680_FILTER_SEL | BME680_GAS_SENSOR_SEL;
|
||||
this->bme680_status_ = bme680_set_sensor_settings(desired_settings, &this->bme680_);
|
||||
if (this->bme680_status_ != BME680_OK) {
|
||||
ESP_LOGW(TAG, "Failed to set sensor settings (BME680 Error Code %d)", this->bme680_status_);
|
||||
return;
|
||||
}
|
||||
|
||||
this->bme680_status_ = bme680_set_sensor_mode(&this->bme680_);
|
||||
if (this->bme680_status_ != BME680_OK) {
|
||||
ESP_LOGW(TAG, "Failed to set sensor mode (BME680 Error Code %d)", this->bme680_status_);
|
||||
return;
|
||||
}
|
||||
|
||||
uint16_t meas_dur = 0;
|
||||
bme680_get_profile_dur(&meas_dur, &this->bme680_);
|
||||
ESP_LOGV(TAG, "Queueing read in %ums", meas_dur);
|
||||
this->set_timeout("read", meas_dur, [this, bme680_settings]() { this->read_(bme680_settings); });
|
||||
}
|
||||
|
||||
void BME680BSECComponent::read_(bsec_bme_settings_t bme680_settings) {
|
||||
ESP_LOGV(TAG, "Reading data");
|
||||
struct bme680_field_data data;
|
||||
this->bme680_status_ = bme680_get_sensor_data(&data, &this->bme680_);
|
||||
|
||||
if (this->bme680_status_ != BME680_OK) {
|
||||
ESP_LOGW(TAG, "Failed to get sensor data (BME680 Error Code %d)", this->bme680_status_);
|
||||
return;
|
||||
}
|
||||
if (!(data.status & BME680_NEW_DATA_MSK)) {
|
||||
ESP_LOGD(TAG, "BME680 did not report new data");
|
||||
return;
|
||||
}
|
||||
|
||||
bsec_input_t inputs[BSEC_MAX_PHYSICAL_SENSOR]; // Temperature, Pressure, Humidity & Gas Resistance
|
||||
uint8_t num_inputs = 0;
|
||||
int64_t curr_time_ns = this->get_time_ns_();
|
||||
|
||||
if (bme680_settings.process_data & BSEC_PROCESS_TEMPERATURE) {
|
||||
inputs[num_inputs].sensor_id = BSEC_INPUT_TEMPERATURE;
|
||||
inputs[num_inputs].signal = data.temperature / 100.0f;
|
||||
inputs[num_inputs].time_stamp = curr_time_ns;
|
||||
num_inputs++;
|
||||
|
||||
// Temperature offset from the real temperature due to external heat sources
|
||||
inputs[num_inputs].sensor_id = BSEC_INPUT_HEATSOURCE;
|
||||
inputs[num_inputs].signal = this->temperature_offset_;
|
||||
inputs[num_inputs].time_stamp = curr_time_ns;
|
||||
num_inputs++;
|
||||
}
|
||||
if (bme680_settings.process_data & BSEC_PROCESS_HUMIDITY) {
|
||||
inputs[num_inputs].sensor_id = BSEC_INPUT_HUMIDITY;
|
||||
inputs[num_inputs].signal = data.humidity / 1000.0f;
|
||||
inputs[num_inputs].time_stamp = curr_time_ns;
|
||||
num_inputs++;
|
||||
}
|
||||
if (bme680_settings.process_data & BSEC_PROCESS_PRESSURE) {
|
||||
inputs[num_inputs].sensor_id = BSEC_INPUT_PRESSURE;
|
||||
inputs[num_inputs].signal = data.pressure;
|
||||
inputs[num_inputs].time_stamp = curr_time_ns;
|
||||
num_inputs++;
|
||||
}
|
||||
if (bme680_settings.process_data & BSEC_PROCESS_GAS) {
|
||||
inputs[num_inputs].sensor_id = BSEC_INPUT_GASRESISTOR;
|
||||
inputs[num_inputs].signal = data.gas_resistance;
|
||||
inputs[num_inputs].time_stamp = curr_time_ns;
|
||||
num_inputs++;
|
||||
}
|
||||
if (num_inputs < 1) {
|
||||
ESP_LOGD(TAG, "No signal inputs available for BSEC");
|
||||
return;
|
||||
}
|
||||
|
||||
bsec_output_t outputs[BSEC_NUMBER_OUTPUTS];
|
||||
uint8_t num_outputs = BSEC_NUMBER_OUTPUTS;
|
||||
this->bsec_status_ = bsec_do_steps(inputs, num_inputs, outputs, &num_outputs);
|
||||
if (this->bsec_status_ != BSEC_OK) {
|
||||
ESP_LOGW(TAG, "BSEC failed to process signals (BSEC Error Code %d)", this->bsec_status_);
|
||||
return;
|
||||
}
|
||||
if (num_outputs < 1) {
|
||||
ESP_LOGD(TAG, "No signal outputs provided by BSEC");
|
||||
return;
|
||||
}
|
||||
|
||||
this->publish_(outputs, num_outputs);
|
||||
}
|
||||
|
||||
void BME680BSECComponent::publish_(const bsec_output_t *outputs, uint8_t num_outputs) {
|
||||
ESP_LOGV(TAG, "Publishing sensor states");
|
||||
for (uint8_t i = 0; i < num_outputs; i++) {
|
||||
switch (outputs[i].sensor_id) {
|
||||
case BSEC_OUTPUT_IAQ:
|
||||
case BSEC_OUTPUT_STATIC_IAQ:
|
||||
uint8_t accuracy;
|
||||
accuracy = outputs[i].accuracy;
|
||||
this->publish_sensor_state_(this->iaq_sensor_, outputs[i].signal);
|
||||
this->publish_sensor_state_(this->iaq_accuracy_text_sensor_, IAQ_ACCURACY_STATES[accuracy]);
|
||||
this->publish_sensor_state_(this->iaq_accuracy_sensor_, accuracy, true);
|
||||
|
||||
// Queue up an opportunity to save state
|
||||
this->defer("save_state", [this, accuracy]() { this->save_state_(accuracy); });
|
||||
break;
|
||||
case BSEC_OUTPUT_CO2_EQUIVALENT:
|
||||
this->publish_sensor_state_(this->co2_equivalent_sensor_, outputs[i].signal);
|
||||
break;
|
||||
case BSEC_OUTPUT_BREATH_VOC_EQUIVALENT:
|
||||
this->publish_sensor_state_(this->breath_voc_equivalent_sensor_, outputs[i].signal);
|
||||
break;
|
||||
case BSEC_OUTPUT_RAW_PRESSURE:
|
||||
this->publish_sensor_state_(this->pressure_sensor_, outputs[i].signal / 100.0f);
|
||||
break;
|
||||
case BSEC_OUTPUT_RAW_GAS:
|
||||
this->publish_sensor_state_(this->gas_resistance_sensor_, outputs[i].signal);
|
||||
break;
|
||||
case BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_TEMPERATURE:
|
||||
this->publish_sensor_state_(this->temperature_sensor_, outputs[i].signal);
|
||||
break;
|
||||
case BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_HUMIDITY:
|
||||
this->publish_sensor_state_(this->humidity_sensor_, outputs[i].signal);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int64_t BME680BSECComponent::get_time_ns_() {
|
||||
int64_t time_ms = millis();
|
||||
if (this->last_time_ms_ > time_ms) {
|
||||
this->millis_overflow_counter_++;
|
||||
}
|
||||
this->last_time_ms_ = time_ms;
|
||||
|
||||
return (time_ms + ((int64_t) this->millis_overflow_counter_ << 32)) * INT64_C(1000000);
|
||||
}
|
||||
|
||||
void BME680BSECComponent::publish_sensor_state_(sensor::Sensor *sensor, float value, bool change_only) {
|
||||
if (!sensor || (change_only && sensor->has_state() && sensor->state == value)) {
|
||||
return;
|
||||
}
|
||||
sensor->publish_state(value);
|
||||
}
|
||||
|
||||
void BME680BSECComponent::publish_sensor_state_(text_sensor::TextSensor *sensor, std::string value) {
|
||||
if (!sensor || (sensor->has_state() && sensor->state == value)) {
|
||||
return;
|
||||
}
|
||||
sensor->publish_state(value);
|
||||
}
|
||||
|
||||
int8_t BME680BSECComponent::read_bytes_wrapper(uint8_t address, uint8_t a_register, uint8_t *data, uint16_t len) {
|
||||
return BME680BSECComponent::instance->read_bytes(a_register, data, len) ? 0 : -1;
|
||||
}
|
||||
|
||||
int8_t BME680BSECComponent::write_bytes_wrapper(uint8_t address, uint8_t a_register, uint8_t *data, uint16_t len) {
|
||||
return BME680BSECComponent::instance->write_bytes(a_register, data, len) ? 0 : -1;
|
||||
}
|
||||
|
||||
void BME680BSECComponent::delay_ms(uint32_t period) {
|
||||
ESP_LOGV(TAG, "Delaying for %ums", period);
|
||||
delay(period);
|
||||
}
|
||||
|
||||
void BME680BSECComponent::load_state_() {
|
||||
uint32_t hash = fnv1_hash("bme680_bsec_state_" + to_string(this->address_));
|
||||
this->bsec_state_ = global_preferences.make_preference<uint8_t[BSEC_MAX_STATE_BLOB_SIZE]>(hash, true);
|
||||
|
||||
uint8_t state[BSEC_MAX_STATE_BLOB_SIZE];
|
||||
if (this->bsec_state_.load(&state)) {
|
||||
ESP_LOGV(TAG, "Loading state");
|
||||
uint8_t work_buffer[BSEC_MAX_WORKBUFFER_SIZE];
|
||||
this->bsec_status_ = bsec_set_state(state, BSEC_MAX_STATE_BLOB_SIZE, work_buffer, sizeof(work_buffer));
|
||||
if (this->bsec_status_ != BSEC_OK) {
|
||||
ESP_LOGW(TAG, "Failed to load state (BSEC Error Code %d)", this->bsec_status_);
|
||||
}
|
||||
ESP_LOGI(TAG, "Loaded state");
|
||||
}
|
||||
}
|
||||
|
||||
void BME680BSECComponent::save_state_(uint8_t accuracy) {
|
||||
if (accuracy < 3 || (millis() - this->last_state_save_ms_ < this->state_save_interval_ms_)) {
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGV(TAG, "Saving state");
|
||||
|
||||
uint8_t state[BSEC_MAX_STATE_BLOB_SIZE];
|
||||
uint8_t work_buffer[BSEC_MAX_STATE_BLOB_SIZE];
|
||||
uint32_t num_serialized_state = BSEC_MAX_STATE_BLOB_SIZE;
|
||||
|
||||
this->bsec_status_ =
|
||||
bsec_get_state(0, state, BSEC_MAX_STATE_BLOB_SIZE, work_buffer, BSEC_MAX_STATE_BLOB_SIZE, &num_serialized_state);
|
||||
if (this->bsec_status_ != BSEC_OK) {
|
||||
ESP_LOGW(TAG, "Failed fetch state for save (BSEC Error Code %d)", this->bsec_status_);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this->bsec_state_.save(&state)) {
|
||||
ESP_LOGW(TAG, "Failed to save state");
|
||||
return;
|
||||
}
|
||||
this->last_state_save_ms_ = millis();
|
||||
|
||||
ESP_LOGI(TAG, "Saved state");
|
||||
}
|
||||
#endif
|
||||
} // namespace bme680_bsec
|
||||
} // namespace esphome
|
108
esphome/components/bme680_bsec/bme680_bsec.h
Normal file
108
esphome/components/bme680_bsec/bme680_bsec.h
Normal file
@@ -0,0 +1,108 @@
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/text_sensor/text_sensor.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
#include "esphome/core/preferences.h"
|
||||
#include <map>
|
||||
|
||||
#ifdef USING_BSEC
|
||||
#include <bsec.h>
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
namespace bme680_bsec {
|
||||
#ifdef USING_BSEC
|
||||
|
||||
enum IAQMode {
|
||||
IAQ_MODE_STATIC = 0,
|
||||
IAQ_MODE_MOBILE = 1,
|
||||
};
|
||||
|
||||
enum SampleRate {
|
||||
SAMPLE_RATE_LP = 0,
|
||||
SAMPLE_RATE_ULP = 1,
|
||||
};
|
||||
|
||||
class BME680BSECComponent : public Component, public i2c::I2CDevice {
|
||||
public:
|
||||
void set_temperature_offset(float offset) { this->temperature_offset_ = offset; }
|
||||
void set_iaq_mode(IAQMode iaq_mode) { this->iaq_mode_ = iaq_mode; }
|
||||
void set_sample_rate(SampleRate sample_rate) { this->sample_rate_ = sample_rate; }
|
||||
void set_state_save_interval(uint32_t interval) { this->state_save_interval_ms_ = interval; }
|
||||
|
||||
void set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_sensor; }
|
||||
void set_pressure_sensor(sensor::Sensor *pressure_sensor) { pressure_sensor_ = pressure_sensor; }
|
||||
void set_humidity_sensor(sensor::Sensor *humidity_sensor) { humidity_sensor_ = humidity_sensor; }
|
||||
void set_gas_resistance_sensor(sensor::Sensor *gas_resistance_sensor) {
|
||||
gas_resistance_sensor_ = gas_resistance_sensor;
|
||||
}
|
||||
void set_iaq_sensor(sensor::Sensor *iaq_sensor) { iaq_sensor_ = iaq_sensor; }
|
||||
void set_iaq_accuracy_text_sensor(text_sensor::TextSensor *iaq_accuracy_text_sensor) {
|
||||
iaq_accuracy_text_sensor_ = iaq_accuracy_text_sensor;
|
||||
}
|
||||
void set_iaq_accuracy_sensor(sensor::Sensor *iaq_accuracy_sensor) { iaq_accuracy_sensor_ = iaq_accuracy_sensor; }
|
||||
void set_co2_equivalent_sensor(sensor::Sensor *co2_equivalent_sensor) {
|
||||
co2_equivalent_sensor_ = co2_equivalent_sensor;
|
||||
}
|
||||
void set_breath_voc_equivalent_sensor(sensor::Sensor *breath_voc_equivalent_sensor) {
|
||||
breath_voc_equivalent_sensor_ = breath_voc_equivalent_sensor;
|
||||
}
|
||||
|
||||
static BME680BSECComponent *instance;
|
||||
static int8_t read_bytes_wrapper(uint8_t address, uint8_t a_register, uint8_t *data, uint16_t len);
|
||||
static int8_t write_bytes_wrapper(uint8_t address, uint8_t a_register, uint8_t *data, uint16_t len);
|
||||
static void delay_ms(uint32_t period);
|
||||
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override;
|
||||
void loop() override;
|
||||
|
||||
protected:
|
||||
void set_config_(const uint8_t *config);
|
||||
void update_subscription_(float sample_rate);
|
||||
|
||||
void run_();
|
||||
void read_(bsec_bme_settings_t bme680_settings);
|
||||
void publish_(const bsec_output_t *outputs, uint8_t num_outputs);
|
||||
int64_t get_time_ns_();
|
||||
|
||||
void publish_sensor_state_(sensor::Sensor *sensor, float value, bool change_only = false);
|
||||
void publish_sensor_state_(text_sensor::TextSensor *sensor, std::string value);
|
||||
|
||||
void load_state_();
|
||||
void save_state_(uint8_t accuracy);
|
||||
|
||||
struct bme680_dev bme680_;
|
||||
bsec_library_return_t bsec_status_{BSEC_OK};
|
||||
int8_t bme680_status_{BME680_OK};
|
||||
|
||||
int64_t last_time_ms_{0};
|
||||
uint32_t millis_overflow_counter_{0};
|
||||
int64_t next_call_ns_{0};
|
||||
|
||||
ESPPreferenceObject bsec_state_;
|
||||
uint32_t state_save_interval_ms_{21600000}; // 6 hours - 4 times a day
|
||||
uint32_t last_state_save_ms_ = 0;
|
||||
|
||||
float temperature_offset_{0};
|
||||
IAQMode iaq_mode_{IAQ_MODE_STATIC};
|
||||
SampleRate sample_rate_{SAMPLE_RATE_LP};
|
||||
|
||||
sensor::Sensor *temperature_sensor_;
|
||||
sensor::Sensor *pressure_sensor_;
|
||||
sensor::Sensor *humidity_sensor_;
|
||||
sensor::Sensor *gas_resistance_sensor_;
|
||||
sensor::Sensor *iaq_sensor_;
|
||||
text_sensor::TextSensor *iaq_accuracy_text_sensor_;
|
||||
sensor::Sensor *iaq_accuracy_sensor_;
|
||||
sensor::Sensor *co2_equivalent_sensor_;
|
||||
sensor::Sensor *breath_voc_equivalent_sensor_;
|
||||
};
|
||||
#endif
|
||||
} // namespace bme680_bsec
|
||||
} // namespace esphome
|
91
esphome/components/bme680_bsec/sensor.py
Normal file
91
esphome/components/bme680_bsec/sensor.py
Normal file
@@ -0,0 +1,91 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import sensor
|
||||
from esphome.const import (
|
||||
CONF_GAS_RESISTANCE,
|
||||
CONF_HUMIDITY,
|
||||
CONF_PRESSURE,
|
||||
CONF_TEMPERATURE,
|
||||
DEVICE_CLASS_EMPTY,
|
||||
DEVICE_CLASS_HUMIDITY,
|
||||
DEVICE_CLASS_PRESSURE,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
UNIT_CELSIUS,
|
||||
UNIT_EMPTY,
|
||||
UNIT_HECTOPASCAL,
|
||||
UNIT_OHM,
|
||||
UNIT_PARTS_PER_MILLION,
|
||||
UNIT_PERCENT,
|
||||
ICON_GAS_CYLINDER,
|
||||
ICON_GAUGE,
|
||||
ICON_THERMOMETER,
|
||||
ICON_WATER_PERCENT,
|
||||
)
|
||||
from esphome.core import coroutine
|
||||
from . import BME680BSECComponent, CONF_BME680_BSEC_ID
|
||||
|
||||
DEPENDENCIES = ["bme680_bsec"]
|
||||
|
||||
CONF_IAQ = "iaq"
|
||||
CONF_IAQ_ACCURACY = "iaq_accuracy"
|
||||
CONF_CO2_EQUIVALENT = "co2_equivalent"
|
||||
CONF_BREATH_VOC_EQUIVALENT = "breath_voc_equivalent"
|
||||
UNIT_IAQ = "IAQ"
|
||||
ICON_ACCURACY = "mdi:checkbox-marked-circle-outline"
|
||||
ICON_TEST_TUBE = "mdi:test-tube"
|
||||
|
||||
TYPES = {
|
||||
CONF_TEMPERATURE: "set_temperature_sensor",
|
||||
CONF_PRESSURE: "set_pressure_sensor",
|
||||
CONF_HUMIDITY: "set_humidity_sensor",
|
||||
CONF_GAS_RESISTANCE: "set_gas_resistance_sensor",
|
||||
CONF_IAQ: "set_iaq_sensor",
|
||||
CONF_IAQ_ACCURACY: "set_iaq_accuracy_sensor",
|
||||
CONF_CO2_EQUIVALENT: "set_co2_equivalent_sensor",
|
||||
CONF_BREATH_VOC_EQUIVALENT: "set_breath_voc_equivalent_sensor",
|
||||
}
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(CONF_BME680_BSEC_ID): cv.use_id(BME680BSECComponent),
|
||||
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
|
||||
UNIT_CELSIUS, ICON_THERMOMETER, 1, DEVICE_CLASS_TEMPERATURE
|
||||
),
|
||||
cv.Optional(CONF_PRESSURE): sensor.sensor_schema(
|
||||
UNIT_HECTOPASCAL, ICON_GAUGE, 1, DEVICE_CLASS_PRESSURE
|
||||
),
|
||||
cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
|
||||
UNIT_PERCENT, ICON_WATER_PERCENT, 1, DEVICE_CLASS_HUMIDITY
|
||||
),
|
||||
cv.Optional(CONF_GAS_RESISTANCE): sensor.sensor_schema(
|
||||
UNIT_OHM, ICON_GAS_CYLINDER, 0, DEVICE_CLASS_EMPTY
|
||||
),
|
||||
cv.Optional(CONF_IAQ): sensor.sensor_schema(
|
||||
UNIT_IAQ, ICON_GAUGE, 0, DEVICE_CLASS_EMPTY
|
||||
),
|
||||
cv.Optional(CONF_IAQ_ACCURACY): sensor.sensor_schema(
|
||||
UNIT_EMPTY, ICON_ACCURACY, 0, DEVICE_CLASS_EMPTY
|
||||
),
|
||||
cv.Optional(CONF_CO2_EQUIVALENT): sensor.sensor_schema(
|
||||
UNIT_PARTS_PER_MILLION, ICON_TEST_TUBE, 1, DEVICE_CLASS_EMPTY
|
||||
),
|
||||
cv.Optional(CONF_BREATH_VOC_EQUIVALENT): sensor.sensor_schema(
|
||||
UNIT_PARTS_PER_MILLION, ICON_TEST_TUBE, 1, DEVICE_CLASS_EMPTY
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@coroutine
|
||||
def setup_conf(config, key, hub, funcName):
|
||||
if key in config:
|
||||
conf = config[key]
|
||||
var = yield sensor.new_sensor(conf)
|
||||
func = getattr(hub, funcName)
|
||||
cg.add(func(var))
|
||||
|
||||
|
||||
def to_code(config):
|
||||
hub = yield cg.get_variable(config[CONF_BME680_BSEC_ID])
|
||||
for key, funcName in TYPES.items():
|
||||
yield setup_conf(config, key, hub, funcName)
|
41
esphome/components/bme680_bsec/text_sensor.py
Normal file
41
esphome/components/bme680_bsec/text_sensor.py
Normal file
@@ -0,0 +1,41 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import text_sensor
|
||||
from esphome.const import CONF_ID, CONF_ICON
|
||||
from esphome.core import coroutine
|
||||
from . import BME680BSECComponent, CONF_BME680_BSEC_ID
|
||||
|
||||
DEPENDENCIES = ["bme680_bsec"]
|
||||
|
||||
CONF_IAQ_ACCURACY = "iaq_accuracy"
|
||||
ICON_ACCURACY = "mdi:checkbox-marked-circle-outline"
|
||||
|
||||
TYPES = {CONF_IAQ_ACCURACY: "set_iaq_accuracy_text_sensor"}
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(CONF_BME680_BSEC_ID): cv.use_id(BME680BSECComponent),
|
||||
cv.Optional(CONF_IAQ_ACCURACY): text_sensor.TEXT_SENSOR_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(text_sensor.TextSensor),
|
||||
cv.Optional(CONF_ICON, default=ICON_ACCURACY): cv.icon,
|
||||
}
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@coroutine
|
||||
def setup_conf(config, key, hub, funcName):
|
||||
if key in config:
|
||||
conf = config[key]
|
||||
var = cg.new_Pvariable(conf[CONF_ID])
|
||||
yield text_sensor.register_text_sensor(var, conf)
|
||||
func = getattr(hub, funcName)
|
||||
cg.add(func(var))
|
||||
|
||||
|
||||
def to_code(config):
|
||||
hub = yield cg.get_variable(config[CONF_BME680_BSEC_ID])
|
||||
for key, funcName in TYPES.items():
|
||||
yield setup_conf(config, key, hub, funcName)
|
@@ -1,19 +1,39 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import i2c, sensor
|
||||
from esphome.const import CONF_ID, CONF_PRESSURE, CONF_TEMPERATURE, \
|
||||
UNIT_CELSIUS, ICON_THERMOMETER, ICON_GAUGE, UNIT_HECTOPASCAL
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_PRESSURE,
|
||||
CONF_TEMPERATURE,
|
||||
DEVICE_CLASS_PRESSURE,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
UNIT_CELSIUS,
|
||||
ICON_EMPTY,
|
||||
UNIT_HECTOPASCAL,
|
||||
)
|
||||
|
||||
DEPENDENCIES = ['i2c']
|
||||
DEPENDENCIES = ["i2c"]
|
||||
|
||||
bmp085_ns = cg.esphome_ns.namespace('bmp085')
|
||||
BMP085Component = bmp085_ns.class_('BMP085Component', cg.PollingComponent, i2c.I2CDevice)
|
||||
bmp085_ns = cg.esphome_ns.namespace("bmp085")
|
||||
BMP085Component = bmp085_ns.class_(
|
||||
"BMP085Component", cg.PollingComponent, i2c.I2CDevice
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema({
|
||||
cv.GenerateID(): cv.declare_id(BMP085Component),
|
||||
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 1),
|
||||
cv.Optional(CONF_PRESSURE): sensor.sensor_schema(UNIT_HECTOPASCAL, ICON_GAUGE, 1),
|
||||
}).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x77))
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(BMP085Component),
|
||||
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
|
||||
UNIT_CELSIUS, ICON_EMPTY, 1, DEVICE_CLASS_TEMPERATURE
|
||||
),
|
||||
cv.Optional(CONF_PRESSURE): sensor.sensor_schema(
|
||||
UNIT_HECTOPASCAL, ICON_EMPTY, 1, DEVICE_CLASS_PRESSURE
|
||||
),
|
||||
}
|
||||
)
|
||||
.extend(cv.polling_component_schema("60s"))
|
||||
.extend(i2c.i2c_device_schema(0x77))
|
||||
)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
|
@@ -1,44 +1,75 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import i2c, sensor
|
||||
from esphome.const import CONF_ID, CONF_PRESSURE, CONF_TEMPERATURE, \
|
||||
UNIT_CELSIUS, ICON_THERMOMETER, ICON_GAUGE, UNIT_HECTOPASCAL, \
|
||||
CONF_IIR_FILTER, CONF_OVERSAMPLING
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_PRESSURE,
|
||||
CONF_TEMPERATURE,
|
||||
DEVICE_CLASS_PRESSURE,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
UNIT_CELSIUS,
|
||||
ICON_EMPTY,
|
||||
UNIT_HECTOPASCAL,
|
||||
CONF_IIR_FILTER,
|
||||
CONF_OVERSAMPLING,
|
||||
)
|
||||
|
||||
DEPENDENCIES = ['i2c']
|
||||
DEPENDENCIES = ["i2c"]
|
||||
|
||||
bmp280_ns = cg.esphome_ns.namespace('bmp280')
|
||||
BMP280Oversampling = bmp280_ns.enum('BMP280Oversampling')
|
||||
bmp280_ns = cg.esphome_ns.namespace("bmp280")
|
||||
BMP280Oversampling = bmp280_ns.enum("BMP280Oversampling")
|
||||
OVERSAMPLING_OPTIONS = {
|
||||
'NONE': BMP280Oversampling.BMP280_OVERSAMPLING_NONE,
|
||||
'1X': BMP280Oversampling.BMP280_OVERSAMPLING_1X,
|
||||
'2X': BMP280Oversampling.BMP280_OVERSAMPLING_2X,
|
||||
'4X': BMP280Oversampling.BMP280_OVERSAMPLING_4X,
|
||||
'8X': BMP280Oversampling.BMP280_OVERSAMPLING_8X,
|
||||
'16X': BMP280Oversampling.BMP280_OVERSAMPLING_16X,
|
||||
"NONE": BMP280Oversampling.BMP280_OVERSAMPLING_NONE,
|
||||
"1X": BMP280Oversampling.BMP280_OVERSAMPLING_1X,
|
||||
"2X": BMP280Oversampling.BMP280_OVERSAMPLING_2X,
|
||||
"4X": BMP280Oversampling.BMP280_OVERSAMPLING_4X,
|
||||
"8X": BMP280Oversampling.BMP280_OVERSAMPLING_8X,
|
||||
"16X": BMP280Oversampling.BMP280_OVERSAMPLING_16X,
|
||||
}
|
||||
|
||||
BMP280IIRFilter = bmp280_ns.enum('BMP280IIRFilter')
|
||||
BMP280IIRFilter = bmp280_ns.enum("BMP280IIRFilter")
|
||||
IIR_FILTER_OPTIONS = {
|
||||
'OFF': BMP280IIRFilter.BMP280_IIR_FILTER_OFF,
|
||||
'2X': BMP280IIRFilter.BMP280_IIR_FILTER_2X,
|
||||
'4X': BMP280IIRFilter.BMP280_IIR_FILTER_4X,
|
||||
'8X': BMP280IIRFilter.BMP280_IIR_FILTER_8X,
|
||||
'16X': BMP280IIRFilter.BMP280_IIR_FILTER_16X,
|
||||
"OFF": BMP280IIRFilter.BMP280_IIR_FILTER_OFF,
|
||||
"2X": BMP280IIRFilter.BMP280_IIR_FILTER_2X,
|
||||
"4X": BMP280IIRFilter.BMP280_IIR_FILTER_4X,
|
||||
"8X": BMP280IIRFilter.BMP280_IIR_FILTER_8X,
|
||||
"16X": BMP280IIRFilter.BMP280_IIR_FILTER_16X,
|
||||
}
|
||||
|
||||
BMP280Component = bmp280_ns.class_('BMP280Component', cg.PollingComponent, i2c.I2CDevice)
|
||||
BMP280Component = bmp280_ns.class_(
|
||||
"BMP280Component", cg.PollingComponent, i2c.I2CDevice
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema({
|
||||
cv.GenerateID(): cv.declare_id(BMP280Component),
|
||||
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 1).extend({
|
||||
cv.Optional(CONF_OVERSAMPLING, default='16X'): cv.enum(OVERSAMPLING_OPTIONS, upper=True),
|
||||
}),
|
||||
cv.Optional(CONF_PRESSURE): sensor.sensor_schema(UNIT_HECTOPASCAL, ICON_GAUGE, 1).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))
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(BMP280Component),
|
||||
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
|
||||
UNIT_CELSIUS, ICON_EMPTY, 1, DEVICE_CLASS_TEMPERATURE
|
||||
).extend(
|
||||
{
|
||||
cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum(
|
||||
OVERSAMPLING_OPTIONS, upper=True
|
||||
),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_PRESSURE): sensor.sensor_schema(
|
||||
UNIT_HECTOPASCAL, ICON_EMPTY, 1, DEVICE_CLASS_PRESSURE
|
||||
).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):
|
||||
|
@@ -4,68 +4,82 @@ from esphome import automation
|
||||
from esphome.core import CORE, coroutine
|
||||
from esphome.const import CONF_ID, CONF_TRIGGER_ID, CONF_DATA
|
||||
|
||||
CODEOWNERS = ['@mvturnho', '@danielschramm']
|
||||
CODEOWNERS = ["@mvturnho", "@danielschramm"]
|
||||
IS_PLATFORM_COMPONENT = True
|
||||
|
||||
CONF_CAN_ID = 'can_id'
|
||||
CONF_USE_EXTENDED_ID = 'use_extended_id'
|
||||
CONF_CANBUS_ID = 'canbus_id'
|
||||
CONF_BIT_RATE = 'bit_rate'
|
||||
CONF_ON_FRAME = 'on_frame'
|
||||
CONF_CANBUS_SEND = 'canbus.send'
|
||||
CONF_CAN_ID = "can_id"
|
||||
CONF_USE_EXTENDED_ID = "use_extended_id"
|
||||
CONF_CANBUS_ID = "canbus_id"
|
||||
CONF_BIT_RATE = "bit_rate"
|
||||
CONF_ON_FRAME = "on_frame"
|
||||
|
||||
|
||||
def validate_id(id_value, id_ext):
|
||||
if not id_ext:
|
||||
if id_value > 0x7ff:
|
||||
if id_value > 0x7FF:
|
||||
raise cv.Invalid("Standard IDs must be 11 Bit (0x000-0x7ff / 0-2047)")
|
||||
|
||||
|
||||
def validate_raw_data(value):
|
||||
if isinstance(value, str):
|
||||
return value.encode('utf-8')
|
||||
return value.encode("utf-8")
|
||||
if isinstance(value, list):
|
||||
return cv.Schema([cv.hex_uint8_t])(value)
|
||||
raise cv.Invalid("data must either be a string wrapped in quotes or a list of bytes")
|
||||
raise cv.Invalid(
|
||||
"data must either be a string wrapped in quotes or a list of bytes"
|
||||
)
|
||||
|
||||
|
||||
canbus_ns = cg.esphome_ns.namespace('canbus')
|
||||
CanbusComponent = canbus_ns.class_('CanbusComponent', cg.Component)
|
||||
CanbusTrigger = canbus_ns.class_('CanbusTrigger',
|
||||
automation.Trigger.template(cg.std_vector.template(cg.uint8)),
|
||||
cg.Component)
|
||||
CanSpeed = canbus_ns.enum('CAN_SPEED')
|
||||
canbus_ns = cg.esphome_ns.namespace("canbus")
|
||||
CanbusComponent = canbus_ns.class_("CanbusComponent", cg.Component)
|
||||
CanbusTrigger = canbus_ns.class_(
|
||||
"CanbusTrigger",
|
||||
automation.Trigger.template(cg.std_vector.template(cg.uint8)),
|
||||
cg.Component,
|
||||
)
|
||||
CanSpeed = canbus_ns.enum("CAN_SPEED")
|
||||
|
||||
CAN_SPEEDS = {
|
||||
'5KBPS': CanSpeed.CAN_5KBPS,
|
||||
'10KBPS': CanSpeed.CAN_10KBPS,
|
||||
'20KBPS': CanSpeed.CAN_20KBPS,
|
||||
'31K25BPS': CanSpeed.CAN_31K25BPS,
|
||||
'33KBPS': CanSpeed.CAN_33KBPS,
|
||||
'40KBPS': CanSpeed.CAN_40KBPS,
|
||||
'50KBPS': CanSpeed.CAN_50KBPS,
|
||||
'80KBPS': CanSpeed.CAN_80KBPS,
|
||||
'83K3BPS': CanSpeed.CAN_83K3BPS,
|
||||
'95KBPS': CanSpeed.CAN_95KBPS,
|
||||
'100KBPS': CanSpeed.CAN_100KBPS,
|
||||
'125KBPS': CanSpeed.CAN_125KBPS,
|
||||
'200KBPS': CanSpeed.CAN_200KBPS,
|
||||
'250KBPS': CanSpeed.CAN_250KBPS,
|
||||
'500KBPS': CanSpeed.CAN_500KBPS,
|
||||
'1000KBPS': CanSpeed.CAN_1000KBPS,
|
||||
"5KBPS": CanSpeed.CAN_5KBPS,
|
||||
"10KBPS": CanSpeed.CAN_10KBPS,
|
||||
"20KBPS": CanSpeed.CAN_20KBPS,
|
||||
"31K25BPS": CanSpeed.CAN_31K25BPS,
|
||||
"33KBPS": CanSpeed.CAN_33KBPS,
|
||||
"40KBPS": CanSpeed.CAN_40KBPS,
|
||||
"50KBPS": CanSpeed.CAN_50KBPS,
|
||||
"80KBPS": CanSpeed.CAN_80KBPS,
|
||||
"83K3BPS": CanSpeed.CAN_83K3BPS,
|
||||
"95KBPS": CanSpeed.CAN_95KBPS,
|
||||
"100KBPS": CanSpeed.CAN_100KBPS,
|
||||
"125KBPS": CanSpeed.CAN_125KBPS,
|
||||
"200KBPS": CanSpeed.CAN_200KBPS,
|
||||
"250KBPS": CanSpeed.CAN_250KBPS,
|
||||
"500KBPS": CanSpeed.CAN_500KBPS,
|
||||
"1000KBPS": CanSpeed.CAN_1000KBPS,
|
||||
}
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema({
|
||||
cv.GenerateID(): cv.declare_id(CanbusComponent),
|
||||
cv.Required(CONF_CAN_ID): cv.int_range(min=0, max=0x1fffffff),
|
||||
cv.Optional(CONF_BIT_RATE, default='125KBPS'): cv.enum(CAN_SPEEDS, upper=True),
|
||||
cv.Optional(CONF_USE_EXTENDED_ID, default=False): cv.boolean,
|
||||
cv.Optional(CONF_ON_FRAME): automation.validate_automation({
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(CanbusTrigger),
|
||||
cv.GenerateID(CONF_CAN_ID): cv.int_range(min=0, max=0x1fffffff),
|
||||
CANBUS_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(CanbusComponent),
|
||||
cv.Required(CONF_CAN_ID): cv.int_range(min=0, max=0x1FFFFFFF),
|
||||
cv.Optional(CONF_BIT_RATE, default="125KBPS"): cv.enum(CAN_SPEEDS, upper=True),
|
||||
cv.Optional(CONF_USE_EXTENDED_ID, default=False): cv.boolean,
|
||||
}),
|
||||
}).extend(cv.COMPONENT_SCHEMA)
|
||||
cv.Optional(CONF_ON_FRAME): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(CanbusTrigger),
|
||||
cv.GenerateID(CONF_CAN_ID): cv.int_range(min=0, max=0x1FFFFFFF),
|
||||
cv.Optional(CONF_USE_EXTENDED_ID, default=False): cv.boolean,
|
||||
cv.Optional(CONF_ON_FRAME): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(CanbusTrigger),
|
||||
cv.GenerateID(CONF_CAN_ID): cv.int_range(min=0, max=0x1FFFFFFF),
|
||||
cv.Optional(CONF_USE_EXTENDED_ID, default=False): cv.boolean,
|
||||
}
|
||||
),
|
||||
}
|
||||
),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
|
||||
@coroutine
|
||||
@@ -82,7 +96,9 @@ def setup_canbus_core_(var, config):
|
||||
validate_id(can_id, ext_id)
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var, can_id, ext_id)
|
||||
yield cg.register_component(trigger, conf)
|
||||
yield automation.build_automation(trigger, [(cg.std_vector.template(cg.uint8), 'x')], conf)
|
||||
yield automation.build_automation(
|
||||
trigger, [(cg.std_vector.template(cg.uint8), "x")], conf
|
||||
)
|
||||
|
||||
|
||||
@coroutine
|
||||
@@ -93,14 +109,19 @@ def register_canbus(var, config):
|
||||
|
||||
|
||||
# Actions
|
||||
@automation.register_action(CONF_CANBUS_SEND,
|
||||
canbus_ns.class_('CanbusSendAction', automation.Action),
|
||||
cv.maybe_simple_value({
|
||||
cv.GenerateID(CONF_CANBUS_ID): cv.use_id(CanbusComponent),
|
||||
cv.Optional(CONF_CAN_ID): cv.int_range(min=0, max=0x1fffffff),
|
||||
cv.Optional(CONF_USE_EXTENDED_ID, default=False): cv.boolean,
|
||||
cv.Required(CONF_DATA): cv.templatable(validate_raw_data),
|
||||
}, key=CONF_DATA))
|
||||
@automation.register_action(
|
||||
"canbus.send",
|
||||
canbus_ns.class_("CanbusSendAction", automation.Action),
|
||||
cv.maybe_simple_value(
|
||||
{
|
||||
cv.GenerateID(CONF_CANBUS_ID): cv.use_id(CanbusComponent),
|
||||
cv.Optional(CONF_CAN_ID): cv.int_range(min=0, max=0x1FFFFFFF),
|
||||
cv.Optional(CONF_USE_EXTENDED_ID, default=False): cv.boolean,
|
||||
cv.Required(CONF_DATA): cv.templatable(validate_raw_data),
|
||||
},
|
||||
key=CONF_DATA,
|
||||
),
|
||||
)
|
||||
def canbus_action_to_code(config, action_id, template_arg, args):
|
||||
validate_id(config[CONF_CAN_ID], config[CONF_USE_EXTENDED_ID])
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
@@ -110,7 +131,9 @@ def canbus_action_to_code(config, action_id, template_arg, args):
|
||||
can_id = yield cg.templatable(config[CONF_CAN_ID], args, cg.uint32)
|
||||
cg.add(var.set_can_id(can_id))
|
||||
|
||||
use_extended_id = yield cg.templatable(config[CONF_USE_EXTENDED_ID], args, cg.uint32)
|
||||
use_extended_id = yield cg.templatable(
|
||||
config[CONF_USE_EXTENDED_ID], args, cg.uint32
|
||||
)
|
||||
cg.add(var.set_use_extended_id(use_extended_id))
|
||||
|
||||
data = config[CONF_DATA]
|
||||
|
@@ -5,17 +5,21 @@ from esphome.components.web_server_base import CONF_WEB_SERVER_BASE_ID
|
||||
from esphome.const import CONF_ID
|
||||
from esphome.core import coroutine_with_priority
|
||||
|
||||
AUTO_LOAD = ['web_server_base']
|
||||
DEPENDENCIES = ['wifi']
|
||||
CODEOWNERS = ['@OttoWinter']
|
||||
AUTO_LOAD = ["web_server_base"]
|
||||
DEPENDENCIES = ["wifi"]
|
||||
CODEOWNERS = ["@OttoWinter"]
|
||||
|
||||
captive_portal_ns = cg.esphome_ns.namespace('captive_portal')
|
||||
CaptivePortal = captive_portal_ns.class_('CaptivePortal', cg.Component)
|
||||
captive_portal_ns = cg.esphome_ns.namespace("captive_portal")
|
||||
CaptivePortal = captive_portal_ns.class_("CaptivePortal", cg.Component)
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema({
|
||||
cv.GenerateID(): cv.declare_id(CaptivePortal),
|
||||
cv.GenerateID(CONF_WEB_SERVER_BASE_ID): cv.use_id(web_server_base.WebServerBase),
|
||||
}).extend(cv.COMPONENT_SCHEMA)
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(CaptivePortal),
|
||||
cv.GenerateID(CONF_WEB_SERVER_BASE_ID): cv.use_id(
|
||||
web_server_base.WebServerBase
|
||||
),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
|
||||
@coroutine_with_priority(64.0)
|
||||
@@ -24,4 +28,4 @@ def to_code(config):
|
||||
|
||||
var = cg.new_Pvariable(config[CONF_ID], paren)
|
||||
yield cg.register_component(var, config)
|
||||
cg.add_define('USE_CAPTIVE_PORTAL')
|
||||
cg.add_define("USE_CAPTIVE_PORTAL")
|
||||
|
@@ -1,28 +1,46 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import i2c, sensor
|
||||
from esphome.const import CONF_ID, ICON_RADIATOR, UNIT_PARTS_PER_MILLION, \
|
||||
UNIT_PARTS_PER_BILLION, CONF_TEMPERATURE, CONF_HUMIDITY, ICON_MOLECULE_CO2
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
DEVICE_CLASS_EMPTY,
|
||||
ICON_RADIATOR,
|
||||
UNIT_PARTS_PER_MILLION,
|
||||
UNIT_PARTS_PER_BILLION,
|
||||
CONF_TEMPERATURE,
|
||||
CONF_TVOC,
|
||||
CONF_HUMIDITY,
|
||||
ICON_MOLECULE_CO2,
|
||||
)
|
||||
|
||||
DEPENDENCIES = ['i2c']
|
||||
DEPENDENCIES = ["i2c"]
|
||||
|
||||
ccs811_ns = cg.esphome_ns.namespace('ccs811')
|
||||
CCS811Component = ccs811_ns.class_('CCS811Component', cg.PollingComponent, i2c.I2CDevice)
|
||||
ccs811_ns = cg.esphome_ns.namespace("ccs811")
|
||||
CCS811Component = ccs811_ns.class_(
|
||||
"CCS811Component", cg.PollingComponent, i2c.I2CDevice
|
||||
)
|
||||
|
||||
CONF_ECO2 = 'eco2'
|
||||
CONF_TVOC = 'tvoc'
|
||||
CONF_BASELINE = 'baseline'
|
||||
CONF_ECO2 = "eco2"
|
||||
CONF_BASELINE = "baseline"
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema({
|
||||
cv.GenerateID(): cv.declare_id(CCS811Component),
|
||||
cv.Required(CONF_ECO2): sensor.sensor_schema(UNIT_PARTS_PER_MILLION, ICON_MOLECULE_CO2,
|
||||
0),
|
||||
cv.Required(CONF_TVOC): sensor.sensor_schema(UNIT_PARTS_PER_BILLION, ICON_RADIATOR, 0),
|
||||
|
||||
cv.Optional(CONF_BASELINE): cv.hex_uint16_t,
|
||||
cv.Optional(CONF_TEMPERATURE): cv.use_id(sensor.Sensor),
|
||||
cv.Optional(CONF_HUMIDITY): cv.use_id(sensor.Sensor),
|
||||
}).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x5A))
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(CCS811Component),
|
||||
cv.Required(CONF_ECO2): sensor.sensor_schema(
|
||||
UNIT_PARTS_PER_MILLION, ICON_MOLECULE_CO2, 0, DEVICE_CLASS_EMPTY
|
||||
),
|
||||
cv.Required(CONF_TVOC): sensor.sensor_schema(
|
||||
UNIT_PARTS_PER_BILLION, ICON_RADIATOR, 0, DEVICE_CLASS_EMPTY
|
||||
),
|
||||
cv.Optional(CONF_BASELINE): cv.hex_uint16_t,
|
||||
cv.Optional(CONF_TEMPERATURE): cv.use_id(sensor.Sensor),
|
||||
cv.Optional(CONF_HUMIDITY): cv.use_id(sensor.Sensor),
|
||||
}
|
||||
)
|
||||
.extend(cv.polling_component_schema("60s"))
|
||||
.extend(i2c.i2c_device_schema(0x5A))
|
||||
)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
|
@@ -2,70 +2,87 @@ import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import automation
|
||||
from esphome.components import mqtt
|
||||
from esphome.const import CONF_AWAY, CONF_ID, CONF_INTERNAL, CONF_MAX_TEMPERATURE, \
|
||||
CONF_MIN_TEMPERATURE, CONF_MODE, CONF_TARGET_TEMPERATURE, \
|
||||
CONF_TARGET_TEMPERATURE_HIGH, CONF_TARGET_TEMPERATURE_LOW, CONF_TEMPERATURE_STEP, CONF_VISUAL, \
|
||||
CONF_MQTT_ID, CONF_NAME, CONF_FAN_MODE, CONF_SWING_MODE
|
||||
from esphome.const import (
|
||||
CONF_AWAY,
|
||||
CONF_ID,
|
||||
CONF_INTERNAL,
|
||||
CONF_MAX_TEMPERATURE,
|
||||
CONF_MIN_TEMPERATURE,
|
||||
CONF_MODE,
|
||||
CONF_TARGET_TEMPERATURE,
|
||||
CONF_TARGET_TEMPERATURE_HIGH,
|
||||
CONF_TARGET_TEMPERATURE_LOW,
|
||||
CONF_TEMPERATURE_STEP,
|
||||
CONF_VISUAL,
|
||||
CONF_MQTT_ID,
|
||||
CONF_NAME,
|
||||
CONF_FAN_MODE,
|
||||
CONF_SWING_MODE,
|
||||
)
|
||||
from esphome.core import CORE, coroutine, coroutine_with_priority
|
||||
|
||||
IS_PLATFORM_COMPONENT = True
|
||||
|
||||
CODEOWNERS = ['@esphome/core']
|
||||
climate_ns = cg.esphome_ns.namespace('climate')
|
||||
CODEOWNERS = ["@esphome/core"]
|
||||
climate_ns = cg.esphome_ns.namespace("climate")
|
||||
|
||||
Climate = climate_ns.class_('Climate', cg.Nameable)
|
||||
ClimateCall = climate_ns.class_('ClimateCall')
|
||||
ClimateTraits = climate_ns.class_('ClimateTraits')
|
||||
Climate = climate_ns.class_("Climate", cg.Nameable)
|
||||
ClimateCall = climate_ns.class_("ClimateCall")
|
||||
ClimateTraits = climate_ns.class_("ClimateTraits")
|
||||
|
||||
ClimateMode = climate_ns.enum('ClimateMode')
|
||||
ClimateMode = climate_ns.enum("ClimateMode")
|
||||
CLIMATE_MODES = {
|
||||
'OFF': ClimateMode.CLIMATE_MODE_OFF,
|
||||
'AUTO': ClimateMode.CLIMATE_MODE_AUTO,
|
||||
'COOL': ClimateMode.CLIMATE_MODE_COOL,
|
||||
'HEAT': ClimateMode.CLIMATE_MODE_HEAT,
|
||||
'DRY': ClimateMode.CLIMATE_MODE_DRY,
|
||||
'FAN_ONLY': ClimateMode.CLIMATE_MODE_FAN_ONLY,
|
||||
"OFF": ClimateMode.CLIMATE_MODE_OFF,
|
||||
"AUTO": ClimateMode.CLIMATE_MODE_AUTO,
|
||||
"COOL": ClimateMode.CLIMATE_MODE_COOL,
|
||||
"HEAT": ClimateMode.CLIMATE_MODE_HEAT,
|
||||
"DRY": ClimateMode.CLIMATE_MODE_DRY,
|
||||
"FAN_ONLY": ClimateMode.CLIMATE_MODE_FAN_ONLY,
|
||||
}
|
||||
validate_climate_mode = cv.enum(CLIMATE_MODES, upper=True)
|
||||
|
||||
ClimateFanMode = climate_ns.enum('ClimateFanMode')
|
||||
ClimateFanMode = climate_ns.enum("ClimateFanMode")
|
||||
CLIMATE_FAN_MODES = {
|
||||
'ON': ClimateFanMode.CLIMATE_FAN_ON,
|
||||
'OFF': ClimateFanMode.CLIMATE_FAN_OFF,
|
||||
'AUTO': ClimateFanMode.CLIMATE_FAN_AUTO,
|
||||
'LOW': ClimateFanMode.CLIMATE_FAN_LOW,
|
||||
'MEDIUM': ClimateFanMode.CLIMATE_FAN_MEDIUM,
|
||||
'HIGH': ClimateFanMode.CLIMATE_FAN_HIGH,
|
||||
'MIDDLE': ClimateFanMode.CLIMATE_FAN_MIDDLE,
|
||||
'FOCUS': ClimateFanMode.CLIMATE_FAN_FOCUS,
|
||||
'DIFFUSE': ClimateFanMode.CLIMATE_FAN_DIFFUSE,
|
||||
"ON": ClimateFanMode.CLIMATE_FAN_ON,
|
||||
"OFF": ClimateFanMode.CLIMATE_FAN_OFF,
|
||||
"AUTO": ClimateFanMode.CLIMATE_FAN_AUTO,
|
||||
"LOW": ClimateFanMode.CLIMATE_FAN_LOW,
|
||||
"MEDIUM": ClimateFanMode.CLIMATE_FAN_MEDIUM,
|
||||
"HIGH": ClimateFanMode.CLIMATE_FAN_HIGH,
|
||||
"MIDDLE": ClimateFanMode.CLIMATE_FAN_MIDDLE,
|
||||
"FOCUS": ClimateFanMode.CLIMATE_FAN_FOCUS,
|
||||
"DIFFUSE": ClimateFanMode.CLIMATE_FAN_DIFFUSE,
|
||||
}
|
||||
|
||||
validate_climate_fan_mode = cv.enum(CLIMATE_FAN_MODES, upper=True)
|
||||
|
||||
ClimateSwingMode = climate_ns.enum('ClimateSwingMode')
|
||||
ClimateSwingMode = climate_ns.enum("ClimateSwingMode")
|
||||
CLIMATE_SWING_MODES = {
|
||||
'OFF': ClimateSwingMode.CLIMATE_SWING_OFF,
|
||||
'BOTH': ClimateSwingMode.CLIMATE_SWING_BOTH,
|
||||
'VERTICAL': ClimateSwingMode.CLIMATE_SWING_VERTICAL,
|
||||
'HORIZONTAL': ClimateSwingMode.CLIMATE_SWING_HORIZONTAL,
|
||||
"OFF": ClimateSwingMode.CLIMATE_SWING_OFF,
|
||||
"BOTH": ClimateSwingMode.CLIMATE_SWING_BOTH,
|
||||
"VERTICAL": ClimateSwingMode.CLIMATE_SWING_VERTICAL,
|
||||
"HORIZONTAL": ClimateSwingMode.CLIMATE_SWING_HORIZONTAL,
|
||||
}
|
||||
|
||||
validate_climate_swing_mode = cv.enum(CLIMATE_SWING_MODES, upper=True)
|
||||
|
||||
# Actions
|
||||
ControlAction = climate_ns.class_('ControlAction', automation.Action)
|
||||
ControlAction = climate_ns.class_("ControlAction", automation.Action)
|
||||
|
||||
CLIMATE_SCHEMA = cv.MQTT_COMMAND_COMPONENT_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_id(Climate),
|
||||
cv.OnlyWith(CONF_MQTT_ID, 'mqtt'): cv.declare_id(mqtt.MQTTClimateComponent),
|
||||
cv.Optional(CONF_VISUAL, default={}): cv.Schema({
|
||||
cv.Optional(CONF_MIN_TEMPERATURE): cv.temperature,
|
||||
cv.Optional(CONF_MAX_TEMPERATURE): cv.temperature,
|
||||
cv.Optional(CONF_TEMPERATURE_STEP): cv.temperature,
|
||||
}),
|
||||
# TODO: MQTT topic options
|
||||
})
|
||||
CLIMATE_SCHEMA = cv.MQTT_COMMAND_COMPONENT_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(Climate),
|
||||
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTClimateComponent),
|
||||
cv.Optional(CONF_VISUAL, default={}): cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_MIN_TEMPERATURE): cv.temperature,
|
||||
cv.Optional(CONF_MAX_TEMPERATURE): cv.temperature,
|
||||
cv.Optional(CONF_TEMPERATURE_STEP): cv.temperature,
|
||||
}
|
||||
),
|
||||
# TODO: MQTT topic options
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@coroutine
|
||||
@@ -94,19 +111,23 @@ def register_climate(var, config):
|
||||
yield setup_climate_core_(var, config)
|
||||
|
||||
|
||||
CLIMATE_CONTROL_ACTION_SCHEMA = cv.Schema({
|
||||
cv.Required(CONF_ID): cv.use_id(Climate),
|
||||
cv.Optional(CONF_MODE): cv.templatable(validate_climate_mode),
|
||||
cv.Optional(CONF_TARGET_TEMPERATURE): cv.templatable(cv.temperature),
|
||||
cv.Optional(CONF_TARGET_TEMPERATURE_LOW): cv.templatable(cv.temperature),
|
||||
cv.Optional(CONF_TARGET_TEMPERATURE_HIGH): cv.templatable(cv.temperature),
|
||||
cv.Optional(CONF_AWAY): cv.templatable(cv.boolean),
|
||||
cv.Optional(CONF_FAN_MODE): cv.templatable(validate_climate_fan_mode),
|
||||
cv.Optional(CONF_SWING_MODE): cv.templatable(validate_climate_swing_mode),
|
||||
})
|
||||
CLIMATE_CONTROL_ACTION_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.use_id(Climate),
|
||||
cv.Optional(CONF_MODE): cv.templatable(validate_climate_mode),
|
||||
cv.Optional(CONF_TARGET_TEMPERATURE): cv.templatable(cv.temperature),
|
||||
cv.Optional(CONF_TARGET_TEMPERATURE_LOW): cv.templatable(cv.temperature),
|
||||
cv.Optional(CONF_TARGET_TEMPERATURE_HIGH): cv.templatable(cv.temperature),
|
||||
cv.Optional(CONF_AWAY): cv.templatable(cv.boolean),
|
||||
cv.Optional(CONF_FAN_MODE): cv.templatable(validate_climate_fan_mode),
|
||||
cv.Optional(CONF_SWING_MODE): cv.templatable(validate_climate_swing_mode),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@automation.register_action('climate.control', ControlAction, CLIMATE_CONTROL_ACTION_SCHEMA)
|
||||
@automation.register_action(
|
||||
"climate.control", ControlAction, CLIMATE_CONTROL_ACTION_SCHEMA
|
||||
)
|
||||
def climate_control_to_code(config, action_id, template_arg, args):
|
||||
paren = yield cg.get_variable(config[CONF_ID])
|
||||
var = cg.new_Pvariable(action_id, template_arg, paren)
|
||||
@@ -117,10 +138,14 @@ def climate_control_to_code(config, action_id, template_arg, args):
|
||||
template_ = yield cg.templatable(config[CONF_TARGET_TEMPERATURE], args, float)
|
||||
cg.add(var.set_target_temperature(template_))
|
||||
if CONF_TARGET_TEMPERATURE_LOW in config:
|
||||
template_ = yield cg.templatable(config[CONF_TARGET_TEMPERATURE_LOW], args, float)
|
||||
template_ = yield cg.templatable(
|
||||
config[CONF_TARGET_TEMPERATURE_LOW], args, float
|
||||
)
|
||||
cg.add(var.set_target_temperature_low(template_))
|
||||
if CONF_TARGET_TEMPERATURE_HIGH in config:
|
||||
template_ = yield cg.templatable(config[CONF_TARGET_TEMPERATURE_HIGH], args, float)
|
||||
template_ = yield cg.templatable(
|
||||
config[CONF_TARGET_TEMPERATURE_HIGH], args, float
|
||||
)
|
||||
cg.add(var.set_target_temperature_high(template_))
|
||||
if CONF_AWAY in config:
|
||||
template_ = yield cg.templatable(config[CONF_AWAY], args, bool)
|
||||
@@ -129,12 +154,14 @@ def climate_control_to_code(config, action_id, template_arg, args):
|
||||
template_ = yield cg.templatable(config[CONF_FAN_MODE], args, ClimateFanMode)
|
||||
cg.add(var.set_fan_mode(template_))
|
||||
if CONF_SWING_MODE in config:
|
||||
template_ = yield cg.templatable(config[CONF_SWING_MODE], args, ClimateSwingMode)
|
||||
template_ = yield cg.templatable(
|
||||
config[CONF_SWING_MODE], args, ClimateSwingMode
|
||||
)
|
||||
cg.add(var.set_swing_mode(template_))
|
||||
yield var
|
||||
|
||||
|
||||
@coroutine_with_priority(100.0)
|
||||
def to_code(config):
|
||||
cg.add_define('USE_CLIMATE')
|
||||
cg.add_define("USE_CLIMATE")
|
||||
cg.add_global(climate_ns.using)
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user