mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 07:03:55 +00:00 
			
		
		
		
	Merge branch 'dev' into bump-1.17.0b1
This commit is contained in:
		
							
								
								
									
										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> | **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: | ## Checklist: | ||||||
|   - [ ] The code change is tested and works locally. |   - [ ] The code change is tested and works locally. | ||||||
|   - [ ] Tests have been added to verify that the new code works (under `tests/` folder). |   - [ ] Tests have been added to verify that the new code works (under `tests/` folder). | ||||||
|  |    | ||||||
| If user exposed functionality or configuration variables are added/changed: | If user exposed functionality or configuration variables are added/changed: | ||||||
|   - [ ] Documentation added/updated in [esphome-docs](https://github.com/esphome/esphome-docs). |   - [ ] Documentation added/updated in [esphome-docs](https://github.com/esphome/esphome-docs). | ||||||
|   | |||||||
							
								
								
									
										4
									
								
								.github/workflows/ci-docker.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/workflows/ci-docker.yml
									
									
									
									
										vendored
									
									
								
							| @@ -26,7 +26,7 @@ jobs: | |||||||
|       - uses: actions/checkout@v2 |       - uses: actions/checkout@v2 | ||||||
|       - name: Set up env variables |       - name: Set up env variables | ||||||
|         run: | |         run: | | ||||||
|           base_version="2.6.0" |           base_version="3.0.0" | ||||||
|  |  | ||||||
|           if [[ "${{ matrix.build_type }}" == "hassio" ]]; then |           if [[ "${{ matrix.build_type }}" == "hassio" ]]; then | ||||||
|             build_from="esphome/esphome-hassio-base-${{ matrix.arch }}:${base_version}" |             build_from="esphome/esphome-hassio-base-${{ matrix.arch }}:${base_version}" | ||||||
| @@ -45,7 +45,7 @@ jobs: | |||||||
|         run: | |         run: | | ||||||
|           docker pull "${BUILD_TO}:dev" || true |           docker pull "${BUILD_TO}:dev" || true | ||||||
|       - name: Register QEMU binfmt |       - name: Register QEMU binfmt | ||||||
|         run: docker run --rm --privileged multiarch/qemu-user-static:5.0.0-2 --reset -p yes |         run: docker run --rm --privileged multiarch/qemu-user-static:5.2.0-2 --reset -p yes | ||||||
|       - run: | |       - run: | | ||||||
|           docker build \ |           docker build \ | ||||||
|             --build-arg "BUILD_FROM=${BUILD_FROM}" \ |             --build-arg "BUILD_FROM=${BUILD_FROM}" \ | ||||||
|   | |||||||
							
								
								
									
										18
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										18
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							| @@ -18,15 +18,6 @@ jobs: | |||||||
|     container: esphome/esphome-lint:latest |     container: esphome/esphome-lint:latest | ||||||
|     steps: |     steps: | ||||||
|       - uses: actions/checkout@v2 |       - uses: actions/checkout@v2 | ||||||
|       # Cache platformio intermediary files (like libraries etc) |  | ||||||
|       # Note: platformio platform versions should be cached via the esphome-lint image |  | ||||||
|       - name: Cache Platformio |  | ||||||
|         uses: actions/cache@v1 |  | ||||||
|         with: |  | ||||||
|           path: .pio |  | ||||||
|           key: lint-cpp-pio-${{ hashFiles('platformio.ini') }} |  | ||||||
|           restore-keys: | |  | ||||||
|             lint-cpp-pio- |  | ||||||
|       # Set up the pio project so that the cpp checks know how files are compiled |       # Set up the pio project so that the cpp checks know how files are compiled | ||||||
|       # (build flags, libraries etc) |       # (build flags, libraries etc) | ||||||
|       - name: Set up platformio environment |       - name: Set up platformio environment | ||||||
| @@ -49,15 +40,6 @@ jobs: | |||||||
|         split: [1, 2, 3, 4] |         split: [1, 2, 3, 4] | ||||||
|     steps: |     steps: | ||||||
|       - uses: actions/checkout@v2 |       - uses: actions/checkout@v2 | ||||||
|       # Cache platformio intermediary files (like libraries etc) |  | ||||||
|       # Note: platformio platform versions should be cached via the esphome-lint image |  | ||||||
|       - name: Cache Platformio |  | ||||||
|         uses: actions/cache@v1 |  | ||||||
|         with: |  | ||||||
|           path: .pio |  | ||||||
|           key: lint-cpp-pio-${{ hashFiles('platformio.ini') }} |  | ||||||
|           restore-keys: | |  | ||||||
|             lint-cpp-pio- |  | ||||||
|       # Set up the pio project so that the cpp checks know how files are compiled |       # Set up the pio project so that the cpp checks know how files are compiled | ||||||
|       # (build flags, libraries etc) |       # (build flags, libraries etc) | ||||||
|       - name: Set up platformio environment |       - name: Set up platformio environment | ||||||
|   | |||||||
							
								
								
									
										22
									
								
								.github/workflows/release-dev.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										22
									
								
								.github/workflows/release-dev.yml
									
									
									
									
										vendored
									
									
								
							| @@ -15,15 +15,6 @@ jobs: | |||||||
|     container: esphome/esphome-lint:latest |     container: esphome/esphome-lint:latest | ||||||
|     steps: |     steps: | ||||||
|       - uses: actions/checkout@v2 |       - uses: actions/checkout@v2 | ||||||
|       # Cache platformio intermediary files (like libraries etc) |  | ||||||
|       # Note: platformio platform versions should be cached via the esphome-lint image |  | ||||||
|       - name: Cache Platformio |  | ||||||
|         uses: actions/cache@v1 |  | ||||||
|         with: |  | ||||||
|           path: .pio |  | ||||||
|           key: lint-cpp-pio-${{ hashFiles('platformio.ini') }} |  | ||||||
|           restore-keys: | |  | ||||||
|             lint-cpp-pio- |  | ||||||
|       # Set up the pio project so that the cpp checks know how files are compiled |       # Set up the pio project so that the cpp checks know how files are compiled | ||||||
|       # (build flags, libraries etc) |       # (build flags, libraries etc) | ||||||
|       - name: Set up platformio environment |       - name: Set up platformio environment | ||||||
| @@ -46,15 +37,6 @@ jobs: | |||||||
|         split: [1, 2, 3, 4] |         split: [1, 2, 3, 4] | ||||||
|     steps: |     steps: | ||||||
|       - uses: actions/checkout@v2 |       - uses: actions/checkout@v2 | ||||||
|       # Cache platformio intermediary files (like libraries etc) |  | ||||||
|       # Note: platformio platform versions should be cached via the esphome-lint image |  | ||||||
|       - name: Cache Platformio |  | ||||||
|         uses: actions/cache@v1 |  | ||||||
|         with: |  | ||||||
|           path: .pio |  | ||||||
|           key: lint-cpp-pio-${{ hashFiles('platformio.ini') }} |  | ||||||
|           restore-keys: | |  | ||||||
|             lint-cpp-pio- |  | ||||||
|       # Set up the pio project so that the cpp checks know how files are compiled |       # Set up the pio project so that the cpp checks know how files are compiled | ||||||
|       # (build flags, libraries etc) |       # (build flags, libraries etc) | ||||||
|       - name: Set up platformio environment |       - name: Set up platformio environment | ||||||
| @@ -192,7 +174,7 @@ jobs: | |||||||
|           echo "TAG=${TAG}" >> $GITHUB_ENV |           echo "TAG=${TAG}" >> $GITHUB_ENV | ||||||
|       - name: Set up env variables |       - name: Set up env variables | ||||||
|         run: | |         run: | | ||||||
|           base_version="2.6.0" |           base_version="3.0.0" | ||||||
|  |  | ||||||
|           if [[ "${{ matrix.build_type }}" == "hassio" ]]; then |           if [[ "${{ matrix.build_type }}" == "hassio" ]]; then | ||||||
|             build_from="esphome/esphome-hassio-base-${{ matrix.arch }}:${base_version}" |             build_from="esphome/esphome-hassio-base-${{ matrix.arch }}:${base_version}" | ||||||
| @@ -211,7 +193,7 @@ jobs: | |||||||
|         run: | |         run: | | ||||||
|           docker pull "${BUILD_TO}:dev" || true |           docker pull "${BUILD_TO}:dev" || true | ||||||
|       - name: Register QEMU binfmt |       - name: Register QEMU binfmt | ||||||
|         run: docker run --rm --privileged multiarch/qemu-user-static:5.0.0-2 --reset -p yes |         run: docker run --rm --privileged multiarch/qemu-user-static:5.2.0-2 --reset -p yes | ||||||
|       - run: | |       - run: | | ||||||
|           docker build \ |           docker build \ | ||||||
|             --build-arg "BUILD_FROM=${BUILD_FROM}" \ |             --build-arg "BUILD_FROM=${BUILD_FROM}" \ | ||||||
|   | |||||||
							
								
								
									
										22
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										22
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							| @@ -14,15 +14,6 @@ jobs: | |||||||
|     container: esphome/esphome-lint:latest |     container: esphome/esphome-lint:latest | ||||||
|     steps: |     steps: | ||||||
|       - uses: actions/checkout@v2 |       - uses: actions/checkout@v2 | ||||||
|       # Cache platformio intermediary files (like libraries etc) |  | ||||||
|       # Note: platformio platform versions should be cached via the esphome-lint image |  | ||||||
|       - name: Cache Platformio |  | ||||||
|         uses: actions/cache@v1 |  | ||||||
|         with: |  | ||||||
|           path: .pio |  | ||||||
|           key: lint-cpp-pio-${{ hashFiles('platformio.ini') }} |  | ||||||
|           restore-keys: | |  | ||||||
|             lint-cpp-pio- |  | ||||||
|       # Set up the pio project so that the cpp checks know how files are compiled |       # Set up the pio project so that the cpp checks know how files are compiled | ||||||
|       # (build flags, libraries etc) |       # (build flags, libraries etc) | ||||||
|       - name: Set up platformio environment |       - name: Set up platformio environment | ||||||
| @@ -45,15 +36,6 @@ jobs: | |||||||
|         split: [1, 2, 3, 4] |         split: [1, 2, 3, 4] | ||||||
|     steps: |     steps: | ||||||
|       - uses: actions/checkout@v2 |       - uses: actions/checkout@v2 | ||||||
|       # Cache platformio intermediary files (like libraries etc) |  | ||||||
|       # Note: platformio platform versions should be cached via the esphome-lint image |  | ||||||
|       - name: Cache Platformio |  | ||||||
|         uses: actions/cache@v1 |  | ||||||
|         with: |  | ||||||
|           path: .pio |  | ||||||
|           key: lint-cpp-pio-${{ hashFiles('platformio.ini') }} |  | ||||||
|           restore-keys: | |  | ||||||
|             lint-cpp-pio- |  | ||||||
|       # Set up the pio project so that the cpp checks know how files are compiled |       # Set up the pio project so that the cpp checks know how files are compiled | ||||||
|       # (build flags, libraries etc) |       # (build flags, libraries etc) | ||||||
|       - name: Set up platformio environment |       - name: Set up platformio environment | ||||||
| @@ -212,7 +194,7 @@ jobs: | |||||||
|           echo "TAG=${TAG}" >> $GITHUB_ENV |           echo "TAG=${TAG}" >> $GITHUB_ENV | ||||||
|       - name: Set up env variables |       - name: Set up env variables | ||||||
|         run: | |         run: | | ||||||
|           base_version="2.6.0" |           base_version="3.0.0" | ||||||
|  |  | ||||||
|           if [[ "${{ matrix.build_type }}" == "hassio" ]]; then |           if [[ "${{ matrix.build_type }}" == "hassio" ]]; then | ||||||
|             build_from="esphome/esphome-hassio-base-${{ matrix.arch }}:${base_version}" |             build_from="esphome/esphome-hassio-base-${{ matrix.arch }}:${base_version}" | ||||||
| @@ -239,7 +221,7 @@ jobs: | |||||||
|         run: | |         run: | | ||||||
|           docker pull "${BUILD_TO}:${CACHE_TAG}" || true |           docker pull "${BUILD_TO}:${CACHE_TAG}" || true | ||||||
|       - name: Register QEMU binfmt |       - name: Register QEMU binfmt | ||||||
|         run: docker run --rm --privileged multiarch/qemu-user-static:5.0.0-2 --reset -p yes |         run: docker run --rm --privileged multiarch/qemu-user-static:5.2.0-2 --reset -p yes | ||||||
|       - run: | |       - run: | | ||||||
|           docker build \ |           docker build \ | ||||||
|             --build-arg "BUILD_FROM=${BUILD_FROM}" \ |             --build-arg "BUILD_FROM=${BUILD_FROM}" \ | ||||||
|   | |||||||
| @@ -1,11 +1,27 @@ | |||||||
| # See https://pre-commit.com for more information | # See https://pre-commit.com for more information | ||||||
| # See https://pre-commit.com/hooks.html for more hooks | # See https://pre-commit.com/hooks.html for more hooks | ||||||
| repos: | repos: | ||||||
| -   repo: https://github.com/pre-commit/pre-commit-hooks |   - repo: https://github.com/ambv/black | ||||||
|     rev: v2.4.0 |     rev: 20.8b1 | ||||||
|     hooks: |     hooks: | ||||||
|     -   id: trailing-whitespace |     - id: black | ||||||
|     -   id: end-of-file-fixer |       args: | ||||||
|     -   id: check-yaml |         - --safe | ||||||
|     -   id: check-added-large-files |         - --quiet | ||||||
|     -   id: flake8 |       files: ^((esphome|script|tests)/.+)?[^/]+\.py$ | ||||||
|  |   - repo: https://gitlab.com/pycqa/flake8 | ||||||
|  |     rev: 3.8.4 | ||||||
|  |     hooks: | ||||||
|  |       - id: flake8 | ||||||
|  |         additional_dependencies: | ||||||
|  |           - flake8-docstrings==1.5.0 | ||||||
|  |           - pydocstyle==5.1.1 | ||||||
|  |         files: ^(esphome|tests)/.+\.py$ | ||||||
|  |   - repo: https://github.com/pre-commit/pre-commit-hooks | ||||||
|  |     rev: v3.4.0 | ||||||
|  |     hooks: | ||||||
|  |       - id: no-commit-to-branch | ||||||
|  |         args: | ||||||
|  |           - --branch=dev | ||||||
|  |           - --branch=master | ||||||
|  |           - --branch=beta | ||||||
|   | |||||||
							
								
								
									
										15
									
								
								CODEOWNERS
									
									
									
									
									
								
							
							
						
						
									
										15
									
								
								CODEOWNERS
									
									
									
									
									
								
							| @@ -13,6 +13,7 @@ esphome/core/* @esphome/core | |||||||
| # Integrations | # Integrations | ||||||
| esphome/components/ac_dimmer/* @glmnet | esphome/components/ac_dimmer/* @glmnet | ||||||
| esphome/components/adc/* @esphome/core | esphome/components/adc/* @esphome/core | ||||||
|  | esphome/components/addressable_light/* @justfalter | ||||||
| esphome/components/animation/* @syndlex | esphome/components/animation/* @syndlex | ||||||
| esphome/components/api/* @OttoWinter | esphome/components/api/* @OttoWinter | ||||||
| esphome/components/async_tcp/* @OttoWinter | esphome/components/async_tcp/* @OttoWinter | ||||||
| @@ -37,6 +38,7 @@ esphome/components/globals/* @esphome/core | |||||||
| esphome/components/gpio/* @esphome/core | esphome/components/gpio/* @esphome/core | ||||||
| esphome/components/homeassistant/* @OttoWinter | esphome/components/homeassistant/* @OttoWinter | ||||||
| esphome/components/i2c/* @esphome/core | esphome/components/i2c/* @esphome/core | ||||||
|  | esphome/components/inkbird_ibsth1_mini/* @fkirill | ||||||
| esphome/components/inkplate6/* @jesserockz | esphome/components/inkplate6/* @jesserockz | ||||||
| esphome/components/integration/* @OttoWinter | esphome/components/integration/* @OttoWinter | ||||||
| esphome/components/interval/* @esphome/core | esphome/components/interval/* @esphome/core | ||||||
| @@ -44,10 +46,18 @@ esphome/components/json/* @OttoWinter | |||||||
| esphome/components/ledc/* @OttoWinter | esphome/components/ledc/* @OttoWinter | ||||||
| esphome/components/light/* @esphome/core | esphome/components/light/* @esphome/core | ||||||
| esphome/components/logger/* @esphome/core | esphome/components/logger/* @esphome/core | ||||||
| esphome/components/mcp23s08/* @SenexCrenshaw | esphome/components/max7219digit/* @rspaargaren | ||||||
| esphome/components/mcp23s17/* @SenexCrenshaw | esphome/components/mcp23008/* @jesserockz | ||||||
|  | esphome/components/mcp23017/* @jesserockz | ||||||
|  | esphome/components/mcp23s08/* @SenexCrenshaw @jesserockz | ||||||
|  | esphome/components/mcp23s17/* @SenexCrenshaw @jesserockz | ||||||
|  | esphome/components/mcp23x08_base/* @jesserockz | ||||||
|  | esphome/components/mcp23x17_base/* @jesserockz | ||||||
|  | esphome/components/mcp23xxx_base/* @jesserockz | ||||||
| esphome/components/mcp2515/* @danielschramm @mvturnho | esphome/components/mcp2515/* @danielschramm @mvturnho | ||||||
| esphome/components/mcp9808/* @k7hpn | esphome/components/mcp9808/* @k7hpn | ||||||
|  | esphome/components/midea_ac/* @dudanov | ||||||
|  | esphome/components/midea_dongle/* @dudanov | ||||||
| esphome/components/network/* @esphome/core | esphome/components/network/* @esphome/core | ||||||
| esphome/components/nfc/* @jesserockz | esphome/components/nfc/* @jesserockz | ||||||
| esphome/components/ota/* @esphome/core | esphome/components/ota/* @esphome/core | ||||||
| @@ -57,6 +67,7 @@ esphome/components/pn532/* @OttoWinter @jesserockz | |||||||
| esphome/components/pn532_i2c/* @OttoWinter @jesserockz | esphome/components/pn532_i2c/* @OttoWinter @jesserockz | ||||||
| esphome/components/pn532_spi/* @OttoWinter @jesserockz | esphome/components/pn532_spi/* @OttoWinter @jesserockz | ||||||
| esphome/components/power_supply/* @esphome/core | esphome/components/power_supply/* @esphome/core | ||||||
|  | esphome/components/pulse_meter/* @stevebaxter | ||||||
| esphome/components/rc522/* @glmnet | esphome/components/rc522/* @glmnet | ||||||
| esphome/components/rc522_i2c/* @glmnet | esphome/components/rc522_i2c/* @glmnet | ||||||
| esphome/components/rc522_spi/* @glmnet | esphome/components/rc522_spi/* @glmnet | ||||||
|   | |||||||
| @@ -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} | FROM ${BUILD_FROM} | ||||||
|  |  | ||||||
| # First install requirements to leverage caching when requirements don't change | # First install requirements to leverage caching when requirements don't change | ||||||
| COPY requirements.txt / | COPY requirements.txt docker/platformio_install_deps.py platformio.ini / | ||||||
| RUN pip3 install --no-cache-dir -r /requirements.txt | RUN \ | ||||||
|  |     pip3 install --no-cache-dir -r /requirements.txt \ | ||||||
|  |     && /platformio_install_deps.py /platformio.ini | ||||||
|  |  | ||||||
| # Then copy esphome and install | # Then copy esphome and install | ||||||
| COPY . . | COPY . . | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| FROM esphome/esphome-base-amd64:2.6.0 | FROM esphome/esphome-base-amd64:3.0.0 | ||||||
|  |  | ||||||
| COPY . . | COPY . . | ||||||
|  |  | ||||||
|   | |||||||
| @@ -2,8 +2,10 @@ ARG BUILD_FROM | |||||||
| FROM ${BUILD_FROM} | FROM ${BUILD_FROM} | ||||||
|  |  | ||||||
| # First install requirements to leverage caching when requirements don't change | # First install requirements to leverage caching when requirements don't change | ||||||
| COPY requirements.txt / | COPY requirements.txt docker/platformio_install_deps.py platformio.ini / | ||||||
| RUN pip3 install --no-cache-dir -r /requirements.txt | RUN \ | ||||||
|  |     pip3 install --no-cache-dir -r /requirements.txt \ | ||||||
|  |     && /platformio_install_deps.py /platformio.ini | ||||||
|  |  | ||||||
| # Copy root filesystem | # Copy root filesystem | ||||||
| COPY docker/rootfs/ / | COPY docker/rootfs/ / | ||||||
|   | |||||||
| @@ -1,7 +1,9 @@ | |||||||
| FROM esphome/esphome-lint-base:2.6.0 | FROM esphome/esphome-lint-base:3.0.0 | ||||||
|  |  | ||||||
| COPY requirements.txt 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 | RUN \ | ||||||
|  |     pip3 install --no-cache-dir -r /requirements.txt -r /requirements_test.txt \ | ||||||
|  |     && /platformio_install_deps.py /platformio.ini | ||||||
|  |  | ||||||
| VOLUME ["/esphome"] | VOLUME ["/esphome"] | ||||||
| WORKDIR /esphome | WORKDIR /esphome | ||||||
|   | |||||||
							
								
								
									
										20
									
								
								docker/platformio_install_deps.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										20
									
								
								docker/platformio_install_deps.py
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,20 @@ | |||||||
|  | #!/usr/bin/env python3 | ||||||
|  | # This script is used in the docker containers to preinstall | ||||||
|  | # all platformio libraries in the global storage | ||||||
|  |  | ||||||
|  | import configparser | ||||||
|  | import re | ||||||
|  | import subprocess | ||||||
|  | import sys | ||||||
|  |  | ||||||
|  | config = configparser.ConfigParser() | ||||||
|  | config.read(sys.argv[1]) | ||||||
|  | libs = [] | ||||||
|  | for line in config['common']['lib_deps'].splitlines(): | ||||||
|  |     # Format: '1655@1.0.2  ; TinyGPSPlus (has name conflict)' (includes comment) | ||||||
|  |     m = re.search(r'([a-zA-Z0-9-_/]+@[0-9\.]+)', line) | ||||||
|  |     if m is None: | ||||||
|  |         continue | ||||||
|  |     libs.append(m.group(1)) | ||||||
|  |  | ||||||
|  | subprocess.check_call(['platformio', 'lib', '-g', 'install', *libs]) | ||||||
| @@ -8,21 +8,36 @@ from datetime import datetime | |||||||
| from esphome import const, writer, yaml_util | from esphome import const, writer, yaml_util | ||||||
| import esphome.codegen as cg | import esphome.codegen as cg | ||||||
| from esphome.config import iter_components, read_config, strip_default_ids | from esphome.config import iter_components, read_config, strip_default_ids | ||||||
| from esphome.const import CONF_BAUD_RATE, CONF_BROKER, CONF_LOGGER, CONF_OTA, \ | from esphome.const import ( | ||||||
|     CONF_PASSWORD, CONF_PORT, CONF_ESPHOME, CONF_PLATFORMIO_OPTIONS |     CONF_BAUD_RATE, | ||||||
|  |     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.core import CORE, EsphomeError, coroutine, coroutine_with_priority | ||||||
| from esphome.helpers import color, indent | from esphome.helpers import color, indent | ||||||
| from esphome.util import run_external_command, run_external_process, safe_print, list_yaml_files, \ | from esphome.util import ( | ||||||
|     get_serial_ports |     run_external_command, | ||||||
|  |     run_external_process, | ||||||
|  |     safe_print, | ||||||
|  |     list_yaml_files, | ||||||
|  |     get_serial_ports, | ||||||
|  | ) | ||||||
|  |  | ||||||
| _LOGGER = logging.getLogger(__name__) | _LOGGER = logging.getLogger(__name__) | ||||||
|  |  | ||||||
|  |  | ||||||
| def choose_prompt(options): | def choose_prompt(options): | ||||||
|     if not options: |     if not options: | ||||||
|         raise EsphomeError("Found no valid options for upload/logging, please make sure relevant " |         raise EsphomeError( | ||||||
|                            "sections (ota, api, mqtt, ...) are in your configuration and/or the " |             "Found no valid options for upload/logging, please make sure relevant " | ||||||
|                            "device is plugged in.") |             "sections (ota, api, mqtt, ...) are in your configuration and/or the " | ||||||
|  |             "device is plugged in." | ||||||
|  |         ) | ||||||
|  |  | ||||||
|     if len(options) == 1: |     if len(options) == 1: | ||||||
|         return options[0][1] |         return options[0][1] | ||||||
| @@ -32,7 +47,7 @@ def choose_prompt(options): | |||||||
|         safe_print(f"  [{i+1}] {desc}") |         safe_print(f"  [{i+1}] {desc}") | ||||||
|  |  | ||||||
|     while True: |     while True: | ||||||
|         opt = input('(number): ') |         opt = input("(number): ") | ||||||
|         if opt in options: |         if opt in options: | ||||||
|             opt = options.index(opt) |             opt = options.index(opt) | ||||||
|             break |             break | ||||||
| @@ -42,7 +57,7 @@ def choose_prompt(options): | |||||||
|                 raise ValueError |                 raise ValueError | ||||||
|             break |             break | ||||||
|         except ValueError: |         except ValueError: | ||||||
|             safe_print(color('red', f"Invalid option: '{opt}'")) |             safe_print(color("red", f"Invalid option: '{opt}'")) | ||||||
|     return options[opt - 1][1] |     return options[opt - 1][1] | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -50,14 +65,14 @@ def choose_upload_log_host(default, check_default, show_ota, show_mqtt, show_api | |||||||
|     options = [] |     options = [] | ||||||
|     for port in get_serial_ports(): |     for port in get_serial_ports(): | ||||||
|         options.append((f"{port.path} ({port.description})", port.path)) |         options.append((f"{port.path} ({port.description})", port.path)) | ||||||
|     if (show_ota and 'ota' in CORE.config) or (show_api and 'api' in CORE.config): |     if (show_ota and "ota" in CORE.config) or (show_api and "api" in CORE.config): | ||||||
|         options.append((f"Over The Air ({CORE.address})", CORE.address)) |         options.append((f"Over The Air ({CORE.address})", CORE.address)) | ||||||
|         if default == 'OTA': |         if default == "OTA": | ||||||
|             return CORE.address |             return CORE.address | ||||||
|     if show_mqtt and 'mqtt' in CORE.config: |     if show_mqtt and "mqtt" in CORE.config: | ||||||
|         options.append(("MQTT ({})".format(CORE.config['mqtt'][CONF_BROKER]), 'MQTT')) |         options.append(("MQTT ({})".format(CORE.config["mqtt"][CONF_BROKER]), "MQTT")) | ||||||
|         if default == 'OTA': |         if default == "OTA": | ||||||
|             return 'MQTT' |             return "MQTT" | ||||||
|     if default is not None: |     if default is not None: | ||||||
|         return default |         return default | ||||||
|     if check_default is not None and check_default in [opt[1] for opt in options]: |     if check_default is not None and check_default in [opt[1] for opt in options]: | ||||||
| @@ -66,11 +81,11 @@ def choose_upload_log_host(default, check_default, show_ota, show_mqtt, show_api | |||||||
|  |  | ||||||
|  |  | ||||||
| def get_port_type(port): | def get_port_type(port): | ||||||
|     if port.startswith('/') or port.startswith('COM'): |     if port.startswith("/") or port.startswith("COM"): | ||||||
|         return 'SERIAL' |         return "SERIAL" | ||||||
|     if port == 'MQTT': |     if port == "MQTT": | ||||||
|         return 'MQTT' |         return "MQTT" | ||||||
|     return 'NETWORK' |     return "NETWORK" | ||||||
|  |  | ||||||
|  |  | ||||||
| def run_miniterm(config, port): | def run_miniterm(config, port): | ||||||
| @@ -80,7 +95,7 @@ def run_miniterm(config, port): | |||||||
|     if CONF_LOGGER not in config: |     if CONF_LOGGER not in config: | ||||||
|         _LOGGER.info("Logger is not enabled. Not starting UART logs.") |         _LOGGER.info("Logger is not enabled. Not starting UART logs.") | ||||||
|         return |         return | ||||||
|     baud_rate = config['logger'][CONF_BAUD_RATE] |     baud_rate = config["logger"][CONF_BAUD_RATE] | ||||||
|     if baud_rate == 0: |     if baud_rate == 0: | ||||||
|         _LOGGER.info("UART logging is disabled (baud_rate=0). Not starting UART logs.") |         _LOGGER.info("UART logging is disabled (baud_rate=0). Not starting UART logs.") | ||||||
|     _LOGGER.info("Starting log output from %s with baud rate %s", port, baud_rate) |     _LOGGER.info("Starting log output from %s with baud rate %s", port, baud_rate) | ||||||
| @@ -93,13 +108,18 @@ def run_miniterm(config, port): | |||||||
|             except serial.SerialException: |             except serial.SerialException: | ||||||
|                 _LOGGER.error("Serial port closed!") |                 _LOGGER.error("Serial port closed!") | ||||||
|                 return |                 return | ||||||
|             line = raw.replace(b'\r', b'').replace(b'\n', b'').decode('utf8', 'backslashreplace') |             line = ( | ||||||
|             time = datetime.now().time().strftime('[%H:%M:%S]') |                 raw.replace(b"\r", b"") | ||||||
|  |                 .replace(b"\n", b"") | ||||||
|  |                 .decode("utf8", "backslashreplace") | ||||||
|  |             ) | ||||||
|  |             time = datetime.now().time().strftime("[%H:%M:%S]") | ||||||
|             message = time + line |             message = time + line | ||||||
|             safe_print(message) |             safe_print(message) | ||||||
|  |  | ||||||
|             backtrace_state = platformio_api.process_stacktrace( |             backtrace_state = platformio_api.process_stacktrace( | ||||||
|                 config, line, backtrace_state=backtrace_state) |                 config, line, backtrace_state=backtrace_state | ||||||
|  |             ) | ||||||
|  |  | ||||||
|  |  | ||||||
| def wrap_to_code(name, comp): | def wrap_to_code(name, comp): | ||||||
| @@ -111,7 +131,7 @@ def wrap_to_code(name, comp): | |||||||
|         cg.add(cg.LineComment(f"{name}:")) |         cg.add(cg.LineComment(f"{name}:")) | ||||||
|         if comp.config_schema is not None: |         if comp.config_schema is not None: | ||||||
|             conf_str = yaml_util.dump(conf) |             conf_str = yaml_util.dump(conf) | ||||||
|             conf_str = conf_str.replace('//', '') |             conf_str = conf_str.replace("//", "") | ||||||
|             cg.add(cg.LineComment(indent(conf_str))) |             cg.add(cg.LineComment(indent(conf_str))) | ||||||
|         yield coro(conf) |         yield coro(conf) | ||||||
|  |  | ||||||
| @@ -151,15 +171,31 @@ def compile_program(args, config): | |||||||
|  |  | ||||||
| def upload_using_esptool(config, port): | def upload_using_esptool(config, port): | ||||||
|     path = CORE.firmware_bin |     path = CORE.firmware_bin | ||||||
|     first_baudrate = config[CONF_ESPHOME][CONF_PLATFORMIO_OPTIONS].get('upload_speed', 460800) |     first_baudrate = config[CONF_ESPHOME][CONF_PLATFORMIO_OPTIONS].get( | ||||||
|  |         "upload_speed", 460800 | ||||||
|  |     ) | ||||||
|  |  | ||||||
|     def run_esptool(baud_rate): |     def run_esptool(baud_rate): | ||||||
|         cmd = ['esptool.py', '--before', 'default_reset', '--after', 'hard_reset', |         cmd = [ | ||||||
|                '--baud', str(baud_rate), |             "esptool.py", | ||||||
|                '--chip', 'esp8266', '--port', port, 'write_flash', '0x0', path] |             "--before", | ||||||
|  |             "default_reset", | ||||||
|  |             "--after", | ||||||
|  |             "hard_reset", | ||||||
|  |             "--baud", | ||||||
|  |             str(baud_rate), | ||||||
|  |             "--chip", | ||||||
|  |             "esp8266", | ||||||
|  |             "--port", | ||||||
|  |             port, | ||||||
|  |             "write_flash", | ||||||
|  |             "0x0", | ||||||
|  |             path, | ||||||
|  |         ] | ||||||
|  |  | ||||||
|         if os.environ.get('ESPHOME_USE_SUBPROCESS') is None: |         if os.environ.get("ESPHOME_USE_SUBPROCESS") is None: | ||||||
|             import esptool |             import esptool | ||||||
|  |  | ||||||
|             # pylint: disable=protected-access |             # pylint: disable=protected-access | ||||||
|             return run_external_command(esptool._main, *cmd) |             return run_external_command(esptool._main, *cmd) | ||||||
|  |  | ||||||
| @@ -169,14 +205,16 @@ def upload_using_esptool(config, port): | |||||||
|     if rc == 0 or first_baudrate == 115200: |     if rc == 0 or first_baudrate == 115200: | ||||||
|         return rc |         return rc | ||||||
|     # Try with 115200 baud rate, with some serial chips the faster baud rates do not work well |     # Try with 115200 baud rate, with some serial chips the faster baud rates do not work well | ||||||
|     _LOGGER.info("Upload with baud rate %s failed. Trying again with baud rate 115200.", |     _LOGGER.info( | ||||||
|                  first_baudrate) |         "Upload with baud rate %s failed. Trying again with baud rate 115200.", | ||||||
|  |         first_baudrate, | ||||||
|  |     ) | ||||||
|     return run_esptool(115200) |     return run_esptool(115200) | ||||||
|  |  | ||||||
|  |  | ||||||
| def upload_program(config, args, host): | def upload_program(config, args, host): | ||||||
|     # if upload is to a serial port use platformio, otherwise assume ota |     # if upload is to a serial port use platformio, otherwise assume ota | ||||||
|     if get_port_type(host) == 'SERIAL': |     if get_port_type(host) == "SERIAL": | ||||||
|         from esphome import platformio_api |         from esphome import platformio_api | ||||||
|  |  | ||||||
|         if CORE.is_esp8266: |         if CORE.is_esp8266: | ||||||
| @@ -186,8 +224,10 @@ def upload_program(config, args, host): | |||||||
|     from esphome import espota2 |     from esphome import espota2 | ||||||
|  |  | ||||||
|     if CONF_OTA not in config: |     if CONF_OTA not in config: | ||||||
|         raise EsphomeError("Cannot upload Over the Air as the config does not include the ota: " |         raise EsphomeError( | ||||||
|                            "component") |             "Cannot upload Over the Air as the config does not include the ota: " | ||||||
|  |             "component" | ||||||
|  |         ) | ||||||
|  |  | ||||||
|     ota_conf = config[CONF_OTA] |     ota_conf = config[CONF_OTA] | ||||||
|     remote_port = ota_conf[CONF_PORT] |     remote_port = ota_conf[CONF_PORT] | ||||||
| @@ -196,19 +236,21 @@ def upload_program(config, args, host): | |||||||
|  |  | ||||||
|  |  | ||||||
| def show_logs(config, args, port): | def show_logs(config, args, port): | ||||||
|     if 'logger' not in config: |     if "logger" not in config: | ||||||
|         raise EsphomeError("Logger is not configured!") |         raise EsphomeError("Logger is not configured!") | ||||||
|     if get_port_type(port) == 'SERIAL': |     if get_port_type(port) == "SERIAL": | ||||||
|         run_miniterm(config, port) |         run_miniterm(config, port) | ||||||
|         return 0 |         return 0 | ||||||
|     if get_port_type(port) == 'NETWORK' and 'api' in config: |     if get_port_type(port) == "NETWORK" and "api" in config: | ||||||
|         from esphome.api.client import run_logs |         from esphome.api.client import run_logs | ||||||
|  |  | ||||||
|         return run_logs(config, port) |         return run_logs(config, port) | ||||||
|     if get_port_type(port) == 'MQTT' and 'mqtt' in config: |     if get_port_type(port) == "MQTT" and "mqtt" in config: | ||||||
|         from esphome import mqtt |         from esphome import mqtt | ||||||
|  |  | ||||||
|         return mqtt.show_logs(config, args.topic, args.username, args.password, args.client_id) |         return mqtt.show_logs( | ||||||
|  |             config, args.topic, args.username, args.password, args.client_id | ||||||
|  |         ) | ||||||
|  |  | ||||||
|     raise EsphomeError("No remote or local logging method configured (api/mqtt/logger)") |     raise EsphomeError("No remote or local logging method configured (api/mqtt/logger)") | ||||||
|  |  | ||||||
| @@ -216,7 +258,9 @@ def show_logs(config, args, port): | |||||||
| def clean_mqtt(config, args): | def clean_mqtt(config, args): | ||||||
|     from esphome import mqtt |     from esphome import mqtt | ||||||
|  |  | ||||||
|     return mqtt.clear_topic(config, args.topic, args.username, args.password, args.client_id) |     return mqtt.clear_topic( | ||||||
|  |         config, args.topic, args.username, args.password, args.client_id | ||||||
|  |     ) | ||||||
|  |  | ||||||
|  |  | ||||||
| def setup_log(debug=False, quiet=False): | def setup_log(debug=False, quiet=False): | ||||||
| @@ -230,27 +274,31 @@ def setup_log(debug=False, quiet=False): | |||||||
|     logging.basicConfig(level=log_level) |     logging.basicConfig(level=log_level) | ||||||
|     fmt = "%(levelname)s %(message)s" |     fmt = "%(levelname)s %(message)s" | ||||||
|     colorfmt = f"%(log_color)s{fmt}%(reset)s" |     colorfmt = f"%(log_color)s{fmt}%(reset)s" | ||||||
|     datefmt = '%H:%M:%S' |     datefmt = "%H:%M:%S" | ||||||
|  |  | ||||||
|     logging.getLogger('urllib3').setLevel(logging.WARNING) |     logging.getLogger("urllib3").setLevel(logging.WARNING) | ||||||
|  |  | ||||||
|     try: |     try: | ||||||
|         import colorama |         import colorama | ||||||
|  |  | ||||||
|         colorama.init(strip=True) |         colorama.init(strip=True) | ||||||
|  |  | ||||||
|         from colorlog import ColoredFormatter |         from colorlog import ColoredFormatter | ||||||
|         logging.getLogger().handlers[0].setFormatter(ColoredFormatter( |  | ||||||
|             colorfmt, |         logging.getLogger().handlers[0].setFormatter( | ||||||
|             datefmt=datefmt, |             ColoredFormatter( | ||||||
|             reset=True, |                 colorfmt, | ||||||
|             log_colors={ |                 datefmt=datefmt, | ||||||
|                 'DEBUG': 'cyan', |                 reset=True, | ||||||
|                 'INFO': 'green', |                 log_colors={ | ||||||
|                 'WARNING': 'yellow', |                     "DEBUG": "cyan", | ||||||
|                 'ERROR': 'red', |                     "INFO": "green", | ||||||
|                 'CRITICAL': 'red', |                     "WARNING": "yellow", | ||||||
|             } |                     "ERROR": "red", | ||||||
|         )) |                     "CRITICAL": "red", | ||||||
|  |                 }, | ||||||
|  |             ) | ||||||
|  |         ) | ||||||
|     except ImportError: |     except ImportError: | ||||||
|         pass |         pass | ||||||
|  |  | ||||||
| @@ -272,6 +320,8 @@ def command_config(args, config): | |||||||
| def command_vscode(args): | def command_vscode(args): | ||||||
|     from esphome import vscode |     from esphome import vscode | ||||||
|  |  | ||||||
|  |     logging.disable(logging.INFO) | ||||||
|  |     logging.disable(logging.WARNING) | ||||||
|     CORE.config_path = args.configuration[0] |     CORE.config_path = args.configuration[0] | ||||||
|     vscode.read_config(args) |     vscode.read_config(args) | ||||||
|  |  | ||||||
| @@ -291,8 +341,13 @@ def command_compile(args, config): | |||||||
|  |  | ||||||
|  |  | ||||||
| def command_upload(args, config): | def command_upload(args, config): | ||||||
|     port = choose_upload_log_host(default=args.upload_port, check_default=None, |     port = choose_upload_log_host( | ||||||
|                                   show_ota=True, show_mqtt=False, show_api=False) |         default=args.upload_port, | ||||||
|  |         check_default=None, | ||||||
|  |         show_ota=True, | ||||||
|  |         show_mqtt=False, | ||||||
|  |         show_api=False, | ||||||
|  |     ) | ||||||
|     exit_code = upload_program(config, args, port) |     exit_code = upload_program(config, args, port) | ||||||
|     if exit_code != 0: |     if exit_code != 0: | ||||||
|         return exit_code |         return exit_code | ||||||
| @@ -301,8 +356,13 @@ def command_upload(args, config): | |||||||
|  |  | ||||||
|  |  | ||||||
| def command_logs(args, config): | def command_logs(args, config): | ||||||
|     port = choose_upload_log_host(default=args.serial_port, check_default=None, |     port = choose_upload_log_host( | ||||||
|                                   show_ota=False, show_mqtt=True, show_api=True) |         default=args.serial_port, | ||||||
|  |         check_default=None, | ||||||
|  |         show_ota=False, | ||||||
|  |         show_mqtt=True, | ||||||
|  |         show_api=True, | ||||||
|  |     ) | ||||||
|     return show_logs(config, args, port) |     return show_logs(config, args, port) | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -314,16 +374,26 @@ def command_run(args, config): | |||||||
|     if exit_code != 0: |     if exit_code != 0: | ||||||
|         return exit_code |         return exit_code | ||||||
|     _LOGGER.info("Successfully compiled program.") |     _LOGGER.info("Successfully compiled program.") | ||||||
|     port = choose_upload_log_host(default=args.upload_port, check_default=None, |     port = choose_upload_log_host( | ||||||
|                                   show_ota=True, show_mqtt=False, show_api=True) |         default=args.upload_port, | ||||||
|  |         check_default=None, | ||||||
|  |         show_ota=True, | ||||||
|  |         show_mqtt=False, | ||||||
|  |         show_api=True, | ||||||
|  |     ) | ||||||
|     exit_code = upload_program(config, args, port) |     exit_code = upload_program(config, args, port) | ||||||
|     if exit_code != 0: |     if exit_code != 0: | ||||||
|         return exit_code |         return exit_code | ||||||
|     _LOGGER.info("Successfully uploaded program.") |     _LOGGER.info("Successfully uploaded program.") | ||||||
|     if args.no_logs: |     if args.no_logs: | ||||||
|         return 0 |         return 0 | ||||||
|     port = choose_upload_log_host(default=args.upload_port, check_default=port, |     port = choose_upload_log_host( | ||||||
|                                   show_ota=False, show_mqtt=True, show_api=True) |         default=args.upload_port, | ||||||
|  |         check_default=port, | ||||||
|  |         show_ota=False, | ||||||
|  |         show_mqtt=True, | ||||||
|  |         show_api=True, | ||||||
|  |     ) | ||||||
|     return show_logs(config, args, port) |     return show_logs(config, args, port) | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -372,137 +442,189 @@ def command_update_all(args): | |||||||
|         click.echo(f"{half_line}{middle_text}{half_line}") |         click.echo(f"{half_line}{middle_text}{half_line}") | ||||||
|  |  | ||||||
|     for f in files: |     for f in files: | ||||||
|         print("Updating {}".format(color('cyan', f))) |         print("Updating {}".format(color("cyan", f))) | ||||||
|         print('-' * twidth) |         print("-" * twidth) | ||||||
|         print() |         print() | ||||||
|         rc = run_external_process('esphome', '--dashboard', f, 'run', '--no-logs', '--upload-port', |         rc = run_external_process( | ||||||
|                                   'OTA') |             "esphome", "--dashboard", f, "run", "--no-logs", "--upload-port", "OTA" | ||||||
|  |         ) | ||||||
|         if rc == 0: |         if rc == 0: | ||||||
|             print_bar("[{}] {}".format(color('bold_green', 'SUCCESS'), f)) |             print_bar("[{}] {}".format(color("bold_green", "SUCCESS"), f)) | ||||||
|             success[f] = True |             success[f] = True | ||||||
|         else: |         else: | ||||||
|             print_bar("[{}] {}".format(color('bold_red', 'ERROR'), f)) |             print_bar("[{}] {}".format(color("bold_red", "ERROR"), f)) | ||||||
|             success[f] = False |             success[f] = False | ||||||
|  |  | ||||||
|         print() |         print() | ||||||
|         print() |         print() | ||||||
|         print() |         print() | ||||||
|  |  | ||||||
|     print_bar('[{}]'.format(color('bold_white', 'SUMMARY'))) |     print_bar("[{}]".format(color("bold_white", "SUMMARY"))) | ||||||
|     failed = 0 |     failed = 0 | ||||||
|     for f in files: |     for f in files: | ||||||
|         if success[f]: |         if success[f]: | ||||||
|             print("  - {}: {}".format(f, color('green', 'SUCCESS'))) |             print("  - {}: {}".format(f, color("green", "SUCCESS"))) | ||||||
|         else: |         else: | ||||||
|             print("  - {}: {}".format(f, color('bold_red', 'FAILED'))) |             print("  - {}: {}".format(f, color("bold_red", "FAILED"))) | ||||||
|             failed += 1 |             failed += 1 | ||||||
|     return failed |     return failed | ||||||
|  |  | ||||||
|  |  | ||||||
| PRE_CONFIG_ACTIONS = { | PRE_CONFIG_ACTIONS = { | ||||||
|     'wizard': command_wizard, |     "wizard": command_wizard, | ||||||
|     'version': command_version, |     "version": command_version, | ||||||
|     'dashboard': command_dashboard, |     "dashboard": command_dashboard, | ||||||
|     'vscode': command_vscode, |     "vscode": command_vscode, | ||||||
|     'update-all': command_update_all, |     "update-all": command_update_all, | ||||||
| } | } | ||||||
|  |  | ||||||
| POST_CONFIG_ACTIONS = { | POST_CONFIG_ACTIONS = { | ||||||
|     'config': command_config, |     "config": command_config, | ||||||
|     'compile': command_compile, |     "compile": command_compile, | ||||||
|     'upload': command_upload, |     "upload": command_upload, | ||||||
|     'logs': command_logs, |     "logs": command_logs, | ||||||
|     'run': command_run, |     "run": command_run, | ||||||
|     'clean-mqtt': command_clean_mqtt, |     "clean-mqtt": command_clean_mqtt, | ||||||
|     'mqtt-fingerprint': command_mqtt_fingerprint, |     "mqtt-fingerprint": command_mqtt_fingerprint, | ||||||
|     'clean': command_clean, |     "clean": command_clean, | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| def parse_args(argv): | def parse_args(argv): | ||||||
|     parser = argparse.ArgumentParser(description=f'ESPHome v{const.__version__}') |     parser = argparse.ArgumentParser(description=f"ESPHome v{const.__version__}") | ||||||
|     parser.add_argument('-v', '--verbose', help="Enable verbose esphome logs.", |     parser.add_argument( | ||||||
|                         action='store_true') |         "-v", "--verbose", help="Enable verbose esphome logs.", action="store_true" | ||||||
|     parser.add_argument('-q', '--quiet', help="Disable all esphome logs.", |     ) | ||||||
|                         action='store_true') |     parser.add_argument( | ||||||
|     parser.add_argument('--dashboard', help=argparse.SUPPRESS, action='store_true') |         "-q", "--quiet", help="Disable all esphome logs.", action="store_true" | ||||||
|     parser.add_argument('-s', '--substitution', nargs=2, action='append', |     ) | ||||||
|                         help='Add a substitution', metavar=('key', 'value')) |     parser.add_argument("--dashboard", help=argparse.SUPPRESS, action="store_true") | ||||||
|     parser.add_argument('configuration', help='Your YAML configuration file.', nargs='*') |     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.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', |     parser_compile = subparsers.add_parser( | ||||||
|                                            help='Read the configuration and compile a program.') |         "compile", help="Read the configuration and compile a program." | ||||||
|     parser_compile.add_argument('--only-generate', |     ) | ||||||
|                                 help="Only generate source code, do not compile.", |     parser_compile.add_argument( | ||||||
|                                 action='store_true') |         "--only-generate", | ||||||
|  |         help="Only generate source code, do not compile.", | ||||||
|  |         action="store_true", | ||||||
|  |     ) | ||||||
|  |  | ||||||
|     parser_upload = subparsers.add_parser('upload', help='Validate the configuration ' |     parser_upload = subparsers.add_parser( | ||||||
|                                                          'and upload the latest binary.') |         "upload", help="Validate the configuration " "and upload the latest binary." | ||||||
|     parser_upload.add_argument('--upload-port', help="Manually specify the upload port to use. " |     ) | ||||||
|                                                      "For example /dev/cu.SLAB_USBtoUART.") |     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 ' |     parser_logs = subparsers.add_parser( | ||||||
|                                                      'and show all MQTT logs.') |         "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("--topic", help="Manually set the topic to subscribe to.") | ||||||
|     parser_logs.add_argument('--password', help='Manually set the password.') |     parser_logs.add_argument("--username", help="Manually set the username.") | ||||||
|     parser_logs.add_argument('--client-id', help='Manually set the client id.') |     parser_logs.add_argument("--password", help="Manually set the password.") | ||||||
|     parser_logs.add_argument('--serial-port', help="Manually specify a serial port to use" |     parser_logs.add_argument("--client-id", help="Manually set the client id.") | ||||||
|                                                    "For example /dev/cu.SLAB_USBtoUART.") |     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, ' |     parser_run = subparsers.add_parser( | ||||||
|                                                    'upload it, and start MQTT logs.') |         "run", | ||||||
|     parser_run.add_argument('--upload-port', help="Manually specify the upload port/ip to use. " |         help="Validate the configuration, create a binary, " | ||||||
|                                                   "For example /dev/cu.SLAB_USBtoUART.") |         "upload it, and start MQTT logs.", | ||||||
|     parser_run.add_argument('--no-logs', help='Disable starting MQTT logs.', |     ) | ||||||
|                             action='store_true') |     parser_run.add_argument( | ||||||
|     parser_run.add_argument('--topic', help='Manually set the topic to subscribe to for logs.') |         "--upload-port", | ||||||
|     parser_run.add_argument('--username', help='Manually set the MQTT username for logs.') |         help="Manually specify the upload port/ip to use. " | ||||||
|     parser_run.add_argument('--password', help='Manually set the MQTT password for logs.') |         "For example /dev/cu.SLAB_USBtoUART.", | ||||||
|     parser_run.add_argument('--client-id', help='Manually set the client id for logs.') |     ) | ||||||
|  |     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 " |     parser_clean = subparsers.add_parser( | ||||||
|                                                             "retain messages.") |         "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("--topic", help="Manually set the topic to subscribe to.") | ||||||
|     parser_clean.add_argument('--password', help='Manually set the password.') |     parser_clean.add_argument("--username", help="Manually set the username.") | ||||||
|     parser_clean.add_argument('--client-id', help='Manually set the client id.') |     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 " |     subparsers.add_parser( | ||||||
|                                          "you through setting up esphome.") |         "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', |     dashboard = subparsers.add_parser( | ||||||
|                                       help="Create a simple web server for a dashboard.") |         "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( | ||||||
|     dashboard.add_argument("--username", help="The optional username to require " |         "--port", | ||||||
|                                               "for authentication.", |         help="The HTTP port to open connections on. Defaults to 6052.", | ||||||
|                            type=str, default='') |         type=int, | ||||||
|     dashboard.add_argument("--password", help="The optional password to require " |         default=6052, | ||||||
|                                               "for authentication.", |     ) | ||||||
|                            type=str, default='') |     dashboard.add_argument( | ||||||
|     dashboard.add_argument("--open-ui", help="Open the dashboard UI in a browser.", |         "--username", | ||||||
|                            action='store_true') |         help="The optional username to require " "for authentication.", | ||||||
|     dashboard.add_argument("--hassio", |         type=str, | ||||||
|                            help=argparse.SUPPRESS, |         default="", | ||||||
|                            action="store_true") |     ) | ||||||
|     dashboard.add_argument("--socket", |     dashboard.add_argument( | ||||||
|                            help="Make the dashboard serve under a unix socket", type=str) |         "--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 = subparsers.add_parser("vscode", help=argparse.SUPPRESS) | ||||||
|     vscode.add_argument('--ace', action='store_true') |     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:]) |     return parser.parse_args(argv[1:]) | ||||||
|  |  | ||||||
| @@ -512,13 +634,15 @@ def run_esphome(argv): | |||||||
|     CORE.dashboard = args.dashboard |     CORE.dashboard = args.dashboard | ||||||
|  |  | ||||||
|     setup_log(args.verbose, args.quiet) |     setup_log(args.verbose, args.quiet) | ||||||
|     if args.command != 'version' and not args.configuration: |     if args.command != "version" and not args.configuration: | ||||||
|         _LOGGER.error("Missing configuration parameter, see esphome --help.") |         _LOGGER.error("Missing configuration parameter, see esphome --help.") | ||||||
|         return 1 |         return 1 | ||||||
|  |  | ||||||
|     if sys.version_info < (3, 6, 0): |     if sys.version_info < (3, 6, 0): | ||||||
|         _LOGGER.error("You're running ESPHome with Python <3.6. ESPHome is no longer compatible " |         _LOGGER.error( | ||||||
|                       "with this Python version. Please reinstall ESPHome with Python 3.6+") |             "You're running ESPHome with Python <3.6. ESPHome is no longer compatible " | ||||||
|  |             "with this Python version. Please reinstall ESPHome with Python 3.6+" | ||||||
|  |         ) | ||||||
|         return 1 |         return 1 | ||||||
|  |  | ||||||
|     if args.command in PRE_CONFIG_ACTIONS: |     if args.command in PRE_CONFIG_ACTIONS: | ||||||
|   | |||||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| @@ -177,10 +177,14 @@ class APIClient(threading.Thread): | |||||||
|         try: |         try: | ||||||
|             ip = resolve_ip_address(self._address) |             ip = resolve_ip_address(self._address) | ||||||
|         except EsphomeError as err: |         except EsphomeError as err: | ||||||
|             _LOGGER.warning("Error resolving IP address of %s. Is it connected to WiFi?", |             _LOGGER.warning( | ||||||
|                             self._address) |                 "Error resolving IP address of %s. Is it connected to WiFi?", | ||||||
|             _LOGGER.warning("(If this error persists, please set a static IP address: " |                 self._address, | ||||||
|                             "https://esphome.io/components/wifi.html#manual-ips)") |             ) | ||||||
|  |             _LOGGER.warning( | ||||||
|  |                 "(If this error persists, please set a static IP address: " | ||||||
|  |                 "https://esphome.io/components/wifi.html#manual-ips)" | ||||||
|  |             ) | ||||||
|             raise APIConnectionError(err) from err |             raise APIConnectionError(err) from err | ||||||
|  |  | ||||||
|         _LOGGER.info("Connecting to %s:%s (%s)", self._address, self._port, ip) |         _LOGGER.info("Connecting to %s:%s (%s)", self._address, self._port, ip) | ||||||
| @@ -198,14 +202,19 @@ class APIClient(threading.Thread): | |||||||
|         self._socket_open_event.set() |         self._socket_open_event.set() | ||||||
|  |  | ||||||
|         hello = pb.HelloRequest() |         hello = pb.HelloRequest() | ||||||
|         hello.client_info = f'ESPHome v{const.__version__}' |         hello.client_info = f"ESPHome v{const.__version__}" | ||||||
|         try: |         try: | ||||||
|             resp = self._send_message_await_response(hello, pb.HelloResponse) |             resp = self._send_message_await_response(hello, pb.HelloResponse) | ||||||
|         except APIConnectionError as err: |         except APIConnectionError as err: | ||||||
|             self._fatal_error(err) |             self._fatal_error(err) | ||||||
|             raise err |             raise err | ||||||
|         _LOGGER.debug("Successfully connected to %s ('%s' API=%s.%s)", self._address, |         _LOGGER.debug( | ||||||
|                       resp.server_info, resp.api_version_major, resp.api_version_minor) |             "Successfully connected to %s ('%s' API=%s.%s)", | ||||||
|  |             self._address, | ||||||
|  |             resp.server_info, | ||||||
|  |             resp.api_version_major, | ||||||
|  |             resp.api_version_minor, | ||||||
|  |         ) | ||||||
|         self._connected = True |         self._connected = True | ||||||
|         self._refresh_ping() |         self._refresh_ping() | ||||||
|         if self.on_connect is not None: |         if self.on_connect is not None: | ||||||
| @@ -270,7 +279,9 @@ class APIClient(threading.Thread): | |||||||
|         req += encoded |         req += encoded | ||||||
|         self._write(req) |         self._write(req) | ||||||
|  |  | ||||||
|     def _send_message_await_response_complex(self, send_msg, do_append, do_stop, timeout=5): |     def _send_message_await_response_complex( | ||||||
|  |         self, send_msg, do_append, do_stop, timeout=5 | ||||||
|  |     ): | ||||||
|         event = threading.Event() |         event = threading.Event() | ||||||
|         responses = [] |         responses = [] | ||||||
|  |  | ||||||
| @@ -295,12 +306,15 @@ class APIClient(threading.Thread): | |||||||
|         def is_response(msg): |         def is_response(msg): | ||||||
|             return isinstance(msg, response_type) |             return isinstance(msg, response_type) | ||||||
|  |  | ||||||
|         return self._send_message_await_response_complex(send_msg, is_response, is_response, |         return self._send_message_await_response_complex( | ||||||
|                                                          timeout)[0] |             send_msg, is_response, is_response, timeout | ||||||
|  |         )[0] | ||||||
|  |  | ||||||
|     def device_info(self): |     def device_info(self): | ||||||
|         self._check_connected() |         self._check_connected() | ||||||
|         return self._send_message_await_response(pb.DeviceInfoRequest(), pb.DeviceInfoResponse) |         return self._send_message_await_response( | ||||||
|  |             pb.DeviceInfoRequest(), pb.DeviceInfoResponse | ||||||
|  |         ) | ||||||
|  |  | ||||||
|     def ping(self): |     def ping(self): | ||||||
|         self._check_connected() |         self._check_connected() | ||||||
| @@ -310,7 +324,9 @@ class APIClient(threading.Thread): | |||||||
|         self._check_connected() |         self._check_connected() | ||||||
|  |  | ||||||
|         try: |         try: | ||||||
|             self._send_message_await_response(pb.DisconnectRequest(), pb.DisconnectResponse) |             self._send_message_await_response( | ||||||
|  |                 pb.DisconnectRequest(), pb.DisconnectResponse | ||||||
|  |             ) | ||||||
|         except APIConnectionError: |         except APIConnectionError: | ||||||
|             pass |             pass | ||||||
|         self._close_socket() |         self._close_socket() | ||||||
| @@ -415,7 +431,7 @@ class APIClient(threading.Thread): | |||||||
|  |  | ||||||
|  |  | ||||||
| def run_logs(config, address): | def run_logs(config, address): | ||||||
|     conf = config['api'] |     conf = config["api"] | ||||||
|     port = conf[CONF_PORT] |     port = conf[CONF_PORT] | ||||||
|     password = conf[CONF_PASSWORD] |     password = conf[CONF_PASSWORD] | ||||||
|     _LOGGER.info("Starting log output from %s using esphome API", address) |     _LOGGER.info("Starting log output from %s using esphome API", address) | ||||||
| @@ -447,24 +463,35 @@ def run_logs(config, address): | |||||||
|             _LOGGER.info("Successfully connected to %s", address) |             _LOGGER.info("Successfully connected to %s", address) | ||||||
|             return |             return | ||||||
|  |  | ||||||
|         wait_time = int(min(1.5**min(tries, 100), 30)) |         wait_time = int(min(1.5 ** min(tries, 100), 30)) | ||||||
|         if not has_connects: |         if not has_connects: | ||||||
|             _LOGGER.warning("Initial connection failed. The ESP might not be connected " |             _LOGGER.warning( | ||||||
|                             "to WiFi yet (%s). Re-Trying in %s seconds", |                 "Initial connection failed. The ESP might not be connected " | ||||||
|                             error, wait_time) |                 "to WiFi yet (%s). Re-Trying in %s seconds", | ||||||
|  |                 error, | ||||||
|  |                 wait_time, | ||||||
|  |             ) | ||||||
|         else: |         else: | ||||||
|             _LOGGER.warning("Couldn't connect to API (%s). Trying to reconnect in %s seconds", |             _LOGGER.warning( | ||||||
|                             error, wait_time) |                 "Couldn't connect to API (%s). Trying to reconnect in %s seconds", | ||||||
|         timer = threading.Timer(wait_time, functools.partial(try_connect, None, tries + 1)) |                 error, | ||||||
|  |                 wait_time, | ||||||
|  |             ) | ||||||
|  |         timer = threading.Timer( | ||||||
|  |             wait_time, functools.partial(try_connect, None, tries + 1) | ||||||
|  |         ) | ||||||
|         timer.start() |         timer.start() | ||||||
|         retry_timer.append(timer) |         retry_timer.append(timer) | ||||||
|  |  | ||||||
|     def on_log(msg): |     def on_log(msg): | ||||||
|         time_ = datetime.now().time().strftime('[%H:%M:%S]') |         time_ = datetime.now().time().strftime("[%H:%M:%S]") | ||||||
|         text = msg.message |         text = msg.message | ||||||
|         if msg.send_failed: |         if msg.send_failed: | ||||||
|             text = color('white', '(Message skipped because it was too big to fit in ' |             text = color( | ||||||
|                                   'TCP buffer - This is only cosmetic)') |                 "white", | ||||||
|  |                 "(Message skipped because it was too big to fit in " | ||||||
|  |                 "TCP buffer - This is only cosmetic)", | ||||||
|  |             ) | ||||||
|         safe_print(time_ + text) |         safe_print(time_ + text) | ||||||
|  |  | ||||||
|     def on_login(): |     def on_login(): | ||||||
|   | |||||||
| @@ -1,8 +1,17 @@ | |||||||
| import esphome.codegen as cg | import esphome.codegen as cg | ||||||
| import esphome.config_validation as cv | import esphome.config_validation as cv | ||||||
| from esphome.const import CONF_AUTOMATION_ID, CONF_CONDITION, CONF_ELSE, CONF_ID, CONF_THEN, \ | from esphome.const import ( | ||||||
|     CONF_TRIGGER_ID, CONF_TYPE_ID, CONF_TIME |     CONF_AUTOMATION_ID, | ||||||
|  |     CONF_CONDITION, | ||||||
|  |     CONF_ELSE, | ||||||
|  |     CONF_ID, | ||||||
|  |     CONF_THEN, | ||||||
|  |     CONF_TRIGGER_ID, | ||||||
|  |     CONF_TYPE_ID, | ||||||
|  |     CONF_TIME, | ||||||
|  | ) | ||||||
| from esphome.core import coroutine | from esphome.core import coroutine | ||||||
|  | from esphome.jsonschema import jschema_extractor | ||||||
| from esphome.util import Registry | from esphome.util import Registry | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -13,7 +22,12 @@ def maybe_simple_id(*validators): | |||||||
| def maybe_conf(conf, *validators): | def maybe_conf(conf, *validators): | ||||||
|     validator = cv.All(*validators) |     validator = cv.All(*validators) | ||||||
|  |  | ||||||
|  |     @jschema_extractor("maybe") | ||||||
|     def validate(value): |     def validate(value): | ||||||
|  |         # pylint: disable=comparison-with-callable | ||||||
|  |         if value == jschema_extractor: | ||||||
|  |             return validator | ||||||
|  |  | ||||||
|         if isinstance(value, dict): |         if isinstance(value, dict): | ||||||
|             return validator(value) |             return validator(value) | ||||||
|         with cv.remove_prepend_path([conf]): |         with cv.remove_prepend_path([conf]): | ||||||
| @@ -30,36 +44,34 @@ def register_condition(name, condition_type, schema): | |||||||
|     return CONDITION_REGISTRY.register(name, condition_type, schema) |     return CONDITION_REGISTRY.register(name, condition_type, schema) | ||||||
|  |  | ||||||
|  |  | ||||||
| Action = cg.esphome_ns.class_('Action') | Action = cg.esphome_ns.class_("Action") | ||||||
| Trigger = cg.esphome_ns.class_('Trigger') | Trigger = cg.esphome_ns.class_("Trigger") | ||||||
| ACTION_REGISTRY = Registry() | ACTION_REGISTRY = Registry() | ||||||
| Condition = cg.esphome_ns.class_('Condition') | Condition = cg.esphome_ns.class_("Condition") | ||||||
| CONDITION_REGISTRY = Registry() | CONDITION_REGISTRY = Registry() | ||||||
| validate_action = cv.validate_registry_entry('action', ACTION_REGISTRY) | validate_action = cv.validate_registry_entry("action", ACTION_REGISTRY) | ||||||
| validate_action_list = cv.validate_registry('action', ACTION_REGISTRY) | validate_action_list = cv.validate_registry("action", ACTION_REGISTRY) | ||||||
| validate_condition = cv.validate_registry_entry('condition', CONDITION_REGISTRY) | validate_condition = cv.validate_registry_entry("condition", CONDITION_REGISTRY) | ||||||
| validate_condition_list = cv.validate_registry('condition', CONDITION_REGISTRY) | validate_condition_list = cv.validate_registry("condition", CONDITION_REGISTRY) | ||||||
|  |  | ||||||
|  |  | ||||||
| def validate_potentially_and_condition(value): | def validate_potentially_and_condition(value): | ||||||
|     if isinstance(value, list): |     if isinstance(value, list): | ||||||
|         with cv.remove_prepend_path(['and']): |         with cv.remove_prepend_path(["and"]): | ||||||
|             return validate_condition({ |             return validate_condition({"and": value}) | ||||||
|                 'and': value |  | ||||||
|             }) |  | ||||||
|     return validate_condition(value) |     return validate_condition(value) | ||||||
|  |  | ||||||
|  |  | ||||||
| DelayAction = cg.esphome_ns.class_('DelayAction', Action, cg.Component) | DelayAction = cg.esphome_ns.class_("DelayAction", Action, cg.Component) | ||||||
| LambdaAction = cg.esphome_ns.class_('LambdaAction', Action) | LambdaAction = cg.esphome_ns.class_("LambdaAction", Action) | ||||||
| IfAction = cg.esphome_ns.class_('IfAction', Action) | IfAction = cg.esphome_ns.class_("IfAction", Action) | ||||||
| WhileAction = cg.esphome_ns.class_('WhileAction', Action) | WhileAction = cg.esphome_ns.class_("WhileAction", Action) | ||||||
| WaitUntilAction = cg.esphome_ns.class_('WaitUntilAction', Action, cg.Component) | WaitUntilAction = cg.esphome_ns.class_("WaitUntilAction", Action, cg.Component) | ||||||
| UpdateComponentAction = cg.esphome_ns.class_('UpdateComponentAction', Action) | UpdateComponentAction = cg.esphome_ns.class_("UpdateComponentAction", Action) | ||||||
| Automation = cg.esphome_ns.class_('Automation') | Automation = cg.esphome_ns.class_("Automation") | ||||||
|  |  | ||||||
| LambdaCondition = cg.esphome_ns.class_('LambdaCondition', Condition) | LambdaCondition = cg.esphome_ns.class_("LambdaCondition", Condition) | ||||||
| ForCondition = cg.esphome_ns.class_('ForCondition', Condition, cg.Component) | ForCondition = cg.esphome_ns.class_("ForCondition", Condition, cg.Component) | ||||||
|  |  | ||||||
|  |  | ||||||
| def validate_automation(extra_schema=None, extra_validators=None, single=False): | def validate_automation(extra_schema=None, extra_validators=None, single=False): | ||||||
| @@ -83,10 +95,10 @@ def validate_automation(extra_schema=None, extra_validators=None, single=False): | |||||||
|                 try: |                 try: | ||||||
|                     return cv.Schema([schema])(value) |                     return cv.Schema([schema])(value) | ||||||
|                 except cv.Invalid as err2: |                 except cv.Invalid as err2: | ||||||
|                     if 'extra keys not allowed' in str(err2) and len(err2.path) == 2: |                     if "extra keys not allowed" in str(err2) and len(err2.path) == 2: | ||||||
|                         # pylint: disable=raise-missing-from |                         # pylint: disable=raise-missing-from | ||||||
|                         raise err |                         raise err | ||||||
|                     if 'Unable to find action' in str(err): |                     if "Unable to find action" in str(err): | ||||||
|                         raise err2 |                         raise err2 | ||||||
|                     raise cv.MultipleInvalid([err, err2]) |                     raise cv.MultipleInvalid([err, err2]) | ||||||
|         elif isinstance(value, dict): |         elif isinstance(value, dict): | ||||||
| @@ -97,7 +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. |         # This should only happen with invalid configs, but let's have a nice error message. | ||||||
|         return [schema(value)] |         return [schema(value)] | ||||||
|  |  | ||||||
|  |     @jschema_extractor("automation") | ||||||
|     def validator(value): |     def validator(value): | ||||||
|  |         # hack to get the schema | ||||||
|  |         # pylint: disable=comparison-with-callable | ||||||
|  |         if value == jschema_extractor: | ||||||
|  |             return schema | ||||||
|  |  | ||||||
|         value = validator_(value) |         value = validator_(value) | ||||||
|         if extra_validators is not None: |         if extra_validators is not None: | ||||||
|             value = cv.Schema([extra_validators])(value) |             value = cv.Schema([extra_validators])(value) | ||||||
| @@ -110,47 +128,59 @@ def validate_automation(extra_schema=None, extra_validators=None, single=False): | |||||||
|     return validator |     return validator | ||||||
|  |  | ||||||
|  |  | ||||||
| AUTOMATION_SCHEMA = cv.Schema({ | AUTOMATION_SCHEMA = cv.Schema( | ||||||
|     cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(Trigger), |     { | ||||||
|     cv.GenerateID(CONF_AUTOMATION_ID): cv.declare_id(Automation), |         cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(Trigger), | ||||||
|     cv.Required(CONF_THEN): validate_action_list, |         cv.GenerateID(CONF_AUTOMATION_ID): cv.declare_id(Automation), | ||||||
| }) |         cv.Required(CONF_THEN): validate_action_list, | ||||||
|  |     } | ||||||
|  | ) | ||||||
|  |  | ||||||
| AndCondition = cg.esphome_ns.class_('AndCondition', Condition) | AndCondition = cg.esphome_ns.class_("AndCondition", Condition) | ||||||
| OrCondition = cg.esphome_ns.class_('OrCondition', Condition) | OrCondition = cg.esphome_ns.class_("OrCondition", Condition) | ||||||
| NotCondition = cg.esphome_ns.class_('NotCondition', Condition) | NotCondition = cg.esphome_ns.class_("NotCondition", Condition) | ||||||
|  |  | ||||||
|  |  | ||||||
| @register_condition('and', AndCondition, validate_condition_list) | @register_condition("and", AndCondition, validate_condition_list) | ||||||
| def and_condition_to_code(config, condition_id, template_arg, args): | def and_condition_to_code(config, condition_id, template_arg, args): | ||||||
|     conditions = yield build_condition_list(config, template_arg, args) |     conditions = yield build_condition_list(config, template_arg, args) | ||||||
|     yield cg.new_Pvariable(condition_id, template_arg, conditions) |     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): | def or_condition_to_code(config, condition_id, template_arg, args): | ||||||
|     conditions = yield build_condition_list(config, template_arg, args) |     conditions = yield build_condition_list(config, template_arg, args) | ||||||
|     yield cg.new_Pvariable(condition_id, template_arg, conditions) |     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): | def not_condition_to_code(config, condition_id, template_arg, args): | ||||||
|     condition = yield build_condition(config, template_arg, args) |     condition = yield build_condition(config, template_arg, args) | ||||||
|     yield cg.new_Pvariable(condition_id, template_arg, condition) |     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): | def lambda_condition_to_code(config, condition_id, template_arg, args): | ||||||
|     lambda_ = yield cg.process_lambda(config, args, return_type=bool) |     lambda_ = yield cg.process_lambda(config, args, return_type=bool) | ||||||
|     yield cg.new_Pvariable(condition_id, template_arg, lambda_) |     yield cg.new_Pvariable(condition_id, template_arg, lambda_) | ||||||
|  |  | ||||||
|  |  | ||||||
| @register_condition('for', ForCondition, cv.Schema({ | @register_condition( | ||||||
|     cv.Required(CONF_TIME): cv.templatable(cv.positive_time_period_milliseconds), |     "for", | ||||||
|     cv.Required(CONF_CONDITION): validate_potentially_and_condition, |     ForCondition, | ||||||
| }).extend(cv.COMPONENT_SCHEMA)) |     cv.Schema( | ||||||
|  |         { | ||||||
|  |             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): | 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) |     var = cg.new_Pvariable(condition_id, template_arg, condition) | ||||||
|     yield cg.register_component(var, config) |     yield cg.register_component(var, config) | ||||||
|     templ = yield cg.templatable(config[CONF_TIME], args, cg.uint32) |     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 |     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): | def delay_action_to_code(config, action_id, template_arg, args): | ||||||
|     var = cg.new_Pvariable(action_id, template_arg) |     var = cg.new_Pvariable(action_id, template_arg) | ||||||
|     yield cg.register_component(var, {}) |     yield cg.register_component(var, {}) | ||||||
| @@ -167,11 +199,18 @@ def delay_action_to_code(config, action_id, template_arg, args): | |||||||
|     yield var |     yield var | ||||||
|  |  | ||||||
|  |  | ||||||
| @register_action('if', IfAction, cv.All({ | @register_action( | ||||||
|     cv.Required(CONF_CONDITION): validate_potentially_and_condition, |     "if", | ||||||
|     cv.Optional(CONF_THEN): validate_action_list, |     IfAction, | ||||||
|     cv.Optional(CONF_ELSE): validate_action_list, |     cv.All( | ||||||
| }, cv.has_at_least_one_key(CONF_THEN, CONF_ELSE))) |         { | ||||||
|  |             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): | def if_action_to_code(config, action_id, template_arg, args): | ||||||
|     conditions = yield build_condition(config[CONF_CONDITION], template_arg, args) |     conditions = yield build_condition(config[CONF_CONDITION], template_arg, args) | ||||||
|     var = cg.new_Pvariable(action_id, template_arg, conditions) |     var = cg.new_Pvariable(action_id, template_arg, conditions) | ||||||
| @@ -184,10 +223,16 @@ def if_action_to_code(config, action_id, template_arg, args): | |||||||
|     yield var |     yield var | ||||||
|  |  | ||||||
|  |  | ||||||
| @register_action('while', WhileAction, cv.Schema({ | @register_action( | ||||||
|     cv.Required(CONF_CONDITION): validate_potentially_and_condition, |     "while", | ||||||
|     cv.Required(CONF_THEN): validate_action_list, |     WhileAction, | ||||||
| })) |     cv.Schema( | ||||||
|  |         { | ||||||
|  |             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): | def while_action_to_code(config, action_id, template_arg, args): | ||||||
|     conditions = yield build_condition(config[CONF_CONDITION], template_arg, args) |     conditions = yield build_condition(config[CONF_CONDITION], template_arg, args) | ||||||
|     var = cg.new_Pvariable(action_id, template_arg, conditions) |     var = cg.new_Pvariable(action_id, template_arg, conditions) | ||||||
| @@ -197,15 +242,17 @@ def while_action_to_code(config, action_id, template_arg, args): | |||||||
|  |  | ||||||
|  |  | ||||||
| def validate_wait_until(value): | def validate_wait_until(value): | ||||||
|     schema = cv.Schema({ |     schema = cv.Schema( | ||||||
|         cv.Required(CONF_CONDITION): validate_potentially_and_condition, |         { | ||||||
|     }) |             cv.Required(CONF_CONDITION): validate_potentially_and_condition, | ||||||
|  |         } | ||||||
|  |     ) | ||||||
|     if isinstance(value, dict) and CONF_CONDITION in value: |     if isinstance(value, dict) and CONF_CONDITION in value: | ||||||
|         return schema(value) |         return schema(value) | ||||||
|     return validate_wait_until({CONF_CONDITION: value}) |     return validate_wait_until({CONF_CONDITION: value}) | ||||||
|  |  | ||||||
|  |  | ||||||
| @register_action('wait_until', WaitUntilAction, validate_wait_until) | @register_action("wait_until", WaitUntilAction, validate_wait_until) | ||||||
| def wait_until_action_to_code(config, action_id, template_arg, args): | def wait_until_action_to_code(config, action_id, template_arg, args): | ||||||
|     conditions = yield build_condition(config[CONF_CONDITION], template_arg, args) |     conditions = yield build_condition(config[CONF_CONDITION], template_arg, args) | ||||||
|     var = cg.new_Pvariable(action_id, template_arg, conditions) |     var = cg.new_Pvariable(action_id, template_arg, conditions) | ||||||
| @@ -213,15 +260,21 @@ def wait_until_action_to_code(config, action_id, template_arg, args): | |||||||
|     yield var |     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): | def lambda_action_to_code(config, action_id, template_arg, args): | ||||||
|     lambda_ = yield cg.process_lambda(config, args, return_type=cg.void) |     lambda_ = yield cg.process_lambda(config, args, return_type=cg.void) | ||||||
|     yield cg.new_Pvariable(action_id, template_arg, lambda_) |     yield cg.new_Pvariable(action_id, template_arg, lambda_) | ||||||
|  |  | ||||||
|  |  | ||||||
| @register_action('component.update', UpdateComponentAction, maybe_simple_id({ | @register_action( | ||||||
|     cv.Required(CONF_ID): cv.use_id(cg.PollingComponent), |     "component.update", | ||||||
| })) |     UpdateComponentAction, | ||||||
|  |     maybe_simple_id( | ||||||
|  |         { | ||||||
|  |             cv.Required(CONF_ID): cv.use_id(cg.PollingComponent), | ||||||
|  |         } | ||||||
|  |     ), | ||||||
|  | ) | ||||||
| def component_update_action_to_code(config, action_id, template_arg, args): | def component_update_action_to_code(config, action_id, template_arg, args): | ||||||
|     comp = yield cg.get_variable(config[CONF_ID]) |     comp = yield cg.get_variable(config[CONF_ID]) | ||||||
|     yield cg.new_Pvariable(action_id, template_arg, comp) |     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 | @coroutine | ||||||
| def build_action(full_config, template_arg, args): | 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] |     action_id = full_config[CONF_TYPE_ID] | ||||||
|     builder = registry_entry.coroutine_fun |     builder = registry_entry.coroutine_fun | ||||||
|     yield builder(config, action_id, template_arg, args) |     yield builder(config, action_id, template_arg, args) | ||||||
| @@ -246,7 +301,9 @@ def build_action_list(config, templ, arg_type): | |||||||
|  |  | ||||||
| @coroutine | @coroutine | ||||||
| def build_condition(full_config, template_arg, args): | 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] |     action_id = full_config[CONF_TYPE_ID] | ||||||
|     builder = registry_entry.coroutine_fun |     builder = registry_entry.coroutine_fun | ||||||
|     yield builder(config, action_id, template_arg, args) |     yield builder(config, action_id, template_arg, args) | ||||||
|   | |||||||
| @@ -9,18 +9,71 @@ | |||||||
|  |  | ||||||
| # pylint: disable=unused-import | # pylint: disable=unused-import | ||||||
| from esphome.cpp_generator import (  # noqa | from esphome.cpp_generator import (  # noqa | ||||||
|     Expression, RawExpression, RawStatement, TemplateArguments, |     Expression, | ||||||
|     StructInitializer, ArrayInitializer, safe_exp, Statement, LineComment, |     RawExpression, | ||||||
|     progmem_array, statement, variable, Pvariable, new_Pvariable, |     RawStatement, | ||||||
|     add, add_global, add_library, add_build_flag, add_define, |     TemplateArguments, | ||||||
|     get_variable, get_variable_with_full_id, process_lambda, is_template, templatable, MockObj, |     StructInitializer, | ||||||
|     MockObjClass) |     ArrayInitializer, | ||||||
|  |     safe_exp, | ||||||
|  |     Statement, | ||||||
|  |     LineComment, | ||||||
|  |     progmem_array, | ||||||
|  |     statement, | ||||||
|  |     variable, | ||||||
|  |     new_variable, | ||||||
|  |     Pvariable, | ||||||
|  |     new_Pvariable, | ||||||
|  |     add, | ||||||
|  |     add_global, | ||||||
|  |     add_library, | ||||||
|  |     add_build_flag, | ||||||
|  |     add_define, | ||||||
|  |     get_variable, | ||||||
|  |     get_variable_with_full_id, | ||||||
|  |     process_lambda, | ||||||
|  |     is_template, | ||||||
|  |     templatable, | ||||||
|  |     MockObj, | ||||||
|  |     MockObjClass, | ||||||
|  | ) | ||||||
| from esphome.cpp_helpers import (  # noqa | from esphome.cpp_helpers import (  # noqa | ||||||
|     gpio_pin_expression, register_component, build_registry_entry, |     gpio_pin_expression, | ||||||
|     build_registry_list, extract_registry_entry_config, register_parented) |     register_component, | ||||||
|  |     build_registry_entry, | ||||||
|  |     build_registry_list, | ||||||
|  |     extract_registry_entry_config, | ||||||
|  |     register_parented, | ||||||
|  | ) | ||||||
| from esphome.cpp_types import (  # noqa | from esphome.cpp_types import (  # noqa | ||||||
|     global_ns, void, nullptr, float_, double, bool_, int_, std_ns, std_string, |     global_ns, | ||||||
|     std_vector, uint8, uint16, uint32, int32, const_char_ptr, NAN, |     void, | ||||||
|     esphome_ns, App, Nameable, Component, ComponentPtr, |     nullptr, | ||||||
|     PollingComponent, Application, optional, arduino_json_ns, JsonObject, |     float_, | ||||||
|     JsonObjectRef, JsonObjectConstRef, Controller, GPIOPin) |     double, | ||||||
|  |     bool_, | ||||||
|  |     int_, | ||||||
|  |     std_ns, | ||||||
|  |     std_string, | ||||||
|  |     std_vector, | ||||||
|  |     uint8, | ||||||
|  |     uint16, | ||||||
|  |     uint32, | ||||||
|  |     int32, | ||||||
|  |     const_char_ptr, | ||||||
|  |     NAN, | ||||||
|  |     esphome_ns, | ||||||
|  |     App, | ||||||
|  |     Nameable, | ||||||
|  |     Component, | ||||||
|  |     ComponentPtr, | ||||||
|  |     PollingComponent, | ||||||
|  |     Application, | ||||||
|  |     optional, | ||||||
|  |     arduino_json_ns, | ||||||
|  |     JsonObject, | ||||||
|  |     JsonObjectRef, | ||||||
|  |     JsonObjectConstRef, | ||||||
|  |     Controller, | ||||||
|  |     GPIOPin, | ||||||
|  | ) | ||||||
|   | |||||||
| @@ -11,6 +11,7 @@ void A4988::setup() { | |||||||
|   if (this->sleep_pin_ != nullptr) { |   if (this->sleep_pin_ != nullptr) { | ||||||
|     this->sleep_pin_->setup(); |     this->sleep_pin_->setup(); | ||||||
|     this->sleep_pin_->digital_write(false); |     this->sleep_pin_->digital_write(false); | ||||||
|  |     this->sleep_pin_state_ = false; | ||||||
|   } |   } | ||||||
|   this->step_pin_->setup(); |   this->step_pin_->setup(); | ||||||
|   this->step_pin_->digital_write(false); |   this->step_pin_->digital_write(false); | ||||||
| @@ -27,7 +28,12 @@ void A4988::dump_config() { | |||||||
| void A4988::loop() { | void A4988::loop() { | ||||||
|   bool at_target = this->has_reached_target(); |   bool at_target = this->has_reached_target(); | ||||||
|   if (this->sleep_pin_ != nullptr) { |   if (this->sleep_pin_ != nullptr) { | ||||||
|  |     bool sleep_rising_edge = !sleep_pin_state_ & !at_target; | ||||||
|     this->sleep_pin_->digital_write(!at_target); |     this->sleep_pin_->digital_write(!at_target); | ||||||
|  |     this->sleep_pin_state_ = !at_target; | ||||||
|  |     if (sleep_rising_edge) { | ||||||
|  |       delayMicroseconds(1000); | ||||||
|  |     } | ||||||
|   } |   } | ||||||
|   if (at_target) { |   if (at_target) { | ||||||
|     this->high_freq_.stop(); |     this->high_freq_.stop(); | ||||||
|   | |||||||
| @@ -21,6 +21,7 @@ class A4988 : public stepper::Stepper, public Component { | |||||||
|   GPIOPin *step_pin_; |   GPIOPin *step_pin_; | ||||||
|   GPIOPin *dir_pin_; |   GPIOPin *dir_pin_; | ||||||
|   GPIOPin *sleep_pin_{nullptr}; |   GPIOPin *sleep_pin_{nullptr}; | ||||||
|  |   bool sleep_pin_state_; | ||||||
|   HighFrequencyLoopRequester high_freq_; |   HighFrequencyLoopRequester high_freq_; | ||||||
| }; | }; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -5,15 +5,17 @@ import esphome.codegen as cg | |||||||
| from esphome.const import CONF_DIR_PIN, CONF_ID, CONF_SLEEP_PIN, CONF_STEP_PIN | from esphome.const import CONF_DIR_PIN, CONF_ID, CONF_SLEEP_PIN, CONF_STEP_PIN | ||||||
|  |  | ||||||
|  |  | ||||||
| a4988_ns = cg.esphome_ns.namespace('a4988') | a4988_ns = cg.esphome_ns.namespace("a4988") | ||||||
| A4988 = a4988_ns.class_('A4988', stepper.Stepper, cg.Component) | A4988 = a4988_ns.class_("A4988", stepper.Stepper, cg.Component) | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = stepper.STEPPER_SCHEMA.extend({ | CONFIG_SCHEMA = stepper.STEPPER_SCHEMA.extend( | ||||||
|     cv.Required(CONF_ID): cv.declare_id(A4988), |     { | ||||||
|     cv.Required(CONF_STEP_PIN): pins.gpio_output_pin_schema, |         cv.Required(CONF_ID): cv.declare_id(A4988), | ||||||
|     cv.Required(CONF_DIR_PIN): pins.gpio_output_pin_schema, |         cv.Required(CONF_STEP_PIN): pins.gpio_output_pin_schema, | ||||||
|     cv.Optional(CONF_SLEEP_PIN): pins.gpio_output_pin_schema, |         cv.Required(CONF_DIR_PIN): pins.gpio_output_pin_schema, | ||||||
| }).extend(cv.COMPONENT_SCHEMA) |         cv.Optional(CONF_SLEEP_PIN): pins.gpio_output_pin_schema, | ||||||
|  |     } | ||||||
|  | ).extend(cv.COMPONENT_SCHEMA) | ||||||
|  |  | ||||||
|  |  | ||||||
| def to_code(config): | def to_code(config): | ||||||
|   | |||||||
| @@ -4,28 +4,32 @@ from esphome import pins | |||||||
| from esphome.components import output | from esphome.components import output | ||||||
| from esphome.const import CONF_ID, CONF_MIN_POWER, CONF_METHOD | from esphome.const import CONF_ID, CONF_MIN_POWER, CONF_METHOD | ||||||
|  |  | ||||||
| CODEOWNERS = ['@glmnet'] | CODEOWNERS = ["@glmnet"] | ||||||
|  |  | ||||||
| ac_dimmer_ns = cg.esphome_ns.namespace('ac_dimmer') | ac_dimmer_ns = cg.esphome_ns.namespace("ac_dimmer") | ||||||
| AcDimmer = ac_dimmer_ns.class_('AcDimmer', output.FloatOutput, cg.Component) | AcDimmer = ac_dimmer_ns.class_("AcDimmer", output.FloatOutput, cg.Component) | ||||||
|  |  | ||||||
| DimMethod = ac_dimmer_ns.enum('DimMethod') | DimMethod = ac_dimmer_ns.enum("DimMethod") | ||||||
| DIM_METHODS = { | DIM_METHODS = { | ||||||
|     'LEADING_PULSE': DimMethod.DIM_METHOD_LEADING_PULSE, |     "LEADING_PULSE": DimMethod.DIM_METHOD_LEADING_PULSE, | ||||||
|     'LEADING': DimMethod.DIM_METHOD_LEADING, |     "LEADING": DimMethod.DIM_METHOD_LEADING, | ||||||
|     'TRAILING': DimMethod.DIM_METHOD_TRAILING, |     "TRAILING": DimMethod.DIM_METHOD_TRAILING, | ||||||
| } | } | ||||||
|  |  | ||||||
| CONF_GATE_PIN = 'gate_pin' | CONF_GATE_PIN = "gate_pin" | ||||||
| CONF_ZERO_CROSS_PIN = 'zero_cross_pin' | CONF_ZERO_CROSS_PIN = "zero_cross_pin" | ||||||
| CONF_INIT_WITH_HALF_CYCLE = 'init_with_half_cycle' | CONF_INIT_WITH_HALF_CYCLE = "init_with_half_cycle" | ||||||
| CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend({ | CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend( | ||||||
|     cv.Required(CONF_ID): cv.declare_id(AcDimmer), |     { | ||||||
|     cv.Required(CONF_GATE_PIN): pins.internal_gpio_output_pin_schema, |         cv.Required(CONF_ID): cv.declare_id(AcDimmer), | ||||||
|     cv.Required(CONF_ZERO_CROSS_PIN): pins.internal_gpio_input_pin_schema, |         cv.Required(CONF_GATE_PIN): pins.internal_gpio_output_pin_schema, | ||||||
|     cv.Optional(CONF_INIT_WITH_HALF_CYCLE, default=True): cv.boolean, |         cv.Required(CONF_ZERO_CROSS_PIN): pins.internal_gpio_input_pin_schema, | ||||||
|     cv.Optional(CONF_METHOD, default='leading pulse'): cv.enum(DIM_METHODS, upper=True, space='_'), |         cv.Optional(CONF_INIT_WITH_HALF_CYCLE, default=True): cv.boolean, | ||||||
| }).extend(cv.COMPONENT_SCHEMA) |         cv.Optional(CONF_METHOD, default="leading pulse"): cv.enum( | ||||||
|  |             DIM_METHODS, upper=True, space="_" | ||||||
|  |         ), | ||||||
|  |     } | ||||||
|  | ).extend(cv.COMPONENT_SCHEMA) | ||||||
|  |  | ||||||
|  |  | ||||||
| def to_code(config): | 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.components.light.effects import register_addressable_effect | ||||||
| from esphome.const import CONF_NAME, CONF_UART_ID | from esphome.const import CONF_NAME, CONF_UART_ID | ||||||
|  |  | ||||||
| DEPENDENCIES = ['uart'] | DEPENDENCIES = ["uart"] | ||||||
|  |  | ||||||
| adalight_ns = cg.esphome_ns.namespace('adalight') | adalight_ns = cg.esphome_ns.namespace("adalight") | ||||||
| AdalightLightEffect = adalight_ns.class_( | AdalightLightEffect = adalight_ns.class_( | ||||||
|     'AdalightLightEffect', uart.UARTDevice, AddressableLightEffect) |     "AdalightLightEffect", uart.UARTDevice, AddressableLightEffect | ||||||
|  | ) | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = cv.Schema({}) | CONFIG_SCHEMA = cv.Schema({}) | ||||||
|  |  | ||||||
|  |  | ||||||
| @register_addressable_effect('adalight', AdalightLightEffect, "Adalight", { | @register_addressable_effect( | ||||||
|     cv.GenerateID(CONF_UART_ID): cv.use_id(uart.UARTComponent) |     "adalight", | ||||||
| }) |     AdalightLightEffect, | ||||||
|  |     "Adalight", | ||||||
|  |     {cv.GenerateID(CONF_UART_ID): cv.use_id(uart.UARTComponent)}, | ||||||
|  | ) | ||||||
| def adalight_light_effect_to_code(config, effect_id): | def adalight_light_effect_to_code(config, effect_id): | ||||||
|     effect = cg.new_Pvariable(effect_id, config[CONF_NAME]) |     effect = cg.new_Pvariable(effect_id, config[CONF_NAME]) | ||||||
|     yield uart.register_uart_device(effect, config) |     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) { | void AdalightLightEffect::blank_all_leds_(light::AddressableLight &it) { | ||||||
|   for (int led = it.size(); led-- > 0;) { |   for (int led = it.size(); led-- > 0;) { | ||||||
|     it[led].set(light::ESPColor::BLACK); |     it[led].set(COLOR_BLACK); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| void AdalightLightEffect::apply(light::AddressableLight &it, const light::ESPColor ¤t_color) { | void AdalightLightEffect::apply(light::AddressableLight &it, const Color ¤t_color) { | ||||||
|   const uint32_t now = millis(); |   const uint32_t now = millis(); | ||||||
|  |  | ||||||
|   if (now - this->last_ack_ >= ADALIGHT_ACK_INTERVAL) { |   if (now - this->last_ack_ >= ADALIGHT_ACK_INTERVAL) { | ||||||
| @@ -130,7 +130,7 @@ AdalightLightEffect::Frame AdalightLightEffect::parse_frame_(light::AddressableL | |||||||
|   for (int led = 0; led < accepted_led_count; led++, led_data += 3) { |   for (int led = 0; led < accepted_led_count; led++, led_data += 3) { | ||||||
|     auto white = std::min(std::min(led_data[0], led_data[1]), led_data[2]); |     auto white = std::min(std::min(led_data[0], led_data[1]), led_data[2]); | ||||||
|  |  | ||||||
|     it[led].set(light::ESPColor(led_data[0], led_data[1], led_data[2], white)); |     it[led].set(Color(led_data[0], led_data[1], led_data[2], white)); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   return CONSUMED; |   return CONSUMED; | ||||||
|   | |||||||
| @@ -16,7 +16,7 @@ class AdalightLightEffect : public light::AddressableLightEffect, public uart::U | |||||||
|  public: |  public: | ||||||
|   void start() override; |   void start() override; | ||||||
|   void stop() override; |   void stop() override; | ||||||
|   void apply(light::AddressableLight &it, const light::ESPColor ¤t_color) override; |   void apply(light::AddressableLight &it, const Color ¤t_color) override; | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   enum Frame { |   enum Frame { | ||||||
|   | |||||||
| @@ -1 +1 @@ | |||||||
| CODEOWNERS = ['@esphome/core'] | CODEOWNERS = ["@esphome/core"] | ||||||
|   | |||||||
| @@ -16,7 +16,9 @@ void ADCSensor::set_attenuation(adc_attenuation_t attenuation) { this->attenuati | |||||||
|  |  | ||||||
| void ADCSensor::setup() { | void ADCSensor::setup() { | ||||||
|   ESP_LOGCONFIG(TAG, "Setting up ADC '%s'...", this->get_name().c_str()); |   ESP_LOGCONFIG(TAG, "Setting up ADC '%s'...", this->get_name().c_str()); | ||||||
|  | #ifndef USE_ADC_SENSOR_VCC | ||||||
|   GPIOPin(this->pin_, INPUT).setup(); |   GPIOPin(this->pin_, INPUT).setup(); | ||||||
|  | #endif | ||||||
|  |  | ||||||
| #ifdef ARDUINO_ARCH_ESP32 | #ifdef ARDUINO_ARCH_ESP32 | ||||||
|   analogSetPinAttenuation(this->pin_, this->attenuation_); |   analogSetPinAttenuation(this->pin_, this->attenuation_); | ||||||
|   | |||||||
| @@ -2,36 +2,51 @@ import esphome.codegen as cg | |||||||
| import esphome.config_validation as cv | import esphome.config_validation as cv | ||||||
| from esphome import pins | from esphome import pins | ||||||
| from esphome.components import sensor, voltage_sampler | from esphome.components import sensor, voltage_sampler | ||||||
| from esphome.const import CONF_ATTENUATION, CONF_ID, CONF_PIN, ICON_FLASH, UNIT_VOLT | from esphome.const import ( | ||||||
|  |     CONF_ATTENUATION, | ||||||
|  |     CONF_ID, | ||||||
|  |     CONF_PIN, | ||||||
|  |     DEVICE_CLASS_VOLTAGE, | ||||||
|  |     ICON_EMPTY, | ||||||
|  |     UNIT_VOLT, | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
| AUTO_LOAD = ['voltage_sampler'] | AUTO_LOAD = ["voltage_sampler"] | ||||||
|  |  | ||||||
| ATTENUATION_MODES = { | ATTENUATION_MODES = { | ||||||
|     '0db': cg.global_ns.ADC_0db, |     "0db": cg.global_ns.ADC_0db, | ||||||
|     '2.5db': cg.global_ns.ADC_2_5db, |     "2.5db": cg.global_ns.ADC_2_5db, | ||||||
|     '6db': cg.global_ns.ADC_6db, |     "6db": cg.global_ns.ADC_6db, | ||||||
|     '11db': cg.global_ns.ADC_11db, |     "11db": cg.global_ns.ADC_11db, | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| def validate_adc_pin(value): | def validate_adc_pin(value): | ||||||
|     vcc = str(value).upper() |     vcc = str(value).upper() | ||||||
|     if vcc == 'VCC': |     if vcc == "VCC": | ||||||
|         return cv.only_on_esp8266(vcc) |         return cv.only_on_esp8266(vcc) | ||||||
|     return pins.analog_pin(value) |     return pins.analog_pin(value) | ||||||
|  |  | ||||||
|  |  | ||||||
| adc_ns = cg.esphome_ns.namespace('adc') | adc_ns = cg.esphome_ns.namespace("adc") | ||||||
| ADCSensor = adc_ns.class_('ADCSensor', sensor.Sensor, cg.PollingComponent, | ADCSensor = adc_ns.class_( | ||||||
|                           voltage_sampler.VoltageSampler) |     "ADCSensor", sensor.Sensor, cg.PollingComponent, voltage_sampler.VoltageSampler | ||||||
|  | ) | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = sensor.sensor_schema(UNIT_VOLT, ICON_FLASH, 2).extend({ | CONFIG_SCHEMA = ( | ||||||
|     cv.GenerateID(): cv.declare_id(ADCSensor), |     sensor.sensor_schema(UNIT_VOLT, ICON_EMPTY, 2, DEVICE_CLASS_VOLTAGE) | ||||||
|     cv.Required(CONF_PIN): validate_adc_pin, |     .extend( | ||||||
|     cv.SplitDefault(CONF_ATTENUATION, esp32='0db'): |         { | ||||||
|         cv.All(cv.only_on_esp32, cv.enum(ATTENUATION_MODES, lower=True)), |             cv.GenerateID(): cv.declare_id(ADCSensor), | ||||||
| }).extend(cv.polling_component_schema('60s')) |             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): | def to_code(config): | ||||||
| @@ -39,8 +54,8 @@ def to_code(config): | |||||||
|     yield cg.register_component(var, config) |     yield cg.register_component(var, config) | ||||||
|     yield sensor.register_sensor(var, config) |     yield sensor.register_sensor(var, config) | ||||||
|  |  | ||||||
|     if config[CONF_PIN] == 'VCC': |     if config[CONF_PIN] == "VCC": | ||||||
|         cg.add_define('USE_ADC_SENSOR_VCC') |         cg.add_define("USE_ADC_SENSOR_VCC") | ||||||
|     else: |     else: | ||||||
|         cg.add(var.set_pin(config[CONF_PIN])) |         cg.add(var.set_pin(config[CONF_PIN])) | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										0
									
								
								esphome/components/addressable_light/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								esphome/components/addressable_light/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,67 @@ | |||||||
|  | #include "addressable_light_display.h" | ||||||
|  | #include "esphome/core/log.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace addressable_light { | ||||||
|  |  | ||||||
|  | static const char* TAG = "addressable_light.display"; | ||||||
|  |  | ||||||
|  | int AddressableLightDisplay::get_width_internal() { return this->width_; } | ||||||
|  | int AddressableLightDisplay::get_height_internal() { return this->height_; } | ||||||
|  |  | ||||||
|  | void AddressableLightDisplay::setup() { | ||||||
|  |   this->addressable_light_buffer_.resize(this->width_ * this->height_, {0, 0, 0, 0}); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void AddressableLightDisplay::update() { | ||||||
|  |   if (!this->enabled_) | ||||||
|  |     return; | ||||||
|  |  | ||||||
|  |   this->do_update_(); | ||||||
|  |   this->display(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void AddressableLightDisplay::display() { | ||||||
|  |   bool dirty = false; | ||||||
|  |   uint8_t old_r, old_g, old_b, old_w; | ||||||
|  |   Color* c; | ||||||
|  |  | ||||||
|  |   for (uint32_t offset = 0; offset < this->addressable_light_buffer_.size(); offset++) { | ||||||
|  |     c = &(this->addressable_light_buffer_[offset]); | ||||||
|  |  | ||||||
|  |     light::ESPColorView pixel = (*this->light_)[offset]; | ||||||
|  |  | ||||||
|  |     // Track the original values for the pixel view. If it has changed updating, then | ||||||
|  |     // we trigger a redraw. Avoiding redraws == avoiding flicker! | ||||||
|  |     old_r = pixel.get_red(); | ||||||
|  |     old_g = pixel.get_green(); | ||||||
|  |     old_b = pixel.get_blue(); | ||||||
|  |     old_w = pixel.get_white(); | ||||||
|  |  | ||||||
|  |     pixel.set_rgbw(c->r, c->g, c->b, c->w); | ||||||
|  |  | ||||||
|  |     // If the actual value of the pixel changed, then schedule a redraw. | ||||||
|  |     if (pixel.get_red() != old_r || pixel.get_green() != old_g || pixel.get_blue() != old_b || | ||||||
|  |         pixel.get_white() != old_w) { | ||||||
|  |       dirty = true; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (dirty) { | ||||||
|  |     this->light_->schedule_show(); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void HOT AddressableLightDisplay::draw_absolute_pixel_internal(int x, int y, Color color) { | ||||||
|  |   if (x >= this->get_width_internal() || x < 0 || y >= this->get_height_internal() || y < 0) | ||||||
|  |     return; | ||||||
|  |  | ||||||
|  |   if (this->pixel_mapper_f_.has_value()) { | ||||||
|  |     // Params are passed by reference, so they may be modified in call. | ||||||
|  |     this->addressable_light_buffer_[(*this->pixel_mapper_f_)(x, y)] = color; | ||||||
|  |   } else { | ||||||
|  |     this->addressable_light_buffer_[y * this->get_width_internal() + x] = color; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | }  // namespace addressable_light | ||||||
|  | }  // namespace esphome | ||||||
| @@ -0,0 +1,59 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include "esphome/core/component.h" | ||||||
|  | #include "esphome/core/color.h" | ||||||
|  | #include "esphome/components/display/display_buffer.h" | ||||||
|  | #include "esphome/components/light/addressable_light.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace addressable_light { | ||||||
|  |  | ||||||
|  | class AddressableLightDisplay : public display::DisplayBuffer, public PollingComponent { | ||||||
|  |  public: | ||||||
|  |   light::AddressableLight *get_light() const { return this->light_; } | ||||||
|  |  | ||||||
|  |   void set_width(int32_t width) { width_ = width; } | ||||||
|  |   void set_height(int32_t height) { height_ = height; } | ||||||
|  |   void set_light(light::LightState *state) { | ||||||
|  |     light_state_ = state; | ||||||
|  |     light_ = static_cast<light::AddressableLight *>(state->get_output()); | ||||||
|  |   } | ||||||
|  |   void set_enabled(bool enabled) { | ||||||
|  |     if (light_state_) { | ||||||
|  |       if (enabled_ && !enabled) {  // enabled -> disabled | ||||||
|  |         // - Tell the parent light to refresh, effectively wiping the display. Also | ||||||
|  |         //   restores the previous effect (if any). | ||||||
|  |         light_state_->make_call().set_effect(this->last_effect_).perform(); | ||||||
|  |  | ||||||
|  |       } else if (!enabled_ && enabled) {  // disabled -> enabled | ||||||
|  |         // - Save the current effect. | ||||||
|  |         this->last_effect_ = light_state_->get_effect_name(); | ||||||
|  |         // - Disable any current effect. | ||||||
|  |         light_state_->make_call().set_effect(0).perform(); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     enabled_ = enabled; | ||||||
|  |   } | ||||||
|  |   bool get_enabled() { return enabled_; } | ||||||
|  |  | ||||||
|  |   void set_pixel_mapper(std::function<int(int, int)> &&pixel_mapper_f) { this->pixel_mapper_f_ = pixel_mapper_f; } | ||||||
|  |   void setup() override; | ||||||
|  |   void display(); | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   int get_width_internal() override; | ||||||
|  |   int get_height_internal() override; | ||||||
|  |   void draw_absolute_pixel_internal(int x, int y, Color color) override; | ||||||
|  |   void update() override; | ||||||
|  |  | ||||||
|  |   light::LightState *light_state_; | ||||||
|  |   light::AddressableLight *light_; | ||||||
|  |   bool enabled_{true}; | ||||||
|  |   int32_t width_; | ||||||
|  |   int32_t height_; | ||||||
|  |   std::vector<Color> addressable_light_buffer_; | ||||||
|  |   optional<std::string> last_effect_; | ||||||
|  |   optional<std::function<int(int, int)>> pixel_mapper_f_; | ||||||
|  | }; | ||||||
|  | }  // namespace addressable_light | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										63
									
								
								esphome/components/addressable_light/display.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								esphome/components/addressable_light/display.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,63 @@ | |||||||
|  | import esphome.codegen as cg | ||||||
|  | import esphome.config_validation as cv | ||||||
|  | from esphome.components import display, light | ||||||
|  | from esphome.const import ( | ||||||
|  |     CONF_ID, | ||||||
|  |     CONF_LAMBDA, | ||||||
|  |     CONF_PAGES, | ||||||
|  |     CONF_ADDRESSABLE_LIGHT_ID, | ||||||
|  |     CONF_HEIGHT, | ||||||
|  |     CONF_WIDTH, | ||||||
|  |     CONF_UPDATE_INTERVAL, | ||||||
|  |     CONF_PIXEL_MAPPER, | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | CODEOWNERS = ["@justfalter"] | ||||||
|  |  | ||||||
|  | addressable_light_ns = cg.esphome_ns.namespace("addressable_light") | ||||||
|  | AddressableLightDisplay = addressable_light_ns.class_( | ||||||
|  |     "AddressableLightDisplay", display.DisplayBuffer, cg.PollingComponent | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | CONFIG_SCHEMA = cv.All( | ||||||
|  |     display.FULL_DISPLAY_SCHEMA.extend( | ||||||
|  |         { | ||||||
|  |             cv.GenerateID(): cv.declare_id(AddressableLightDisplay), | ||||||
|  |             cv.Required(CONF_ADDRESSABLE_LIGHT_ID): cv.use_id( | ||||||
|  |                 light.AddressableLightState | ||||||
|  |             ), | ||||||
|  |             cv.Required(CONF_WIDTH): cv.positive_int, | ||||||
|  |             cv.Required(CONF_HEIGHT): cv.positive_int, | ||||||
|  |             cv.Optional( | ||||||
|  |                 CONF_UPDATE_INTERVAL, default="16ms" | ||||||
|  |             ): cv.positive_time_period_milliseconds, | ||||||
|  |             cv.Optional(CONF_PIXEL_MAPPER): cv.returning_lambda, | ||||||
|  |         } | ||||||
|  |     ), | ||||||
|  |     cv.has_at_most_one_key(CONF_PAGES, CONF_LAMBDA), | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | 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 | import esphome.config_validation as cv | ||||||
| from esphome.components import sensor, i2c | from esphome.components import sensor, i2c | ||||||
| from esphome import pins | from esphome import pins | ||||||
| from esphome.const import CONF_ID, CONF_VOLTAGE, \ | from esphome.const import ( | ||||||
|     UNIT_VOLT, ICON_FLASH, UNIT_AMPERE, UNIT_WATT |     CONF_ID, | ||||||
|  |     CONF_VOLTAGE, | ||||||
|  |     DEVICE_CLASS_CURRENT, | ||||||
|  |     DEVICE_CLASS_POWER, | ||||||
|  |     DEVICE_CLASS_VOLTAGE, | ||||||
|  |     ICON_EMPTY, | ||||||
|  |     UNIT_VOLT, | ||||||
|  |     UNIT_AMPERE, | ||||||
|  |     UNIT_WATT, | ||||||
|  | ) | ||||||
|  |  | ||||||
| DEPENDENCIES = ['i2c'] | DEPENDENCIES = ["i2c"] | ||||||
|  |  | ||||||
| ade7953_ns = cg.esphome_ns.namespace('ade7953') | ade7953_ns = cg.esphome_ns.namespace("ade7953") | ||||||
| ADE7953 = ade7953_ns.class_('ADE7953', cg.PollingComponent, i2c.I2CDevice) | ADE7953 = ade7953_ns.class_("ADE7953", cg.PollingComponent, i2c.I2CDevice) | ||||||
|  |  | ||||||
| CONF_IRQ_PIN = 'irq_pin' | CONF_IRQ_PIN = "irq_pin" | ||||||
| CONF_CURRENT_A = 'current_a' | CONF_CURRENT_A = "current_a" | ||||||
| CONF_CURRENT_B = 'current_b' | CONF_CURRENT_B = "current_b" | ||||||
| CONF_ACTIVE_POWER_A = 'active_power_a' | CONF_ACTIVE_POWER_A = "active_power_a" | ||||||
| CONF_ACTIVE_POWER_B = 'active_power_b' | CONF_ACTIVE_POWER_B = "active_power_b" | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = cv.Schema({ | CONFIG_SCHEMA = ( | ||||||
|     cv.GenerateID(): cv.declare_id(ADE7953), |     cv.Schema( | ||||||
|     cv.Optional(CONF_IRQ_PIN): pins.input_pin, |         { | ||||||
|     cv.Optional(CONF_VOLTAGE): sensor.sensor_schema(UNIT_VOLT, ICON_FLASH, 1), |             cv.GenerateID(): cv.declare_id(ADE7953), | ||||||
|     cv.Optional(CONF_CURRENT_A): sensor.sensor_schema(UNIT_AMPERE, ICON_FLASH, 2), |             cv.Optional(CONF_IRQ_PIN): pins.input_pin, | ||||||
|     cv.Optional(CONF_CURRENT_B): sensor.sensor_schema(UNIT_AMPERE, ICON_FLASH, 2), |             cv.Optional(CONF_VOLTAGE): sensor.sensor_schema( | ||||||
|     cv.Optional(CONF_ACTIVE_POWER_A): sensor.sensor_schema(UNIT_WATT, ICON_FLASH, 1), |                 UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE | ||||||
|     cv.Optional(CONF_ACTIVE_POWER_B): sensor.sensor_schema(UNIT_WATT, ICON_FLASH, 1), |             ), | ||||||
| }).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x38)) |             cv.Optional(CONF_CURRENT_A): sensor.sensor_schema( | ||||||
|  |                 UNIT_AMPERE, ICON_EMPTY, 2, DEVICE_CLASS_CURRENT | ||||||
|  |             ), | ||||||
|  |             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): | def to_code(config): | ||||||
| @@ -35,10 +60,15 @@ def to_code(config): | |||||||
|     if CONF_IRQ_PIN in config: |     if CONF_IRQ_PIN in config: | ||||||
|         cg.add(var.set_irq_pin(config[CONF_IRQ_PIN])) |         cg.add(var.set_irq_pin(config[CONF_IRQ_PIN])) | ||||||
|  |  | ||||||
|     for key in [CONF_VOLTAGE, CONF_CURRENT_A, CONF_CURRENT_B, CONF_ACTIVE_POWER_A, |     for key in [ | ||||||
|                 CONF_ACTIVE_POWER_B]: |         CONF_VOLTAGE, | ||||||
|  |         CONF_CURRENT_A, | ||||||
|  |         CONF_CURRENT_B, | ||||||
|  |         CONF_ACTIVE_POWER_A, | ||||||
|  |         CONF_ACTIVE_POWER_B, | ||||||
|  |     ]: | ||||||
|         if key not in config: |         if key not in config: | ||||||
|             continue |             continue | ||||||
|         conf = config[key] |         conf = config[key] | ||||||
|         sens = yield sensor.new_sensor(conf) |         sens = 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.components import i2c | ||||||
| from esphome.const import CONF_ID | from esphome.const import CONF_ID | ||||||
|  |  | ||||||
| DEPENDENCIES = ['i2c'] | DEPENDENCIES = ["i2c"] | ||||||
| AUTO_LOAD = ['sensor', 'voltage_sampler'] | AUTO_LOAD = ["sensor", "voltage_sampler"] | ||||||
| MULTI_CONF = True | MULTI_CONF = True | ||||||
|  |  | ||||||
| ads1115_ns = cg.esphome_ns.namespace('ads1115') | ads1115_ns = cg.esphome_ns.namespace("ads1115") | ||||||
| ADS1115Component = ads1115_ns.class_('ADS1115Component', cg.Component, i2c.I2CDevice) | ADS1115Component = ads1115_ns.class_("ADS1115Component", cg.Component, i2c.I2CDevice) | ||||||
|  |  | ||||||
| CONF_CONTINUOUS_MODE = 'continuous_mode' | CONF_CONTINUOUS_MODE = "continuous_mode" | ||||||
| CONFIG_SCHEMA = cv.Schema({ | CONFIG_SCHEMA = ( | ||||||
|     cv.GenerateID(): cv.declare_id(ADS1115Component), |     cv.Schema( | ||||||
|     cv.Optional(CONF_CONTINUOUS_MODE, default=False): cv.boolean, |         { | ||||||
| }).extend(cv.COMPONENT_SCHEMA).extend(i2c.i2c_device_schema(None)) |             cv.GenerateID(): cv.declare_id(ADS1115Component), | ||||||
|  |             cv.Optional(CONF_CONTINUOUS_MODE, default=False): cv.boolean, | ||||||
|  |         } | ||||||
|  |     ) | ||||||
|  |     .extend(cv.COMPONENT_SCHEMA) | ||||||
|  |     .extend(i2c.i2c_device_schema(None)) | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
| def to_code(config): | def to_code(config): | ||||||
|   | |||||||
| @@ -1,53 +1,67 @@ | |||||||
| import esphome.codegen as cg | import esphome.codegen as cg | ||||||
| import esphome.config_validation as cv | import esphome.config_validation as cv | ||||||
| from esphome.components import sensor, voltage_sampler | from esphome.components import sensor, voltage_sampler | ||||||
| from esphome.const import CONF_GAIN, CONF_MULTIPLEXER, ICON_FLASH, UNIT_VOLT, CONF_ID | from esphome.const import ( | ||||||
|  |     CONF_GAIN, | ||||||
|  |     CONF_MULTIPLEXER, | ||||||
|  |     DEVICE_CLASS_VOLTAGE, | ||||||
|  |     ICON_EMPTY, | ||||||
|  |     UNIT_VOLT, | ||||||
|  |     CONF_ID, | ||||||
|  | ) | ||||||
| from . import ads1115_ns, ADS1115Component | from . import ads1115_ns, ADS1115Component | ||||||
|  |  | ||||||
| DEPENDENCIES = ['ads1115'] | DEPENDENCIES = ["ads1115"] | ||||||
|  |  | ||||||
| ADS1115Multiplexer = ads1115_ns.enum('ADS1115Multiplexer') | ADS1115Multiplexer = ads1115_ns.enum("ADS1115Multiplexer") | ||||||
| MUX = { | MUX = { | ||||||
|     'A0_A1': ADS1115Multiplexer.ADS1115_MULTIPLEXER_P0_N1, |     "A0_A1": ADS1115Multiplexer.ADS1115_MULTIPLEXER_P0_N1, | ||||||
|     'A0_A3': ADS1115Multiplexer.ADS1115_MULTIPLEXER_P0_N3, |     "A0_A3": ADS1115Multiplexer.ADS1115_MULTIPLEXER_P0_N3, | ||||||
|     'A1_A3': ADS1115Multiplexer.ADS1115_MULTIPLEXER_P1_N3, |     "A1_A3": ADS1115Multiplexer.ADS1115_MULTIPLEXER_P1_N3, | ||||||
|     'A2_A3': ADS1115Multiplexer.ADS1115_MULTIPLEXER_P2_N3, |     "A2_A3": ADS1115Multiplexer.ADS1115_MULTIPLEXER_P2_N3, | ||||||
|     'A0_GND': ADS1115Multiplexer.ADS1115_MULTIPLEXER_P0_NG, |     "A0_GND": ADS1115Multiplexer.ADS1115_MULTIPLEXER_P0_NG, | ||||||
|     'A1_GND': ADS1115Multiplexer.ADS1115_MULTIPLEXER_P1_NG, |     "A1_GND": ADS1115Multiplexer.ADS1115_MULTIPLEXER_P1_NG, | ||||||
|     'A2_GND': ADS1115Multiplexer.ADS1115_MULTIPLEXER_P2_NG, |     "A2_GND": ADS1115Multiplexer.ADS1115_MULTIPLEXER_P2_NG, | ||||||
|     'A3_GND': ADS1115Multiplexer.ADS1115_MULTIPLEXER_P3_NG, |     "A3_GND": ADS1115Multiplexer.ADS1115_MULTIPLEXER_P3_NG, | ||||||
| } | } | ||||||
|  |  | ||||||
| ADS1115Gain = ads1115_ns.enum('ADS1115Gain') | ADS1115Gain = ads1115_ns.enum("ADS1115Gain") | ||||||
| GAIN = { | GAIN = { | ||||||
|     '6.144': ADS1115Gain.ADS1115_GAIN_6P144, |     "6.144": ADS1115Gain.ADS1115_GAIN_6P144, | ||||||
|     '4.096': ADS1115Gain.ADS1115_GAIN_4P096, |     "4.096": ADS1115Gain.ADS1115_GAIN_4P096, | ||||||
|     '2.048': ADS1115Gain.ADS1115_GAIN_2P048, |     "2.048": ADS1115Gain.ADS1115_GAIN_2P048, | ||||||
|     '1.024': ADS1115Gain.ADS1115_GAIN_1P024, |     "1.024": ADS1115Gain.ADS1115_GAIN_1P024, | ||||||
|     '0.512': ADS1115Gain.ADS1115_GAIN_0P512, |     "0.512": ADS1115Gain.ADS1115_GAIN_0P512, | ||||||
|     '0.256': ADS1115Gain.ADS1115_GAIN_0P256, |     "0.256": ADS1115Gain.ADS1115_GAIN_0P256, | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| def validate_gain(value): | def validate_gain(value): | ||||||
|     if isinstance(value, float): |     if isinstance(value, float): | ||||||
|         value = f'{value:0.03f}' |         value = f"{value:0.03f}" | ||||||
|     elif not isinstance(value, str): |     elif not isinstance(value, str): | ||||||
|         raise cv.Invalid(f'invalid gain "{value}"') |         raise cv.Invalid(f'invalid gain "{value}"') | ||||||
|  |  | ||||||
|     return cv.enum(GAIN)(value) |     return cv.enum(GAIN)(value) | ||||||
|  |  | ||||||
|  |  | ||||||
| ADS1115Sensor = ads1115_ns.class_('ADS1115Sensor', sensor.Sensor, cg.PollingComponent, | ADS1115Sensor = ads1115_ns.class_( | ||||||
|                                   voltage_sampler.VoltageSampler) |     "ADS1115Sensor", sensor.Sensor, cg.PollingComponent, voltage_sampler.VoltageSampler | ||||||
|  | ) | ||||||
|  |  | ||||||
| CONF_ADS1115_ID = 'ads1115_id' | CONF_ADS1115_ID = "ads1115_id" | ||||||
| CONFIG_SCHEMA = sensor.sensor_schema(UNIT_VOLT, ICON_FLASH, 3).extend({ | CONFIG_SCHEMA = ( | ||||||
|     cv.GenerateID(): cv.declare_id(ADS1115Sensor), |     sensor.sensor_schema(UNIT_VOLT, ICON_EMPTY, 3, DEVICE_CLASS_VOLTAGE) | ||||||
|     cv.GenerateID(CONF_ADS1115_ID): cv.use_id(ADS1115Component), |     .extend( | ||||||
|     cv.Required(CONF_MULTIPLEXER): cv.enum(MUX, upper=True, space='_'), |         { | ||||||
|     cv.Required(CONF_GAIN): validate_gain, |             cv.GenerateID(): cv.declare_id(ADS1115Sensor), | ||||||
| }).extend(cv.polling_component_schema('60s')) |             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): | def to_code(config): | ||||||
|   | |||||||
| @@ -1,19 +1,37 @@ | |||||||
| import esphome.codegen as cg | import esphome.codegen as cg | ||||||
| import esphome.config_validation as cv | import esphome.config_validation as cv | ||||||
| from esphome.components import i2c, sensor | from esphome.components import i2c, sensor | ||||||
| from esphome.const import CONF_HUMIDITY, CONF_ID, CONF_TEMPERATURE, \ | from esphome.const import ( | ||||||
|     UNIT_CELSIUS, ICON_THERMOMETER, ICON_WATER_PERCENT, UNIT_PERCENT |     CONF_HUMIDITY, | ||||||
|  |     CONF_ID, | ||||||
|  |     CONF_TEMPERATURE, | ||||||
|  |     DEVICE_CLASS_HUMIDITY, | ||||||
|  |     DEVICE_CLASS_TEMPERATURE, | ||||||
|  |     ICON_EMPTY, | ||||||
|  |     UNIT_CELSIUS, | ||||||
|  |     UNIT_PERCENT, | ||||||
|  | ) | ||||||
|  |  | ||||||
| DEPENDENCIES = ['i2c'] | DEPENDENCIES = ["i2c"] | ||||||
|  |  | ||||||
| aht10_ns = cg.esphome_ns.namespace('aht10') | aht10_ns = cg.esphome_ns.namespace("aht10") | ||||||
| AHT10Component = aht10_ns.class_('AHT10Component', cg.PollingComponent, i2c.I2CDevice) | AHT10Component = aht10_ns.class_("AHT10Component", cg.PollingComponent, i2c.I2CDevice) | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = cv.Schema({ | CONFIG_SCHEMA = ( | ||||||
|     cv.GenerateID(): cv.declare_id(AHT10Component), |     cv.Schema( | ||||||
|     cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 2), |         { | ||||||
|     cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(UNIT_PERCENT, ICON_WATER_PERCENT, 2), |             cv.GenerateID(): cv.declare_id(AHT10Component), | ||||||
| }).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x38)) |             cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( | ||||||
|  |                 UNIT_CELSIUS, ICON_EMPTY, 2, DEVICE_CLASS_TEMPERATURE | ||||||
|  |             ), | ||||||
|  |             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): | def to_code(config): | ||||||
|   | |||||||
| @@ -1,19 +1,39 @@ | |||||||
| import esphome.codegen as cg | import esphome.codegen as cg | ||||||
| import esphome.config_validation as cv | import esphome.config_validation as cv | ||||||
| from esphome.components import i2c, sensor | from esphome.components import i2c, sensor | ||||||
| from esphome.const import CONF_HUMIDITY, CONF_ID, CONF_TEMPERATURE, \ | from esphome.const import ( | ||||||
|     UNIT_CELSIUS, ICON_THERMOMETER, ICON_WATER_PERCENT, UNIT_PERCENT |     CONF_HUMIDITY, | ||||||
|  |     CONF_ID, | ||||||
|  |     CONF_TEMPERATURE, | ||||||
|  |     DEVICE_CLASS_HUMIDITY, | ||||||
|  |     DEVICE_CLASS_TEMPERATURE, | ||||||
|  |     UNIT_CELSIUS, | ||||||
|  |     ICON_EMPTY, | ||||||
|  |     UNIT_PERCENT, | ||||||
|  | ) | ||||||
|  |  | ||||||
| DEPENDENCIES = ['i2c'] | DEPENDENCIES = ["i2c"] | ||||||
|  |  | ||||||
| am2320_ns = cg.esphome_ns.namespace('am2320') | am2320_ns = cg.esphome_ns.namespace("am2320") | ||||||
| AM2320Component = am2320_ns.class_('AM2320Component', cg.PollingComponent, i2c.I2CDevice) | AM2320Component = am2320_ns.class_( | ||||||
|  |     "AM2320Component", cg.PollingComponent, i2c.I2CDevice | ||||||
|  | ) | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = cv.Schema({ | CONFIG_SCHEMA = ( | ||||||
|     cv.GenerateID(): cv.declare_id(AM2320Component), |     cv.Schema( | ||||||
|     cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 1), |         { | ||||||
|     cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(UNIT_PERCENT, ICON_WATER_PERCENT, 1), |             cv.GenerateID(): cv.declare_id(AM2320Component), | ||||||
| }).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x5C)) |             cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( | ||||||
|  |                 UNIT_CELSIUS, ICON_EMPTY, 1, DEVICE_CLASS_TEMPERATURE | ||||||
|  |             ), | ||||||
|  |             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): | def to_code(config): | ||||||
|   | |||||||
| @@ -10,24 +10,28 @@ from esphome.core import CORE, HexInt | |||||||
|  |  | ||||||
| _LOGGER = logging.getLogger(__name__) | _LOGGER = logging.getLogger(__name__) | ||||||
|  |  | ||||||
| DEPENDENCIES = ['display'] | DEPENDENCIES = ["display"] | ||||||
| MULTI_CONF = True | MULTI_CONF = True | ||||||
|  |  | ||||||
| Animation_ = display.display_ns.class_('Animation') | Animation_ = display.display_ns.class_("Animation") | ||||||
|  |  | ||||||
| CONF_RAW_DATA_ID = 'raw_data_id' | CONF_RAW_DATA_ID = "raw_data_id" | ||||||
|  |  | ||||||
| ANIMATION_SCHEMA = cv.Schema({ | ANIMATION_SCHEMA = cv.Schema( | ||||||
|     cv.Required(CONF_ID): cv.declare_id(Animation_), |     { | ||||||
|     cv.Required(CONF_FILE): cv.file_, |         cv.Required(CONF_ID): cv.declare_id(Animation_), | ||||||
|     cv.Optional(CONF_RESIZE): cv.dimensions, |         cv.Required(CONF_FILE): cv.file_, | ||||||
|     cv.Optional(CONF_TYPE, default='BINARY'): cv.enum(espImage.IMAGE_TYPE, upper=True), |         cv.Optional(CONF_RESIZE): cv.dimensions, | ||||||
|     cv.GenerateID(CONF_RAW_DATA_ID): cv.declare_id(cg.uint8), |         cv.Optional(CONF_TYPE, default="BINARY"): cv.enum( | ||||||
| }) |             espImage.IMAGE_TYPE, upper=True | ||||||
|  |         ), | ||||||
|  |         cv.GenerateID(CONF_RAW_DATA_ID): cv.declare_id(cg.uint8), | ||||||
|  |     } | ||||||
|  | ) | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = cv.All(font.validate_pillow_installed, ANIMATION_SCHEMA) | CONFIG_SCHEMA = cv.All(font.validate_pillow_installed, ANIMATION_SCHEMA) | ||||||
|  |  | ||||||
| CODEOWNERS = ['@syndlex'] | CODEOWNERS = ["@syndlex"] | ||||||
|  |  | ||||||
|  |  | ||||||
| def to_code(config): | def to_code(config): | ||||||
| @@ -46,26 +50,28 @@ def to_code(config): | |||||||
|         width, height = image.size |         width, height = image.size | ||||||
|     else: |     else: | ||||||
|         if width > 500 or height > 500: |         if width > 500 or height > 500: | ||||||
|             _LOGGER.warning("The image you requested is very big. Please consider using" |             _LOGGER.warning( | ||||||
|                             " the resize parameter.") |                 "The image you requested is very big. Please consider using" | ||||||
|  |                 " the resize parameter." | ||||||
|  |             ) | ||||||
|  |  | ||||||
|     if config[CONF_TYPE] == 'GRAYSCALE': |     if config[CONF_TYPE] == "GRAYSCALE": | ||||||
|         data = [0 for _ in range(height * width * frames)] |         data = [0 for _ in range(height * width * frames)] | ||||||
|         pos = 0 |         pos = 0 | ||||||
|         for frameIndex in range(frames): |         for frameIndex in range(frames): | ||||||
|             image.seek(frameIndex) |             image.seek(frameIndex) | ||||||
|             frame = image.convert('L', dither=Image.NONE) |             frame = image.convert("L", dither=Image.NONE) | ||||||
|             pixels = list(frame.getdata()) |             pixels = list(frame.getdata()) | ||||||
|             for pix in pixels: |             for pix in pixels: | ||||||
|                 data[pos] = pix |                 data[pos] = pix | ||||||
|                 pos += 1 |                 pos += 1 | ||||||
|  |  | ||||||
|     elif config[CONF_TYPE] == 'RGB24': |     elif config[CONF_TYPE] == "RGB24": | ||||||
|         data = [0 for _ in range(height * width * 3 * frames)] |         data = [0 for _ in range(height * width * 3 * frames)] | ||||||
|         pos = 0 |         pos = 0 | ||||||
|         for frameIndex in range(frames): |         for frameIndex in range(frames): | ||||||
|             image.seek(frameIndex) |             image.seek(frameIndex) | ||||||
|             frame = image.convert('RGB') |             frame = image.convert("RGB") | ||||||
|             pixels = list(frame.getdata()) |             pixels = list(frame.getdata()) | ||||||
|             for pix in pixels: |             for pix in pixels: | ||||||
|                 data[pos] = pix[0] |                 data[pos] = pix[0] | ||||||
| @@ -75,12 +81,12 @@ def to_code(config): | |||||||
|                 data[pos] = pix[2] |                 data[pos] = pix[2] | ||||||
|                 pos += 1 |                 pos += 1 | ||||||
|  |  | ||||||
|     elif config[CONF_TYPE] == 'BINARY': |     elif config[CONF_TYPE] == "BINARY": | ||||||
|         width8 = ((width + 7) // 8) * 8 |         width8 = ((width + 7) // 8) * 8 | ||||||
|         data = [0 for _ in range((height * width8 // 8) * frames)] |         data = [0 for _ in range((height * width8 // 8) * frames)] | ||||||
|         for frameIndex in range(frames): |         for frameIndex in range(frames): | ||||||
|             image.seek(frameIndex) |             image.seek(frameIndex) | ||||||
|             frame = image.convert('1', dither=Image.NONE) |             frame = image.convert("1", dither=Image.NONE) | ||||||
|             for y in range(height): |             for y in range(height): | ||||||
|                 for x in range(width): |                 for x in range(width): | ||||||
|                     if frame.getpixel((x, y)): |                     if frame.getpixel((x, y)): | ||||||
| @@ -90,5 +96,11 @@ def to_code(config): | |||||||
|  |  | ||||||
|     rhs = [HexInt(x) for x in data] |     rhs = [HexInt(x) for x in data] | ||||||
|     prog_arr = cg.progmem_array(config[CONF_RAW_DATA_ID], rhs) |     prog_arr = cg.progmem_array(config[CONF_RAW_DATA_ID], rhs) | ||||||
|     cg.new_Pvariable(config[CONF_ID], prog_arr, width, height, frames, |     cg.new_Pvariable( | ||||||
|                      espImage.IMAGE_TYPE[config[CONF_TYPE]]) |         config[CONF_ID], | ||||||
|  |         prog_arr, | ||||||
|  |         width, | ||||||
|  |         height, | ||||||
|  |         frames, | ||||||
|  |         espImage.IMAGE_TYPE[config[CONF_TYPE]], | ||||||
|  |     ) | ||||||
|   | |||||||
| @@ -3,18 +3,24 @@ import esphome.config_validation as cv | |||||||
| from esphome.components import i2c | from esphome.components import i2c | ||||||
| from esphome.const import CONF_ID | from esphome.const import CONF_ID | ||||||
|  |  | ||||||
| DEPENDENCIES = ['i2c'] | DEPENDENCIES = ["i2c"] | ||||||
| AUTO_LOAD = ['sensor', 'binary_sensor'] | AUTO_LOAD = ["sensor", "binary_sensor"] | ||||||
| MULTI_CONF = True | MULTI_CONF = True | ||||||
|  |  | ||||||
| CONF_APDS9960_ID = 'apds9960_id' | CONF_APDS9960_ID = "apds9960_id" | ||||||
|  |  | ||||||
| apds9960_nds = cg.esphome_ns.namespace('apds9960') | apds9960_nds = cg.esphome_ns.namespace("apds9960") | ||||||
| APDS9960 = apds9960_nds.class_('APDS9960', cg.PollingComponent, i2c.I2CDevice) | APDS9960 = apds9960_nds.class_("APDS9960", cg.PollingComponent, i2c.I2CDevice) | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = cv.Schema({ | CONFIG_SCHEMA = ( | ||||||
|     cv.GenerateID(): cv.declare_id(APDS9960), |     cv.Schema( | ||||||
| }).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x39)) |         { | ||||||
|  |             cv.GenerateID(): cv.declare_id(APDS9960), | ||||||
|  |         } | ||||||
|  |     ) | ||||||
|  |     .extend(cv.polling_component_schema("60s")) | ||||||
|  |     .extend(i2c.i2c_device_schema(0x39)) | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
| def to_code(config): | 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 esphome.const import CONF_DIRECTION, CONF_DEVICE_CLASS, DEVICE_CLASS_MOVING | ||||||
| from . import APDS9960, CONF_APDS9960_ID | from . import APDS9960, CONF_APDS9960_ID | ||||||
|  |  | ||||||
| DEPENDENCIES = ['apds9960'] | DEPENDENCIES = ["apds9960"] | ||||||
|  |  | ||||||
| DIRECTIONS = { | DIRECTIONS = { | ||||||
|     'UP': 'set_up_direction', |     "UP": "set_up_direction", | ||||||
|     'DOWN': 'set_down_direction', |     "DOWN": "set_down_direction", | ||||||
|     'LEFT': 'set_left_direction', |     "LEFT": "set_left_direction", | ||||||
|     'RIGHT': 'set_right_direction', |     "RIGHT": "set_right_direction", | ||||||
| } | } | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend({ | CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend( | ||||||
|     cv.Required(CONF_DIRECTION): cv.one_of(*DIRECTIONS, upper=True), |     { | ||||||
|     cv.GenerateID(CONF_APDS9960_ID): cv.use_id(APDS9960), |         cv.Required(CONF_DIRECTION): cv.one_of(*DIRECTIONS, upper=True), | ||||||
|     cv.Optional(CONF_DEVICE_CLASS, default=DEVICE_CLASS_MOVING): binary_sensor.device_class, |         cv.GenerateID(CONF_APDS9960_ID): cv.use_id(APDS9960), | ||||||
| }) |         cv.Optional( | ||||||
|  |             CONF_DEVICE_CLASS, default=DEVICE_CLASS_MOVING | ||||||
|  |         ): binary_sensor.device_class, | ||||||
|  |     } | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
| def to_code(config): | def to_code(config): | ||||||
|   | |||||||
| @@ -1,23 +1,27 @@ | |||||||
| import esphome.codegen as cg | import esphome.codegen as cg | ||||||
| import esphome.config_validation as cv | import esphome.config_validation as cv | ||||||
| from esphome.components import sensor | from esphome.components import sensor | ||||||
| from esphome.const import CONF_TYPE, UNIT_PERCENT, ICON_LIGHTBULB | from esphome.const import CONF_TYPE, DEVICE_CLASS_EMPTY, UNIT_PERCENT, ICON_LIGHTBULB | ||||||
| from . import APDS9960, CONF_APDS9960_ID | from . import APDS9960, CONF_APDS9960_ID | ||||||
|  |  | ||||||
| DEPENDENCIES = ['apds9960'] | DEPENDENCIES = ["apds9960"] | ||||||
|  |  | ||||||
| TYPES = { | TYPES = { | ||||||
|     'CLEAR': 'set_clear_channel', |     "CLEAR": "set_clear_channel", | ||||||
|     'RED': 'set_red_channel', |     "RED": "set_red_channel", | ||||||
|     'GREEN': 'set_green_channel', |     "GREEN": "set_green_channel", | ||||||
|     'BLUE': 'set_blue_channel', |     "BLUE": "set_blue_channel", | ||||||
|     'PROXIMITY': 'set_proximity', |     "PROXIMITY": "set_proximity", | ||||||
| } | } | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = sensor.sensor_schema(UNIT_PERCENT, ICON_LIGHTBULB, 1).extend({ | CONFIG_SCHEMA = sensor.sensor_schema( | ||||||
|     cv.Required(CONF_TYPE): cv.one_of(*TYPES, upper=True), |     UNIT_PERCENT, ICON_LIGHTBULB, 1, DEVICE_CLASS_EMPTY | ||||||
|     cv.GenerateID(CONF_APDS9960_ID): cv.use_id(APDS9960), | ).extend( | ||||||
| }) |     { | ||||||
|  |         cv.Required(CONF_TYPE): cv.one_of(*TYPES, upper=True), | ||||||
|  |         cv.GenerateID(CONF_APDS9960_ID): cv.use_id(APDS9960), | ||||||
|  |     } | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
| def to_code(config): | def to_code(config): | ||||||
|   | |||||||
| @@ -2,46 +2,69 @@ import esphome.codegen as cg | |||||||
| import esphome.config_validation as cv | import esphome.config_validation as cv | ||||||
| from esphome import automation | from esphome import automation | ||||||
| from esphome.automation import Condition | from esphome.automation import Condition | ||||||
| from esphome.const import CONF_DATA, CONF_DATA_TEMPLATE, CONF_ID, CONF_PASSWORD, CONF_PORT, \ | from esphome.const import ( | ||||||
|     CONF_REBOOT_TIMEOUT, CONF_SERVICE, CONF_VARIABLES, CONF_SERVICES, CONF_TRIGGER_ID, CONF_EVENT, \ |     CONF_DATA, | ||||||
|     CONF_TAG |     CONF_DATA_TEMPLATE, | ||||||
|  |     CONF_ID, | ||||||
|  |     CONF_PASSWORD, | ||||||
|  |     CONF_PORT, | ||||||
|  |     CONF_REBOOT_TIMEOUT, | ||||||
|  |     CONF_SERVICE, | ||||||
|  |     CONF_VARIABLES, | ||||||
|  |     CONF_SERVICES, | ||||||
|  |     CONF_TRIGGER_ID, | ||||||
|  |     CONF_EVENT, | ||||||
|  |     CONF_TAG, | ||||||
|  | ) | ||||||
| from esphome.core import coroutine_with_priority | from esphome.core import coroutine_with_priority | ||||||
|  |  | ||||||
| DEPENDENCIES = ['network'] | DEPENDENCIES = ["network"] | ||||||
| AUTO_LOAD = ['async_tcp'] | AUTO_LOAD = ["async_tcp"] | ||||||
| CODEOWNERS = ['@OttoWinter'] | CODEOWNERS = ["@OttoWinter"] | ||||||
|  |  | ||||||
| api_ns = cg.esphome_ns.namespace('api') | api_ns = cg.esphome_ns.namespace("api") | ||||||
| APIServer = api_ns.class_('APIServer', cg.Component, cg.Controller) | APIServer = api_ns.class_("APIServer", cg.Component, cg.Controller) | ||||||
| HomeAssistantServiceCallAction = api_ns.class_('HomeAssistantServiceCallAction', automation.Action) | HomeAssistantServiceCallAction = api_ns.class_( | ||||||
| APIConnectedCondition = api_ns.class_('APIConnectedCondition', Condition) |     "HomeAssistantServiceCallAction", automation.Action | ||||||
|  | ) | ||||||
|  | APIConnectedCondition = api_ns.class_("APIConnectedCondition", Condition) | ||||||
|  |  | ||||||
| UserServiceTrigger = api_ns.class_('UserServiceTrigger', automation.Trigger) | UserServiceTrigger = api_ns.class_("UserServiceTrigger", automation.Trigger) | ||||||
| ListEntitiesServicesArgument = api_ns.class_('ListEntitiesServicesArgument') | ListEntitiesServicesArgument = api_ns.class_("ListEntitiesServicesArgument") | ||||||
| SERVICE_ARG_NATIVE_TYPES = { | SERVICE_ARG_NATIVE_TYPES = { | ||||||
|     'bool': bool, |     "bool": bool, | ||||||
|     'int': cg.int32, |     "int": cg.int32, | ||||||
|     'float': float, |     "float": float, | ||||||
|     'string': cg.std_string, |     "string": cg.std_string, | ||||||
|     'bool[]': cg.std_vector.template(bool), |     "bool[]": cg.std_vector.template(bool), | ||||||
|     'int[]': cg.std_vector.template(cg.int32), |     "int[]": cg.std_vector.template(cg.int32), | ||||||
|     'float[]': cg.std_vector.template(float), |     "float[]": cg.std_vector.template(float), | ||||||
|     'string[]': cg.std_vector.template(cg.std_string), |     "string[]": cg.std_vector.template(cg.std_string), | ||||||
| } | } | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = cv.Schema({ | CONFIG_SCHEMA = cv.Schema( | ||||||
|     cv.GenerateID(): cv.declare_id(APIServer), |     { | ||||||
|     cv.Optional(CONF_PORT, default=6053): cv.port, |         cv.GenerateID(): cv.declare_id(APIServer), | ||||||
|     cv.Optional(CONF_PASSWORD, default=''): cv.string_strict, |         cv.Optional(CONF_PORT, default=6053): cv.port, | ||||||
|     cv.Optional(CONF_REBOOT_TIMEOUT, default='15min'): cv.positive_time_period_milliseconds, |         cv.Optional(CONF_PASSWORD, default=""): cv.string_strict, | ||||||
|     cv.Optional(CONF_SERVICES): automation.validate_automation({ |         cv.Optional( | ||||||
|         cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(UserServiceTrigger), |             CONF_REBOOT_TIMEOUT, default="15min" | ||||||
|         cv.Required(CONF_SERVICE): cv.valid_name, |         ): cv.positive_time_period_milliseconds, | ||||||
|         cv.Optional(CONF_VARIABLES, default={}): cv.Schema({ |         cv.Optional(CONF_SERVICES): automation.validate_automation( | ||||||
|             cv.validate_id_name: cv.one_of(*SERVICE_ARG_NATIVE_TYPES, lower=True), |             { | ||||||
|         }), |                 cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(UserServiceTrigger), | ||||||
|     }), |                 cv.Required(CONF_SERVICE): cv.valid_name, | ||||||
| }).extend(cv.COMPONENT_SCHEMA) |                 cv.Optional(CONF_VARIABLES, default={}): cv.Schema( | ||||||
|  |                     { | ||||||
|  |                         cv.validate_id_name: cv.one_of( | ||||||
|  |                             *SERVICE_ARG_NATIVE_TYPES, lower=True | ||||||
|  |                         ), | ||||||
|  |                     } | ||||||
|  |                 ), | ||||||
|  |             } | ||||||
|  |         ), | ||||||
|  |     } | ||||||
|  | ).extend(cv.COMPONENT_SCHEMA) | ||||||
|  |  | ||||||
|  |  | ||||||
| @coroutine_with_priority(40.0) | @coroutine_with_priority(40.0) | ||||||
| @@ -63,28 +86,36 @@ def to_code(config): | |||||||
|             func_args.append((native, name)) |             func_args.append((native, name)) | ||||||
|             service_arg_names.append(name) |             service_arg_names.append(name) | ||||||
|         templ = cg.TemplateArguments(*template_args) |         templ = cg.TemplateArguments(*template_args) | ||||||
|         trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], templ, |         trigger = cg.new_Pvariable( | ||||||
|                                    conf[CONF_SERVICE], service_arg_names) |             conf[CONF_TRIGGER_ID], templ, conf[CONF_SERVICE], service_arg_names | ||||||
|  |         ) | ||||||
|         cg.add(var.register_user_service(trigger)) |         cg.add(var.register_user_service(trigger)) | ||||||
|         yield automation.build_automation(trigger, func_args, conf) |         yield automation.build_automation(trigger, func_args, conf) | ||||||
|  |  | ||||||
|     cg.add_define('USE_API') |     cg.add_define("USE_API") | ||||||
|     cg.add_global(api_ns.using) |     cg.add_global(api_ns.using) | ||||||
|  |  | ||||||
|  |  | ||||||
| KEY_VALUE_SCHEMA = cv.Schema({cv.string: cv.templatable(cv.string_strict)}) | KEY_VALUE_SCHEMA = cv.Schema({cv.string: cv.templatable(cv.string_strict)}) | ||||||
|  |  | ||||||
| HOMEASSISTANT_SERVICE_ACTION_SCHEMA = cv.Schema({ | HOMEASSISTANT_SERVICE_ACTION_SCHEMA = cv.Schema( | ||||||
|     cv.GenerateID(): cv.use_id(APIServer), |     { | ||||||
|     cv.Required(CONF_SERVICE): cv.templatable(cv.string), |         cv.GenerateID(): cv.use_id(APIServer), | ||||||
|     cv.Optional(CONF_DATA, default={}): KEY_VALUE_SCHEMA, |         cv.Required(CONF_SERVICE): cv.templatable(cv.string), | ||||||
|     cv.Optional(CONF_DATA_TEMPLATE, default={}): KEY_VALUE_SCHEMA, |         cv.Optional(CONF_DATA, default={}): KEY_VALUE_SCHEMA, | ||||||
|     cv.Optional(CONF_VARIABLES, default={}): cv.Schema({cv.string: cv.returning_lambda}), |         cv.Optional(CONF_DATA_TEMPLATE, default={}): KEY_VALUE_SCHEMA, | ||||||
| }) |         cv.Optional(CONF_VARIABLES, default={}): cv.Schema( | ||||||
|  |             {cv.string: cv.returning_lambda} | ||||||
|  |         ), | ||||||
|  |     } | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
| @automation.register_action('homeassistant.service', HomeAssistantServiceCallAction, | @automation.register_action( | ||||||
|                             HOMEASSISTANT_SERVICE_ACTION_SCHEMA) |     "homeassistant.service", | ||||||
|  |     HomeAssistantServiceCallAction, | ||||||
|  |     HOMEASSISTANT_SERVICE_ACTION_SCHEMA, | ||||||
|  | ) | ||||||
| def homeassistant_service_to_code(config, action_id, template_arg, args): | def homeassistant_service_to_code(config, action_id, template_arg, args): | ||||||
|     serv = yield cg.get_variable(config[CONF_ID]) |     serv = yield cg.get_variable(config[CONF_ID]) | ||||||
|     var = cg.new_Pvariable(action_id, template_arg, serv, False) |     var = cg.new_Pvariable(action_id, template_arg, serv, False) | ||||||
| @@ -104,23 +135,30 @@ def homeassistant_service_to_code(config, action_id, template_arg, args): | |||||||
|  |  | ||||||
| def validate_homeassistant_event(value): | def validate_homeassistant_event(value): | ||||||
|     value = cv.string(value) |     value = cv.string(value) | ||||||
|     if not value.startswith('esphome.'): |     if not value.startswith("esphome."): | ||||||
|         raise cv.Invalid("ESPHome can only generate Home Assistant events that begin with " |         raise cv.Invalid( | ||||||
|                          "esphome. For example 'esphome.xyz'") |             "ESPHome can only generate Home Assistant events that begin with " | ||||||
|  |             "esphome. For example 'esphome.xyz'" | ||||||
|  |         ) | ||||||
|     return value |     return value | ||||||
|  |  | ||||||
|  |  | ||||||
| HOMEASSISTANT_EVENT_ACTION_SCHEMA = cv.Schema({ | HOMEASSISTANT_EVENT_ACTION_SCHEMA = cv.Schema( | ||||||
|     cv.GenerateID(): cv.use_id(APIServer), |     { | ||||||
|     cv.Required(CONF_EVENT): validate_homeassistant_event, |         cv.GenerateID(): cv.use_id(APIServer), | ||||||
|     cv.Optional(CONF_DATA, default={}): KEY_VALUE_SCHEMA, |         cv.Required(CONF_EVENT): validate_homeassistant_event, | ||||||
|     cv.Optional(CONF_DATA_TEMPLATE, default={}): KEY_VALUE_SCHEMA, |         cv.Optional(CONF_DATA, default={}): KEY_VALUE_SCHEMA, | ||||||
|     cv.Optional(CONF_VARIABLES, default={}): KEY_VALUE_SCHEMA, |         cv.Optional(CONF_DATA_TEMPLATE, default={}): KEY_VALUE_SCHEMA, | ||||||
| }) |         cv.Optional(CONF_VARIABLES, default={}): KEY_VALUE_SCHEMA, | ||||||
|  |     } | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
| @automation.register_action('homeassistant.event', HomeAssistantServiceCallAction, | @automation.register_action( | ||||||
|                             HOMEASSISTANT_EVENT_ACTION_SCHEMA) |     "homeassistant.event", | ||||||
|  |     HomeAssistantServiceCallAction, | ||||||
|  |     HOMEASSISTANT_EVENT_ACTION_SCHEMA, | ||||||
|  | ) | ||||||
| def homeassistant_event_to_code(config, action_id, template_arg, args): | def homeassistant_event_to_code(config, action_id, template_arg, args): | ||||||
|     serv = yield cg.get_variable(config[CONF_ID]) |     serv = yield cg.get_variable(config[CONF_ID]) | ||||||
|     var = cg.new_Pvariable(action_id, template_arg, serv, True) |     var = cg.new_Pvariable(action_id, template_arg, serv, True) | ||||||
| @@ -138,23 +176,29 @@ def homeassistant_event_to_code(config, action_id, template_arg, args): | |||||||
|     yield var |     yield var | ||||||
|  |  | ||||||
|  |  | ||||||
| HOMEASSISTANT_TAG_SCANNED_ACTION_SCHEMA = cv.maybe_simple_value({ | HOMEASSISTANT_TAG_SCANNED_ACTION_SCHEMA = cv.maybe_simple_value( | ||||||
|     cv.GenerateID(): cv.use_id(APIServer), |     { | ||||||
|     cv.Required(CONF_TAG): cv.templatable(cv.string_strict), |         cv.GenerateID(): cv.use_id(APIServer), | ||||||
| }, key=CONF_TAG) |         cv.Required(CONF_TAG): cv.templatable(cv.string_strict), | ||||||
|  |     }, | ||||||
|  |     key=CONF_TAG, | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
| @automation.register_action('homeassistant.tag_scanned', HomeAssistantServiceCallAction, | @automation.register_action( | ||||||
|                             HOMEASSISTANT_TAG_SCANNED_ACTION_SCHEMA) |     "homeassistant.tag_scanned", | ||||||
|  |     HomeAssistantServiceCallAction, | ||||||
|  |     HOMEASSISTANT_TAG_SCANNED_ACTION_SCHEMA, | ||||||
|  | ) | ||||||
| def homeassistant_tag_scanned_to_code(config, action_id, template_arg, args): | def homeassistant_tag_scanned_to_code(config, action_id, template_arg, args): | ||||||
|     serv = yield cg.get_variable(config[CONF_ID]) |     serv = yield cg.get_variable(config[CONF_ID]) | ||||||
|     var = cg.new_Pvariable(action_id, template_arg, serv, True) |     var = cg.new_Pvariable(action_id, template_arg, serv, True) | ||||||
|     cg.add(var.set_service('esphome.tag_scanned')) |     cg.add(var.set_service("esphome.tag_scanned")) | ||||||
|     templ = yield cg.templatable(config[CONF_TAG], args, cg.std_string) |     templ = 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 |     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): | def api_connected_to_code(config, condition_id, template_arg, args): | ||||||
|     yield cg.new_Pvariable(condition_id, template_arg) |     yield cg.new_Pvariable(condition_id, template_arg) | ||||||
|   | |||||||
| @@ -46,6 +46,7 @@ service APIConnection { | |||||||
| // The Home Assistant protocol is structured as a simple | // The Home Assistant protocol is structured as a simple | ||||||
| // TCP socket with short binary messages encoded in the protocol buffers format | // TCP socket with short binary messages encoded in the protocol buffers format | ||||||
| // First, a message in this protocol has a specific format: | // First, a message in this protocol has a specific format: | ||||||
|  | //  * A zero byte. | ||||||
| //  * VarInt denoting the size of the message object. (type is not part of this) | //  * VarInt denoting the size of the message object. (type is not part of this) | ||||||
| //  * VarInt denoting the type of message. | //  * VarInt denoting the type of message. | ||||||
| //  * The message object encoded as a ProtoBuf message | //  * The message object encoded as a ProtoBuf message | ||||||
| @@ -302,6 +303,7 @@ message ListEntitiesFanResponse { | |||||||
|   bool supports_oscillation = 5; |   bool supports_oscillation = 5; | ||||||
|   bool supports_speed = 6; |   bool supports_speed = 6; | ||||||
|   bool supports_direction = 7; |   bool supports_direction = 7; | ||||||
|  |   int32 supported_speed_count = 8; | ||||||
| } | } | ||||||
| enum FanSpeed { | enum FanSpeed { | ||||||
|   FAN_SPEED_LOW = 0; |   FAN_SPEED_LOW = 0; | ||||||
| @@ -321,8 +323,9 @@ message FanStateResponse { | |||||||
|   fixed32 key = 1; |   fixed32 key = 1; | ||||||
|   bool state = 2; |   bool state = 2; | ||||||
|   bool oscillating = 3; |   bool oscillating = 3; | ||||||
|   FanSpeed speed = 4; |   FanSpeed speed = 4 [deprecated = true]; | ||||||
|   FanDirection direction = 5; |   FanDirection direction = 5; | ||||||
|  |   int32 speed_level = 6; | ||||||
| } | } | ||||||
| message FanCommandRequest { | message FanCommandRequest { | ||||||
|   option (id) = 31; |   option (id) = 31; | ||||||
| @@ -333,12 +336,14 @@ message FanCommandRequest { | |||||||
|   fixed32 key = 1; |   fixed32 key = 1; | ||||||
|   bool has_state = 2; |   bool has_state = 2; | ||||||
|   bool state = 3; |   bool state = 3; | ||||||
|   bool has_speed = 4; |   bool has_speed = 4 [deprecated = true]; | ||||||
|   FanSpeed speed = 5; |   FanSpeed speed = 5 [deprecated = true]; | ||||||
|   bool has_oscillating = 6; |   bool has_oscillating = 6; | ||||||
|   bool oscillating = 7; |   bool oscillating = 7; | ||||||
|   bool has_direction = 8; |   bool has_direction = 8; | ||||||
|   FanDirection direction = 9; |   FanDirection direction = 9; | ||||||
|  |   bool has_speed_level = 10; | ||||||
|  |   int32 speed_level = 11; | ||||||
| } | } | ||||||
|  |  | ||||||
| // ==================== LIGHT ==================== | // ==================== LIGHT ==================== | ||||||
| @@ -418,6 +423,7 @@ message ListEntitiesSensorResponse { | |||||||
|   string unit_of_measurement = 6; |   string unit_of_measurement = 6; | ||||||
|   int32 accuracy_decimals = 7; |   int32 accuracy_decimals = 7; | ||||||
|   bool force_update = 8; |   bool force_update = 8; | ||||||
|  |   string device_class = 9; | ||||||
| } | } | ||||||
| message SensorStateResponse { | message SensorStateResponse { | ||||||
|   option (id) = 25; |   option (id) = 25; | ||||||
|   | |||||||
| @@ -9,6 +9,9 @@ | |||||||
| #ifdef USE_HOMEASSISTANT_TIME | #ifdef USE_HOMEASSISTANT_TIME | ||||||
| #include "esphome/components/homeassistant/time/homeassistant_time.h" | #include "esphome/components/homeassistant/time/homeassistant_time.h" | ||||||
| #endif | #endif | ||||||
|  | #ifdef USE_FAN | ||||||
|  | #include "esphome/components/fan/fan_helpers.h" | ||||||
|  | #endif | ||||||
|  |  | ||||||
| namespace esphome { | namespace esphome { | ||||||
| namespace api { | namespace api { | ||||||
| @@ -246,8 +249,10 @@ bool APIConnection::send_fan_state(fan::FanState *fan) { | |||||||
|   resp.state = fan->state; |   resp.state = fan->state; | ||||||
|   if (traits.supports_oscillation()) |   if (traits.supports_oscillation()) | ||||||
|     resp.oscillating = fan->oscillating; |     resp.oscillating = fan->oscillating; | ||||||
|   if (traits.supports_speed()) |   if (traits.supports_speed()) { | ||||||
|     resp.speed = static_cast<enums::FanSpeed>(fan->speed); |     resp.speed_level = fan->speed; | ||||||
|  |     resp.speed = static_cast<enums::FanSpeed>(fan::speed_level_to_enum(fan->speed, traits.supported_speed_count())); | ||||||
|  |   } | ||||||
|   if (traits.supports_direction()) |   if (traits.supports_direction()) | ||||||
|     resp.direction = static_cast<enums::FanDirection>(fan->direction); |     resp.direction = static_cast<enums::FanDirection>(fan->direction); | ||||||
|   return this->send_fan_state_response(resp); |   return this->send_fan_state_response(resp); | ||||||
| @@ -262,6 +267,7 @@ bool APIConnection::send_fan_info(fan::FanState *fan) { | |||||||
|   msg.supports_oscillation = traits.supports_oscillation(); |   msg.supports_oscillation = traits.supports_oscillation(); | ||||||
|   msg.supports_speed = traits.supports_speed(); |   msg.supports_speed = traits.supports_speed(); | ||||||
|   msg.supports_direction = traits.supports_direction(); |   msg.supports_direction = traits.supports_direction(); | ||||||
|  |   msg.supported_speed_count = traits.supported_speed_count(); | ||||||
|   return this->send_list_entities_fan_response(msg); |   return this->send_list_entities_fan_response(msg); | ||||||
| } | } | ||||||
| void APIConnection::fan_command(const FanCommandRequest &msg) { | void APIConnection::fan_command(const FanCommandRequest &msg) { | ||||||
| @@ -269,13 +275,19 @@ void APIConnection::fan_command(const FanCommandRequest &msg) { | |||||||
|   if (fan == nullptr) |   if (fan == nullptr) | ||||||
|     return; |     return; | ||||||
|  |  | ||||||
|  |   auto traits = fan->get_traits(); | ||||||
|  |  | ||||||
|   auto call = fan->make_call(); |   auto call = fan->make_call(); | ||||||
|   if (msg.has_state) |   if (msg.has_state) | ||||||
|     call.set_state(msg.state); |     call.set_state(msg.state); | ||||||
|   if (msg.has_oscillating) |   if (msg.has_oscillating) | ||||||
|     call.set_oscillating(msg.oscillating); |     call.set_oscillating(msg.oscillating); | ||||||
|   if (msg.has_speed) |   if (msg.has_speed_level) { | ||||||
|     call.set_speed(static_cast<fan::FanSpeed>(msg.speed)); |     // Prefer level | ||||||
|  |     call.set_speed(msg.speed_level); | ||||||
|  |   } else if (msg.has_speed) { | ||||||
|  |     call.set_speed(fan::speed_enum_to_level(static_cast<fan::FanSpeed>(msg.speed), traits.supported_speed_count())); | ||||||
|  |   } | ||||||
|   if (msg.has_direction) |   if (msg.has_direction) | ||||||
|     call.set_direction(static_cast<fan::FanDirection>(msg.direction)); |     call.set_direction(static_cast<fan::FanDirection>(msg.direction)); | ||||||
|   call.perform(); |   call.perform(); | ||||||
| @@ -382,6 +394,7 @@ bool APIConnection::send_sensor_info(sensor::Sensor *sensor) { | |||||||
|   msg.unit_of_measurement = sensor->get_unit_of_measurement(); |   msg.unit_of_measurement = sensor->get_unit_of_measurement(); | ||||||
|   msg.accuracy_decimals = sensor->get_accuracy_decimals(); |   msg.accuracy_decimals = sensor->get_accuracy_decimals(); | ||||||
|   msg.force_update = sensor->get_force_update(); |   msg.force_update = sensor->get_force_update(); | ||||||
|  |   msg.device_class = sensor->get_device_class(); | ||||||
|   return this->send_list_entities_sensor_response(msg); |   return this->send_list_entities_sensor_response(msg); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| @@ -589,7 +602,7 @@ HelloResponse APIConnection::hello(const HelloRequest &msg) { | |||||||
|  |  | ||||||
|   HelloResponse resp; |   HelloResponse resp; | ||||||
|   resp.api_version_major = 1; |   resp.api_version_major = 1; | ||||||
|   resp.api_version_minor = 3; |   resp.api_version_minor = 4; | ||||||
|   resp.server_info = App.get_name() + " (esphome v" ESPHOME_VERSION ")"; |   resp.server_info = App.get_name() + " (esphome v" ESPHOME_VERSION ")"; | ||||||
|   this->connection_state_ = ConnectionState::CONNECTED; |   this->connection_state_ = ConnectionState::CONNECTED; | ||||||
|   return resp; |   return resp; | ||||||
|   | |||||||
| @@ -774,6 +774,10 @@ bool ListEntitiesFanResponse::decode_varint(uint32_t field_id, ProtoVarInt value | |||||||
|       this->supports_direction = value.as_bool(); |       this->supports_direction = value.as_bool(); | ||||||
|       return true; |       return true; | ||||||
|     } |     } | ||||||
|  |     case 8: { | ||||||
|  |       this->supported_speed_count = value.as_int32(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|     default: |     default: | ||||||
|       return false; |       return false; | ||||||
|   } |   } | ||||||
| @@ -814,6 +818,7 @@ void ListEntitiesFanResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
|   buffer.encode_bool(5, this->supports_oscillation); |   buffer.encode_bool(5, this->supports_oscillation); | ||||||
|   buffer.encode_bool(6, this->supports_speed); |   buffer.encode_bool(6, this->supports_speed); | ||||||
|   buffer.encode_bool(7, this->supports_direction); |   buffer.encode_bool(7, this->supports_direction); | ||||||
|  |   buffer.encode_int32(8, this->supported_speed_count); | ||||||
| } | } | ||||||
| void ListEntitiesFanResponse::dump_to(std::string &out) const { | void ListEntitiesFanResponse::dump_to(std::string &out) const { | ||||||
|   char buffer[64]; |   char buffer[64]; | ||||||
| @@ -846,6 +851,11 @@ void ListEntitiesFanResponse::dump_to(std::string &out) const { | |||||||
|   out.append("  supports_direction: "); |   out.append("  supports_direction: "); | ||||||
|   out.append(YESNO(this->supports_direction)); |   out.append(YESNO(this->supports_direction)); | ||||||
|   out.append("\n"); |   out.append("\n"); | ||||||
|  |  | ||||||
|  |   out.append("  supported_speed_count: "); | ||||||
|  |   sprintf(buffer, "%d", this->supported_speed_count); | ||||||
|  |   out.append(buffer); | ||||||
|  |   out.append("\n"); | ||||||
|   out.append("}"); |   out.append("}"); | ||||||
| } | } | ||||||
| bool FanStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { | bool FanStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { | ||||||
| @@ -866,6 +876,10 @@ bool FanStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { | |||||||
|       this->direction = value.as_enum<enums::FanDirection>(); |       this->direction = value.as_enum<enums::FanDirection>(); | ||||||
|       return true; |       return true; | ||||||
|     } |     } | ||||||
|  |     case 6: { | ||||||
|  |       this->speed_level = value.as_int32(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|     default: |     default: | ||||||
|       return false; |       return false; | ||||||
|   } |   } | ||||||
| @@ -886,6 +900,7 @@ void FanStateResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
|   buffer.encode_bool(3, this->oscillating); |   buffer.encode_bool(3, this->oscillating); | ||||||
|   buffer.encode_enum<enums::FanSpeed>(4, this->speed); |   buffer.encode_enum<enums::FanSpeed>(4, this->speed); | ||||||
|   buffer.encode_enum<enums::FanDirection>(5, this->direction); |   buffer.encode_enum<enums::FanDirection>(5, this->direction); | ||||||
|  |   buffer.encode_int32(6, this->speed_level); | ||||||
| } | } | ||||||
| void FanStateResponse::dump_to(std::string &out) const { | void FanStateResponse::dump_to(std::string &out) const { | ||||||
|   char buffer[64]; |   char buffer[64]; | ||||||
| @@ -910,6 +925,11 @@ void FanStateResponse::dump_to(std::string &out) const { | |||||||
|   out.append("  direction: "); |   out.append("  direction: "); | ||||||
|   out.append(proto_enum_to_string<enums::FanDirection>(this->direction)); |   out.append(proto_enum_to_string<enums::FanDirection>(this->direction)); | ||||||
|   out.append("\n"); |   out.append("\n"); | ||||||
|  |  | ||||||
|  |   out.append("  speed_level: "); | ||||||
|  |   sprintf(buffer, "%d", this->speed_level); | ||||||
|  |   out.append(buffer); | ||||||
|  |   out.append("\n"); | ||||||
|   out.append("}"); |   out.append("}"); | ||||||
| } | } | ||||||
| bool FanCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { | bool FanCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { | ||||||
| @@ -946,6 +966,14 @@ bool FanCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { | |||||||
|       this->direction = value.as_enum<enums::FanDirection>(); |       this->direction = value.as_enum<enums::FanDirection>(); | ||||||
|       return true; |       return true; | ||||||
|     } |     } | ||||||
|  |     case 10: { | ||||||
|  |       this->has_speed_level = value.as_bool(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|  |     case 11: { | ||||||
|  |       this->speed_level = value.as_int32(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|     default: |     default: | ||||||
|       return false; |       return false; | ||||||
|   } |   } | ||||||
| @@ -970,6 +998,8 @@ void FanCommandRequest::encode(ProtoWriteBuffer buffer) const { | |||||||
|   buffer.encode_bool(7, this->oscillating); |   buffer.encode_bool(7, this->oscillating); | ||||||
|   buffer.encode_bool(8, this->has_direction); |   buffer.encode_bool(8, this->has_direction); | ||||||
|   buffer.encode_enum<enums::FanDirection>(9, this->direction); |   buffer.encode_enum<enums::FanDirection>(9, this->direction); | ||||||
|  |   buffer.encode_bool(10, this->has_speed_level); | ||||||
|  |   buffer.encode_int32(11, this->speed_level); | ||||||
| } | } | ||||||
| void FanCommandRequest::dump_to(std::string &out) const { | void FanCommandRequest::dump_to(std::string &out) const { | ||||||
|   char buffer[64]; |   char buffer[64]; | ||||||
| @@ -1010,6 +1040,15 @@ void FanCommandRequest::dump_to(std::string &out) const { | |||||||
|   out.append("  direction: "); |   out.append("  direction: "); | ||||||
|   out.append(proto_enum_to_string<enums::FanDirection>(this->direction)); |   out.append(proto_enum_to_string<enums::FanDirection>(this->direction)); | ||||||
|   out.append("\n"); |   out.append("\n"); | ||||||
|  |  | ||||||
|  |   out.append("  has_speed_level: "); | ||||||
|  |   out.append(YESNO(this->has_speed_level)); | ||||||
|  |   out.append("\n"); | ||||||
|  |  | ||||||
|  |   out.append("  speed_level: "); | ||||||
|  |   sprintf(buffer, "%d", this->speed_level); | ||||||
|  |   out.append(buffer); | ||||||
|  |   out.append("\n"); | ||||||
|   out.append("}"); |   out.append("}"); | ||||||
| } | } | ||||||
| bool ListEntitiesLightResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { | bool ListEntitiesLightResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { | ||||||
| @@ -1494,6 +1533,10 @@ bool ListEntitiesSensorResponse::decode_length(uint32_t field_id, ProtoLengthDel | |||||||
|       this->unit_of_measurement = value.as_string(); |       this->unit_of_measurement = value.as_string(); | ||||||
|       return true; |       return true; | ||||||
|     } |     } | ||||||
|  |     case 9: { | ||||||
|  |       this->device_class = value.as_string(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|     default: |     default: | ||||||
|       return false; |       return false; | ||||||
|   } |   } | ||||||
| @@ -1517,6 +1560,7 @@ void ListEntitiesSensorResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
|   buffer.encode_string(6, this->unit_of_measurement); |   buffer.encode_string(6, this->unit_of_measurement); | ||||||
|   buffer.encode_int32(7, this->accuracy_decimals); |   buffer.encode_int32(7, this->accuracy_decimals); | ||||||
|   buffer.encode_bool(8, this->force_update); |   buffer.encode_bool(8, this->force_update); | ||||||
|  |   buffer.encode_string(9, this->device_class); | ||||||
| } | } | ||||||
| void ListEntitiesSensorResponse::dump_to(std::string &out) const { | void ListEntitiesSensorResponse::dump_to(std::string &out) const { | ||||||
|   char buffer[64]; |   char buffer[64]; | ||||||
| @@ -1554,6 +1598,10 @@ void ListEntitiesSensorResponse::dump_to(std::string &out) const { | |||||||
|   out.append("  force_update: "); |   out.append("  force_update: "); | ||||||
|   out.append(YESNO(this->force_update)); |   out.append(YESNO(this->force_update)); | ||||||
|   out.append("\n"); |   out.append("\n"); | ||||||
|  |  | ||||||
|  |   out.append("  device_class: "); | ||||||
|  |   out.append("'").append(this->device_class).append("'"); | ||||||
|  |   out.append("\n"); | ||||||
|   out.append("}"); |   out.append("}"); | ||||||
| } | } | ||||||
| bool SensorStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { | 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_oscillation{false};  // NOLINT | ||||||
|   bool supports_speed{false};        // NOLINT |   bool supports_speed{false};        // NOLINT | ||||||
|   bool supports_direction{false};    // NOLINT |   bool supports_direction{false};    // NOLINT | ||||||
|  |   int32_t supported_speed_count{0};  // NOLINT | ||||||
|   void encode(ProtoWriteBuffer buffer) const override; |   void encode(ProtoWriteBuffer buffer) const override; | ||||||
|   void dump_to(std::string &out) const override; |   void dump_to(std::string &out) const override; | ||||||
|  |  | ||||||
| @@ -299,6 +300,7 @@ class FanStateResponse : public ProtoMessage { | |||||||
|   bool oscillating{false};          // NOLINT |   bool oscillating{false};          // NOLINT | ||||||
|   enums::FanSpeed speed{};          // NOLINT |   enums::FanSpeed speed{};          // NOLINT | ||||||
|   enums::FanDirection direction{};  // NOLINT |   enums::FanDirection direction{};  // NOLINT | ||||||
|  |   int32_t speed_level{0};           // NOLINT | ||||||
|   void encode(ProtoWriteBuffer buffer) const override; |   void encode(ProtoWriteBuffer buffer) const override; | ||||||
|   void dump_to(std::string &out) const override; |   void dump_to(std::string &out) const override; | ||||||
|  |  | ||||||
| @@ -317,6 +319,8 @@ class FanCommandRequest : public ProtoMessage { | |||||||
|   bool oscillating{false};          // NOLINT |   bool oscillating{false};          // NOLINT | ||||||
|   bool has_direction{false};        // NOLINT |   bool has_direction{false};        // NOLINT | ||||||
|   enums::FanDirection direction{};  // NOLINT |   enums::FanDirection direction{};  // NOLINT | ||||||
|  |   bool has_speed_level{false};      // NOLINT | ||||||
|  |   int32_t speed_level{0};           // NOLINT | ||||||
|   void encode(ProtoWriteBuffer buffer) const override; |   void encode(ProtoWriteBuffer buffer) const override; | ||||||
|   void dump_to(std::string &out) const override; |   void dump_to(std::string &out) const override; | ||||||
|  |  | ||||||
| @@ -403,6 +407,7 @@ class ListEntitiesSensorResponse : public ProtoMessage { | |||||||
|   std::string unit_of_measurement{};  // NOLINT |   std::string unit_of_measurement{};  // NOLINT | ||||||
|   int32_t accuracy_decimals{0};       // NOLINT |   int32_t accuracy_decimals{0};       // NOLINT | ||||||
|   bool force_update{false};           // NOLINT |   bool force_update{false};           // NOLINT | ||||||
|  |   std::string device_class{};         // NOLINT | ||||||
|   void encode(ProtoWriteBuffer buffer) const override; |   void encode(ProtoWriteBuffer buffer) const override; | ||||||
|   void dump_to(std::string &out) const override; |   void dump_to(std::string &out) const override; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,33 +1,43 @@ | |||||||
| import esphome.codegen as cg | import esphome.codegen as cg | ||||||
| import esphome.config_validation as cv | import esphome.config_validation as cv | ||||||
| from esphome import pins | from esphome import pins | ||||||
| from esphome.const import CONF_INDOOR, CONF_WATCHDOG_THRESHOLD, \ | from esphome.const import ( | ||||||
|     CONF_NOISE_LEVEL, CONF_SPIKE_REJECTION, CONF_LIGHTNING_THRESHOLD, \ |     CONF_INDOOR, | ||||||
|     CONF_MASK_DISTURBER, CONF_DIV_RATIO, CONF_CAPACITANCE |     CONF_WATCHDOG_THRESHOLD, | ||||||
|  |     CONF_NOISE_LEVEL, | ||||||
|  |     CONF_SPIKE_REJECTION, | ||||||
|  |     CONF_LIGHTNING_THRESHOLD, | ||||||
|  |     CONF_MASK_DISTURBER, | ||||||
|  |     CONF_DIV_RATIO, | ||||||
|  |     CONF_CAPACITANCE, | ||||||
|  | ) | ||||||
| from esphome.core import coroutine | from esphome.core import coroutine | ||||||
|  |  | ||||||
| AUTO_LOAD = ['sensor', 'binary_sensor'] | AUTO_LOAD = ["sensor", "binary_sensor"] | ||||||
| MULTI_CONF = True | MULTI_CONF = True | ||||||
|  |  | ||||||
| CONF_AS3935_ID = 'as3935_id' | CONF_AS3935_ID = "as3935_id" | ||||||
|  |  | ||||||
| as3935_ns = cg.esphome_ns.namespace('as3935') | as3935_ns = cg.esphome_ns.namespace("as3935") | ||||||
| AS3935 = as3935_ns.class_('AS3935Component', cg.Component) | AS3935 = as3935_ns.class_("AS3935Component", cg.Component) | ||||||
|  |  | ||||||
| CONF_IRQ_PIN = 'irq_pin' | CONF_IRQ_PIN = "irq_pin" | ||||||
| AS3935_SCHEMA = cv.Schema({ | AS3935_SCHEMA = cv.Schema( | ||||||
|     cv.GenerateID(): cv.declare_id(AS3935), |     { | ||||||
|     cv.Required(CONF_IRQ_PIN): pins.gpio_input_pin_schema, |         cv.GenerateID(): cv.declare_id(AS3935), | ||||||
|  |         cv.Required(CONF_IRQ_PIN): pins.gpio_input_pin_schema, | ||||||
|     cv.Optional(CONF_INDOOR, default=True): cv.boolean, |         cv.Optional(CONF_INDOOR, default=True): cv.boolean, | ||||||
|     cv.Optional(CONF_NOISE_LEVEL, default=2): cv.int_range(min=1, max=7), |         cv.Optional(CONF_NOISE_LEVEL, default=2): cv.int_range(min=1, max=7), | ||||||
|     cv.Optional(CONF_WATCHDOG_THRESHOLD, default=2): cv.int_range(min=1, max=10), |         cv.Optional(CONF_WATCHDOG_THRESHOLD, default=2): cv.int_range(min=1, max=10), | ||||||
|     cv.Optional(CONF_SPIKE_REJECTION, default=2): cv.int_range(min=1, max=11), |         cv.Optional(CONF_SPIKE_REJECTION, default=2): cv.int_range(min=1, max=11), | ||||||
|     cv.Optional(CONF_LIGHTNING_THRESHOLD, default=1): cv.one_of(1, 5, 9, 16, int=True), |         cv.Optional(CONF_LIGHTNING_THRESHOLD, default=1): cv.one_of( | ||||||
|     cv.Optional(CONF_MASK_DISTURBER, default=False): cv.boolean, |             1, 5, 9, 16, int=True | ||||||
|     cv.Optional(CONF_DIV_RATIO, default=0): cv.one_of(0, 16, 32, 64, 128, int=True), |         ), | ||||||
|     cv.Optional(CONF_CAPACITANCE, default=0): cv.int_range(min=0, max=15), |         cv.Optional(CONF_MASK_DISTURBER, default=False): cv.boolean, | ||||||
| }) |         cv.Optional(CONF_DIV_RATIO, default=0): cv.one_of(0, 16, 32, 64, 128, int=True), | ||||||
|  |         cv.Optional(CONF_CAPACITANCE, default=0): cv.int_range(min=0, max=15), | ||||||
|  |     } | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
| @coroutine | @coroutine | ||||||
|   | |||||||
| @@ -3,11 +3,13 @@ import esphome.config_validation as cv | |||||||
| from esphome.components import binary_sensor | from esphome.components import binary_sensor | ||||||
| from . import AS3935, CONF_AS3935_ID | from . import AS3935, CONF_AS3935_ID | ||||||
|  |  | ||||||
| DEPENDENCIES = ['as3935'] | DEPENDENCIES = ["as3935"] | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend({ | CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend( | ||||||
|     cv.GenerateID(CONF_AS3935_ID): cv.use_id(AS3935), |     { | ||||||
| }) |         cv.GenerateID(CONF_AS3935_ID): cv.use_id(AS3935), | ||||||
|  |     } | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
| def to_code(config): | def to_code(config): | ||||||
|   | |||||||
| @@ -1,19 +1,30 @@ | |||||||
| import esphome.codegen as cg | import esphome.codegen as cg | ||||||
| import esphome.config_validation as cv | import esphome.config_validation as cv | ||||||
| from esphome.components import sensor | from esphome.components import sensor | ||||||
| from esphome.const import CONF_DISTANCE, CONF_LIGHTNING_ENERGY, \ | from esphome.const import ( | ||||||
|     UNIT_KILOMETER, UNIT_EMPTY, ICON_SIGNAL_DISTANCE_VARIANT, ICON_FLASH |     CONF_DISTANCE, | ||||||
|  |     CONF_LIGHTNING_ENERGY, | ||||||
|  |     DEVICE_CLASS_EMPTY, | ||||||
|  |     UNIT_KILOMETER, | ||||||
|  |     UNIT_EMPTY, | ||||||
|  |     ICON_SIGNAL_DISTANCE_VARIANT, | ||||||
|  |     ICON_FLASH, | ||||||
|  | ) | ||||||
| from . import AS3935, CONF_AS3935_ID | from . import AS3935, CONF_AS3935_ID | ||||||
|  |  | ||||||
| DEPENDENCIES = ['as3935'] | DEPENDENCIES = ["as3935"] | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = cv.Schema({ | CONFIG_SCHEMA = cv.Schema( | ||||||
|     cv.GenerateID(CONF_AS3935_ID): cv.use_id(AS3935), |     { | ||||||
|     cv.Optional(CONF_DISTANCE): |         cv.GenerateID(CONF_AS3935_ID): cv.use_id(AS3935), | ||||||
|         sensor.sensor_schema(UNIT_KILOMETER, ICON_SIGNAL_DISTANCE_VARIANT, 1), |         cv.Optional(CONF_DISTANCE): sensor.sensor_schema( | ||||||
|     cv.Optional(CONF_LIGHTNING_ENERGY): |             UNIT_KILOMETER, ICON_SIGNAL_DISTANCE_VARIANT, 1, DEVICE_CLASS_EMPTY | ||||||
|         sensor.sensor_schema(UNIT_EMPTY, ICON_FLASH, 1), |         ), | ||||||
| }).extend(cv.COMPONENT_SCHEMA) |         cv.Optional(CONF_LIGHTNING_ENERGY): sensor.sensor_schema( | ||||||
|  |             UNIT_EMPTY, ICON_FLASH, 1, DEVICE_CLASS_EMPTY | ||||||
|  |         ), | ||||||
|  |     } | ||||||
|  | ).extend(cv.COMPONENT_SCHEMA) | ||||||
|  |  | ||||||
|  |  | ||||||
| def to_code(config): | def to_code(config): | ||||||
|   | |||||||
| @@ -3,15 +3,21 @@ import esphome.config_validation as cv | |||||||
| from esphome.components import as3935, i2c | from esphome.components import as3935, i2c | ||||||
| from esphome.const import CONF_ID | from esphome.const import CONF_ID | ||||||
|  |  | ||||||
| AUTO_LOAD = ['as3935'] | AUTO_LOAD = ["as3935"] | ||||||
| DEPENDENCIES = ['i2c'] | DEPENDENCIES = ["i2c"] | ||||||
|  |  | ||||||
| as3935_i2c_ns = cg.esphome_ns.namespace('as3935_i2c') | as3935_i2c_ns = cg.esphome_ns.namespace("as3935_i2c") | ||||||
| I2CAS3935 = as3935_i2c_ns.class_('I2CAS3935Component', as3935.AS3935, i2c.I2CDevice) | I2CAS3935 = as3935_i2c_ns.class_("I2CAS3935Component", as3935.AS3935, i2c.I2CDevice) | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = cv.All(as3935.AS3935_SCHEMA.extend({ | CONFIG_SCHEMA = cv.All( | ||||||
|     cv.GenerateID(): cv.declare_id(I2CAS3935), |     as3935.AS3935_SCHEMA.extend( | ||||||
| }).extend(cv.COMPONENT_SCHEMA).extend(i2c.i2c_device_schema(0x03))) |         { | ||||||
|  |             cv.GenerateID(): cv.declare_id(I2CAS3935), | ||||||
|  |         } | ||||||
|  |     ) | ||||||
|  |     .extend(cv.COMPONENT_SCHEMA) | ||||||
|  |     .extend(i2c.i2c_device_schema(0x03)) | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
| def to_code(config): | def to_code(config): | ||||||
|   | |||||||
| @@ -3,15 +3,21 @@ import esphome.config_validation as cv | |||||||
| from esphome.components import as3935, spi | from esphome.components import as3935, spi | ||||||
| from esphome.const import CONF_ID | from esphome.const import CONF_ID | ||||||
|  |  | ||||||
| AUTO_LOAD = ['as3935'] | AUTO_LOAD = ["as3935"] | ||||||
| DEPENDENCIES = ['spi'] | DEPENDENCIES = ["spi"] | ||||||
|  |  | ||||||
| as3935_spi_ns = cg.esphome_ns.namespace('as3935_spi') | as3935_spi_ns = cg.esphome_ns.namespace("as3935_spi") | ||||||
| SPIAS3935 = as3935_spi_ns.class_('SPIAS3935Component', as3935.AS3935, spi.SPIDevice) | SPIAS3935 = as3935_spi_ns.class_("SPIAS3935Component", as3935.AS3935, spi.SPIDevice) | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = cv.All(as3935.AS3935_SCHEMA.extend({ | CONFIG_SCHEMA = cv.All( | ||||||
|     cv.GenerateID(): cv.declare_id(SPIAS3935), |     as3935.AS3935_SCHEMA.extend( | ||||||
| }).extend(cv.COMPONENT_SCHEMA).extend(spi.spi_device_schema(cs_pin_required=True))) |         { | ||||||
|  |             cv.GenerateID(): cv.declare_id(SPIAS3935), | ||||||
|  |         } | ||||||
|  |     ) | ||||||
|  |     .extend(cv.COMPONENT_SCHEMA) | ||||||
|  |     .extend(spi.spi_device_schema(cs_pin_required=True)) | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
| def to_code(config): | def to_code(config): | ||||||
|   | |||||||
| @@ -2,14 +2,14 @@ | |||||||
| import esphome.codegen as cg | import esphome.codegen as cg | ||||||
| from esphome.core import CORE, coroutine_with_priority | from esphome.core import CORE, coroutine_with_priority | ||||||
|  |  | ||||||
| CODEOWNERS = ['@OttoWinter'] | CODEOWNERS = ["@OttoWinter"] | ||||||
|  |  | ||||||
|  |  | ||||||
| @coroutine_with_priority(200.0) | @coroutine_with_priority(200.0) | ||||||
| def to_code(config): | def to_code(config): | ||||||
|     if CORE.is_esp32: |     if CORE.is_esp32: | ||||||
|         # https://github.com/OttoWinter/AsyncTCP/blob/master/library.json |         # https://github.com/OttoWinter/AsyncTCP/blob/master/library.json | ||||||
|         cg.add_library('AsyncTCP-esphome', '1.1.1') |         cg.add_library("AsyncTCP-esphome", "1.1.1") | ||||||
|     elif CORE.is_esp8266: |     elif CORE.is_esp8266: | ||||||
|         # https://github.com/OttoWinter/ESPAsyncTCP |         # https://github.com/OttoWinter/ESPAsyncTCP | ||||||
|         cg.add_library('ESPAsyncTCP-esphome', '1.2.3') |         cg.add_library("ESPAsyncTCP-esphome", "1.2.3") | ||||||
|   | |||||||
| @@ -1,27 +1,54 @@ | |||||||
| import esphome.codegen as cg | import esphome.codegen as cg | ||||||
| import esphome.config_validation as cv | import esphome.config_validation as cv | ||||||
| from esphome.components import sensor, esp32_ble_tracker | from esphome.components import sensor, esp32_ble_tracker | ||||||
| from esphome.const import CONF_BATTERY_LEVEL, CONF_BATTERY_VOLTAGE, CONF_MAC_ADDRESS, \ | from esphome.const import ( | ||||||
|     CONF_HUMIDITY, CONF_TEMPERATURE, CONF_ID, UNIT_CELSIUS, UNIT_PERCENT, UNIT_VOLT, \ |     CONF_BATTERY_LEVEL, | ||||||
|     ICON_BATTERY, ICON_THERMOMETER, ICON_WATER_PERCENT |     CONF_BATTERY_VOLTAGE, | ||||||
|  |     CONF_MAC_ADDRESS, | ||||||
|  |     CONF_HUMIDITY, | ||||||
|  |     CONF_TEMPERATURE, | ||||||
|  |     CONF_ID, | ||||||
|  |     DEVICE_CLASS_BATTERY, | ||||||
|  |     DEVICE_CLASS_HUMIDITY, | ||||||
|  |     DEVICE_CLASS_TEMPERATURE, | ||||||
|  |     DEVICE_CLASS_VOLTAGE, | ||||||
|  |     ICON_EMPTY, | ||||||
|  |     UNIT_CELSIUS, | ||||||
|  |     UNIT_PERCENT, | ||||||
|  |     UNIT_VOLT, | ||||||
|  | ) | ||||||
|  |  | ||||||
| CODEOWNERS = ['@ahpohl'] | CODEOWNERS = ["@ahpohl"] | ||||||
|  |  | ||||||
| DEPENDENCIES = ['esp32_ble_tracker'] | DEPENDENCIES = ["esp32_ble_tracker"] | ||||||
|  |  | ||||||
| atc_mithermometer_ns = cg.esphome_ns.namespace('atc_mithermometer') | atc_mithermometer_ns = cg.esphome_ns.namespace("atc_mithermometer") | ||||||
| ATCMiThermometer = atc_mithermometer_ns.class_('ATCMiThermometer', | ATCMiThermometer = atc_mithermometer_ns.class_( | ||||||
|                                                esp32_ble_tracker.ESPBTDeviceListener, |     "ATCMiThermometer", esp32_ble_tracker.ESPBTDeviceListener, cg.Component | ||||||
|                                                cg.Component) | ) | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = cv.Schema({ | CONFIG_SCHEMA = ( | ||||||
|     cv.GenerateID(): cv.declare_id(ATCMiThermometer), |     cv.Schema( | ||||||
|     cv.Required(CONF_MAC_ADDRESS): cv.mac_address, |         { | ||||||
|     cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 1), |             cv.GenerateID(): cv.declare_id(ATCMiThermometer), | ||||||
|     cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(UNIT_PERCENT, ICON_WATER_PERCENT, 0), |             cv.Required(CONF_MAC_ADDRESS): cv.mac_address, | ||||||
|     cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema(UNIT_PERCENT, ICON_BATTERY, 0), |             cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( | ||||||
|     cv.Optional(CONF_BATTERY_VOLTAGE): sensor.sensor_schema(UNIT_VOLT, ICON_BATTERY, 3), |                 UNIT_CELSIUS, ICON_EMPTY, 1, DEVICE_CLASS_TEMPERATURE | ||||||
| }).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA).extend(cv.COMPONENT_SCHEMA) |             ), | ||||||
|  |             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): | def to_code(config): | ||||||
|   | |||||||
| @@ -1,61 +1,106 @@ | |||||||
| import esphome.codegen as cg | import esphome.codegen as cg | ||||||
| import esphome.config_validation as cv | import esphome.config_validation as cv | ||||||
| from esphome.components import sensor, spi | from esphome.components import sensor, spi | ||||||
| from esphome.const import \ | from esphome.const import ( | ||||||
|     CONF_ID, CONF_VOLTAGE, CONF_CURRENT, CONF_POWER, CONF_POWER_FACTOR, CONF_FREQUENCY, \ |     CONF_ID, | ||||||
|     ICON_FLASH, ICON_LIGHTBULB, ICON_CURRENT_AC, ICON_THERMOMETER, \ |     CONF_VOLTAGE, | ||||||
|     UNIT_HERTZ, UNIT_VOLT, UNIT_AMPERE, UNIT_WATT, UNIT_EMPTY, UNIT_CELSIUS, UNIT_VOLT_AMPS_REACTIVE |     CONF_CURRENT, | ||||||
|  |     CONF_POWER, | ||||||
|  |     CONF_POWER_FACTOR, | ||||||
|  |     CONF_FREQUENCY, | ||||||
|  |     DEVICE_CLASS_CURRENT, | ||||||
|  |     DEVICE_CLASS_EMPTY, | ||||||
|  |     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, | ||||||
|  | ) | ||||||
|  |  | ||||||
| CONF_PHASE_A = 'phase_a' | CONF_PHASE_A = "phase_a" | ||||||
| CONF_PHASE_B = 'phase_b' | CONF_PHASE_B = "phase_b" | ||||||
| CONF_PHASE_C = 'phase_c' | CONF_PHASE_C = "phase_c" | ||||||
|  |  | ||||||
| CONF_REACTIVE_POWER = 'reactive_power' | CONF_REACTIVE_POWER = "reactive_power" | ||||||
| CONF_LINE_FREQUENCY = 'line_frequency' | CONF_LINE_FREQUENCY = "line_frequency" | ||||||
| CONF_CHIP_TEMPERATURE = 'chip_temperature' | CONF_CHIP_TEMPERATURE = "chip_temperature" | ||||||
| CONF_GAIN_PGA = 'gain_pga' | CONF_GAIN_PGA = "gain_pga" | ||||||
| CONF_CURRENT_PHASES = 'current_phases' | CONF_CURRENT_PHASES = "current_phases" | ||||||
| CONF_GAIN_VOLTAGE = 'gain_voltage' | CONF_GAIN_VOLTAGE = "gain_voltage" | ||||||
| CONF_GAIN_CT = 'gain_ct' | CONF_GAIN_CT = "gain_ct" | ||||||
| LINE_FREQS = { | LINE_FREQS = { | ||||||
|     '50HZ': 50, |     "50HZ": 50, | ||||||
|     '60HZ': 60, |     "60HZ": 60, | ||||||
| } | } | ||||||
| CURRENT_PHASES = { | CURRENT_PHASES = { | ||||||
|     '2': 2, |     "2": 2, | ||||||
|     '3': 3, |     "3": 3, | ||||||
| } | } | ||||||
| PGA_GAINS = { | PGA_GAINS = { | ||||||
|     '1X': 0x0, |     "1X": 0x0, | ||||||
|     '2X': 0x15, |     "2X": 0x15, | ||||||
|     '4X': 0x2A, |     "4X": 0x2A, | ||||||
| } | } | ||||||
|  |  | ||||||
| atm90e32_ns = cg.esphome_ns.namespace('atm90e32') | atm90e32_ns = cg.esphome_ns.namespace("atm90e32") | ||||||
| ATM90E32Component = atm90e32_ns.class_('ATM90E32Component', cg.PollingComponent, spi.SPIDevice) | ATM90E32Component = atm90e32_ns.class_( | ||||||
|  |     "ATM90E32Component", cg.PollingComponent, spi.SPIDevice | ||||||
|  | ) | ||||||
|  |  | ||||||
| ATM90E32_PHASE_SCHEMA = cv.Schema({ | ATM90E32_PHASE_SCHEMA = cv.Schema( | ||||||
|     cv.Optional(CONF_VOLTAGE): sensor.sensor_schema(UNIT_VOLT, ICON_FLASH, 2), |     { | ||||||
|     cv.Optional(CONF_CURRENT): sensor.sensor_schema(UNIT_AMPERE, ICON_CURRENT_AC, 2), |         cv.Optional(CONF_VOLTAGE): sensor.sensor_schema( | ||||||
|     cv.Optional(CONF_POWER): sensor.sensor_schema(UNIT_WATT, ICON_FLASH, 2), |             UNIT_VOLT, ICON_EMPTY, 2, DEVICE_CLASS_VOLTAGE | ||||||
|     cv.Optional(CONF_REACTIVE_POWER): sensor.sensor_schema(UNIT_VOLT_AMPS_REACTIVE, |         ), | ||||||
|                                                            ICON_LIGHTBULB, 2), |         cv.Optional(CONF_CURRENT): sensor.sensor_schema( | ||||||
|     cv.Optional(CONF_POWER_FACTOR): sensor.sensor_schema(UNIT_EMPTY, ICON_FLASH, 2), |             UNIT_AMPERE, ICON_EMPTY, 2, DEVICE_CLASS_CURRENT | ||||||
|     cv.Optional(CONF_GAIN_VOLTAGE, default=7305): cv.uint16_t, |         ), | ||||||
|     cv.Optional(CONF_GAIN_CT, default=27961): cv.uint16_t, |         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_GAIN_VOLTAGE, default=7305): cv.uint16_t, | ||||||
|  |         cv.Optional(CONF_GAIN_CT, default=27961): cv.uint16_t, | ||||||
|  |     } | ||||||
|  | ) | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = cv.Schema({ | CONFIG_SCHEMA = ( | ||||||
|     cv.GenerateID(): cv.declare_id(ATM90E32Component), |     cv.Schema( | ||||||
|     cv.Optional(CONF_PHASE_A): ATM90E32_PHASE_SCHEMA, |         { | ||||||
|     cv.Optional(CONF_PHASE_B): ATM90E32_PHASE_SCHEMA, |             cv.GenerateID(): cv.declare_id(ATM90E32Component), | ||||||
|     cv.Optional(CONF_PHASE_C): ATM90E32_PHASE_SCHEMA, |             cv.Optional(CONF_PHASE_A): ATM90E32_PHASE_SCHEMA, | ||||||
|     cv.Optional(CONF_FREQUENCY): sensor.sensor_schema(UNIT_HERTZ, ICON_CURRENT_AC, 1), |             cv.Optional(CONF_PHASE_B): ATM90E32_PHASE_SCHEMA, | ||||||
|     cv.Optional(CONF_CHIP_TEMPERATURE): sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 1), |             cv.Optional(CONF_PHASE_C): ATM90E32_PHASE_SCHEMA, | ||||||
|     cv.Required(CONF_LINE_FREQUENCY): cv.enum(LINE_FREQS, upper=True), |             cv.Optional(CONF_FREQUENCY): sensor.sensor_schema( | ||||||
|     cv.Optional(CONF_CURRENT_PHASES, default='3'): cv.enum(CURRENT_PHASES, upper=True), |                 UNIT_HERTZ, ICON_CURRENT_AC, 1, DEVICE_CLASS_EMPTY | ||||||
|     cv.Optional(CONF_GAIN_PGA, default='2X'): cv.enum(PGA_GAINS, upper=True), |             ), | ||||||
| }).extend(cv.polling_component_schema('60s')).extend(spi.spi_device_schema()) |             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): | def to_code(config): | ||||||
|   | |||||||
| @@ -1 +1 @@ | |||||||
| CODEOWNERS = ['@OttoWinter'] | CODEOWNERS = ["@OttoWinter"] | ||||||
|   | |||||||
| @@ -2,27 +2,41 @@ import esphome.codegen as cg | |||||||
| import esphome.config_validation as cv | import esphome.config_validation as cv | ||||||
| from esphome import automation | from esphome import automation | ||||||
| from esphome.components import climate, sensor | from esphome.components import climate, sensor | ||||||
| from esphome.const import CONF_AWAY_CONFIG, CONF_COOL_ACTION, \ | from esphome.const import ( | ||||||
|     CONF_DEFAULT_TARGET_TEMPERATURE_HIGH, CONF_DEFAULT_TARGET_TEMPERATURE_LOW, CONF_HEAT_ACTION, \ |     CONF_AWAY_CONFIG, | ||||||
|     CONF_ID, CONF_IDLE_ACTION, CONF_SENSOR |     CONF_COOL_ACTION, | ||||||
|  |     CONF_DEFAULT_TARGET_TEMPERATURE_HIGH, | ||||||
|  |     CONF_DEFAULT_TARGET_TEMPERATURE_LOW, | ||||||
|  |     CONF_HEAT_ACTION, | ||||||
|  |     CONF_ID, | ||||||
|  |     CONF_IDLE_ACTION, | ||||||
|  |     CONF_SENSOR, | ||||||
|  | ) | ||||||
|  |  | ||||||
| bang_bang_ns = cg.esphome_ns.namespace('bang_bang') | bang_bang_ns = cg.esphome_ns.namespace("bang_bang") | ||||||
| BangBangClimate = bang_bang_ns.class_('BangBangClimate', climate.Climate, cg.Component) | BangBangClimate = bang_bang_ns.class_("BangBangClimate", climate.Climate, cg.Component) | ||||||
| BangBangClimateTargetTempConfig = bang_bang_ns.struct('BangBangClimateTargetTempConfig') | BangBangClimateTargetTempConfig = bang_bang_ns.struct("BangBangClimateTargetTempConfig") | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = cv.All(climate.CLIMATE_SCHEMA.extend({ | CONFIG_SCHEMA = cv.All( | ||||||
|     cv.GenerateID(): cv.declare_id(BangBangClimate), |     climate.CLIMATE_SCHEMA.extend( | ||||||
|     cv.Required(CONF_SENSOR): cv.use_id(sensor.Sensor), |         { | ||||||
|     cv.Required(CONF_DEFAULT_TARGET_TEMPERATURE_LOW): cv.temperature, |             cv.GenerateID(): cv.declare_id(BangBangClimate), | ||||||
|     cv.Required(CONF_DEFAULT_TARGET_TEMPERATURE_HIGH): cv.temperature, |             cv.Required(CONF_SENSOR): cv.use_id(sensor.Sensor), | ||||||
|     cv.Required(CONF_IDLE_ACTION): automation.validate_automation(single=True), |             cv.Required(CONF_DEFAULT_TARGET_TEMPERATURE_LOW): cv.temperature, | ||||||
|     cv.Optional(CONF_COOL_ACTION): automation.validate_automation(single=True), |             cv.Required(CONF_DEFAULT_TARGET_TEMPERATURE_HIGH): cv.temperature, | ||||||
|     cv.Optional(CONF_HEAT_ACTION): automation.validate_automation(single=True), |             cv.Required(CONF_IDLE_ACTION): automation.validate_automation(single=True), | ||||||
|     cv.Optional(CONF_AWAY_CONFIG): cv.Schema({ |             cv.Optional(CONF_COOL_ACTION): automation.validate_automation(single=True), | ||||||
|         cv.Required(CONF_DEFAULT_TARGET_TEMPERATURE_LOW): cv.temperature, |             cv.Optional(CONF_HEAT_ACTION): automation.validate_automation(single=True), | ||||||
|         cv.Required(CONF_DEFAULT_TARGET_TEMPERATURE_HIGH): cv.temperature, |             cv.Optional(CONF_AWAY_CONFIG): cv.Schema( | ||||||
|     }), |                 { | ||||||
| }).extend(cv.COMPONENT_SCHEMA), cv.has_at_least_one_key(CONF_COOL_ACTION, CONF_HEAT_ACTION)) |                     cv.Required(CONF_DEFAULT_TARGET_TEMPERATURE_LOW): cv.temperature, | ||||||
|  |                     cv.Required(CONF_DEFAULT_TARGET_TEMPERATURE_HIGH): cv.temperature, | ||||||
|  |                 } | ||||||
|  |             ), | ||||||
|  |         } | ||||||
|  |     ).extend(cv.COMPONENT_SCHEMA), | ||||||
|  |     cv.has_at_least_one_key(CONF_COOL_ACTION, CONF_HEAT_ACTION), | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
| def to_code(config): | def to_code(config): | ||||||
| @@ -35,23 +49,29 @@ def to_code(config): | |||||||
|  |  | ||||||
|     normal_config = BangBangClimateTargetTempConfig( |     normal_config = BangBangClimateTargetTempConfig( | ||||||
|         config[CONF_DEFAULT_TARGET_TEMPERATURE_LOW], |         config[CONF_DEFAULT_TARGET_TEMPERATURE_LOW], | ||||||
|         config[CONF_DEFAULT_TARGET_TEMPERATURE_HIGH] |         config[CONF_DEFAULT_TARGET_TEMPERATURE_HIGH], | ||||||
|     ) |     ) | ||||||
|     cg.add(var.set_normal_config(normal_config)) |     cg.add(var.set_normal_config(normal_config)) | ||||||
|  |  | ||||||
|     yield automation.build_automation(var.get_idle_trigger(), [], config[CONF_IDLE_ACTION]) |     yield automation.build_automation( | ||||||
|  |         var.get_idle_trigger(), [], config[CONF_IDLE_ACTION] | ||||||
|  |     ) | ||||||
|  |  | ||||||
|     if CONF_COOL_ACTION in config: |     if CONF_COOL_ACTION in config: | ||||||
|         yield automation.build_automation(var.get_cool_trigger(), [], config[CONF_COOL_ACTION]) |         yield automation.build_automation( | ||||||
|  |             var.get_cool_trigger(), [], config[CONF_COOL_ACTION] | ||||||
|  |         ) | ||||||
|         cg.add(var.set_supports_cool(True)) |         cg.add(var.set_supports_cool(True)) | ||||||
|     if CONF_HEAT_ACTION in config: |     if CONF_HEAT_ACTION in config: | ||||||
|         yield automation.build_automation(var.get_heat_trigger(), [], config[CONF_HEAT_ACTION]) |         yield automation.build_automation( | ||||||
|  |             var.get_heat_trigger(), [], config[CONF_HEAT_ACTION] | ||||||
|  |         ) | ||||||
|         cg.add(var.set_supports_heat(True)) |         cg.add(var.set_supports_heat(True)) | ||||||
|  |  | ||||||
|     if CONF_AWAY_CONFIG in config: |     if CONF_AWAY_CONFIG in config: | ||||||
|         away = config[CONF_AWAY_CONFIG] |         away = config[CONF_AWAY_CONFIG] | ||||||
|         away_config = BangBangClimateTargetTempConfig( |         away_config = BangBangClimateTargetTempConfig( | ||||||
|             away[CONF_DEFAULT_TARGET_TEMPERATURE_LOW], |             away[CONF_DEFAULT_TARGET_TEMPERATURE_LOW], | ||||||
|             away[CONF_DEFAULT_TARGET_TEMPERATURE_HIGH] |             away[CONF_DEFAULT_TARGET_TEMPERATURE_HIGH], | ||||||
|         ) |         ) | ||||||
|         cg.add(var.set_away_config(away_config)) |         cg.add(var.set_away_config(away_config)) | ||||||
|   | |||||||
| @@ -1,26 +1,45 @@ | |||||||
| import esphome.codegen as cg | import esphome.codegen as cg | ||||||
| import esphome.config_validation as cv | import esphome.config_validation as cv | ||||||
| from esphome.components import i2c, sensor | from esphome.components import i2c, sensor | ||||||
| from esphome.const import CONF_ID, CONF_RESOLUTION, UNIT_LUX, ICON_BRIGHTNESS_5 | from esphome.const import ( | ||||||
|  |     CONF_ID, | ||||||
|  |     CONF_RESOLUTION, | ||||||
|  |     DEVICE_CLASS_ILLUMINANCE, | ||||||
|  |     ICON_EMPTY, | ||||||
|  |     UNIT_LUX, | ||||||
|  | ) | ||||||
|  |  | ||||||
| DEPENDENCIES = ['i2c'] | DEPENDENCIES = ["i2c"] | ||||||
|  |  | ||||||
| bh1750_ns = cg.esphome_ns.namespace('bh1750') | bh1750_ns = cg.esphome_ns.namespace("bh1750") | ||||||
| BH1750Resolution = bh1750_ns.enum('BH1750Resolution') | BH1750Resolution = bh1750_ns.enum("BH1750Resolution") | ||||||
| BH1750_RESOLUTIONS = { | BH1750_RESOLUTIONS = { | ||||||
|     4.0: BH1750Resolution.BH1750_RESOLUTION_4P0_LX, |     4.0: BH1750Resolution.BH1750_RESOLUTION_4P0_LX, | ||||||
|     1.0: BH1750Resolution.BH1750_RESOLUTION_1P0_LX, |     1.0: BH1750Resolution.BH1750_RESOLUTION_1P0_LX, | ||||||
|     0.5: BH1750Resolution.BH1750_RESOLUTION_0P5_LX, |     0.5: BH1750Resolution.BH1750_RESOLUTION_0P5_LX, | ||||||
| } | } | ||||||
|  |  | ||||||
| BH1750Sensor = bh1750_ns.class_('BH1750Sensor', sensor.Sensor, cg.PollingComponent, i2c.I2CDevice) | BH1750Sensor = bh1750_ns.class_( | ||||||
|  |     "BH1750Sensor", sensor.Sensor, cg.PollingComponent, i2c.I2CDevice | ||||||
|  | ) | ||||||
|  |  | ||||||
| CONF_MEASUREMENT_TIME = 'measurement_time' | CONF_MEASUREMENT_TIME = "measurement_time" | ||||||
| CONFIG_SCHEMA = sensor.sensor_schema(UNIT_LUX, ICON_BRIGHTNESS_5, 1).extend({ | CONFIG_SCHEMA = ( | ||||||
|     cv.GenerateID(): cv.declare_id(BH1750Sensor), |     sensor.sensor_schema(UNIT_LUX, ICON_EMPTY, 1, DEVICE_CLASS_ILLUMINANCE) | ||||||
|     cv.Optional(CONF_RESOLUTION, default=0.5): cv.enum(BH1750_RESOLUTIONS, float=True), |     .extend( | ||||||
|     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)) |             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)) | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
| def to_code(config): | def to_code(config): | ||||||
|   | |||||||
| @@ -1,3 +1,3 @@ | |||||||
| import esphome.codegen as cg | import esphome.codegen as cg | ||||||
|  |  | ||||||
| binary_ns = cg.esphome_ns.namespace('binary') | binary_ns = cg.esphome_ns.namespace("binary") | ||||||
|   | |||||||
| @@ -1,18 +1,24 @@ | |||||||
| import esphome.codegen as cg | import esphome.codegen as cg | ||||||
| import esphome.config_validation as cv | import esphome.config_validation as cv | ||||||
| from esphome.components import fan, output | from esphome.components import fan, output | ||||||
| from esphome.const import CONF_DIRECTION_OUTPUT, CONF_OSCILLATION_OUTPUT, \ | from esphome.const import ( | ||||||
|     CONF_OUTPUT, CONF_OUTPUT_ID |     CONF_DIRECTION_OUTPUT, | ||||||
|  |     CONF_OSCILLATION_OUTPUT, | ||||||
|  |     CONF_OUTPUT, | ||||||
|  |     CONF_OUTPUT_ID, | ||||||
|  | ) | ||||||
| from .. import binary_ns | from .. import binary_ns | ||||||
|  |  | ||||||
| BinaryFan = binary_ns.class_('BinaryFan', cg.Component) | BinaryFan = binary_ns.class_("BinaryFan", cg.Component) | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = fan.FAN_SCHEMA.extend({ | CONFIG_SCHEMA = fan.FAN_SCHEMA.extend( | ||||||
|     cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(BinaryFan), |     { | ||||||
|     cv.Required(CONF_OUTPUT): cv.use_id(output.BinaryOutput), |         cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(BinaryFan), | ||||||
|     cv.Optional(CONF_DIRECTION_OUTPUT): cv.use_id(output.BinaryOutput), |         cv.Required(CONF_OUTPUT): cv.use_id(output.BinaryOutput), | ||||||
|     cv.Optional(CONF_OSCILLATION_OUTPUT): cv.use_id(output.BinaryOutput), |         cv.Optional(CONF_DIRECTION_OUTPUT): cv.use_id(output.BinaryOutput), | ||||||
| }).extend(cv.COMPONENT_SCHEMA) |         cv.Optional(CONF_OSCILLATION_OUTPUT): cv.use_id(output.BinaryOutput), | ||||||
|  |     } | ||||||
|  | ).extend(cv.COMPONENT_SCHEMA) | ||||||
|  |  | ||||||
|  |  | ||||||
| def to_code(config): | def to_code(config): | ||||||
|   | |||||||
| @@ -16,7 +16,7 @@ void binary::BinaryFan::dump_config() { | |||||||
|   } |   } | ||||||
| } | } | ||||||
| void BinaryFan::setup() { | void BinaryFan::setup() { | ||||||
|   auto traits = fan::FanTraits(this->oscillating_ != nullptr, false, this->direction_ != nullptr); |   auto traits = fan::FanTraits(this->oscillating_ != nullptr, false, this->direction_ != nullptr, 0); | ||||||
|   this->fan_->set_traits(traits); |   this->fan_->set_traits(traits); | ||||||
|   this->fan_->add_on_state_callback([this]() { this->next_update_ = true; }); |   this->fan_->add_on_state_callback([this]() { this->next_update_ = true; }); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -4,12 +4,14 @@ from esphome.components import light, output | |||||||
| from esphome.const import CONF_OUTPUT_ID, CONF_OUTPUT | from esphome.const import CONF_OUTPUT_ID, CONF_OUTPUT | ||||||
| from .. import binary_ns | from .. import binary_ns | ||||||
|  |  | ||||||
| BinaryLightOutput = binary_ns.class_('BinaryLightOutput', light.LightOutput) | BinaryLightOutput = binary_ns.class_("BinaryLightOutput", light.LightOutput) | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = light.BINARY_LIGHT_SCHEMA.extend({ | CONFIG_SCHEMA = light.BINARY_LIGHT_SCHEMA.extend( | ||||||
|     cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(BinaryLightOutput), |     { | ||||||
|     cv.Required(CONF_OUTPUT): cv.use_id(output.BinaryOutput), |         cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(BinaryLightOutput), | ||||||
| }) |         cv.Required(CONF_OUTPUT): cv.use_id(output.BinaryOutput), | ||||||
|  |     } | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
| def to_code(config): | def to_code(config): | ||||||
|   | |||||||
| @@ -3,131 +3,214 @@ import esphome.config_validation as cv | |||||||
| from esphome import automation, core | from esphome import automation, core | ||||||
| from esphome.automation import Condition, maybe_simple_id | from esphome.automation import Condition, maybe_simple_id | ||||||
| from esphome.components import mqtt | from esphome.components import mqtt | ||||||
| from esphome.const import CONF_DEVICE_CLASS, CONF_FILTERS, \ | from esphome.const import ( | ||||||
|     CONF_ID, CONF_INTERNAL, CONF_INVALID_COOLDOWN, CONF_INVERTED, \ |     CONF_DEVICE_CLASS, | ||||||
|     CONF_MAX_LENGTH, CONF_MIN_LENGTH, CONF_ON_CLICK, \ |     CONF_FILTERS, | ||||||
|     CONF_ON_DOUBLE_CLICK, CONF_ON_MULTI_CLICK, CONF_ON_PRESS, CONF_ON_RELEASE, CONF_ON_STATE, \ |     CONF_ID, | ||||||
|     CONF_STATE, CONF_TIMING, CONF_TRIGGER_ID, CONF_FOR, CONF_NAME, CONF_MQTT_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.core import CORE, coroutine, coroutine_with_priority | ||||||
| from esphome.util import Registry | from esphome.util import Registry | ||||||
|  |  | ||||||
| CODEOWNERS = ['@esphome/core'] | CODEOWNERS = ["@esphome/core"] | ||||||
| DEVICE_CLASSES = [ | DEVICE_CLASSES = [ | ||||||
|     '', 'battery', 'cold', 'connectivity', 'door', 'garage_door', 'gas', |     DEVICE_CLASS_EMPTY, | ||||||
|     'heat', 'light', 'lock', 'moisture', 'motion', 'moving', 'occupancy', |     DEVICE_CLASS_BATTERY, | ||||||
|     'opening', 'plug', 'power', 'presence', 'problem', 'safety', 'smoke', |     DEVICE_CLASS_BATTERY_CHARGING, | ||||||
|     'sound', 'vibration', 'window' |     DEVICE_CLASS_COLD, | ||||||
|  |     DEVICE_CLASS_CONNECTIVITY, | ||||||
|  |     DEVICE_CLASS_DOOR, | ||||||
|  |     DEVICE_CLASS_GARAGE_DOOR, | ||||||
|  |     DEVICE_CLASS_GAS, | ||||||
|  |     DEVICE_CLASS_HEAT, | ||||||
|  |     DEVICE_CLASS_LIGHT, | ||||||
|  |     DEVICE_CLASS_LOCK, | ||||||
|  |     DEVICE_CLASS_MOISTURE, | ||||||
|  |     DEVICE_CLASS_MOTION, | ||||||
|  |     DEVICE_CLASS_MOVING, | ||||||
|  |     DEVICE_CLASS_OCCUPANCY, | ||||||
|  |     DEVICE_CLASS_OPENING, | ||||||
|  |     DEVICE_CLASS_PLUG, | ||||||
|  |     DEVICE_CLASS_POWER, | ||||||
|  |     DEVICE_CLASS_PRESENCE, | ||||||
|  |     DEVICE_CLASS_PROBLEM, | ||||||
|  |     DEVICE_CLASS_SAFETY, | ||||||
|  |     DEVICE_CLASS_SMOKE, | ||||||
|  |     DEVICE_CLASS_SOUND, | ||||||
|  |     DEVICE_CLASS_VIBRATION, | ||||||
|  |     DEVICE_CLASS_WINDOW, | ||||||
| ] | ] | ||||||
|  |  | ||||||
| IS_PLATFORM_COMPONENT = True | IS_PLATFORM_COMPONENT = True | ||||||
|  |  | ||||||
| binary_sensor_ns = cg.esphome_ns.namespace('binary_sensor') | binary_sensor_ns = cg.esphome_ns.namespace("binary_sensor") | ||||||
| BinarySensor = binary_sensor_ns.class_('BinarySensor', cg.Nameable) | BinarySensor = binary_sensor_ns.class_("BinarySensor", cg.Nameable) | ||||||
| BinarySensorInitiallyOff = binary_sensor_ns.class_('BinarySensorInitiallyOff', BinarySensor) | BinarySensorInitiallyOff = binary_sensor_ns.class_( | ||||||
| BinarySensorPtr = BinarySensor.operator('ptr') |     "BinarySensorInitiallyOff", BinarySensor | ||||||
|  | ) | ||||||
|  | BinarySensorPtr = BinarySensor.operator("ptr") | ||||||
|  |  | ||||||
| # Triggers | # Triggers | ||||||
| PressTrigger = binary_sensor_ns.class_('PressTrigger', automation.Trigger.template()) | PressTrigger = binary_sensor_ns.class_("PressTrigger", automation.Trigger.template()) | ||||||
| ReleaseTrigger = binary_sensor_ns.class_('ReleaseTrigger', automation.Trigger.template()) | ReleaseTrigger = binary_sensor_ns.class_( | ||||||
| ClickTrigger = binary_sensor_ns.class_('ClickTrigger', automation.Trigger.template()) |     "ReleaseTrigger", automation.Trigger.template() | ||||||
| DoubleClickTrigger = binary_sensor_ns.class_('DoubleClickTrigger', automation.Trigger.template()) | ) | ||||||
| MultiClickTrigger = binary_sensor_ns.class_('MultiClickTrigger', automation.Trigger.template(), | ClickTrigger = binary_sensor_ns.class_("ClickTrigger", automation.Trigger.template()) | ||||||
|                                             cg.Component) | DoubleClickTrigger = binary_sensor_ns.class_( | ||||||
| MultiClickTriggerEvent = binary_sensor_ns.struct('MultiClickTriggerEvent') |     "DoubleClickTrigger", automation.Trigger.template() | ||||||
| StateTrigger = binary_sensor_ns.class_('StateTrigger', automation.Trigger.template(bool)) | ) | ||||||
| BinarySensorPublishAction = binary_sensor_ns.class_('BinarySensorPublishAction', automation.Action) | MultiClickTrigger = binary_sensor_ns.class_( | ||||||
|  |     "MultiClickTrigger", automation.Trigger.template(), cg.Component | ||||||
|  | ) | ||||||
|  | MultiClickTriggerEvent = binary_sensor_ns.struct("MultiClickTriggerEvent") | ||||||
|  | StateTrigger = binary_sensor_ns.class_( | ||||||
|  |     "StateTrigger", automation.Trigger.template(bool) | ||||||
|  | ) | ||||||
|  | BinarySensorPublishAction = binary_sensor_ns.class_( | ||||||
|  |     "BinarySensorPublishAction", automation.Action | ||||||
|  | ) | ||||||
|  |  | ||||||
| # Condition | # Condition | ||||||
| BinarySensorCondition = binary_sensor_ns.class_('BinarySensorCondition', Condition) | BinarySensorCondition = binary_sensor_ns.class_("BinarySensorCondition", Condition) | ||||||
|  |  | ||||||
| # Filters | # Filters | ||||||
| Filter = binary_sensor_ns.class_('Filter') | Filter = binary_sensor_ns.class_("Filter") | ||||||
| DelayedOnOffFilter = binary_sensor_ns.class_('DelayedOnOffFilter', Filter, cg.Component) | DelayedOnOffFilter = binary_sensor_ns.class_("DelayedOnOffFilter", Filter, cg.Component) | ||||||
| DelayedOnFilter = binary_sensor_ns.class_('DelayedOnFilter', Filter, cg.Component) | DelayedOnFilter = binary_sensor_ns.class_("DelayedOnFilter", Filter, cg.Component) | ||||||
| DelayedOffFilter = binary_sensor_ns.class_('DelayedOffFilter', Filter, cg.Component) | DelayedOffFilter = binary_sensor_ns.class_("DelayedOffFilter", Filter, cg.Component) | ||||||
| InvertFilter = binary_sensor_ns.class_('InvertFilter', Filter) | InvertFilter = binary_sensor_ns.class_("InvertFilter", Filter) | ||||||
| LambdaFilter = binary_sensor_ns.class_('LambdaFilter', Filter) | LambdaFilter = binary_sensor_ns.class_("LambdaFilter", Filter) | ||||||
|  |  | ||||||
| FILTER_REGISTRY = Registry() | FILTER_REGISTRY = Registry() | ||||||
| validate_filters = cv.validate_registry('filter', FILTER_REGISTRY) | validate_filters = cv.validate_registry("filter", FILTER_REGISTRY) | ||||||
|  |  | ||||||
|  |  | ||||||
| @FILTER_REGISTRY.register('invert', InvertFilter, {}) | @FILTER_REGISTRY.register("invert", InvertFilter, {}) | ||||||
| def invert_filter_to_code(config, filter_id): | def invert_filter_to_code(config, filter_id): | ||||||
|     yield cg.new_Pvariable(filter_id) |     yield cg.new_Pvariable(filter_id) | ||||||
|  |  | ||||||
|  |  | ||||||
| @FILTER_REGISTRY.register('delayed_on_off', DelayedOnOffFilter, | @FILTER_REGISTRY.register( | ||||||
|                           cv.positive_time_period_milliseconds) |     "delayed_on_off", DelayedOnOffFilter, cv.positive_time_period_milliseconds | ||||||
|  | ) | ||||||
| def delayed_on_off_filter_to_code(config, filter_id): | def delayed_on_off_filter_to_code(config, filter_id): | ||||||
|     var = cg.new_Pvariable(filter_id, config) |     var = cg.new_Pvariable(filter_id, config) | ||||||
|     yield cg.register_component(var, {}) |     yield cg.register_component(var, {}) | ||||||
|     yield var |     yield var | ||||||
|  |  | ||||||
|  |  | ||||||
| @FILTER_REGISTRY.register('delayed_on', DelayedOnFilter, | @FILTER_REGISTRY.register( | ||||||
|                           cv.positive_time_period_milliseconds) |     "delayed_on", DelayedOnFilter, cv.positive_time_period_milliseconds | ||||||
|  | ) | ||||||
| def delayed_on_filter_to_code(config, filter_id): | def delayed_on_filter_to_code(config, filter_id): | ||||||
|     var = cg.new_Pvariable(filter_id, config) |     var = cg.new_Pvariable(filter_id, config) | ||||||
|     yield cg.register_component(var, {}) |     yield cg.register_component(var, {}) | ||||||
|     yield 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): | def delayed_off_filter_to_code(config, filter_id): | ||||||
|     var = cg.new_Pvariable(filter_id, config) |     var = cg.new_Pvariable(filter_id, config) | ||||||
|     yield cg.register_component(var, {}) |     yield cg.register_component(var, {}) | ||||||
|     yield 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): | 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_) |     yield cg.new_Pvariable(filter_id, lambda_) | ||||||
|  |  | ||||||
|  |  | ||||||
| MULTI_CLICK_TIMING_SCHEMA = cv.Schema({ | 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_STATE): cv.boolean, | ||||||
|     cv.Optional(CONF_MAX_LENGTH): cv.positive_time_period_milliseconds, |         cv.Optional(CONF_MIN_LENGTH): cv.positive_time_period_milliseconds, | ||||||
| }) |         cv.Optional(CONF_MAX_LENGTH): cv.positive_time_period_milliseconds, | ||||||
|  |     } | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
| def parse_multi_click_timing_str(value): | def parse_multi_click_timing_str(value): | ||||||
|     if not isinstance(value, str): |     if not isinstance(value, str): | ||||||
|         return value |         return value | ||||||
|  |  | ||||||
|     parts = value.lower().split(' ') |     parts = value.lower().split(" ") | ||||||
|     if len(parts) != 5: |     if len(parts) != 5: | ||||||
|         raise cv.Invalid("Multi click timing grammar consists of exactly 5 words, not {}" |         raise cv.Invalid( | ||||||
|                          "".format(len(parts))) |             "Multi click timing grammar consists of exactly 5 words, not {}" | ||||||
|  |             "".format(len(parts)) | ||||||
|  |         ) | ||||||
|     try: |     try: | ||||||
|         state = cv.boolean(parts[0]) |         state = cv.boolean(parts[0]) | ||||||
|     except cv.Invalid: |     except cv.Invalid: | ||||||
|         # pylint: disable=raise-missing-from |         # pylint: disable=raise-missing-from | ||||||
|         raise cv.Invalid("First word must either be ON or OFF, not {}".format(parts[0])) |         raise cv.Invalid("First word must either be ON or OFF, not {}".format(parts[0])) | ||||||
|  |  | ||||||
|     if parts[1] != 'for': |     if parts[1] != "for": | ||||||
|         raise cv.Invalid("Second word must be 'for', got {}".format(parts[1])) |         raise cv.Invalid("Second word must be 'for', got {}".format(parts[1])) | ||||||
|  |  | ||||||
|     if parts[2] == 'at': |     if parts[2] == "at": | ||||||
|         if parts[3] == 'least': |         if parts[3] == "least": | ||||||
|             key = CONF_MIN_LENGTH |             key = CONF_MIN_LENGTH | ||||||
|         elif parts[3] == 'most': |         elif parts[3] == "most": | ||||||
|             key = CONF_MAX_LENGTH |             key = CONF_MAX_LENGTH | ||||||
|         else: |         else: | ||||||
|             raise cv.Invalid("Third word after at must either be 'least' or 'most', got {}" |             raise cv.Invalid( | ||||||
|                              "".format(parts[3])) |                 "Third word after at must either be 'least' or 'most', got {}" | ||||||
|  |                 "".format(parts[3]) | ||||||
|  |             ) | ||||||
|         try: |         try: | ||||||
|             length = cv.positive_time_period_milliseconds(parts[4]) |             length = cv.positive_time_period_milliseconds(parts[4]) | ||||||
|         except cv.Invalid as err: |         except cv.Invalid as err: | ||||||
|             raise cv.Invalid(f"Multi Click Grammar Parsing length failed: {err}") |             raise cv.Invalid(f"Multi Click Grammar Parsing length failed: {err}") | ||||||
|         return { |         return {CONF_STATE: state, key: str(length)} | ||||||
|             CONF_STATE: state, |  | ||||||
|             key: str(length) |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|     if parts[3] != 'to': |     if parts[3] != "to": | ||||||
|         raise cv.Invalid("Multi click grammar: 4th word must be 'to'") |         raise cv.Invalid("Multi click grammar: 4th word must be 'to'") | ||||||
|  |  | ||||||
|     try: |     try: | ||||||
| @@ -143,7 +226,7 @@ def parse_multi_click_timing_str(value): | |||||||
|     return { |     return { | ||||||
|         CONF_STATE: state, |         CONF_STATE: state, | ||||||
|         CONF_MIN_LENGTH: str(min_length), |         CONF_MIN_LENGTH: str(min_length), | ||||||
|         CONF_MAX_LENGTH: str(max_length) |         CONF_MAX_LENGTH: str(max_length), | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -163,11 +246,15 @@ def validate_multi_click_timing(value): | |||||||
|  |  | ||||||
|         new_state = v_.get(CONF_STATE, not state) |         new_state = v_.get(CONF_STATE, not state) | ||||||
|         if new_state == state: |         if new_state == state: | ||||||
|             raise cv.Invalid("Timings must have alternating state. Indices {} and {} have " |             raise cv.Invalid( | ||||||
|                              "the same state {}".format(i, i + 1, state)) |                 "Timings must have alternating state. Indices {} and {} have " | ||||||
|  |                 "the same state {}".format(i, i + 1, state) | ||||||
|  |             ) | ||||||
|         if max_length is not None and max_length < min_length: |         if max_length is not None and max_length < min_length: | ||||||
|             raise cv.Invalid("Max length ({}) must be larger than min length ({})." |             raise cv.Invalid( | ||||||
|                              "".format(max_length, min_length)) |                 "Max length ({}) must be larger than min length ({})." | ||||||
|  |                 "".format(max_length, min_length) | ||||||
|  |             ) | ||||||
|  |  | ||||||
|         state = new_state |         state = new_state | ||||||
|         tim = { |         tim = { | ||||||
| @@ -180,46 +267,71 @@ def validate_multi_click_timing(value): | |||||||
|     return timings |     return timings | ||||||
|  |  | ||||||
|  |  | ||||||
| device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space='_') | device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space="_") | ||||||
|  |  | ||||||
| BINARY_SENSOR_SCHEMA = cv.MQTT_COMPONENT_SCHEMA.extend({ | BINARY_SENSOR_SCHEMA = cv.MQTT_COMPONENT_SCHEMA.extend( | ||||||
|     cv.GenerateID(): cv.declare_id(BinarySensor), |     { | ||||||
|     cv.OnlyWith(CONF_MQTT_ID, 'mqtt'): cv.declare_id(mqtt.MQTTBinarySensorComponent), |         cv.GenerateID(): cv.declare_id(BinarySensor), | ||||||
|  |         cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id( | ||||||
|     cv.Optional(CONF_DEVICE_CLASS): device_class, |             mqtt.MQTTBinarySensorComponent | ||||||
|     cv.Optional(CONF_FILTERS): validate_filters, |         ), | ||||||
|     cv.Optional(CONF_ON_PRESS): automation.validate_automation({ |         cv.Optional(CONF_DEVICE_CLASS): device_class, | ||||||
|         cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(PressTrigger), |         cv.Optional(CONF_FILTERS): validate_filters, | ||||||
|     }), |         cv.Optional(CONF_ON_PRESS): automation.validate_automation( | ||||||
|     cv.Optional(CONF_ON_RELEASE): automation.validate_automation({ |             { | ||||||
|         cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ReleaseTrigger), |                 cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(PressTrigger), | ||||||
|     }), |             } | ||||||
|     cv.Optional(CONF_ON_CLICK): automation.validate_automation({ |         ), | ||||||
|         cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ClickTrigger), |         cv.Optional(CONF_ON_RELEASE): automation.validate_automation( | ||||||
|         cv.Optional(CONF_MIN_LENGTH, default='50ms'): cv.positive_time_period_milliseconds, |             { | ||||||
|         cv.Optional(CONF_MAX_LENGTH, default='350ms'): cv.positive_time_period_milliseconds, |                 cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ReleaseTrigger), | ||||||
|     }), |             } | ||||||
|     cv.Optional(CONF_ON_DOUBLE_CLICK): automation.validate_automation({ |         ), | ||||||
|         cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(DoubleClickTrigger), |         cv.Optional(CONF_ON_CLICK): automation.validate_automation( | ||||||
|         cv.Optional(CONF_MIN_LENGTH, default='50ms'): cv.positive_time_period_milliseconds, |             { | ||||||
|         cv.Optional(CONF_MAX_LENGTH, default='350ms'): cv.positive_time_period_milliseconds, |                 cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ClickTrigger), | ||||||
|     }), |                 cv.Optional( | ||||||
|     cv.Optional(CONF_ON_MULTI_CLICK): automation.validate_automation({ |                     CONF_MIN_LENGTH, default="50ms" | ||||||
|         cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(MultiClickTrigger), |                 ): cv.positive_time_period_milliseconds, | ||||||
|         cv.Required(CONF_TIMING): cv.All([parse_multi_click_timing_str], |                 cv.Optional( | ||||||
|                                          validate_multi_click_timing), |                     CONF_MAX_LENGTH, default="350ms" | ||||||
|         cv.Optional(CONF_INVALID_COOLDOWN, default='1s'): cv.positive_time_period_milliseconds, |                 ): cv.positive_time_period_milliseconds, | ||||||
|     }), |             } | ||||||
|     cv.Optional(CONF_ON_STATE): automation.validate_automation({ |         ), | ||||||
|         cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(StateTrigger), |         cv.Optional(CONF_ON_DOUBLE_CLICK): automation.validate_automation( | ||||||
|     }), |             { | ||||||
|  |                 cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(DoubleClickTrigger), | ||||||
|     cv.Optional(CONF_INVERTED): cv.invalid( |                 cv.Optional( | ||||||
|         "The inverted binary_sensor property has been replaced by the " |                     CONF_MIN_LENGTH, default="50ms" | ||||||
|         "new 'invert' binary  sensor filter. Please see " |                 ): cv.positive_time_period_milliseconds, | ||||||
|         "https://esphome.io/components/binary_sensor/index.html." |                 cv.Optional( | ||||||
|     ), |                     CONF_MAX_LENGTH, default="350ms" | ||||||
| }) |                 ): cv.positive_time_period_milliseconds, | ||||||
|  |             } | ||||||
|  |         ), | ||||||
|  |         cv.Optional(CONF_ON_MULTI_CLICK): automation.validate_automation( | ||||||
|  |             { | ||||||
|  |                 cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(MultiClickTrigger), | ||||||
|  |                 cv.Required(CONF_TIMING): cv.All( | ||||||
|  |                     [parse_multi_click_timing_str], validate_multi_click_timing | ||||||
|  |                 ), | ||||||
|  |                 cv.Optional( | ||||||
|  |                     CONF_INVALID_COOLDOWN, default="1s" | ||||||
|  |                 ): cv.positive_time_period_milliseconds, | ||||||
|  |             } | ||||||
|  |         ), | ||||||
|  |         cv.Optional(CONF_ON_STATE): automation.validate_automation( | ||||||
|  |             { | ||||||
|  |                 cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(StateTrigger), | ||||||
|  |             } | ||||||
|  |         ), | ||||||
|  |         cv.Optional(CONF_INVERTED): cv.invalid( | ||||||
|  |             "The inverted binary_sensor property has been replaced by the " | ||||||
|  |             "new 'invert' binary  sensor filter. Please see " | ||||||
|  |             "https://esphome.io/components/binary_sensor/index.html." | ||||||
|  |         ), | ||||||
|  |     } | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
| @coroutine | @coroutine | ||||||
| @@ -244,24 +356,28 @@ def setup_binary_sensor_core_(var, config): | |||||||
|         yield automation.build_automation(trigger, [], conf) |         yield automation.build_automation(trigger, [], conf) | ||||||
|  |  | ||||||
|     for conf in config.get(CONF_ON_CLICK, []): |     for conf in config.get(CONF_ON_CLICK, []): | ||||||
|         trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var, |         trigger = cg.new_Pvariable( | ||||||
|                                    conf[CONF_MIN_LENGTH], conf[CONF_MAX_LENGTH]) |             conf[CONF_TRIGGER_ID], var, conf[CONF_MIN_LENGTH], conf[CONF_MAX_LENGTH] | ||||||
|  |         ) | ||||||
|         yield automation.build_automation(trigger, [], conf) |         yield automation.build_automation(trigger, [], conf) | ||||||
|  |  | ||||||
|     for conf in config.get(CONF_ON_DOUBLE_CLICK, []): |     for conf in config.get(CONF_ON_DOUBLE_CLICK, []): | ||||||
|         trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var, |         trigger = cg.new_Pvariable( | ||||||
|                                    conf[CONF_MIN_LENGTH], conf[CONF_MAX_LENGTH]) |             conf[CONF_TRIGGER_ID], var, conf[CONF_MIN_LENGTH], conf[CONF_MAX_LENGTH] | ||||||
|  |         ) | ||||||
|         yield automation.build_automation(trigger, [], conf) |         yield automation.build_automation(trigger, [], conf) | ||||||
|  |  | ||||||
|     for conf in config.get(CONF_ON_MULTI_CLICK, []): |     for conf in config.get(CONF_ON_MULTI_CLICK, []): | ||||||
|         timings = [] |         timings = [] | ||||||
|         for tim in conf[CONF_TIMING]: |         for tim in conf[CONF_TIMING]: | ||||||
|             timings.append(cg.StructInitializer( |             timings.append( | ||||||
|                 MultiClickTriggerEvent, |                 cg.StructInitializer( | ||||||
|                 ('state', tim[CONF_STATE]), |                     MultiClickTriggerEvent, | ||||||
|                 ('min_length', tim[CONF_MIN_LENGTH]), |                     ("state", tim[CONF_STATE]), | ||||||
|                 ('max_length', tim.get(CONF_MAX_LENGTH, 4294967294)), |                     ("min_length", tim[CONF_MIN_LENGTH]), | ||||||
|             )) |                     ("max_length", tim.get(CONF_MAX_LENGTH, 4294967294)), | ||||||
|  |                 ) | ||||||
|  |             ) | ||||||
|         trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var, timings) |         trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var, timings) | ||||||
|         if CONF_INVALID_COOLDOWN in conf: |         if CONF_INVALID_COOLDOWN in conf: | ||||||
|             cg.add(trigger.set_invalid_cooldown(conf[CONF_INVALID_COOLDOWN])) |             cg.add(trigger.set_invalid_cooldown(conf[CONF_INVALID_COOLDOWN])) | ||||||
| @@ -270,7 +386,7 @@ def setup_binary_sensor_core_(var, config): | |||||||
|  |  | ||||||
|     for conf in config.get(CONF_ON_STATE, []): |     for conf in config.get(CONF_ON_STATE, []): | ||||||
|         trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) |         trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) | ||||||
|         yield automation.build_automation(trigger, [(bool, 'x')], conf) |         yield automation.build_automation(trigger, [(bool, "x")], conf) | ||||||
|  |  | ||||||
|     if CONF_MQTT_ID in config: |     if CONF_MQTT_ID in config: | ||||||
|         mqtt_ = cg.new_Pvariable(config[CONF_MQTT_ID], var) |         mqtt_ = cg.new_Pvariable(config[CONF_MQTT_ID], var) | ||||||
| @@ -292,22 +408,28 @@ def new_binary_sensor(config): | |||||||
|     yield var |     yield var | ||||||
|  |  | ||||||
|  |  | ||||||
| BINARY_SENSOR_CONDITION_SCHEMA = maybe_simple_id({ | BINARY_SENSOR_CONDITION_SCHEMA = maybe_simple_id( | ||||||
|     cv.Required(CONF_ID): cv.use_id(BinarySensor), |     { | ||||||
|     cv.Optional(CONF_FOR): cv.invalid("This option has been removed in 1.13, please use the " |         cv.Required(CONF_ID): cv.use_id(BinarySensor), | ||||||
|                                       "'for' condition instead."), |         cv.Optional(CONF_FOR): cv.invalid( | ||||||
| }) |             "This option has been removed in 1.13, please use the " | ||||||
|  |             "'for' condition instead." | ||||||
|  |         ), | ||||||
|  |     } | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
| @automation.register_condition('binary_sensor.is_on', BinarySensorCondition, | @automation.register_condition( | ||||||
|                                BINARY_SENSOR_CONDITION_SCHEMA) |     "binary_sensor.is_on", BinarySensorCondition, BINARY_SENSOR_CONDITION_SCHEMA | ||||||
|  | ) | ||||||
| def binary_sensor_is_on_to_code(config, condition_id, template_arg, args): | def binary_sensor_is_on_to_code(config, condition_id, template_arg, args): | ||||||
|     paren = yield cg.get_variable(config[CONF_ID]) |     paren = yield cg.get_variable(config[CONF_ID]) | ||||||
|     yield cg.new_Pvariable(condition_id, template_arg, paren, True) |     yield cg.new_Pvariable(condition_id, template_arg, paren, True) | ||||||
|  |  | ||||||
|  |  | ||||||
| @automation.register_condition('binary_sensor.is_off', BinarySensorCondition, | @automation.register_condition( | ||||||
|                                BINARY_SENSOR_CONDITION_SCHEMA) |     "binary_sensor.is_off", BinarySensorCondition, BINARY_SENSOR_CONDITION_SCHEMA | ||||||
|  | ) | ||||||
| def binary_sensor_is_off_to_code(config, condition_id, template_arg, args): | def binary_sensor_is_off_to_code(config, condition_id, template_arg, args): | ||||||
|     paren = yield cg.get_variable(config[CONF_ID]) |     paren = yield cg.get_variable(config[CONF_ID]) | ||||||
|     yield cg.new_Pvariable(condition_id, template_arg, paren, False) |     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) | @coroutine_with_priority(100.0) | ||||||
| def to_code(config): | def to_code(config): | ||||||
|     cg.add_define('USE_BINARY_SENSOR') |     cg.add_define("USE_BINARY_SENSOR") | ||||||
|     cg.add_global(binary_sensor_ns.using) |     cg.add_global(binary_sensor_ns.using) | ||||||
|   | |||||||
| @@ -2,14 +2,25 @@ import esphome.codegen as cg | |||||||
| import esphome.config_validation as cv | import esphome.config_validation as cv | ||||||
|  |  | ||||||
| from esphome.components import sensor, binary_sensor | from esphome.components import sensor, binary_sensor | ||||||
| from esphome.const import CONF_ID, CONF_CHANNELS, CONF_VALUE, CONF_TYPE, UNIT_EMPTY, \ | from esphome.const import ( | ||||||
|     ICON_CHECK_CIRCLE_OUTLINE, CONF_BINARY_SENSOR, CONF_GROUP |     CONF_ID, | ||||||
|  |     CONF_CHANNELS, | ||||||
|  |     CONF_VALUE, | ||||||
|  |     CONF_TYPE, | ||||||
|  |     DEVICE_CLASS_EMPTY, | ||||||
|  |     UNIT_EMPTY, | ||||||
|  |     ICON_CHECK_CIRCLE_OUTLINE, | ||||||
|  |     CONF_BINARY_SENSOR, | ||||||
|  |     CONF_GROUP, | ||||||
|  | ) | ||||||
|  |  | ||||||
| DEPENDENCIES = ['binary_sensor'] | DEPENDENCIES = ["binary_sensor"] | ||||||
|  |  | ||||||
| binary_sensor_map_ns = cg.esphome_ns.namespace('binary_sensor_map') | binary_sensor_map_ns = cg.esphome_ns.namespace("binary_sensor_map") | ||||||
| BinarySensorMap = binary_sensor_map_ns.class_('BinarySensorMap', cg.Component, sensor.Sensor) | BinarySensorMap = binary_sensor_map_ns.class_( | ||||||
| SensorMapType = binary_sensor_map_ns.enum('SensorMapType') |     "BinarySensorMap", cg.Component, sensor.Sensor | ||||||
|  | ) | ||||||
|  | SensorMapType = binary_sensor_map_ns.enum("SensorMapType") | ||||||
|  |  | ||||||
| SENSOR_MAP_TYPES = { | SENSOR_MAP_TYPES = { | ||||||
|     CONF_GROUP: SensorMapType.BINARY_SENSOR_MAP_TYPE_GROUP, |     CONF_GROUP: SensorMapType.BINARY_SENSOR_MAP_TYPE_GROUP, | ||||||
| @@ -20,12 +31,21 @@ entry = { | |||||||
|     cv.Required(CONF_VALUE): cv.float_, |     cv.Required(CONF_VALUE): cv.float_, | ||||||
| } | } | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = cv.typed_schema({ | CONFIG_SCHEMA = cv.typed_schema( | ||||||
|     CONF_GROUP: sensor.sensor_schema(UNIT_EMPTY, ICON_CHECK_CIRCLE_OUTLINE, 0).extend({ |     { | ||||||
|         cv.GenerateID(): cv.declare_id(BinarySensorMap), |         CONF_GROUP: sensor.sensor_schema( | ||||||
|         cv.Required(CONF_CHANNELS): cv.All(cv.ensure_list(entry), cv.Length(min=1)), |             UNIT_EMPTY, ICON_CHECK_CIRCLE_OUTLINE, 0, DEVICE_CLASS_EMPTY | ||||||
|     }), |         ).extend( | ||||||
| }, lower=True) |             { | ||||||
|  |                 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): | def to_code(config): | ||||||
|   | |||||||
| @@ -3,18 +3,28 @@ import esphome.config_validation as cv | |||||||
| from esphome.components import binary_sensor, esp32_ble_tracker | from esphome.components import binary_sensor, esp32_ble_tracker | ||||||
| from esphome.const import CONF_MAC_ADDRESS, CONF_SERVICE_UUID, CONF_ID | from esphome.const import CONF_MAC_ADDRESS, CONF_SERVICE_UUID, CONF_ID | ||||||
|  |  | ||||||
| DEPENDENCIES = ['esp32_ble_tracker'] | DEPENDENCIES = ["esp32_ble_tracker"] | ||||||
|  |  | ||||||
| ble_presence_ns = cg.esphome_ns.namespace('ble_presence') | ble_presence_ns = cg.esphome_ns.namespace("ble_presence") | ||||||
| BLEPresenceDevice = ble_presence_ns.class_('BLEPresenceDevice', binary_sensor.BinarySensor, | BLEPresenceDevice = ble_presence_ns.class_( | ||||||
|                                            cg.Component, esp32_ble_tracker.ESPBTDeviceListener) |     "BLEPresenceDevice", | ||||||
|  |     binary_sensor.BinarySensor, | ||||||
|  |     cg.Component, | ||||||
|  |     esp32_ble_tracker.ESPBTDeviceListener, | ||||||
|  | ) | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = cv.All(binary_sensor.BINARY_SENSOR_SCHEMA.extend({ | CONFIG_SCHEMA = cv.All( | ||||||
|     cv.GenerateID(): cv.declare_id(BLEPresenceDevice), |     binary_sensor.BINARY_SENSOR_SCHEMA.extend( | ||||||
|     cv.Optional(CONF_MAC_ADDRESS): cv.mac_address, |         { | ||||||
|     cv.Optional(CONF_SERVICE_UUID): esp32_ble_tracker.bt_uuid, |             cv.GenerateID(): cv.declare_id(BLEPresenceDevice), | ||||||
| }).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA).extend( |             cv.Optional(CONF_MAC_ADDRESS): cv.mac_address, | ||||||
|     cv.COMPONENT_SCHEMA), cv.has_exactly_one_key(CONF_MAC_ADDRESS, CONF_SERVICE_UUID)) |             cv.Optional(CONF_SERVICE_UUID): esp32_ble_tracker.bt_uuid, | ||||||
|  |         } | ||||||
|  |     ) | ||||||
|  |     .extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA) | ||||||
|  |     .extend(cv.COMPONENT_SCHEMA), | ||||||
|  |     cv.has_exactly_one_key(CONF_MAC_ADDRESS, CONF_SERVICE_UUID), | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
| def to_code(config): | def to_code(config): | ||||||
| @@ -28,9 +38,17 @@ def to_code(config): | |||||||
|  |  | ||||||
|     if CONF_SERVICE_UUID in config: |     if CONF_SERVICE_UUID in config: | ||||||
|         if len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid16_format): |         if len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid16_format): | ||||||
|             cg.add(var.set_service_uuid16(esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID]))) |             cg.add( | ||||||
|  |                 var.set_service_uuid16( | ||||||
|  |                     esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID]) | ||||||
|  |                 ) | ||||||
|  |             ) | ||||||
|         elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid32_format): |         elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid32_format): | ||||||
|             cg.add(var.set_service_uuid32(esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID]))) |             cg.add( | ||||||
|  |                 var.set_service_uuid32( | ||||||
|  |                     esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID]) | ||||||
|  |                 ) | ||||||
|  |             ) | ||||||
|         elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid128_format): |         elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid128_format): | ||||||
|             uuid128 = esp32_ble_tracker.as_hex_array(config[CONF_SERVICE_UUID]) |             uuid128 = esp32_ble_tracker.as_hex_array(config[CONF_SERVICE_UUID]) | ||||||
|             cg.add(var.set_service_uuid128(uuid128)) |             cg.add(var.set_service_uuid128(uuid128)) | ||||||
|   | |||||||
| @@ -1,20 +1,35 @@ | |||||||
| import esphome.codegen as cg | import esphome.codegen as cg | ||||||
| import esphome.config_validation as cv | import esphome.config_validation as cv | ||||||
| from esphome.components import sensor, esp32_ble_tracker | from esphome.components import sensor, esp32_ble_tracker | ||||||
| from esphome.const import CONF_SERVICE_UUID, CONF_MAC_ADDRESS, CONF_ID, UNIT_DECIBEL, ICON_SIGNAL | from esphome.const import ( | ||||||
|  |     CONF_SERVICE_UUID, | ||||||
|  |     CONF_MAC_ADDRESS, | ||||||
|  |     CONF_ID, | ||||||
|  |     DEVICE_CLASS_SIGNAL_STRENGTH, | ||||||
|  |     UNIT_DECIBEL, | ||||||
|  |     ICON_EMPTY, | ||||||
|  | ) | ||||||
|  |  | ||||||
| DEPENDENCIES = ['esp32_ble_tracker'] | DEPENDENCIES = ["esp32_ble_tracker"] | ||||||
|  |  | ||||||
| ble_rssi_ns = cg.esphome_ns.namespace('ble_rssi') | ble_rssi_ns = cg.esphome_ns.namespace("ble_rssi") | ||||||
| BLERSSISensor = ble_rssi_ns.class_('BLERSSISensor', sensor.Sensor, cg.Component, | BLERSSISensor = ble_rssi_ns.class_( | ||||||
|                                    esp32_ble_tracker.ESPBTDeviceListener) |     "BLERSSISensor", sensor.Sensor, cg.Component, esp32_ble_tracker.ESPBTDeviceListener | ||||||
|  | ) | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = cv.All(sensor.sensor_schema(UNIT_DECIBEL, ICON_SIGNAL, 0).extend({ | CONFIG_SCHEMA = cv.All( | ||||||
|     cv.GenerateID(): cv.declare_id(BLERSSISensor), |     sensor.sensor_schema(UNIT_DECIBEL, ICON_EMPTY, 0, DEVICE_CLASS_SIGNAL_STRENGTH) | ||||||
|     cv.Optional(CONF_MAC_ADDRESS): cv.mac_address, |     .extend( | ||||||
|     cv.Optional(CONF_SERVICE_UUID): esp32_ble_tracker.bt_uuid, |         { | ||||||
| }).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA).extend( |             cv.GenerateID(): cv.declare_id(BLERSSISensor), | ||||||
|     cv.COMPONENT_SCHEMA), cv.has_exactly_one_key(CONF_MAC_ADDRESS, CONF_SERVICE_UUID)) |             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): | def to_code(config): | ||||||
| @@ -28,9 +43,17 @@ def to_code(config): | |||||||
|  |  | ||||||
|     if CONF_SERVICE_UUID in config: |     if CONF_SERVICE_UUID in config: | ||||||
|         if len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid16_format): |         if len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid16_format): | ||||||
|             cg.add(var.set_service_uuid16(esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID]))) |             cg.add( | ||||||
|  |                 var.set_service_uuid16( | ||||||
|  |                     esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID]) | ||||||
|  |                 ) | ||||||
|  |             ) | ||||||
|         elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid32_format): |         elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid32_format): | ||||||
|             cg.add(var.set_service_uuid32(esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID]))) |             cg.add( | ||||||
|  |                 var.set_service_uuid32( | ||||||
|  |                     esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID]) | ||||||
|  |                 ) | ||||||
|  |             ) | ||||||
|         elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid128_format): |         elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid128_format): | ||||||
|             uuid128 = esp32_ble_tracker.as_hex_array(config[CONF_SERVICE_UUID]) |             uuid128 = esp32_ble_tracker.as_hex_array(config[CONF_SERVICE_UUID]) | ||||||
|             cg.add(var.set_service_uuid128(uuid128)) |             cg.add(var.set_service_uuid128(uuid128)) | ||||||
|   | |||||||
| @@ -3,16 +3,25 @@ import esphome.config_validation as cv | |||||||
| from esphome.components import text_sensor, esp32_ble_tracker | from esphome.components import text_sensor, esp32_ble_tracker | ||||||
| from esphome.const import CONF_ID | from esphome.const import CONF_ID | ||||||
|  |  | ||||||
| DEPENDENCIES = ['esp32_ble_tracker'] | DEPENDENCIES = ["esp32_ble_tracker"] | ||||||
|  |  | ||||||
| ble_scanner_ns = cg.esphome_ns.namespace('ble_scanner') | ble_scanner_ns = cg.esphome_ns.namespace("ble_scanner") | ||||||
| BLEScanner = ble_scanner_ns.class_('BLEScanner', text_sensor.TextSensor, cg.Component, | BLEScanner = ble_scanner_ns.class_( | ||||||
|                                    esp32_ble_tracker.ESPBTDeviceListener) |     "BLEScanner", | ||||||
|  |     text_sensor.TextSensor, | ||||||
|  |     cg.Component, | ||||||
|  |     esp32_ble_tracker.ESPBTDeviceListener, | ||||||
|  | ) | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = cv.All(text_sensor.TEXT_SENSOR_SCHEMA.extend({ | CONFIG_SCHEMA = cv.All( | ||||||
|     cv.GenerateID(): cv.declare_id(BLEScanner), |     text_sensor.TEXT_SENSOR_SCHEMA.extend( | ||||||
| }).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA).extend( |         { | ||||||
|     cv.COMPONENT_SCHEMA)) |             cv.GenerateID(): cv.declare_id(BLEScanner), | ||||||
|  |         } | ||||||
|  |     ) | ||||||
|  |     .extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA) | ||||||
|  |     .extend(cv.COMPONENT_SCHEMA) | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
| def to_code(config): | def to_code(config): | ||||||
|   | |||||||
| @@ -1,53 +1,87 @@ | |||||||
| import esphome.codegen as cg | import esphome.codegen as cg | ||||||
| import esphome.config_validation as cv | import esphome.config_validation as cv | ||||||
| from esphome.components import i2c, sensor | from esphome.components import i2c, sensor | ||||||
| from esphome.const import CONF_HUMIDITY, CONF_ID, CONF_IIR_FILTER, CONF_OVERSAMPLING, \ | from esphome.const import ( | ||||||
|     CONF_PRESSURE, CONF_TEMPERATURE, ICON_THERMOMETER, \ |     CONF_HUMIDITY, | ||||||
|     UNIT_CELSIUS, UNIT_HECTOPASCAL, ICON_GAUGE, ICON_WATER_PERCENT, UNIT_PERCENT |     CONF_ID, | ||||||
|  |     CONF_IIR_FILTER, | ||||||
|  |     CONF_OVERSAMPLING, | ||||||
|  |     CONF_PRESSURE, | ||||||
|  |     CONF_TEMPERATURE, | ||||||
|  |     DEVICE_CLASS_HUMIDITY, | ||||||
|  |     DEVICE_CLASS_PRESSURE, | ||||||
|  |     DEVICE_CLASS_TEMPERATURE, | ||||||
|  |     ICON_EMPTY, | ||||||
|  |     UNIT_CELSIUS, | ||||||
|  |     UNIT_HECTOPASCAL, | ||||||
|  |     UNIT_PERCENT, | ||||||
|  | ) | ||||||
|  |  | ||||||
| DEPENDENCIES = ['i2c'] | DEPENDENCIES = ["i2c"] | ||||||
|  |  | ||||||
| bme280_ns = cg.esphome_ns.namespace('bme280') | bme280_ns = cg.esphome_ns.namespace("bme280") | ||||||
| BME280Oversampling = bme280_ns.enum('BME280Oversampling') | BME280Oversampling = bme280_ns.enum("BME280Oversampling") | ||||||
| OVERSAMPLING_OPTIONS = { | OVERSAMPLING_OPTIONS = { | ||||||
|     'NONE': BME280Oversampling.BME280_OVERSAMPLING_NONE, |     "NONE": BME280Oversampling.BME280_OVERSAMPLING_NONE, | ||||||
|     '1X': BME280Oversampling.BME280_OVERSAMPLING_1X, |     "1X": BME280Oversampling.BME280_OVERSAMPLING_1X, | ||||||
|     '2X': BME280Oversampling.BME280_OVERSAMPLING_2X, |     "2X": BME280Oversampling.BME280_OVERSAMPLING_2X, | ||||||
|     '4X': BME280Oversampling.BME280_OVERSAMPLING_4X, |     "4X": BME280Oversampling.BME280_OVERSAMPLING_4X, | ||||||
|     '8X': BME280Oversampling.BME280_OVERSAMPLING_8X, |     "8X": BME280Oversampling.BME280_OVERSAMPLING_8X, | ||||||
|     '16X': BME280Oversampling.BME280_OVERSAMPLING_16X, |     "16X": BME280Oversampling.BME280_OVERSAMPLING_16X, | ||||||
| } | } | ||||||
|  |  | ||||||
| BME280IIRFilter = bme280_ns.enum('BME280IIRFilter') | BME280IIRFilter = bme280_ns.enum("BME280IIRFilter") | ||||||
| IIR_FILTER_OPTIONS = { | IIR_FILTER_OPTIONS = { | ||||||
|     'OFF': BME280IIRFilter.BME280_IIR_FILTER_OFF, |     "OFF": BME280IIRFilter.BME280_IIR_FILTER_OFF, | ||||||
|     '2X': BME280IIRFilter.BME280_IIR_FILTER_2X, |     "2X": BME280IIRFilter.BME280_IIR_FILTER_2X, | ||||||
|     '4X': BME280IIRFilter.BME280_IIR_FILTER_4X, |     "4X": BME280IIRFilter.BME280_IIR_FILTER_4X, | ||||||
|     '8X': BME280IIRFilter.BME280_IIR_FILTER_8X, |     "8X": BME280IIRFilter.BME280_IIR_FILTER_8X, | ||||||
|     '16X': BME280IIRFilter.BME280_IIR_FILTER_16X, |     "16X": BME280IIRFilter.BME280_IIR_FILTER_16X, | ||||||
| } | } | ||||||
|  |  | ||||||
| BME280Component = bme280_ns.class_('BME280Component', cg.PollingComponent, i2c.I2CDevice) | BME280Component = bme280_ns.class_( | ||||||
|  |     "BME280Component", cg.PollingComponent, i2c.I2CDevice | ||||||
|  | ) | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = cv.Schema({ | CONFIG_SCHEMA = ( | ||||||
|     cv.GenerateID(): cv.declare_id(BME280Component), |     cv.Schema( | ||||||
|     cv.Optional(CONF_TEMPERATURE): |         { | ||||||
|         sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 1).extend({ |             cv.GenerateID(): cv.declare_id(BME280Component), | ||||||
|             cv.Optional(CONF_OVERSAMPLING, default='16X'): |             cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( | ||||||
|                 cv.enum(OVERSAMPLING_OPTIONS, upper=True), |                 UNIT_CELSIUS, ICON_EMPTY, 1, DEVICE_CLASS_TEMPERATURE | ||||||
|         }), |             ).extend( | ||||||
|     cv.Optional(CONF_PRESSURE): |                 { | ||||||
|         sensor.sensor_schema(UNIT_HECTOPASCAL, ICON_GAUGE, 1).extend({ |                     cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum( | ||||||
|             cv.Optional(CONF_OVERSAMPLING, default='16X'): |                         OVERSAMPLING_OPTIONS, upper=True | ||||||
|                 cv.enum(OVERSAMPLING_OPTIONS, upper=True), |                     ), | ||||||
|         }), |                 } | ||||||
|     cv.Optional(CONF_HUMIDITY): |             ), | ||||||
|         sensor.sensor_schema(UNIT_PERCENT, ICON_WATER_PERCENT, 1).extend({ |             cv.Optional(CONF_PRESSURE): sensor.sensor_schema( | ||||||
|             cv.Optional(CONF_OVERSAMPLING, default='16X'): |                 UNIT_HECTOPASCAL, ICON_EMPTY, 1, DEVICE_CLASS_PRESSURE | ||||||
|                 cv.enum(OVERSAMPLING_OPTIONS, upper=True), |             ).extend( | ||||||
|         }), |                 { | ||||||
|     cv.Optional(CONF_IIR_FILTER, default='OFF'): cv.enum(IIR_FILTER_OPTIONS, upper=True), |                     cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum( | ||||||
| }).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x77)) |                         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): | def to_code(config): | ||||||
|   | |||||||
| @@ -2,64 +2,116 @@ import esphome.codegen as cg | |||||||
| import esphome.config_validation as cv | import esphome.config_validation as cv | ||||||
| from esphome import core | from esphome import core | ||||||
| from esphome.components import i2c, sensor | from esphome.components import i2c, sensor | ||||||
| from esphome.const import CONF_DURATION, CONF_GAS_RESISTANCE, CONF_HEATER, \ | from esphome.const import ( | ||||||
|     CONF_HUMIDITY, CONF_ID, CONF_IIR_FILTER, CONF_OVERSAMPLING, CONF_PRESSURE, \ |     CONF_DURATION, | ||||||
|     CONF_TEMPERATURE, UNIT_OHM, ICON_GAS_CYLINDER, UNIT_CELSIUS, \ |     CONF_GAS_RESISTANCE, | ||||||
|     ICON_THERMOMETER, UNIT_HECTOPASCAL, ICON_GAUGE, ICON_WATER_PERCENT, UNIT_PERCENT |     CONF_HEATER, | ||||||
|  |     CONF_HUMIDITY, | ||||||
|  |     CONF_ID, | ||||||
|  |     CONF_IIR_FILTER, | ||||||
|  |     CONF_OVERSAMPLING, | ||||||
|  |     CONF_PRESSURE, | ||||||
|  |     CONF_TEMPERATURE, | ||||||
|  |     DEVICE_CLASS_EMPTY, | ||||||
|  |     DEVICE_CLASS_HUMIDITY, | ||||||
|  |     DEVICE_CLASS_PRESSURE, | ||||||
|  |     DEVICE_CLASS_TEMPERATURE, | ||||||
|  |     UNIT_OHM, | ||||||
|  |     ICON_GAS_CYLINDER, | ||||||
|  |     UNIT_CELSIUS, | ||||||
|  |     ICON_EMPTY, | ||||||
|  |     UNIT_HECTOPASCAL, | ||||||
|  |     UNIT_PERCENT, | ||||||
|  | ) | ||||||
|  |  | ||||||
| DEPENDENCIES = ['i2c'] | DEPENDENCIES = ["i2c"] | ||||||
|  |  | ||||||
| bme680_ns = cg.esphome_ns.namespace('bme680') | bme680_ns = cg.esphome_ns.namespace("bme680") | ||||||
| BME680Oversampling = bme680_ns.enum('BME680Oversampling') | BME680Oversampling = bme680_ns.enum("BME680Oversampling") | ||||||
| OVERSAMPLING_OPTIONS = { | OVERSAMPLING_OPTIONS = { | ||||||
|     'NONE': BME680Oversampling.BME680_OVERSAMPLING_NONE, |     "NONE": BME680Oversampling.BME680_OVERSAMPLING_NONE, | ||||||
|     '1X': BME680Oversampling.BME680_OVERSAMPLING_1X, |     "1X": BME680Oversampling.BME680_OVERSAMPLING_1X, | ||||||
|     '2X': BME680Oversampling.BME680_OVERSAMPLING_2X, |     "2X": BME680Oversampling.BME680_OVERSAMPLING_2X, | ||||||
|     '4X': BME680Oversampling.BME680_OVERSAMPLING_4X, |     "4X": BME680Oversampling.BME680_OVERSAMPLING_4X, | ||||||
|     '8X': BME680Oversampling.BME680_OVERSAMPLING_8X, |     "8X": BME680Oversampling.BME680_OVERSAMPLING_8X, | ||||||
|     '16X': BME680Oversampling.BME680_OVERSAMPLING_16X, |     "16X": BME680Oversampling.BME680_OVERSAMPLING_16X, | ||||||
| } | } | ||||||
|  |  | ||||||
| BME680IIRFilter = bme680_ns.enum('BME680IIRFilter') | BME680IIRFilter = bme680_ns.enum("BME680IIRFilter") | ||||||
| IIR_FILTER_OPTIONS = { | IIR_FILTER_OPTIONS = { | ||||||
|     'OFF': BME680IIRFilter.BME680_IIR_FILTER_OFF, |     "OFF": BME680IIRFilter.BME680_IIR_FILTER_OFF, | ||||||
|     '1X': BME680IIRFilter.BME680_IIR_FILTER_1X, |     "1X": BME680IIRFilter.BME680_IIR_FILTER_1X, | ||||||
|     '3X': BME680IIRFilter.BME680_IIR_FILTER_3X, |     "3X": BME680IIRFilter.BME680_IIR_FILTER_3X, | ||||||
|     '7X': BME680IIRFilter.BME680_IIR_FILTER_7X, |     "7X": BME680IIRFilter.BME680_IIR_FILTER_7X, | ||||||
|     '15X': BME680IIRFilter.BME680_IIR_FILTER_15X, |     "15X": BME680IIRFilter.BME680_IIR_FILTER_15X, | ||||||
|     '31X': BME680IIRFilter.BME680_IIR_FILTER_31X, |     "31X": BME680IIRFilter.BME680_IIR_FILTER_31X, | ||||||
|     '63X': BME680IIRFilter.BME680_IIR_FILTER_63X, |     "63X": BME680IIRFilter.BME680_IIR_FILTER_63X, | ||||||
|     '127X': BME680IIRFilter.BME680_IIR_FILTER_127X, |     "127X": BME680IIRFilter.BME680_IIR_FILTER_127X, | ||||||
| } | } | ||||||
|  |  | ||||||
| BME680Component = bme680_ns.class_('BME680Component', cg.PollingComponent, i2c.I2CDevice) | BME680Component = bme680_ns.class_( | ||||||
|  |     "BME680Component", cg.PollingComponent, i2c.I2CDevice | ||||||
|  | ) | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = cv.Schema({ | CONFIG_SCHEMA = ( | ||||||
|     cv.GenerateID(): cv.declare_id(BME680Component), |     cv.Schema( | ||||||
|     cv.Optional(CONF_TEMPERATURE): |         { | ||||||
|         sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 1).extend({ |             cv.GenerateID(): cv.declare_id(BME680Component), | ||||||
|             cv.Optional(CONF_OVERSAMPLING, default='16X'): |             cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( | ||||||
|                 cv.enum(OVERSAMPLING_OPTIONS, upper=True), |                 UNIT_CELSIUS, ICON_EMPTY, 1, DEVICE_CLASS_TEMPERATURE | ||||||
|         }), |             ).extend( | ||||||
|     cv.Optional(CONF_PRESSURE): |                 { | ||||||
|         sensor.sensor_schema(UNIT_HECTOPASCAL, ICON_GAUGE, 1).extend({ |                     cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum( | ||||||
|             cv.Optional(CONF_OVERSAMPLING, default='16X'): |                         OVERSAMPLING_OPTIONS, upper=True | ||||||
|                 cv.enum(OVERSAMPLING_OPTIONS, upper=True), |                     ), | ||||||
|         }), |                 } | ||||||
|     cv.Optional(CONF_HUMIDITY): |             ), | ||||||
|         sensor.sensor_schema(UNIT_PERCENT, ICON_WATER_PERCENT, 1).extend({ |             cv.Optional(CONF_PRESSURE): sensor.sensor_schema( | ||||||
|             cv.Optional(CONF_OVERSAMPLING, default='16X'): |                 UNIT_HECTOPASCAL, ICON_EMPTY, 1, DEVICE_CLASS_PRESSURE | ||||||
|                 cv.enum(OVERSAMPLING_OPTIONS, upper=True), |             ).extend( | ||||||
|         }), |                 { | ||||||
|     cv.Optional(CONF_GAS_RESISTANCE): |                     cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum( | ||||||
|         sensor.sensor_schema(UNIT_OHM, ICON_GAS_CYLINDER, 1), |                         OVERSAMPLING_OPTIONS, upper=True | ||||||
|     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.Optional(CONF_HUMIDITY): sensor.sensor_schema( | ||||||
|             cv.positive_time_period_milliseconds, cv.Range(max=core.TimePeriod(milliseconds=4032))) |                 UNIT_PERCENT, ICON_EMPTY, 1, DEVICE_CLASS_HUMIDITY | ||||||
|     }), cv.has_at_least_one_key(CONF_TEMPERATURE, CONF_DURATION))), |             ).extend( | ||||||
| }).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x76)) |                 { | ||||||
|  |                     cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum( | ||||||
|  |                         OVERSAMPLING_OPTIONS, upper=True | ||||||
|  |                     ), | ||||||
|  |                 } | ||||||
|  |             ), | ||||||
|  |             cv.Optional(CONF_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): | def to_code(config): | ||||||
|   | |||||||
| @@ -1,19 +1,39 @@ | |||||||
| import esphome.codegen as cg | import esphome.codegen as cg | ||||||
| import esphome.config_validation as cv | import esphome.config_validation as cv | ||||||
| from esphome.components import i2c, sensor | from esphome.components import i2c, sensor | ||||||
| from esphome.const import CONF_ID, CONF_PRESSURE, CONF_TEMPERATURE, \ | from esphome.const import ( | ||||||
|     UNIT_CELSIUS, ICON_THERMOMETER, ICON_GAUGE, UNIT_HECTOPASCAL |     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') | bmp085_ns = cg.esphome_ns.namespace("bmp085") | ||||||
| BMP085Component = bmp085_ns.class_('BMP085Component', cg.PollingComponent, i2c.I2CDevice) | BMP085Component = bmp085_ns.class_( | ||||||
|  |     "BMP085Component", cg.PollingComponent, i2c.I2CDevice | ||||||
|  | ) | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = cv.Schema({ | CONFIG_SCHEMA = ( | ||||||
|     cv.GenerateID(): cv.declare_id(BMP085Component), |     cv.Schema( | ||||||
|     cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 1), |         { | ||||||
|     cv.Optional(CONF_PRESSURE): sensor.sensor_schema(UNIT_HECTOPASCAL, ICON_GAUGE, 1), |             cv.GenerateID(): cv.declare_id(BMP085Component), | ||||||
| }).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x77)) |             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): | def to_code(config): | ||||||
|   | |||||||
| @@ -1,44 +1,75 @@ | |||||||
| import esphome.codegen as cg | import esphome.codegen as cg | ||||||
| import esphome.config_validation as cv | import esphome.config_validation as cv | ||||||
| from esphome.components import i2c, sensor | from esphome.components import i2c, sensor | ||||||
| from esphome.const import CONF_ID, CONF_PRESSURE, CONF_TEMPERATURE, \ | from esphome.const import ( | ||||||
|     UNIT_CELSIUS, ICON_THERMOMETER, ICON_GAUGE, UNIT_HECTOPASCAL, \ |     CONF_ID, | ||||||
|     CONF_IIR_FILTER, CONF_OVERSAMPLING |     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') | bmp280_ns = cg.esphome_ns.namespace("bmp280") | ||||||
| BMP280Oversampling = bmp280_ns.enum('BMP280Oversampling') | BMP280Oversampling = bmp280_ns.enum("BMP280Oversampling") | ||||||
| OVERSAMPLING_OPTIONS = { | OVERSAMPLING_OPTIONS = { | ||||||
|     'NONE': BMP280Oversampling.BMP280_OVERSAMPLING_NONE, |     "NONE": BMP280Oversampling.BMP280_OVERSAMPLING_NONE, | ||||||
|     '1X': BMP280Oversampling.BMP280_OVERSAMPLING_1X, |     "1X": BMP280Oversampling.BMP280_OVERSAMPLING_1X, | ||||||
|     '2X': BMP280Oversampling.BMP280_OVERSAMPLING_2X, |     "2X": BMP280Oversampling.BMP280_OVERSAMPLING_2X, | ||||||
|     '4X': BMP280Oversampling.BMP280_OVERSAMPLING_4X, |     "4X": BMP280Oversampling.BMP280_OVERSAMPLING_4X, | ||||||
|     '8X': BMP280Oversampling.BMP280_OVERSAMPLING_8X, |     "8X": BMP280Oversampling.BMP280_OVERSAMPLING_8X, | ||||||
|     '16X': BMP280Oversampling.BMP280_OVERSAMPLING_16X, |     "16X": BMP280Oversampling.BMP280_OVERSAMPLING_16X, | ||||||
| } | } | ||||||
|  |  | ||||||
| BMP280IIRFilter = bmp280_ns.enum('BMP280IIRFilter') | BMP280IIRFilter = bmp280_ns.enum("BMP280IIRFilter") | ||||||
| IIR_FILTER_OPTIONS = { | IIR_FILTER_OPTIONS = { | ||||||
|     'OFF': BMP280IIRFilter.BMP280_IIR_FILTER_OFF, |     "OFF": BMP280IIRFilter.BMP280_IIR_FILTER_OFF, | ||||||
|     '2X': BMP280IIRFilter.BMP280_IIR_FILTER_2X, |     "2X": BMP280IIRFilter.BMP280_IIR_FILTER_2X, | ||||||
|     '4X': BMP280IIRFilter.BMP280_IIR_FILTER_4X, |     "4X": BMP280IIRFilter.BMP280_IIR_FILTER_4X, | ||||||
|     '8X': BMP280IIRFilter.BMP280_IIR_FILTER_8X, |     "8X": BMP280IIRFilter.BMP280_IIR_FILTER_8X, | ||||||
|     '16X': BMP280IIRFilter.BMP280_IIR_FILTER_16X, |     "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({ | CONFIG_SCHEMA = ( | ||||||
|     cv.GenerateID(): cv.declare_id(BMP280Component), |     cv.Schema( | ||||||
|     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.GenerateID(): cv.declare_id(BMP280Component), | ||||||
|     }), |             cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( | ||||||
|     cv.Optional(CONF_PRESSURE): sensor.sensor_schema(UNIT_HECTOPASCAL, ICON_GAUGE, 1).extend({ |                 UNIT_CELSIUS, ICON_EMPTY, 1, DEVICE_CLASS_TEMPERATURE | ||||||
|         cv.Optional(CONF_OVERSAMPLING, default='16X'): cv.enum(OVERSAMPLING_OPTIONS, upper=True), |             ).extend( | ||||||
|     }), |                 { | ||||||
|     cv.Optional(CONF_IIR_FILTER, default='OFF'): cv.enum(IIR_FILTER_OPTIONS, upper=True), |                     cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum( | ||||||
| }).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x77)) |                         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): | def to_code(config): | ||||||
|   | |||||||
| @@ -4,68 +4,82 @@ from esphome import automation | |||||||
| from esphome.core import CORE, coroutine | from esphome.core import CORE, coroutine | ||||||
| from esphome.const import CONF_ID, CONF_TRIGGER_ID, CONF_DATA | from esphome.const import CONF_ID, CONF_TRIGGER_ID, CONF_DATA | ||||||
|  |  | ||||||
| CODEOWNERS = ['@mvturnho', '@danielschramm'] | CODEOWNERS = ["@mvturnho", "@danielschramm"] | ||||||
| IS_PLATFORM_COMPONENT = True | IS_PLATFORM_COMPONENT = True | ||||||
|  |  | ||||||
| CONF_CAN_ID = 'can_id' | CONF_CAN_ID = "can_id" | ||||||
| CONF_USE_EXTENDED_ID = 'use_extended_id' | CONF_USE_EXTENDED_ID = "use_extended_id" | ||||||
| CONF_CANBUS_ID = 'canbus_id' | CONF_CANBUS_ID = "canbus_id" | ||||||
| CONF_BIT_RATE = 'bit_rate' | CONF_BIT_RATE = "bit_rate" | ||||||
| CONF_ON_FRAME = 'on_frame' | CONF_ON_FRAME = "on_frame" | ||||||
| CONF_CANBUS_SEND = 'canbus.send' |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def validate_id(id_value, id_ext): | def validate_id(id_value, id_ext): | ||||||
|     if not 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)") |             raise cv.Invalid("Standard IDs must be 11 Bit (0x000-0x7ff / 0-2047)") | ||||||
|  |  | ||||||
|  |  | ||||||
| def validate_raw_data(value): | def validate_raw_data(value): | ||||||
|     if isinstance(value, str): |     if isinstance(value, str): | ||||||
|         return value.encode('utf-8') |         return value.encode("utf-8") | ||||||
|     if isinstance(value, list): |     if isinstance(value, list): | ||||||
|         return cv.Schema([cv.hex_uint8_t])(value) |         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') | canbus_ns = cg.esphome_ns.namespace("canbus") | ||||||
| CanbusComponent = canbus_ns.class_('CanbusComponent', cg.Component) | CanbusComponent = canbus_ns.class_("CanbusComponent", cg.Component) | ||||||
| CanbusTrigger = canbus_ns.class_('CanbusTrigger', | CanbusTrigger = canbus_ns.class_( | ||||||
|                                  automation.Trigger.template(cg.std_vector.template(cg.uint8)), |     "CanbusTrigger", | ||||||
|                                  cg.Component) |     automation.Trigger.template(cg.std_vector.template(cg.uint8)), | ||||||
| CanSpeed = canbus_ns.enum('CAN_SPEED') |     cg.Component, | ||||||
|  | ) | ||||||
|  | CanSpeed = canbus_ns.enum("CAN_SPEED") | ||||||
|  |  | ||||||
| CAN_SPEEDS = { | CAN_SPEEDS = { | ||||||
|     '5KBPS': CanSpeed.CAN_5KBPS, |     "5KBPS": CanSpeed.CAN_5KBPS, | ||||||
|     '10KBPS': CanSpeed.CAN_10KBPS, |     "10KBPS": CanSpeed.CAN_10KBPS, | ||||||
|     '20KBPS': CanSpeed.CAN_20KBPS, |     "20KBPS": CanSpeed.CAN_20KBPS, | ||||||
|     '31K25BPS': CanSpeed.CAN_31K25BPS, |     "31K25BPS": CanSpeed.CAN_31K25BPS, | ||||||
|     '33KBPS': CanSpeed.CAN_33KBPS, |     "33KBPS": CanSpeed.CAN_33KBPS, | ||||||
|     '40KBPS': CanSpeed.CAN_40KBPS, |     "40KBPS": CanSpeed.CAN_40KBPS, | ||||||
|     '50KBPS': CanSpeed.CAN_50KBPS, |     "50KBPS": CanSpeed.CAN_50KBPS, | ||||||
|     '80KBPS': CanSpeed.CAN_80KBPS, |     "80KBPS": CanSpeed.CAN_80KBPS, | ||||||
|     '83K3BPS': CanSpeed.CAN_83K3BPS, |     "83K3BPS": CanSpeed.CAN_83K3BPS, | ||||||
|     '95KBPS': CanSpeed.CAN_95KBPS, |     "95KBPS": CanSpeed.CAN_95KBPS, | ||||||
|     '100KBPS': CanSpeed.CAN_100KBPS, |     "100KBPS": CanSpeed.CAN_100KBPS, | ||||||
|     '125KBPS': CanSpeed.CAN_125KBPS, |     "125KBPS": CanSpeed.CAN_125KBPS, | ||||||
|     '200KBPS': CanSpeed.CAN_200KBPS, |     "200KBPS": CanSpeed.CAN_200KBPS, | ||||||
|     '250KBPS': CanSpeed.CAN_250KBPS, |     "250KBPS": CanSpeed.CAN_250KBPS, | ||||||
|     '500KBPS': CanSpeed.CAN_500KBPS, |     "500KBPS": CanSpeed.CAN_500KBPS, | ||||||
|     '1000KBPS': CanSpeed.CAN_1000KBPS, |     "1000KBPS": CanSpeed.CAN_1000KBPS, | ||||||
| } | } | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = cv.Schema({ | CANBUS_SCHEMA = cv.Schema( | ||||||
|     cv.GenerateID(): cv.declare_id(CanbusComponent), |     { | ||||||
|     cv.Required(CONF_CAN_ID): cv.int_range(min=0, max=0x1fffffff), |         cv.GenerateID(): cv.declare_id(CanbusComponent), | ||||||
|     cv.Optional(CONF_BIT_RATE, default='125KBPS'): cv.enum(CAN_SPEEDS, upper=True), |         cv.Required(CONF_CAN_ID): cv.int_range(min=0, max=0x1FFFFFFF), | ||||||
|     cv.Optional(CONF_USE_EXTENDED_ID, default=False): cv.boolean, |         cv.Optional(CONF_BIT_RATE, default="125KBPS"): cv.enum(CAN_SPEEDS, upper=True), | ||||||
|     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_USE_EXTENDED_ID, default=False): cv.boolean, | ||||||
|     }), |         cv.Optional(CONF_ON_FRAME): automation.validate_automation( | ||||||
| }).extend(cv.COMPONENT_SCHEMA) |             { | ||||||
|  |                 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 | @coroutine | ||||||
| @@ -82,7 +96,9 @@ def setup_canbus_core_(var, config): | |||||||
|         validate_id(can_id, ext_id) |         validate_id(can_id, ext_id) | ||||||
|         trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var, can_id, ext_id) |         trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var, can_id, ext_id) | ||||||
|         yield cg.register_component(trigger, conf) |         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 | @coroutine | ||||||
| @@ -93,14 +109,19 @@ def register_canbus(var, config): | |||||||
|  |  | ||||||
|  |  | ||||||
| # Actions | # Actions | ||||||
| @automation.register_action(CONF_CANBUS_SEND, | @automation.register_action( | ||||||
|                             canbus_ns.class_('CanbusSendAction', automation.Action), |     "canbus.send", | ||||||
|                             cv.maybe_simple_value({ |     canbus_ns.class_("CanbusSendAction", automation.Action), | ||||||
|                                 cv.GenerateID(CONF_CANBUS_ID): cv.use_id(CanbusComponent), |     cv.maybe_simple_value( | ||||||
|                                 cv.Optional(CONF_CAN_ID): cv.int_range(min=0, max=0x1fffffff), |         { | ||||||
|                                 cv.Optional(CONF_USE_EXTENDED_ID, default=False): cv.boolean, |             cv.GenerateID(CONF_CANBUS_ID): cv.use_id(CanbusComponent), | ||||||
|                                 cv.Required(CONF_DATA): cv.templatable(validate_raw_data), |             cv.Optional(CONF_CAN_ID): cv.int_range(min=0, max=0x1FFFFFFF), | ||||||
|                             }, key=CONF_DATA)) |             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): | def canbus_action_to_code(config, action_id, template_arg, args): | ||||||
|     validate_id(config[CONF_CAN_ID], config[CONF_USE_EXTENDED_ID]) |     validate_id(config[CONF_CAN_ID], config[CONF_USE_EXTENDED_ID]) | ||||||
|     var = cg.new_Pvariable(action_id, template_arg) |     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) |         can_id = yield cg.templatable(config[CONF_CAN_ID], args, cg.uint32) | ||||||
|         cg.add(var.set_can_id(can_id)) |         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)) |     cg.add(var.set_use_extended_id(use_extended_id)) | ||||||
|  |  | ||||||
|     data = config[CONF_DATA] |     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.const import CONF_ID | ||||||
| from esphome.core import coroutine_with_priority | from esphome.core import coroutine_with_priority | ||||||
|  |  | ||||||
| AUTO_LOAD = ['web_server_base'] | AUTO_LOAD = ["web_server_base"] | ||||||
| DEPENDENCIES = ['wifi'] | DEPENDENCIES = ["wifi"] | ||||||
| CODEOWNERS = ['@OttoWinter'] | CODEOWNERS = ["@OttoWinter"] | ||||||
|  |  | ||||||
| captive_portal_ns = cg.esphome_ns.namespace('captive_portal') | captive_portal_ns = cg.esphome_ns.namespace("captive_portal") | ||||||
| CaptivePortal = captive_portal_ns.class_('CaptivePortal', cg.Component) | CaptivePortal = captive_portal_ns.class_("CaptivePortal", cg.Component) | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = cv.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), |         cv.GenerateID(): cv.declare_id(CaptivePortal), | ||||||
| }).extend(cv.COMPONENT_SCHEMA) |         cv.GenerateID(CONF_WEB_SERVER_BASE_ID): cv.use_id( | ||||||
|  |             web_server_base.WebServerBase | ||||||
|  |         ), | ||||||
|  |     } | ||||||
|  | ).extend(cv.COMPONENT_SCHEMA) | ||||||
|  |  | ||||||
|  |  | ||||||
| @coroutine_with_priority(64.0) | @coroutine_with_priority(64.0) | ||||||
| @@ -24,4 +28,4 @@ def to_code(config): | |||||||
|  |  | ||||||
|     var = cg.new_Pvariable(config[CONF_ID], paren) |     var = cg.new_Pvariable(config[CONF_ID], paren) | ||||||
|     yield cg.register_component(var, config) |     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.codegen as cg | ||||||
| import esphome.config_validation as cv | import esphome.config_validation as cv | ||||||
| from esphome.components import i2c, sensor | from esphome.components import i2c, sensor | ||||||
| from esphome.const import CONF_ID, ICON_RADIATOR, UNIT_PARTS_PER_MILLION, \ | from esphome.const import ( | ||||||
|     UNIT_PARTS_PER_BILLION, CONF_TEMPERATURE, CONF_HUMIDITY, ICON_MOLECULE_CO2 |     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') | ccs811_ns = cg.esphome_ns.namespace("ccs811") | ||||||
| CCS811Component = ccs811_ns.class_('CCS811Component', cg.PollingComponent, i2c.I2CDevice) | CCS811Component = ccs811_ns.class_( | ||||||
|  |     "CCS811Component", cg.PollingComponent, i2c.I2CDevice | ||||||
|  | ) | ||||||
|  |  | ||||||
| CONF_ECO2 = 'eco2' | CONF_ECO2 = "eco2" | ||||||
| CONF_TVOC = 'tvoc' | CONF_BASELINE = "baseline" | ||||||
| CONF_BASELINE = 'baseline' |  | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = cv.Schema({ | CONFIG_SCHEMA = ( | ||||||
|     cv.GenerateID(): cv.declare_id(CCS811Component), |     cv.Schema( | ||||||
|     cv.Required(CONF_ECO2): sensor.sensor_schema(UNIT_PARTS_PER_MILLION, ICON_MOLECULE_CO2, |         { | ||||||
|                                                  0), |             cv.GenerateID(): cv.declare_id(CCS811Component), | ||||||
|     cv.Required(CONF_TVOC): sensor.sensor_schema(UNIT_PARTS_PER_BILLION, ICON_RADIATOR, 0), |             cv.Required(CONF_ECO2): sensor.sensor_schema( | ||||||
|  |                 UNIT_PARTS_PER_MILLION, ICON_MOLECULE_CO2, 0, DEVICE_CLASS_EMPTY | ||||||
|     cv.Optional(CONF_BASELINE): cv.hex_uint16_t, |             ), | ||||||
|     cv.Optional(CONF_TEMPERATURE): cv.use_id(sensor.Sensor), |             cv.Required(CONF_TVOC): sensor.sensor_schema( | ||||||
|     cv.Optional(CONF_HUMIDITY): cv.use_id(sensor.Sensor), |                 UNIT_PARTS_PER_BILLION, ICON_RADIATOR, 0, DEVICE_CLASS_EMPTY | ||||||
| }).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x5A)) |             ), | ||||||
|  |             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): | def to_code(config): | ||||||
|   | |||||||
| @@ -2,70 +2,87 @@ import esphome.codegen as cg | |||||||
| import esphome.config_validation as cv | import esphome.config_validation as cv | ||||||
| from esphome import automation | from esphome import automation | ||||||
| from esphome.components import mqtt | from esphome.components import mqtt | ||||||
| from esphome.const import CONF_AWAY, CONF_ID, CONF_INTERNAL, CONF_MAX_TEMPERATURE, \ | from esphome.const import ( | ||||||
|     CONF_MIN_TEMPERATURE, CONF_MODE, CONF_TARGET_TEMPERATURE, \ |     CONF_AWAY, | ||||||
|     CONF_TARGET_TEMPERATURE_HIGH, CONF_TARGET_TEMPERATURE_LOW, CONF_TEMPERATURE_STEP, CONF_VISUAL, \ |     CONF_ID, | ||||||
|     CONF_MQTT_ID, CONF_NAME, CONF_FAN_MODE, CONF_SWING_MODE |     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 | from esphome.core import CORE, coroutine, coroutine_with_priority | ||||||
|  |  | ||||||
| IS_PLATFORM_COMPONENT = True | IS_PLATFORM_COMPONENT = True | ||||||
|  |  | ||||||
| CODEOWNERS = ['@esphome/core'] | CODEOWNERS = ["@esphome/core"] | ||||||
| climate_ns = cg.esphome_ns.namespace('climate') | climate_ns = cg.esphome_ns.namespace("climate") | ||||||
|  |  | ||||||
| Climate = climate_ns.class_('Climate', cg.Nameable) | Climate = climate_ns.class_("Climate", cg.Nameable) | ||||||
| ClimateCall = climate_ns.class_('ClimateCall') | ClimateCall = climate_ns.class_("ClimateCall") | ||||||
| ClimateTraits = climate_ns.class_('ClimateTraits') | ClimateTraits = climate_ns.class_("ClimateTraits") | ||||||
|  |  | ||||||
| ClimateMode = climate_ns.enum('ClimateMode') | ClimateMode = climate_ns.enum("ClimateMode") | ||||||
| CLIMATE_MODES = { | CLIMATE_MODES = { | ||||||
|     'OFF': ClimateMode.CLIMATE_MODE_OFF, |     "OFF": ClimateMode.CLIMATE_MODE_OFF, | ||||||
|     'AUTO': ClimateMode.CLIMATE_MODE_AUTO, |     "AUTO": ClimateMode.CLIMATE_MODE_AUTO, | ||||||
|     'COOL': ClimateMode.CLIMATE_MODE_COOL, |     "COOL": ClimateMode.CLIMATE_MODE_COOL, | ||||||
|     'HEAT': ClimateMode.CLIMATE_MODE_HEAT, |     "HEAT": ClimateMode.CLIMATE_MODE_HEAT, | ||||||
|     'DRY': ClimateMode.CLIMATE_MODE_DRY, |     "DRY": ClimateMode.CLIMATE_MODE_DRY, | ||||||
|     'FAN_ONLY': ClimateMode.CLIMATE_MODE_FAN_ONLY, |     "FAN_ONLY": ClimateMode.CLIMATE_MODE_FAN_ONLY, | ||||||
| } | } | ||||||
| validate_climate_mode = cv.enum(CLIMATE_MODES, upper=True) | validate_climate_mode = cv.enum(CLIMATE_MODES, upper=True) | ||||||
|  |  | ||||||
| ClimateFanMode = climate_ns.enum('ClimateFanMode') | ClimateFanMode = climate_ns.enum("ClimateFanMode") | ||||||
| CLIMATE_FAN_MODES = { | CLIMATE_FAN_MODES = { | ||||||
|     'ON': ClimateFanMode.CLIMATE_FAN_ON, |     "ON": ClimateFanMode.CLIMATE_FAN_ON, | ||||||
|     'OFF': ClimateFanMode.CLIMATE_FAN_OFF, |     "OFF": ClimateFanMode.CLIMATE_FAN_OFF, | ||||||
|     'AUTO': ClimateFanMode.CLIMATE_FAN_AUTO, |     "AUTO": ClimateFanMode.CLIMATE_FAN_AUTO, | ||||||
|     'LOW': ClimateFanMode.CLIMATE_FAN_LOW, |     "LOW": ClimateFanMode.CLIMATE_FAN_LOW, | ||||||
|     'MEDIUM': ClimateFanMode.CLIMATE_FAN_MEDIUM, |     "MEDIUM": ClimateFanMode.CLIMATE_FAN_MEDIUM, | ||||||
|     'HIGH': ClimateFanMode.CLIMATE_FAN_HIGH, |     "HIGH": ClimateFanMode.CLIMATE_FAN_HIGH, | ||||||
|     'MIDDLE': ClimateFanMode.CLIMATE_FAN_MIDDLE, |     "MIDDLE": ClimateFanMode.CLIMATE_FAN_MIDDLE, | ||||||
|     'FOCUS': ClimateFanMode.CLIMATE_FAN_FOCUS, |     "FOCUS": ClimateFanMode.CLIMATE_FAN_FOCUS, | ||||||
|     'DIFFUSE': ClimateFanMode.CLIMATE_FAN_DIFFUSE, |     "DIFFUSE": ClimateFanMode.CLIMATE_FAN_DIFFUSE, | ||||||
| } | } | ||||||
|  |  | ||||||
| validate_climate_fan_mode = cv.enum(CLIMATE_FAN_MODES, upper=True) | validate_climate_fan_mode = cv.enum(CLIMATE_FAN_MODES, upper=True) | ||||||
|  |  | ||||||
| ClimateSwingMode = climate_ns.enum('ClimateSwingMode') | ClimateSwingMode = climate_ns.enum("ClimateSwingMode") | ||||||
| CLIMATE_SWING_MODES = { | CLIMATE_SWING_MODES = { | ||||||
|     'OFF': ClimateSwingMode.CLIMATE_SWING_OFF, |     "OFF": ClimateSwingMode.CLIMATE_SWING_OFF, | ||||||
|     'BOTH': ClimateSwingMode.CLIMATE_SWING_BOTH, |     "BOTH": ClimateSwingMode.CLIMATE_SWING_BOTH, | ||||||
|     'VERTICAL': ClimateSwingMode.CLIMATE_SWING_VERTICAL, |     "VERTICAL": ClimateSwingMode.CLIMATE_SWING_VERTICAL, | ||||||
|     'HORIZONTAL': ClimateSwingMode.CLIMATE_SWING_HORIZONTAL, |     "HORIZONTAL": ClimateSwingMode.CLIMATE_SWING_HORIZONTAL, | ||||||
| } | } | ||||||
|  |  | ||||||
| validate_climate_swing_mode = cv.enum(CLIMATE_SWING_MODES, upper=True) | validate_climate_swing_mode = cv.enum(CLIMATE_SWING_MODES, upper=True) | ||||||
|  |  | ||||||
| # Actions | # Actions | ||||||
| ControlAction = climate_ns.class_('ControlAction', automation.Action) | ControlAction = climate_ns.class_("ControlAction", automation.Action) | ||||||
|  |  | ||||||
| CLIMATE_SCHEMA = cv.MQTT_COMMAND_COMPONENT_SCHEMA.extend({ | 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.GenerateID(): cv.declare_id(Climate), | ||||||
|     cv.Optional(CONF_VISUAL, default={}): cv.Schema({ |         cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTClimateComponent), | ||||||
|         cv.Optional(CONF_MIN_TEMPERATURE): cv.temperature, |         cv.Optional(CONF_VISUAL, default={}): cv.Schema( | ||||||
|         cv.Optional(CONF_MAX_TEMPERATURE): cv.temperature, |             { | ||||||
|         cv.Optional(CONF_TEMPERATURE_STEP): cv.temperature, |                 cv.Optional(CONF_MIN_TEMPERATURE): cv.temperature, | ||||||
|     }), |                 cv.Optional(CONF_MAX_TEMPERATURE): cv.temperature, | ||||||
|     # TODO: MQTT topic options |                 cv.Optional(CONF_TEMPERATURE_STEP): cv.temperature, | ||||||
| }) |             } | ||||||
|  |         ), | ||||||
|  |         # TODO: MQTT topic options | ||||||
|  |     } | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
| @coroutine | @coroutine | ||||||
| @@ -94,19 +111,23 @@ def register_climate(var, config): | |||||||
|     yield setup_climate_core_(var, config) |     yield setup_climate_core_(var, config) | ||||||
|  |  | ||||||
|  |  | ||||||
| CLIMATE_CONTROL_ACTION_SCHEMA = cv.Schema({ | CLIMATE_CONTROL_ACTION_SCHEMA = cv.Schema( | ||||||
|     cv.Required(CONF_ID): cv.use_id(Climate), |     { | ||||||
|     cv.Optional(CONF_MODE): cv.templatable(validate_climate_mode), |         cv.Required(CONF_ID): cv.use_id(Climate), | ||||||
|     cv.Optional(CONF_TARGET_TEMPERATURE): cv.templatable(cv.temperature), |         cv.Optional(CONF_MODE): cv.templatable(validate_climate_mode), | ||||||
|     cv.Optional(CONF_TARGET_TEMPERATURE_LOW): cv.templatable(cv.temperature), |         cv.Optional(CONF_TARGET_TEMPERATURE): cv.templatable(cv.temperature), | ||||||
|     cv.Optional(CONF_TARGET_TEMPERATURE_HIGH): cv.templatable(cv.temperature), |         cv.Optional(CONF_TARGET_TEMPERATURE_LOW): cv.templatable(cv.temperature), | ||||||
|     cv.Optional(CONF_AWAY): cv.templatable(cv.boolean), |         cv.Optional(CONF_TARGET_TEMPERATURE_HIGH): cv.templatable(cv.temperature), | ||||||
|     cv.Optional(CONF_FAN_MODE): cv.templatable(validate_climate_fan_mode), |         cv.Optional(CONF_AWAY): cv.templatable(cv.boolean), | ||||||
|     cv.Optional(CONF_SWING_MODE): cv.templatable(validate_climate_swing_mode), |         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): | def climate_control_to_code(config, action_id, template_arg, args): | ||||||
|     paren = yield cg.get_variable(config[CONF_ID]) |     paren = yield cg.get_variable(config[CONF_ID]) | ||||||
|     var = cg.new_Pvariable(action_id, template_arg, paren) |     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) |         template_ = yield cg.templatable(config[CONF_TARGET_TEMPERATURE], args, float) | ||||||
|         cg.add(var.set_target_temperature(template_)) |         cg.add(var.set_target_temperature(template_)) | ||||||
|     if CONF_TARGET_TEMPERATURE_LOW in config: |     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_)) |         cg.add(var.set_target_temperature_low(template_)) | ||||||
|     if CONF_TARGET_TEMPERATURE_HIGH in config: |     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_)) |         cg.add(var.set_target_temperature_high(template_)) | ||||||
|     if CONF_AWAY in config: |     if CONF_AWAY in config: | ||||||
|         template_ = yield cg.templatable(config[CONF_AWAY], args, bool) |         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) |         template_ = yield cg.templatable(config[CONF_FAN_MODE], args, ClimateFanMode) | ||||||
|         cg.add(var.set_fan_mode(template_)) |         cg.add(var.set_fan_mode(template_)) | ||||||
|     if CONF_SWING_MODE in config: |     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_)) |         cg.add(var.set_swing_mode(template_)) | ||||||
|     yield var |     yield var | ||||||
|  |  | ||||||
|  |  | ||||||
| @coroutine_with_priority(100.0) | @coroutine_with_priority(100.0) | ||||||
| def to_code(config): | def to_code(config): | ||||||
|     cg.add_define('USE_CLIMATE') |     cg.add_define("USE_CLIMATE") | ||||||
|     cg.add_global(climate_ns.using) |     cg.add_global(climate_ns.using) | ||||||
|   | |||||||
| @@ -1,27 +1,42 @@ | |||||||
| import esphome.codegen as cg | import esphome.codegen as cg | ||||||
| import esphome.config_validation as cv | import esphome.config_validation as cv | ||||||
| from esphome.components import climate, remote_transmitter, remote_receiver, sensor, remote_base | from esphome.components import ( | ||||||
|  |     climate, | ||||||
|  |     remote_transmitter, | ||||||
|  |     remote_receiver, | ||||||
|  |     sensor, | ||||||
|  |     remote_base, | ||||||
|  | ) | ||||||
| from esphome.components.remote_base import CONF_RECEIVER_ID, CONF_TRANSMITTER_ID | from esphome.components.remote_base import CONF_RECEIVER_ID, CONF_TRANSMITTER_ID | ||||||
| from esphome.const import CONF_SUPPORTS_COOL, CONF_SUPPORTS_HEAT, CONF_SENSOR | from esphome.const import CONF_SUPPORTS_COOL, CONF_SUPPORTS_HEAT, CONF_SENSOR | ||||||
| from esphome.core import coroutine | from esphome.core import coroutine | ||||||
|  |  | ||||||
| AUTO_LOAD = ['sensor', 'remote_base'] | AUTO_LOAD = ["sensor", "remote_base"] | ||||||
| CODEOWNERS = ['@glmnet'] | CODEOWNERS = ["@glmnet"] | ||||||
|  |  | ||||||
| climate_ir_ns = cg.esphome_ns.namespace('climate_ir') | climate_ir_ns = cg.esphome_ns.namespace("climate_ir") | ||||||
| ClimateIR = climate_ir_ns.class_('ClimateIR', climate.Climate, cg.Component, | ClimateIR = climate_ir_ns.class_( | ||||||
|                                  remote_base.RemoteReceiverListener) |     "ClimateIR", climate.Climate, cg.Component, remote_base.RemoteReceiverListener | ||||||
|  | ) | ||||||
|  |  | ||||||
| CLIMATE_IR_SCHEMA = climate.CLIMATE_SCHEMA.extend({ | CLIMATE_IR_SCHEMA = climate.CLIMATE_SCHEMA.extend( | ||||||
|     cv.GenerateID(CONF_TRANSMITTER_ID): cv.use_id(remote_transmitter.RemoteTransmitterComponent), |     { | ||||||
|     cv.Optional(CONF_SUPPORTS_COOL, default=True): cv.boolean, |         cv.GenerateID(CONF_TRANSMITTER_ID): cv.use_id( | ||||||
|     cv.Optional(CONF_SUPPORTS_HEAT, default=True): cv.boolean, |             remote_transmitter.RemoteTransmitterComponent | ||||||
|     cv.Optional(CONF_SENSOR): cv.use_id(sensor.Sensor), |         ), | ||||||
| }).extend(cv.COMPONENT_SCHEMA) |         cv.Optional(CONF_SUPPORTS_COOL, default=True): cv.boolean, | ||||||
|  |         cv.Optional(CONF_SUPPORTS_HEAT, default=True): cv.boolean, | ||||||
|  |         cv.Optional(CONF_SENSOR): cv.use_id(sensor.Sensor), | ||||||
|  |     } | ||||||
|  | ).extend(cv.COMPONENT_SCHEMA) | ||||||
|  |  | ||||||
| CLIMATE_IR_WITH_RECEIVER_SCHEMA = CLIMATE_IR_SCHEMA.extend({ | CLIMATE_IR_WITH_RECEIVER_SCHEMA = CLIMATE_IR_SCHEMA.extend( | ||||||
|     cv.Optional(CONF_RECEIVER_ID): cv.use_id(remote_receiver.RemoteReceiverComponent), |     { | ||||||
| }) |         cv.Optional(CONF_RECEIVER_ID): cv.use_id( | ||||||
|  |             remote_receiver.RemoteReceiverComponent | ||||||
|  |         ), | ||||||
|  |     } | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
| @coroutine | @coroutine | ||||||
|   | |||||||
| @@ -3,16 +3,45 @@ import esphome.config_validation as cv | |||||||
| from esphome.components import climate_ir | from esphome.components import climate_ir | ||||||
| from esphome.const import CONF_ID | from esphome.const import CONF_ID | ||||||
|  |  | ||||||
| AUTO_LOAD = ['climate_ir'] | AUTO_LOAD = ["climate_ir"] | ||||||
|  |  | ||||||
| climate_ir_lg_ns = cg.esphome_ns.namespace('climate_ir_lg') | climate_ir_lg_ns = cg.esphome_ns.namespace("climate_ir_lg") | ||||||
| LgIrClimate = climate_ir_lg_ns.class_('LgIrClimate', climate_ir.ClimateIR) | LgIrClimate = climate_ir_lg_ns.class_("LgIrClimate", climate_ir.ClimateIR) | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend({ | CONF_HEADER_HIGH = "header_high" | ||||||
|     cv.GenerateID(): cv.declare_id(LgIrClimate), | CONF_HEADER_LOW = "header_low" | ||||||
| }) | CONF_BIT_HIGH = "bit_high" | ||||||
|  | CONF_BIT_ONE_LOW = "bit_one_low" | ||||||
|  | CONF_BIT_ZERO_LOW = "bit_zero_low" | ||||||
|  |  | ||||||
|  | CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend( | ||||||
|  |     { | ||||||
|  |         cv.GenerateID(): cv.declare_id(LgIrClimate), | ||||||
|  |         cv.Optional( | ||||||
|  |             CONF_HEADER_HIGH, default="8000us" | ||||||
|  |         ): cv.positive_time_period_microseconds, | ||||||
|  |         cv.Optional( | ||||||
|  |             CONF_HEADER_LOW, default="4000us" | ||||||
|  |         ): cv.positive_time_period_microseconds, | ||||||
|  |         cv.Optional( | ||||||
|  |             CONF_BIT_HIGH, default="600us" | ||||||
|  |         ): cv.positive_time_period_microseconds, | ||||||
|  |         cv.Optional( | ||||||
|  |             CONF_BIT_ONE_LOW, default="1600us" | ||||||
|  |         ): cv.positive_time_period_microseconds, | ||||||
|  |         cv.Optional( | ||||||
|  |             CONF_BIT_ZERO_LOW, default="550us" | ||||||
|  |         ): cv.positive_time_period_microseconds, | ||||||
|  |     } | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
| def to_code(config): | def to_code(config): | ||||||
|     var = cg.new_Pvariable(config[CONF_ID]) |     var = cg.new_Pvariable(config[CONF_ID]) | ||||||
|     yield climate_ir.register_climate_ir(var, config) |     yield climate_ir.register_climate_ir(var, config) | ||||||
|  |  | ||||||
|  |     cg.add(var.set_header_high(config[CONF_HEADER_HIGH])) | ||||||
|  |     cg.add(var.set_header_low(config[CONF_HEADER_LOW])) | ||||||
|  |     cg.add(var.set_bit_high(config[CONF_BIT_HIGH])) | ||||||
|  |     cg.add(var.set_bit_one_low(config[CONF_BIT_ONE_LOW])) | ||||||
|  |     cg.add(var.set_bit_zero_low(config[CONF_BIT_ZERO_LOW])) | ||||||
|   | |||||||
| @@ -9,6 +9,7 @@ static const char *TAG = "climate.climate_ir_lg"; | |||||||
| const uint32_t COMMAND_ON = 0x00000; | const uint32_t COMMAND_ON = 0x00000; | ||||||
| const uint32_t COMMAND_ON_AI = 0x03000; | const uint32_t COMMAND_ON_AI = 0x03000; | ||||||
| const uint32_t COMMAND_COOL = 0x08000; | const uint32_t COMMAND_COOL = 0x08000; | ||||||
|  | const uint32_t COMMAND_HEAT = 0x0C000; | ||||||
| const uint32_t COMMAND_OFF = 0xC0000; | const uint32_t COMMAND_OFF = 0xC0000; | ||||||
| const uint32_t COMMAND_SWING = 0x10000; | const uint32_t COMMAND_SWING = 0x10000; | ||||||
| // On, 25C, Mode: Auto, Fan: Auto, Zone Follow: Off, Sensor Temp: Ignore. | // On, 25C, Mode: Auto, Fan: Auto, Zone Follow: Off, Sensor Temp: Ignore. | ||||||
| @@ -28,13 +29,6 @@ const uint8_t TEMP_RANGE = TEMP_MAX - TEMP_MIN + 1; | |||||||
| const uint32_t TEMP_MASK = 0XF00; | const uint32_t TEMP_MASK = 0XF00; | ||||||
| const uint32_t TEMP_SHIFT = 8; | const uint32_t TEMP_SHIFT = 8; | ||||||
|  |  | ||||||
| // Constants |  | ||||||
| static const uint32_t HEADER_HIGH_US = 8000; |  | ||||||
| static const uint32_t HEADER_LOW_US = 4000; |  | ||||||
| static const uint32_t BIT_HIGH_US = 600; |  | ||||||
| static const uint32_t BIT_ONE_LOW_US = 1600; |  | ||||||
| static const uint32_t BIT_ZERO_LOW_US = 550; |  | ||||||
|  |  | ||||||
| const uint16_t BITS = 28; | const uint16_t BITS = 28; | ||||||
|  |  | ||||||
| void LgIrClimate::transmit_state() { | void LgIrClimate::transmit_state() { | ||||||
| @@ -55,6 +49,9 @@ void LgIrClimate::transmit_state() { | |||||||
|         case climate::CLIMATE_MODE_COOL: |         case climate::CLIMATE_MODE_COOL: | ||||||
|           remote_state |= COMMAND_COOL; |           remote_state |= COMMAND_COOL; | ||||||
|           break; |           break; | ||||||
|  |         case climate::CLIMATE_MODE_HEAT: | ||||||
|  |           remote_state |= COMMAND_HEAT; | ||||||
|  |           break; | ||||||
|         case climate::CLIMATE_MODE_AUTO: |         case climate::CLIMATE_MODE_AUTO: | ||||||
|           remote_state |= COMMAND_AUTO; |           remote_state |= COMMAND_AUTO; | ||||||
|           break; |           break; | ||||||
| @@ -73,7 +70,8 @@ void LgIrClimate::transmit_state() { | |||||||
|  |  | ||||||
|     if (this->mode == climate::CLIMATE_MODE_OFF) { |     if (this->mode == climate::CLIMATE_MODE_OFF) { | ||||||
|       remote_state |= FAN_AUTO; |       remote_state |= FAN_AUTO; | ||||||
|     } else if (this->mode == climate::CLIMATE_MODE_COOL || this->mode == climate::CLIMATE_MODE_DRY) { |     } else if (this->mode == climate::CLIMATE_MODE_COOL || this->mode == climate::CLIMATE_MODE_DRY || | ||||||
|  |                this->mode == climate::CLIMATE_MODE_HEAT) { | ||||||
|       switch (this->fan_mode) { |       switch (this->fan_mode) { | ||||||
|         case climate::CLIMATE_FAN_HIGH: |         case climate::CLIMATE_FAN_HIGH: | ||||||
|           remote_state |= FAN_MAX; |           remote_state |= FAN_MAX; | ||||||
| @@ -95,7 +93,7 @@ void LgIrClimate::transmit_state() { | |||||||
|       this->fan_mode = climate::CLIMATE_FAN_AUTO; |       this->fan_mode = climate::CLIMATE_FAN_AUTO; | ||||||
|       // remote_state |= FAN_MODE_AUTO_DRY; |       // remote_state |= FAN_MODE_AUTO_DRY; | ||||||
|     } |     } | ||||||
|     if (this->mode == climate::CLIMATE_MODE_COOL) { |     if (this->mode == climate::CLIMATE_MODE_COOL || this->mode == climate::CLIMATE_MODE_HEAT) { | ||||||
|       auto temp = (uint8_t) roundf(clamp(this->target_temperature, TEMP_MIN, TEMP_MAX)); |       auto temp = (uint8_t) roundf(clamp(this->target_temperature, TEMP_MIN, TEMP_MAX)); | ||||||
|       remote_state |= ((temp - 15) << TEMP_SHIFT); |       remote_state |= ((temp - 15) << TEMP_SHIFT); | ||||||
|     } |     } | ||||||
| @@ -108,13 +106,13 @@ bool LgIrClimate::on_receive(remote_base::RemoteReceiveData data) { | |||||||
|   uint8_t nbits = 0; |   uint8_t nbits = 0; | ||||||
|   uint32_t remote_state = 0; |   uint32_t remote_state = 0; | ||||||
|  |  | ||||||
|   if (!data.expect_item(HEADER_HIGH_US, HEADER_LOW_US)) |   if (!data.expect_item(this->header_high_, this->header_low_)) | ||||||
|     return false; |     return false; | ||||||
|  |  | ||||||
|   for (nbits = 0; nbits < 32; nbits++) { |   for (nbits = 0; nbits < 32; nbits++) { | ||||||
|     if (data.expect_item(BIT_HIGH_US, BIT_ONE_LOW_US)) { |     if (data.expect_item(this->bit_high_, this->bit_one_low_)) { | ||||||
|       remote_state = (remote_state << 1) | 1; |       remote_state = (remote_state << 1) | 1; | ||||||
|     } else if (data.expect_item(BIT_HIGH_US, BIT_ZERO_LOW_US)) { |     } else if (data.expect_item(this->bit_high_, this->bit_zero_low_)) { | ||||||
|       remote_state = (remote_state << 1) | 0; |       remote_state = (remote_state << 1) | 0; | ||||||
|     } else if (nbits == BITS) { |     } else if (nbits == BITS) { | ||||||
|       break; |       break; | ||||||
| @@ -141,29 +139,32 @@ bool LgIrClimate::on_receive(remote_base::RemoteReceiveData data) { | |||||||
|   } else { |   } else { | ||||||
|     if ((remote_state & COMMAND_MASK) == COMMAND_AUTO) |     if ((remote_state & COMMAND_MASK) == COMMAND_AUTO) | ||||||
|       this->mode = climate::CLIMATE_MODE_AUTO; |       this->mode = climate::CLIMATE_MODE_AUTO; | ||||||
|     else if ((remote_state & COMMAND_MASK) == COMMAND_DRY_FAN) { |     else if ((remote_state & COMMAND_MASK) == COMMAND_DRY_FAN) | ||||||
|       this->mode = climate::CLIMATE_MODE_DRY; |       this->mode = climate::CLIMATE_MODE_DRY; | ||||||
|  |     else if ((remote_state & COMMAND_MASK) == COMMAND_HEAT) { | ||||||
|  |       this->mode = climate::CLIMATE_MODE_HEAT; | ||||||
|     } else { |     } else { | ||||||
|       this->mode = climate::CLIMATE_MODE_COOL; |       this->mode = climate::CLIMATE_MODE_COOL; | ||||||
|     } |     } | ||||||
|   } |  | ||||||
|  |  | ||||||
|   // Temperature |     // Temperature | ||||||
|   if (this->mode == climate::CLIMATE_MODE_COOL) |     if (this->mode == climate::CLIMATE_MODE_COOL || this->mode == climate::CLIMATE_MODE_HEAT) | ||||||
|     this->target_temperature = ((remote_state & TEMP_MASK) >> TEMP_SHIFT) + 15; |       this->target_temperature = ((remote_state & TEMP_MASK) >> TEMP_SHIFT) + 15; | ||||||
|  |  | ||||||
|   // Fan Speed |     // Fan Speed | ||||||
|   if (this->mode == climate::CLIMATE_MODE_AUTO) { |     if (this->mode == climate::CLIMATE_MODE_AUTO) { | ||||||
|     this->fan_mode = climate::CLIMATE_FAN_AUTO; |  | ||||||
|   } else if (this->mode == climate::CLIMATE_MODE_COOL || this->mode == climate::CLIMATE_MODE_DRY) { |  | ||||||
|     if ((remote_state & FAN_MASK) == FAN_AUTO) |  | ||||||
|       this->fan_mode = climate::CLIMATE_FAN_AUTO; |       this->fan_mode = climate::CLIMATE_FAN_AUTO; | ||||||
|     else if ((remote_state & FAN_MASK) == FAN_MIN) |     } else if (this->mode == climate::CLIMATE_MODE_COOL || this->mode == climate::CLIMATE_MODE_HEAT || | ||||||
|       this->fan_mode = climate::CLIMATE_FAN_LOW; |                this->mode == climate::CLIMATE_MODE_DRY) { | ||||||
|     else if ((remote_state & FAN_MASK) == FAN_MED) |       if ((remote_state & FAN_MASK) == FAN_AUTO) | ||||||
|       this->fan_mode = climate::CLIMATE_FAN_MEDIUM; |         this->fan_mode = climate::CLIMATE_FAN_AUTO; | ||||||
|     else if ((remote_state & FAN_MASK) == FAN_MAX) |       else if ((remote_state & FAN_MASK) == FAN_MIN) | ||||||
|       this->fan_mode = climate::CLIMATE_FAN_HIGH; |         this->fan_mode = climate::CLIMATE_FAN_LOW; | ||||||
|  |       else if ((remote_state & FAN_MASK) == FAN_MED) | ||||||
|  |         this->fan_mode = climate::CLIMATE_FAN_MEDIUM; | ||||||
|  |       else if ((remote_state & FAN_MASK) == FAN_MAX) | ||||||
|  |         this->fan_mode = climate::CLIMATE_FAN_HIGH; | ||||||
|  |     } | ||||||
|   } |   } | ||||||
|   this->publish_state(); |   this->publish_state(); | ||||||
|  |  | ||||||
| @@ -179,15 +180,16 @@ void LgIrClimate::transmit_(uint32_t value) { | |||||||
|   data->set_carrier_frequency(38000); |   data->set_carrier_frequency(38000); | ||||||
|   data->reserve(2 + BITS * 2u); |   data->reserve(2 + BITS * 2u); | ||||||
|  |  | ||||||
|   data->item(HEADER_HIGH_US, HEADER_LOW_US); |   data->item(this->header_high_, this->header_low_); | ||||||
|  |  | ||||||
|   for (uint32_t mask = 1UL << (BITS - 1); mask != 0; mask >>= 1) { |   for (uint32_t mask = 1UL << (BITS - 1); mask != 0; mask >>= 1) { | ||||||
|     if (value & mask) |     if (value & mask) { | ||||||
|       data->item(BIT_HIGH_US, BIT_ONE_LOW_US); |       data->item(this->bit_high_, this->bit_one_low_); | ||||||
|     else |     } else { | ||||||
|       data->item(BIT_HIGH_US, BIT_ZERO_LOW_US); |       data->item(this->bit_high_, this->bit_zero_low_); | ||||||
|  |     } | ||||||
|   } |   } | ||||||
|   data->mark(BIT_HIGH_US); |   data->mark(this->bit_high_); | ||||||
|   transmit.perform(); |   transmit.perform(); | ||||||
| } | } | ||||||
| void LgIrClimate::calc_checksum_(uint32_t &value) { | void LgIrClimate::calc_checksum_(uint32_t &value) { | ||||||
|   | |||||||
| @@ -25,6 +25,11 @@ class LgIrClimate : public climate_ir::ClimateIR { | |||||||
|       this->swing_mode = climate::CLIMATE_SWING_OFF; |       this->swing_mode = climate::CLIMATE_SWING_OFF; | ||||||
|     climate_ir::ClimateIR::control(call); |     climate_ir::ClimateIR::control(call); | ||||||
|   } |   } | ||||||
|  |   void set_header_high(uint32_t header_high) { this->header_high_ = header_high; } | ||||||
|  |   void set_header_low(uint32_t header_low) { this->header_low_ = header_low; } | ||||||
|  |   void set_bit_high(uint32_t bit_high) { this->bit_high_ = bit_high; } | ||||||
|  |   void set_bit_one_low(uint32_t bit_one_low) { this->bit_one_low_ = bit_one_low; } | ||||||
|  |   void set_bit_zero_low(uint32_t bit_zero_low) { this->bit_zero_low_ = bit_zero_low; } | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   /// Transmit via IR the state of this climate controller. |   /// Transmit via IR the state of this climate controller. | ||||||
| @@ -37,6 +42,12 @@ class LgIrClimate : public climate_ir::ClimateIR { | |||||||
|   void calc_checksum_(uint32_t &value); |   void calc_checksum_(uint32_t &value); | ||||||
|   void transmit_(uint32_t value); |   void transmit_(uint32_t value); | ||||||
|  |  | ||||||
|  |   uint32_t header_high_; | ||||||
|  |   uint32_t header_low_; | ||||||
|  |   uint32_t bit_high_; | ||||||
|  |   uint32_t bit_one_low_; | ||||||
|  |   uint32_t bit_zero_low_; | ||||||
|  |  | ||||||
|   climate::ClimateMode mode_before_{climate::CLIMATE_MODE_OFF}; |   climate::ClimateMode mode_before_{climate::CLIMATE_MODE_OFF}; | ||||||
| }; | }; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -2,22 +2,56 @@ from esphome import config_validation as cv | |||||||
| from esphome import codegen as cg | from esphome import codegen as cg | ||||||
| from esphome.const import CONF_BLUE, CONF_GREEN, CONF_ID, CONF_RED, CONF_WHITE | from esphome.const import CONF_BLUE, CONF_GREEN, CONF_ID, CONF_RED, CONF_WHITE | ||||||
|  |  | ||||||
| ColorStruct = cg.esphome_ns.struct('Color') | ColorStruct = cg.esphome_ns.struct("Color") | ||||||
|  |  | ||||||
| MULTI_CONF = True | MULTI_CONF = True | ||||||
| CONFIG_SCHEMA = cv.Schema({ |  | ||||||
|     cv.Required(CONF_ID): cv.declare_id(ColorStruct), | CONF_RED_INT = "red_int" | ||||||
|     cv.Optional(CONF_RED, default=0.0): cv.percentage, | CONF_GREEN_INT = "green_int" | ||||||
|     cv.Optional(CONF_GREEN, default=0.0): cv.percentage, | CONF_BLUE_INT = "blue_int" | ||||||
|     cv.Optional(CONF_BLUE, default=0.0): cv.percentage, | CONF_WHITE_INT = "white_int" | ||||||
|     cv.Optional(CONF_WHITE, default=0.0): cv.percentage, |  | ||||||
| }).extend(cv.COMPONENT_SCHEMA) | CONFIG_SCHEMA = cv.Schema( | ||||||
|  |     { | ||||||
|  |         cv.Required(CONF_ID): cv.declare_id(ColorStruct), | ||||||
|  |         cv.Exclusive(CONF_RED, "red"): cv.percentage, | ||||||
|  |         cv.Exclusive(CONF_RED_INT, "red"): cv.uint8_t, | ||||||
|  |         cv.Exclusive(CONF_GREEN, "green"): cv.percentage, | ||||||
|  |         cv.Exclusive(CONF_GREEN_INT, "green"): cv.uint8_t, | ||||||
|  |         cv.Exclusive(CONF_BLUE, "blue"): cv.percentage, | ||||||
|  |         cv.Exclusive(CONF_BLUE_INT, "blue"): cv.uint8_t, | ||||||
|  |         cv.Exclusive(CONF_WHITE, "white"): cv.percentage, | ||||||
|  |         cv.Exclusive(CONF_WHITE_INT, "white"): cv.uint8_t, | ||||||
|  |     } | ||||||
|  | ).extend(cv.COMPONENT_SCHEMA) | ||||||
|  |  | ||||||
|  |  | ||||||
| def to_code(config): | def to_code(config): | ||||||
|     cg.variable(config[CONF_ID], cg.StructInitializer( |     r = 0 | ||||||
|         ColorStruct, |     if CONF_RED in config: | ||||||
|         ('r', config[CONF_RED]), |         r = int(config[CONF_RED] * 255) | ||||||
|         ('g', config[CONF_GREEN]), |     elif CONF_RED_INT in config: | ||||||
|         ('b', config[CONF_BLUE]), |         r = config[CONF_RED_INT] | ||||||
|         ('w', config[CONF_WHITE]))) |  | ||||||
|  |     g = 0 | ||||||
|  |     if CONF_GREEN in config: | ||||||
|  |         g = int(config[CONF_GREEN] * 255) | ||||||
|  |     elif CONF_GREEN_INT in config: | ||||||
|  |         g = config[CONF_GREEN_INT] | ||||||
|  |  | ||||||
|  |     b = 0 | ||||||
|  |     if CONF_BLUE in config: | ||||||
|  |         b = int(config[CONF_BLUE] * 255) | ||||||
|  |     elif CONF_BLUE_INT in config: | ||||||
|  |         b = config[CONF_BLUE_INT] | ||||||
|  |  | ||||||
|  |     w = 0 | ||||||
|  |     if CONF_WHITE in config: | ||||||
|  |         w = int(config[CONF_WHITE] * 255) | ||||||
|  |     elif CONF_WHITE_INT in config: | ||||||
|  |         w = config[CONF_WHITE_INT] | ||||||
|  |  | ||||||
|  |     cg.new_variable( | ||||||
|  |         config[CONF_ID], | ||||||
|  |         cg.StructInitializer(ColorStruct, ("r", r), ("g", g), ("b", b), ("w", w)), | ||||||
|  |     ) | ||||||
|   | |||||||
| @@ -3,15 +3,17 @@ import esphome.config_validation as cv | |||||||
| from esphome.components import climate_ir | from esphome.components import climate_ir | ||||||
| from esphome.const import CONF_ID | from esphome.const import CONF_ID | ||||||
|  |  | ||||||
| AUTO_LOAD = ['climate_ir'] | AUTO_LOAD = ["climate_ir"] | ||||||
| CODEOWNERS = ['@glmnet'] | CODEOWNERS = ["@glmnet"] | ||||||
|  |  | ||||||
| coolix_ns = cg.esphome_ns.namespace('coolix') | coolix_ns = cg.esphome_ns.namespace("coolix") | ||||||
| CoolixClimate = coolix_ns.class_('CoolixClimate', climate_ir.ClimateIR) | CoolixClimate = coolix_ns.class_("CoolixClimate", climate_ir.ClimateIR) | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend({ | CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend( | ||||||
|     cv.GenerateID(): cv.declare_id(CoolixClimate), |     { | ||||||
| }) |         cv.GenerateID(): cv.declare_id(CoolixClimate), | ||||||
|  |     } | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
| def to_code(config): | def to_code(config): | ||||||
|   | |||||||
| @@ -3,54 +3,74 @@ import esphome.config_validation as cv | |||||||
| from esphome import automation | from esphome import automation | ||||||
| from esphome.automation import maybe_simple_id, Condition | from esphome.automation import maybe_simple_id, Condition | ||||||
| from esphome.components import mqtt | from esphome.components import mqtt | ||||||
| from esphome.const import CONF_ID, CONF_INTERNAL, CONF_DEVICE_CLASS, CONF_STATE, \ | from esphome.const import ( | ||||||
|     CONF_POSITION, CONF_TILT, CONF_STOP, CONF_MQTT_ID, CONF_NAME |     CONF_ID, | ||||||
|  |     CONF_INTERNAL, | ||||||
|  |     CONF_DEVICE_CLASS, | ||||||
|  |     CONF_STATE, | ||||||
|  |     CONF_POSITION, | ||||||
|  |     CONF_TILT, | ||||||
|  |     CONF_STOP, | ||||||
|  |     CONF_MQTT_ID, | ||||||
|  |     CONF_NAME, | ||||||
|  | ) | ||||||
| from esphome.core import CORE, coroutine, coroutine_with_priority | from esphome.core import CORE, coroutine, coroutine_with_priority | ||||||
|  |  | ||||||
| IS_PLATFORM_COMPONENT = True | IS_PLATFORM_COMPONENT = True | ||||||
|  |  | ||||||
| CODEOWNERS = ['@esphome/core'] | CODEOWNERS = ["@esphome/core"] | ||||||
| DEVICE_CLASSES = [ | DEVICE_CLASSES = [ | ||||||
|     '', 'awning', 'blind', 'curtain', 'damper', 'door', 'garage', |     "", | ||||||
|     'gate', 'shade', 'shutter', 'window' |     "awning", | ||||||
|  |     "blind", | ||||||
|  |     "curtain", | ||||||
|  |     "damper", | ||||||
|  |     "door", | ||||||
|  |     "garage", | ||||||
|  |     "gate", | ||||||
|  |     "shade", | ||||||
|  |     "shutter", | ||||||
|  |     "window", | ||||||
| ] | ] | ||||||
|  |  | ||||||
| cover_ns = cg.esphome_ns.namespace('cover') | cover_ns = cg.esphome_ns.namespace("cover") | ||||||
|  |  | ||||||
| Cover = cover_ns.class_('Cover', cg.Nameable) | Cover = cover_ns.class_("Cover", cg.Nameable) | ||||||
|  |  | ||||||
| COVER_OPEN = cover_ns.COVER_OPEN | COVER_OPEN = cover_ns.COVER_OPEN | ||||||
| COVER_CLOSED = cover_ns.COVER_CLOSED | COVER_CLOSED = cover_ns.COVER_CLOSED | ||||||
|  |  | ||||||
| COVER_STATES = { | COVER_STATES = { | ||||||
|     'OPEN': COVER_OPEN, |     "OPEN": COVER_OPEN, | ||||||
|     'CLOSED': COVER_CLOSED, |     "CLOSED": COVER_CLOSED, | ||||||
| } | } | ||||||
| validate_cover_state = cv.enum(COVER_STATES, upper=True) | validate_cover_state = cv.enum(COVER_STATES, upper=True) | ||||||
|  |  | ||||||
| CoverOperation = cover_ns.enum('CoverOperation') | CoverOperation = cover_ns.enum("CoverOperation") | ||||||
| COVER_OPERATIONS = { | COVER_OPERATIONS = { | ||||||
|     'IDLE': CoverOperation.COVER_OPERATION_IDLE, |     "IDLE": CoverOperation.COVER_OPERATION_IDLE, | ||||||
|     'OPENING': CoverOperation.COVER_OPERATION_OPENING, |     "OPENING": CoverOperation.COVER_OPERATION_OPENING, | ||||||
|     'CLOSING': CoverOperation.COVER_OPERATION_CLOSING, |     "CLOSING": CoverOperation.COVER_OPERATION_CLOSING, | ||||||
| } | } | ||||||
| validate_cover_operation = cv.enum(COVER_OPERATIONS, upper=True) | validate_cover_operation = cv.enum(COVER_OPERATIONS, upper=True) | ||||||
|  |  | ||||||
| # Actions | # Actions | ||||||
| OpenAction = cover_ns.class_('OpenAction', automation.Action) | OpenAction = cover_ns.class_("OpenAction", automation.Action) | ||||||
| CloseAction = cover_ns.class_('CloseAction', automation.Action) | CloseAction = cover_ns.class_("CloseAction", automation.Action) | ||||||
| StopAction = cover_ns.class_('StopAction', automation.Action) | StopAction = cover_ns.class_("StopAction", automation.Action) | ||||||
| ControlAction = cover_ns.class_('ControlAction', automation.Action) | ControlAction = cover_ns.class_("ControlAction", automation.Action) | ||||||
| CoverPublishAction = cover_ns.class_('CoverPublishAction', automation.Action) | CoverPublishAction = cover_ns.class_("CoverPublishAction", automation.Action) | ||||||
| CoverIsOpenCondition = cover_ns.class_('CoverIsOpenCondition', Condition) | CoverIsOpenCondition = cover_ns.class_("CoverIsOpenCondition", Condition) | ||||||
| CoverIsClosedCondition = cover_ns.class_('CoverIsClosedCondition', Condition) | CoverIsClosedCondition = cover_ns.class_("CoverIsClosedCondition", Condition) | ||||||
|  |  | ||||||
| COVER_SCHEMA = cv.MQTT_COMMAND_COMPONENT_SCHEMA.extend({ | COVER_SCHEMA = cv.MQTT_COMMAND_COMPONENT_SCHEMA.extend( | ||||||
|     cv.GenerateID(): cv.declare_id(Cover), |     { | ||||||
|     cv.OnlyWith(CONF_MQTT_ID, 'mqtt'): cv.declare_id(mqtt.MQTTCoverComponent), |         cv.GenerateID(): cv.declare_id(Cover), | ||||||
|     cv.Optional(CONF_DEVICE_CLASS): cv.one_of(*DEVICE_CLASSES, lower=True), |         cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTCoverComponent), | ||||||
|     # TODO: MQTT topic options |         cv.Optional(CONF_DEVICE_CLASS): cv.one_of(*DEVICE_CLASSES, lower=True), | ||||||
| }) |         # TODO: MQTT topic options | ||||||
|  |     } | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
| @coroutine | @coroutine | ||||||
| @@ -74,39 +94,43 @@ def register_cover(var, config): | |||||||
|     yield setup_cover_core_(var, config) |     yield setup_cover_core_(var, config) | ||||||
|  |  | ||||||
|  |  | ||||||
| COVER_ACTION_SCHEMA = maybe_simple_id({ | COVER_ACTION_SCHEMA = maybe_simple_id( | ||||||
|     cv.Required(CONF_ID): cv.use_id(Cover), |     { | ||||||
| }) |         cv.Required(CONF_ID): cv.use_id(Cover), | ||||||
|  |     } | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
| @automation.register_action('cover.open', OpenAction, COVER_ACTION_SCHEMA) | @automation.register_action("cover.open", OpenAction, COVER_ACTION_SCHEMA) | ||||||
| def cover_open_to_code(config, action_id, template_arg, args): | def cover_open_to_code(config, action_id, template_arg, args): | ||||||
|     paren = yield cg.get_variable(config[CONF_ID]) |     paren = yield cg.get_variable(config[CONF_ID]) | ||||||
|     yield cg.new_Pvariable(action_id, template_arg, paren) |     yield cg.new_Pvariable(action_id, template_arg, paren) | ||||||
|  |  | ||||||
|  |  | ||||||
| @automation.register_action('cover.close', CloseAction, COVER_ACTION_SCHEMA) | @automation.register_action("cover.close", CloseAction, COVER_ACTION_SCHEMA) | ||||||
| def cover_close_to_code(config, action_id, template_arg, args): | def cover_close_to_code(config, action_id, template_arg, args): | ||||||
|     paren = yield cg.get_variable(config[CONF_ID]) |     paren = yield cg.get_variable(config[CONF_ID]) | ||||||
|     yield cg.new_Pvariable(action_id, template_arg, paren) |     yield cg.new_Pvariable(action_id, template_arg, paren) | ||||||
|  |  | ||||||
|  |  | ||||||
| @automation.register_action('cover.stop', StopAction, COVER_ACTION_SCHEMA) | @automation.register_action("cover.stop", StopAction, COVER_ACTION_SCHEMA) | ||||||
| def cover_stop_to_code(config, action_id, template_arg, args): | def cover_stop_to_code(config, action_id, template_arg, args): | ||||||
|     paren = yield cg.get_variable(config[CONF_ID]) |     paren = yield cg.get_variable(config[CONF_ID]) | ||||||
|     yield cg.new_Pvariable(action_id, template_arg, paren) |     yield cg.new_Pvariable(action_id, template_arg, paren) | ||||||
|  |  | ||||||
|  |  | ||||||
| COVER_CONTROL_ACTION_SCHEMA = cv.Schema({ | COVER_CONTROL_ACTION_SCHEMA = cv.Schema( | ||||||
|     cv.Required(CONF_ID): cv.use_id(Cover), |     { | ||||||
|     cv.Optional(CONF_STOP): cv.templatable(cv.boolean), |         cv.Required(CONF_ID): cv.use_id(Cover), | ||||||
|     cv.Exclusive(CONF_STATE, 'pos'): cv.templatable(validate_cover_state), |         cv.Optional(CONF_STOP): cv.templatable(cv.boolean), | ||||||
|     cv.Exclusive(CONF_POSITION, 'pos'): cv.templatable(cv.percentage), |         cv.Exclusive(CONF_STATE, "pos"): cv.templatable(validate_cover_state), | ||||||
|     cv.Optional(CONF_TILT): cv.templatable(cv.percentage), |         cv.Exclusive(CONF_POSITION, "pos"): cv.templatable(cv.percentage), | ||||||
| }) |         cv.Optional(CONF_TILT): cv.templatable(cv.percentage), | ||||||
|  |     } | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
| @automation.register_action('cover.control', ControlAction, COVER_CONTROL_ACTION_SCHEMA) | @automation.register_action("cover.control", ControlAction, COVER_CONTROL_ACTION_SCHEMA) | ||||||
| def cover_control_to_code(config, action_id, template_arg, args): | def cover_control_to_code(config, action_id, template_arg, args): | ||||||
|     paren = yield cg.get_variable(config[CONF_ID]) |     paren = yield cg.get_variable(config[CONF_ID]) | ||||||
|     var = cg.new_Pvariable(action_id, template_arg, paren) |     var = cg.new_Pvariable(action_id, template_arg, paren) | ||||||
| @@ -127,5 +151,5 @@ def cover_control_to_code(config, action_id, template_arg, args): | |||||||
|  |  | ||||||
| @coroutine_with_priority(100.0) | @coroutine_with_priority(100.0) | ||||||
| def to_code(config): | def to_code(config): | ||||||
|     cg.add_define('USE_COVER') |     cg.add_define("USE_COVER") | ||||||
|     cg.add_global(cover_ns.using) |     cg.add_global(cover_ns.using) | ||||||
|   | |||||||
| @@ -1,21 +1,45 @@ | |||||||
| import esphome.codegen as cg | import esphome.codegen as cg | ||||||
| import esphome.config_validation as cv | import esphome.config_validation as cv | ||||||
| from esphome.components import sensor, uart | from esphome.components import sensor, uart | ||||||
| from esphome.const import CONF_CURRENT, CONF_ID, CONF_POWER, CONF_VOLTAGE, \ | from esphome.const import ( | ||||||
|     UNIT_VOLT, ICON_FLASH, UNIT_AMPERE, UNIT_WATT |     CONF_CURRENT, | ||||||
|  |     CONF_ID, | ||||||
|  |     CONF_POWER, | ||||||
|  |     CONF_VOLTAGE, | ||||||
|  |     DEVICE_CLASS_CURRENT, | ||||||
|  |     DEVICE_CLASS_POWER, | ||||||
|  |     DEVICE_CLASS_VOLTAGE, | ||||||
|  |     ICON_EMPTY, | ||||||
|  |     UNIT_VOLT, | ||||||
|  |     UNIT_AMPERE, | ||||||
|  |     UNIT_WATT, | ||||||
|  | ) | ||||||
|  |  | ||||||
| DEPENDENCIES = ['uart'] | DEPENDENCIES = ["uart"] | ||||||
|  |  | ||||||
| cse7766_ns = cg.esphome_ns.namespace('cse7766') | cse7766_ns = cg.esphome_ns.namespace("cse7766") | ||||||
| CSE7766Component = cse7766_ns.class_('CSE7766Component', cg.PollingComponent, uart.UARTDevice) | CSE7766Component = cse7766_ns.class_( | ||||||
|  |     "CSE7766Component", cg.PollingComponent, uart.UARTDevice | ||||||
|  | ) | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = cv.Schema({ | CONFIG_SCHEMA = ( | ||||||
|     cv.GenerateID(): cv.declare_id(CSE7766Component), |     cv.Schema( | ||||||
|  |         { | ||||||
|     cv.Optional(CONF_VOLTAGE): sensor.sensor_schema(UNIT_VOLT, ICON_FLASH, 1), |             cv.GenerateID(): cv.declare_id(CSE7766Component), | ||||||
|     cv.Optional(CONF_CURRENT): sensor.sensor_schema(UNIT_AMPERE, ICON_FLASH, 2), |             cv.Optional(CONF_VOLTAGE): sensor.sensor_schema( | ||||||
|     cv.Optional(CONF_POWER): sensor.sensor_schema(UNIT_WATT, ICON_FLASH, 1), |                 UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE | ||||||
| }).extend(cv.polling_component_schema('60s')).extend(uart.UART_DEVICE_SCHEMA) |             ), | ||||||
|  |             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, 1, DEVICE_CLASS_POWER | ||||||
|  |             ), | ||||||
|  |         } | ||||||
|  |     ) | ||||||
|  |     .extend(cv.polling_component_schema("60s")) | ||||||
|  |     .extend(uart.UART_DEVICE_SCHEMA) | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
| def to_code(config): | def to_code(config): | ||||||
|   | |||||||
| @@ -1,21 +1,35 @@ | |||||||
| import esphome.codegen as cg | import esphome.codegen as cg | ||||||
| import esphome.config_validation as cv | import esphome.config_validation as cv | ||||||
| from esphome.components import sensor, voltage_sampler | from esphome.components import sensor, voltage_sampler | ||||||
| from esphome.const import CONF_SENSOR, CONF_ID, ICON_FLASH, UNIT_AMPERE | from esphome.const import ( | ||||||
|  |     CONF_SENSOR, | ||||||
|  |     CONF_ID, | ||||||
|  |     DEVICE_CLASS_CURRENT, | ||||||
|  |     ICON_EMPTY, | ||||||
|  |     UNIT_AMPERE, | ||||||
|  | ) | ||||||
|  |  | ||||||
| AUTO_LOAD = ['voltage_sampler'] | AUTO_LOAD = ["voltage_sampler"] | ||||||
| CODEOWNERS = ['@jesserockz'] | CODEOWNERS = ["@jesserockz"] | ||||||
|  |  | ||||||
| CONF_SAMPLE_DURATION = 'sample_duration' | CONF_SAMPLE_DURATION = "sample_duration" | ||||||
|  |  | ||||||
| ct_clamp_ns = cg.esphome_ns.namespace('ct_clamp') | ct_clamp_ns = cg.esphome_ns.namespace("ct_clamp") | ||||||
| CTClampSensor = ct_clamp_ns.class_('CTClampSensor', sensor.Sensor, cg.PollingComponent) | CTClampSensor = ct_clamp_ns.class_("CTClampSensor", sensor.Sensor, cg.PollingComponent) | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = sensor.sensor_schema(UNIT_AMPERE, ICON_FLASH, 2).extend({ | CONFIG_SCHEMA = ( | ||||||
|     cv.GenerateID(): cv.declare_id(CTClampSensor), |     sensor.sensor_schema(UNIT_AMPERE, ICON_EMPTY, 2, DEVICE_CLASS_CURRENT) | ||||||
|     cv.Required(CONF_SENSOR): cv.use_id(voltage_sampler.VoltageSampler), |     .extend( | ||||||
|     cv.Optional(CONF_SAMPLE_DURATION, default='200ms'): cv.positive_time_period_milliseconds, |         { | ||||||
| }).extend(cv.polling_component_schema('60s')) |             cv.GenerateID(): cv.declare_id(CTClampSensor), | ||||||
|  |             cv.Required(CONF_SENSOR): cv.use_id(voltage_sampler.VoltageSampler), | ||||||
|  |             cv.Optional( | ||||||
|  |                 CONF_SAMPLE_DURATION, default="200ms" | ||||||
|  |             ): cv.positive_time_period_milliseconds, | ||||||
|  |         } | ||||||
|  |     ) | ||||||
|  |     .extend(cv.polling_component_schema("60s")) | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
| def to_code(config): | def to_code(config): | ||||||
|   | |||||||
| @@ -1,3 +1,3 @@ | |||||||
| import esphome.codegen as cg | import esphome.codegen as cg | ||||||
|  |  | ||||||
| custom_ns = cg.esphome_ns.namespace('custom') | custom_ns = cg.esphome_ns.namespace("custom") | ||||||
|   | |||||||
| @@ -4,18 +4,25 @@ from esphome.components import binary_sensor | |||||||
| from esphome.const import CONF_BINARY_SENSORS, CONF_ID, CONF_LAMBDA | from esphome.const import CONF_BINARY_SENSORS, CONF_ID, CONF_LAMBDA | ||||||
| from .. import custom_ns | from .. import custom_ns | ||||||
|  |  | ||||||
| CustomBinarySensorConstructor = custom_ns.class_('CustomBinarySensorConstructor') | CustomBinarySensorConstructor = custom_ns.class_("CustomBinarySensorConstructor") | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = cv.Schema({ | CONFIG_SCHEMA = cv.Schema( | ||||||
|     cv.GenerateID(): cv.declare_id(CustomBinarySensorConstructor), |     { | ||||||
|     cv.Required(CONF_LAMBDA): cv.returning_lambda, |         cv.GenerateID(): cv.declare_id(CustomBinarySensorConstructor), | ||||||
|     cv.Required(CONF_BINARY_SENSORS): cv.ensure_list(binary_sensor.BINARY_SENSOR_SCHEMA), |         cv.Required(CONF_LAMBDA): cv.returning_lambda, | ||||||
| }) |         cv.Required(CONF_BINARY_SENSORS): cv.ensure_list( | ||||||
|  |             binary_sensor.BINARY_SENSOR_SCHEMA | ||||||
|  |         ), | ||||||
|  |     } | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
| def to_code(config): | def to_code(config): | ||||||
|     template_ = yield cg.process_lambda( |     template_ = yield cg.process_lambda( | ||||||
|         config[CONF_LAMBDA], [], return_type=cg.std_vector.template(binary_sensor.BinarySensorPtr)) |         config[CONF_LAMBDA], | ||||||
|  |         [], | ||||||
|  |         return_type=cg.std_vector.template(binary_sensor.BinarySensorPtr), | ||||||
|  |     ) | ||||||
|  |  | ||||||
|     rhs = CustomBinarySensorConstructor(template_) |     rhs = CustomBinarySensorConstructor(template_) | ||||||
|     custom = cg.variable(config[CONF_ID], rhs) |     custom = cg.variable(config[CONF_ID], rhs) | ||||||
|   | |||||||
| @@ -4,20 +4,24 @@ from esphome.components import climate | |||||||
| from esphome.const import CONF_ID, CONF_LAMBDA | from esphome.const import CONF_ID, CONF_LAMBDA | ||||||
| from .. import custom_ns | from .. import custom_ns | ||||||
|  |  | ||||||
| CustomClimateConstructor = custom_ns.class_('CustomClimateConstructor') | CustomClimateConstructor = custom_ns.class_("CustomClimateConstructor") | ||||||
| CONF_CLIMATES = 'climates' | CONF_CLIMATES = "climates" | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = cv.Schema({ | CONFIG_SCHEMA = cv.Schema( | ||||||
|     cv.GenerateID(): cv.declare_id(CustomClimateConstructor), |     { | ||||||
|     cv.Required(CONF_LAMBDA): cv.returning_lambda, |         cv.GenerateID(): cv.declare_id(CustomClimateConstructor), | ||||||
|     cv.Required(CONF_CLIMATES): cv.ensure_list(climate.CLIMATE_SCHEMA), |         cv.Required(CONF_LAMBDA): cv.returning_lambda, | ||||||
| }) |         cv.Required(CONF_CLIMATES): cv.ensure_list(climate.CLIMATE_SCHEMA), | ||||||
|  |     } | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
| def to_code(config): | def to_code(config): | ||||||
|     template_ = yield cg.process_lambda( |     template_ = yield cg.process_lambda( | ||||||
|         config[CONF_LAMBDA], [], |         config[CONF_LAMBDA], | ||||||
|         return_type=cg.std_vector.template(climate.Climate.operator('ptr'))) |         [], | ||||||
|  |         return_type=cg.std_vector.template(climate.Climate.operator("ptr")), | ||||||
|  |     ) | ||||||
|  |  | ||||||
|     rhs = CustomClimateConstructor(template_) |     rhs = CustomClimateConstructor(template_) | ||||||
|     custom = cg.variable(config[CONF_ID], rhs) |     custom = cg.variable(config[CONF_ID], rhs) | ||||||
|   | |||||||
| @@ -4,20 +4,24 @@ from esphome.components import cover | |||||||
| from esphome.const import CONF_ID, CONF_LAMBDA | from esphome.const import CONF_ID, CONF_LAMBDA | ||||||
| from .. import custom_ns | from .. import custom_ns | ||||||
|  |  | ||||||
| CustomCoverConstructor = custom_ns.class_('CustomCoverConstructor') | CustomCoverConstructor = custom_ns.class_("CustomCoverConstructor") | ||||||
| CONF_COVERS = 'covers' | CONF_COVERS = "covers" | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = cv.Schema({ | CONFIG_SCHEMA = cv.Schema( | ||||||
|     cv.GenerateID(): cv.declare_id(CustomCoverConstructor), |     { | ||||||
|     cv.Required(CONF_LAMBDA): cv.returning_lambda, |         cv.GenerateID(): cv.declare_id(CustomCoverConstructor), | ||||||
|     cv.Required(CONF_COVERS): cv.ensure_list(cover.COVER_SCHEMA), |         cv.Required(CONF_LAMBDA): cv.returning_lambda, | ||||||
| }) |         cv.Required(CONF_COVERS): cv.ensure_list(cover.COVER_SCHEMA), | ||||||
|  |     } | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
| def to_code(config): | def to_code(config): | ||||||
|     template_ = yield cg.process_lambda( |     template_ = yield cg.process_lambda( | ||||||
|         config[CONF_LAMBDA], [], |         config[CONF_LAMBDA], | ||||||
|         return_type=cg.std_vector.template(cover.Cover.operator('ptr'))) |         [], | ||||||
|  |         return_type=cg.std_vector.template(cover.Cover.operator("ptr")), | ||||||
|  |     ) | ||||||
|  |  | ||||||
|     rhs = CustomCoverConstructor(template_) |     rhs = CustomCoverConstructor(template_) | ||||||
|     custom = cg.variable(config[CONF_ID], rhs) |     custom = cg.variable(config[CONF_ID], rhs) | ||||||
|   | |||||||
| @@ -4,20 +4,24 @@ from esphome.components import light | |||||||
| from esphome.const import CONF_ID, CONF_LAMBDA | from esphome.const import CONF_ID, CONF_LAMBDA | ||||||
| from .. import custom_ns | from .. import custom_ns | ||||||
|  |  | ||||||
| CustomLightOutputConstructor = custom_ns.class_('CustomLightOutputConstructor') | CustomLightOutputConstructor = custom_ns.class_("CustomLightOutputConstructor") | ||||||
| CONF_LIGHTS = 'lights' | CONF_LIGHTS = "lights" | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = cv.Schema({ | CONFIG_SCHEMA = cv.Schema( | ||||||
|     cv.GenerateID(): cv.declare_id(CustomLightOutputConstructor), |     { | ||||||
|     cv.Required(CONF_LAMBDA): cv.returning_lambda, |         cv.GenerateID(): cv.declare_id(CustomLightOutputConstructor), | ||||||
|     cv.Required(CONF_LIGHTS): cv.ensure_list(light.ADDRESSABLE_LIGHT_SCHEMA), |         cv.Required(CONF_LAMBDA): cv.returning_lambda, | ||||||
| }) |         cv.Required(CONF_LIGHTS): cv.ensure_list(light.ADDRESSABLE_LIGHT_SCHEMA), | ||||||
|  |     } | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
| def to_code(config): | def to_code(config): | ||||||
|     template_ = yield cg.process_lambda( |     template_ = yield cg.process_lambda( | ||||||
|         config[CONF_LAMBDA], [], |         config[CONF_LAMBDA], | ||||||
|         return_type=cg.std_vector.template(light.LightOutput.operator('ptr'))) |         [], | ||||||
|  |         return_type=cg.std_vector.template(light.LightOutput.operator("ptr")), | ||||||
|  |     ) | ||||||
|  |  | ||||||
|     rhs = CustomLightOutputConstructor(template_) |     rhs = CustomLightOutputConstructor(template_) | ||||||
|     custom = cg.variable(config[CONF_ID], rhs) |     custom = cg.variable(config[CONF_ID], rhs) | ||||||
|   | |||||||
| @@ -4,41 +4,55 @@ from esphome.components import output | |||||||
| from esphome.const import CONF_ID, CONF_LAMBDA, CONF_OUTPUTS, CONF_TYPE, CONF_BINARY | from esphome.const import CONF_ID, CONF_LAMBDA, CONF_OUTPUTS, CONF_TYPE, CONF_BINARY | ||||||
| from .. import custom_ns | from .. import custom_ns | ||||||
|  |  | ||||||
| CustomBinaryOutputConstructor = custom_ns.class_('CustomBinaryOutputConstructor') | CustomBinaryOutputConstructor = custom_ns.class_("CustomBinaryOutputConstructor") | ||||||
| CustomFloatOutputConstructor = custom_ns.class_('CustomFloatOutputConstructor') | CustomFloatOutputConstructor = custom_ns.class_("CustomFloatOutputConstructor") | ||||||
|  |  | ||||||
| CONF_FLOAT = 'float' | CONF_FLOAT = "float" | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = cv.typed_schema({ | CONFIG_SCHEMA = cv.typed_schema( | ||||||
|     CONF_BINARY: cv.Schema({ |     { | ||||||
|         cv.GenerateID(): cv.declare_id(CustomBinaryOutputConstructor), |         CONF_BINARY: cv.Schema( | ||||||
|         cv.Required(CONF_LAMBDA): cv.returning_lambda, |             { | ||||||
|         cv.Required(CONF_OUTPUTS): |                 cv.GenerateID(): cv.declare_id(CustomBinaryOutputConstructor), | ||||||
|             cv.ensure_list(output.BINARY_OUTPUT_SCHEMA.extend({ |                 cv.Required(CONF_LAMBDA): cv.returning_lambda, | ||||||
|                 cv.GenerateID(): cv.declare_id(output.BinaryOutput), |                 cv.Required(CONF_OUTPUTS): cv.ensure_list( | ||||||
|             })), |                     output.BINARY_OUTPUT_SCHEMA.extend( | ||||||
|     }), |                         { | ||||||
|     CONF_FLOAT: cv.Schema({ |                             cv.GenerateID(): cv.declare_id(output.BinaryOutput), | ||||||
|         cv.GenerateID(): cv.declare_id(CustomFloatOutputConstructor), |                         } | ||||||
|         cv.Required(CONF_LAMBDA): cv.returning_lambda, |                     ) | ||||||
|         cv.Required(CONF_OUTPUTS): |                 ), | ||||||
|             cv.ensure_list(output.FLOAT_OUTPUT_SCHEMA.extend({ |             } | ||||||
|                 cv.GenerateID(): cv.declare_id(output.FloatOutput), |         ), | ||||||
|             })), |         CONF_FLOAT: cv.Schema( | ||||||
|     }) |             { | ||||||
| }, lower=True) |                 cv.GenerateID(): cv.declare_id(CustomFloatOutputConstructor), | ||||||
|  |                 cv.Required(CONF_LAMBDA): cv.returning_lambda, | ||||||
|  |                 cv.Required(CONF_OUTPUTS): cv.ensure_list( | ||||||
|  |                     output.FLOAT_OUTPUT_SCHEMA.extend( | ||||||
|  |                         { | ||||||
|  |                             cv.GenerateID(): cv.declare_id(output.FloatOutput), | ||||||
|  |                         } | ||||||
|  |                     ) | ||||||
|  |                 ), | ||||||
|  |             } | ||||||
|  |         ), | ||||||
|  |     }, | ||||||
|  |     lower=True, | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
| def to_code(config): | def to_code(config): | ||||||
|     type = config[CONF_TYPE] |     type = config[CONF_TYPE] | ||||||
|     if type == 'binary': |     if type == "binary": | ||||||
|         ret_type = output.BinaryOutputPtr |         ret_type = output.BinaryOutputPtr | ||||||
|         klass = CustomBinaryOutputConstructor |         klass = CustomBinaryOutputConstructor | ||||||
|     else: |     else: | ||||||
|         ret_type = output.FloatOutputPtr |         ret_type = output.FloatOutputPtr | ||||||
|         klass = CustomFloatOutputConstructor |         klass = CustomFloatOutputConstructor | ||||||
|     template_ = yield cg.process_lambda(config[CONF_LAMBDA], [], |     template_ = yield cg.process_lambda( | ||||||
|                                         return_type=cg.std_vector.template(ret_type)) |         config[CONF_LAMBDA], [], return_type=cg.std_vector.template(ret_type) | ||||||
|  |     ) | ||||||
|  |  | ||||||
|     rhs = klass(template_) |     rhs = klass(template_) | ||||||
|     custom = cg.variable(config[CONF_ID], rhs) |     custom = cg.variable(config[CONF_ID], rhs) | ||||||
|   | |||||||
| @@ -4,18 +4,21 @@ from esphome.components import sensor | |||||||
| from esphome.const import CONF_ID, CONF_LAMBDA, CONF_SENSORS | from esphome.const import CONF_ID, CONF_LAMBDA, CONF_SENSORS | ||||||
| from .. import custom_ns | from .. import custom_ns | ||||||
|  |  | ||||||
| CustomSensorConstructor = custom_ns.class_('CustomSensorConstructor') | CustomSensorConstructor = custom_ns.class_("CustomSensorConstructor") | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = cv.Schema({ | CONFIG_SCHEMA = cv.Schema( | ||||||
|     cv.GenerateID(): cv.declare_id(CustomSensorConstructor), |     { | ||||||
|     cv.Required(CONF_LAMBDA): cv.returning_lambda, |         cv.GenerateID(): cv.declare_id(CustomSensorConstructor), | ||||||
|     cv.Required(CONF_SENSORS): cv.ensure_list(sensor.SENSOR_SCHEMA), |         cv.Required(CONF_LAMBDA): cv.returning_lambda, | ||||||
| }) |         cv.Required(CONF_SENSORS): cv.ensure_list(sensor.SENSOR_SCHEMA), | ||||||
|  |     } | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
| def to_code(config): | def to_code(config): | ||||||
|     template_ = yield cg.process_lambda( |     template_ = yield cg.process_lambda( | ||||||
|         config[CONF_LAMBDA], [], return_type=cg.std_vector.template(sensor.SensorPtr)) |         config[CONF_LAMBDA], [], return_type=cg.std_vector.template(sensor.SensorPtr) | ||||||
|  |     ) | ||||||
|  |  | ||||||
|     rhs = CustomSensorConstructor(template_) |     rhs = CustomSensorConstructor(template_) | ||||||
|     var = cg.variable(config[CONF_ID], rhs) |     var = cg.variable(config[CONF_ID], rhs) | ||||||
|   | |||||||
| @@ -4,21 +4,27 @@ from esphome.components import switch | |||||||
| from esphome.const import CONF_ID, CONF_LAMBDA, CONF_SWITCHES | from esphome.const import CONF_ID, CONF_LAMBDA, CONF_SWITCHES | ||||||
| from .. import custom_ns | from .. import custom_ns | ||||||
|  |  | ||||||
| CustomSwitchConstructor = custom_ns.class_('CustomSwitchConstructor') | CustomSwitchConstructor = custom_ns.class_("CustomSwitchConstructor") | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = cv.Schema({ | CONFIG_SCHEMA = cv.Schema( | ||||||
|     cv.GenerateID(): cv.declare_id(CustomSwitchConstructor), |     { | ||||||
|     cv.Required(CONF_LAMBDA): cv.returning_lambda, |         cv.GenerateID(): cv.declare_id(CustomSwitchConstructor), | ||||||
|     cv.Required(CONF_SWITCHES): |         cv.Required(CONF_LAMBDA): cv.returning_lambda, | ||||||
|         cv.ensure_list(switch.SWITCH_SCHEMA.extend({ |         cv.Required(CONF_SWITCHES): cv.ensure_list( | ||||||
|             cv.GenerateID(): cv.declare_id(switch.Switch), |             switch.SWITCH_SCHEMA.extend( | ||||||
|         })), |                 { | ||||||
| }) |                     cv.GenerateID(): cv.declare_id(switch.Switch), | ||||||
|  |                 } | ||||||
|  |             ) | ||||||
|  |         ), | ||||||
|  |     } | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
| def to_code(config): | def to_code(config): | ||||||
|     template_ = yield cg.process_lambda( |     template_ = yield cg.process_lambda( | ||||||
|         config[CONF_LAMBDA], [], return_type=cg.std_vector.template(switch.SwitchPtr)) |         config[CONF_LAMBDA], [], return_type=cg.std_vector.template(switch.SwitchPtr) | ||||||
|  |     ) | ||||||
|  |  | ||||||
|     rhs = CustomSwitchConstructor(template_) |     rhs = CustomSwitchConstructor(template_) | ||||||
|     var = cg.variable(config[CONF_ID], rhs) |     var = cg.variable(config[CONF_ID], rhs) | ||||||
|   | |||||||
| @@ -4,21 +4,29 @@ from esphome.components import text_sensor | |||||||
| from esphome.const import CONF_ID, CONF_LAMBDA, CONF_TEXT_SENSORS | from esphome.const import CONF_ID, CONF_LAMBDA, CONF_TEXT_SENSORS | ||||||
| from .. import custom_ns | from .. import custom_ns | ||||||
|  |  | ||||||
| CustomTextSensorConstructor = custom_ns.class_('CustomTextSensorConstructor') | CustomTextSensorConstructor = custom_ns.class_("CustomTextSensorConstructor") | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = cv.Schema({ | CONFIG_SCHEMA = cv.Schema( | ||||||
|     cv.GenerateID(): cv.declare_id(CustomTextSensorConstructor), |     { | ||||||
|     cv.Required(CONF_LAMBDA): cv.returning_lambda, |         cv.GenerateID(): cv.declare_id(CustomTextSensorConstructor), | ||||||
|     cv.Required(CONF_TEXT_SENSORS): |         cv.Required(CONF_LAMBDA): cv.returning_lambda, | ||||||
|         cv.ensure_list(text_sensor.TEXT_SENSOR_SCHEMA.extend({ |         cv.Required(CONF_TEXT_SENSORS): cv.ensure_list( | ||||||
|             cv.GenerateID(): cv.declare_id(text_sensor.TextSensor), |             text_sensor.TEXT_SENSOR_SCHEMA.extend( | ||||||
|         })), |                 { | ||||||
| }) |                     cv.GenerateID(): cv.declare_id(text_sensor.TextSensor), | ||||||
|  |                 } | ||||||
|  |             ) | ||||||
|  |         ), | ||||||
|  |     } | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
| def to_code(config): | def to_code(config): | ||||||
|     template_ = yield cg.process_lambda( |     template_ = yield cg.process_lambda( | ||||||
|         config[CONF_LAMBDA], [], return_type=cg.std_vector.template(text_sensor.TextSensorPtr)) |         config[CONF_LAMBDA], | ||||||
|  |         [], | ||||||
|  |         return_type=cg.std_vector.template(text_sensor.TextSensorPtr), | ||||||
|  |     ) | ||||||
|  |  | ||||||
|     rhs = CustomTextSensorConstructor(template_) |     rhs = CustomTextSensorConstructor(template_) | ||||||
|     var = cg.variable(config[CONF_ID], rhs) |     var = cg.variable(config[CONF_ID], rhs) | ||||||
|   | |||||||
| @@ -2,22 +2,27 @@ import esphome.codegen as cg | |||||||
| import esphome.config_validation as cv | import esphome.config_validation as cv | ||||||
| from esphome.const import CONF_COMPONENTS, CONF_ID, CONF_LAMBDA | from esphome.const import CONF_COMPONENTS, CONF_ID, CONF_LAMBDA | ||||||
|  |  | ||||||
| custom_component_ns = cg.esphome_ns.namespace('custom_component') | custom_component_ns = cg.esphome_ns.namespace("custom_component") | ||||||
| CustomComponentConstructor = custom_component_ns.class_('CustomComponentConstructor') | CustomComponentConstructor = custom_component_ns.class_("CustomComponentConstructor") | ||||||
|  |  | ||||||
| MULTI_CONF = True | MULTI_CONF = True | ||||||
| CONFIG_SCHEMA = cv.Schema({ | CONFIG_SCHEMA = cv.Schema( | ||||||
|     cv.GenerateID(): cv.declare_id(CustomComponentConstructor), |     { | ||||||
|     cv.Required(CONF_LAMBDA): cv.returning_lambda, |         cv.GenerateID(): cv.declare_id(CustomComponentConstructor), | ||||||
|     cv.Optional(CONF_COMPONENTS): cv.ensure_list(cv.Schema({ |         cv.Required(CONF_LAMBDA): cv.returning_lambda, | ||||||
|         cv.GenerateID(): cv.declare_id(cg.Component) |         cv.Optional(CONF_COMPONENTS): cv.ensure_list( | ||||||
|     }).extend(cv.COMPONENT_SCHEMA)), |             cv.Schema({cv.GenerateID(): cv.declare_id(cg.Component)}).extend( | ||||||
| }) |                 cv.COMPONENT_SCHEMA | ||||||
|  |             ) | ||||||
|  |         ), | ||||||
|  |     } | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
| def to_code(config): | def to_code(config): | ||||||
|     template_ = yield cg.process_lambda( |     template_ = yield cg.process_lambda( | ||||||
|         config[CONF_LAMBDA], [], return_type=cg.std_vector.template(cg.ComponentPtr)) |         config[CONF_LAMBDA], [], return_type=cg.std_vector.template(cg.ComponentPtr) | ||||||
|  |     ) | ||||||
|  |  | ||||||
|     rhs = CustomComponentConstructor(template_) |     rhs = CustomComponentConstructor(template_) | ||||||
|     var = cg.variable(config[CONF_ID], rhs) |     var = cg.variable(config[CONF_ID], rhs) | ||||||
|   | |||||||
| @@ -1,22 +1,29 @@ | |||||||
| import esphome.codegen as cg | import esphome.codegen as cg | ||||||
| import esphome.config_validation as cv | import esphome.config_validation as cv | ||||||
| from esphome.components import light, output | from esphome.components import light, output | ||||||
| from esphome.const import CONF_OUTPUT_ID, CONF_COLD_WHITE, CONF_WARM_WHITE, \ | from esphome.const import ( | ||||||
|     CONF_COLD_WHITE_COLOR_TEMPERATURE, CONF_WARM_WHITE_COLOR_TEMPERATURE |     CONF_OUTPUT_ID, | ||||||
|  |     CONF_COLD_WHITE, | ||||||
|  |     CONF_WARM_WHITE, | ||||||
|  |     CONF_COLD_WHITE_COLOR_TEMPERATURE, | ||||||
|  |     CONF_WARM_WHITE_COLOR_TEMPERATURE, | ||||||
|  | ) | ||||||
|  |  | ||||||
| cwww_ns = cg.esphome_ns.namespace('cwww') | cwww_ns = cg.esphome_ns.namespace("cwww") | ||||||
| CWWWLightOutput = cwww_ns.class_('CWWWLightOutput', light.LightOutput) | CWWWLightOutput = cwww_ns.class_("CWWWLightOutput", light.LightOutput) | ||||||
|  |  | ||||||
| CONF_CONSTANT_BRIGHTNESS = 'constant_brightness' | CONF_CONSTANT_BRIGHTNESS = "constant_brightness" | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = light.RGB_LIGHT_SCHEMA.extend({ | CONFIG_SCHEMA = light.RGB_LIGHT_SCHEMA.extend( | ||||||
|     cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(CWWWLightOutput), |     { | ||||||
|     cv.Required(CONF_COLD_WHITE): cv.use_id(output.FloatOutput), |         cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(CWWWLightOutput), | ||||||
|     cv.Required(CONF_WARM_WHITE): cv.use_id(output.FloatOutput), |         cv.Required(CONF_COLD_WHITE): cv.use_id(output.FloatOutput), | ||||||
|     cv.Required(CONF_COLD_WHITE_COLOR_TEMPERATURE): cv.color_temperature, |         cv.Required(CONF_WARM_WHITE): cv.use_id(output.FloatOutput), | ||||||
|     cv.Required(CONF_WARM_WHITE_COLOR_TEMPERATURE): cv.color_temperature, |         cv.Required(CONF_COLD_WHITE_COLOR_TEMPERATURE): cv.color_temperature, | ||||||
|     cv.Optional(CONF_CONSTANT_BRIGHTNESS, default=False): cv.boolean, |         cv.Required(CONF_WARM_WHITE_COLOR_TEMPERATURE): cv.color_temperature, | ||||||
| }) |         cv.Optional(CONF_CONSTANT_BRIGHTNESS, default=False): cv.boolean, | ||||||
|  |     } | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
| def to_code(config): | def to_code(config): | ||||||
|   | |||||||
| @@ -3,14 +3,16 @@ import esphome.config_validation as cv | |||||||
| from esphome.components import climate_ir | from esphome.components import climate_ir | ||||||
| from esphome.const import CONF_ID | from esphome.const import CONF_ID | ||||||
|  |  | ||||||
| AUTO_LOAD = ['climate_ir'] | AUTO_LOAD = ["climate_ir"] | ||||||
|  |  | ||||||
| daikin_ns = cg.esphome_ns.namespace('daikin') | daikin_ns = cg.esphome_ns.namespace("daikin") | ||||||
| DaikinClimate = daikin_ns.class_('DaikinClimate', climate_ir.ClimateIR) | DaikinClimate = daikin_ns.class_("DaikinClimate", climate_ir.ClimateIR) | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend({ | CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend( | ||||||
|     cv.GenerateID(): cv.declare_id(DaikinClimate), |     { | ||||||
| }) |         cv.GenerateID(): cv.declare_id(DaikinClimate), | ||||||
|  |     } | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
| def to_code(config): | def to_code(config): | ||||||
|   | |||||||
| @@ -4,18 +4,20 @@ from esphome import pins | |||||||
| from esphome.const import CONF_ID, CONF_PIN | from esphome.const import CONF_ID, CONF_PIN | ||||||
|  |  | ||||||
| MULTI_CONF = True | MULTI_CONF = True | ||||||
| AUTO_LOAD = ['sensor'] | AUTO_LOAD = ["sensor"] | ||||||
|  |  | ||||||
| CONF_ONE_WIRE_ID = 'one_wire_id' | CONF_ONE_WIRE_ID = "one_wire_id" | ||||||
| dallas_ns = cg.esphome_ns.namespace('dallas') | dallas_ns = cg.esphome_ns.namespace("dallas") | ||||||
| DallasComponent = dallas_ns.class_('DallasComponent', cg.PollingComponent) | DallasComponent = dallas_ns.class_("DallasComponent", cg.PollingComponent) | ||||||
| ESPOneWire = dallas_ns.class_('ESPOneWire') | ESPOneWire = dallas_ns.class_("ESPOneWire") | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = cv.Schema({ | CONFIG_SCHEMA = cv.Schema( | ||||||
|     cv.GenerateID(): cv.declare_id(DallasComponent), |     { | ||||||
|     cv.GenerateID(CONF_ONE_WIRE_ID): cv.declare_id(ESPOneWire), |         cv.GenerateID(): cv.declare_id(DallasComponent), | ||||||
|     cv.Required(CONF_PIN): pins.gpio_input_pin_schema, |         cv.GenerateID(CONF_ONE_WIRE_ID): cv.declare_id(ESPOneWire), | ||||||
| }).extend(cv.polling_component_schema('60s')) |         cv.Required(CONF_PIN): pins.gpio_input_pin_schema, | ||||||
|  |     } | ||||||
|  | ).extend(cv.polling_component_schema("60s")) | ||||||
|  |  | ||||||
|  |  | ||||||
| def to_code(config): | def to_code(config): | ||||||
|   | |||||||
| @@ -1,20 +1,32 @@ | |||||||
| import esphome.codegen as cg | import esphome.codegen as cg | ||||||
| import esphome.config_validation as cv | import esphome.config_validation as cv | ||||||
| from esphome.components import sensor | from esphome.components import sensor | ||||||
| from esphome.const import CONF_ADDRESS, CONF_DALLAS_ID, CONF_INDEX, CONF_RESOLUTION, UNIT_CELSIUS, \ | from esphome.const import ( | ||||||
|     ICON_THERMOMETER, CONF_ID |     CONF_ADDRESS, | ||||||
|  |     CONF_DALLAS_ID, | ||||||
|  |     CONF_INDEX, | ||||||
|  |     CONF_RESOLUTION, | ||||||
|  |     DEVICE_CLASS_TEMPERATURE, | ||||||
|  |     ICON_EMPTY, | ||||||
|  |     UNIT_CELSIUS, | ||||||
|  |     CONF_ID, | ||||||
|  | ) | ||||||
| from . import DallasComponent, dallas_ns | from . import DallasComponent, dallas_ns | ||||||
|  |  | ||||||
| DallasTemperatureSensor = dallas_ns.class_('DallasTemperatureSensor', sensor.Sensor) | DallasTemperatureSensor = dallas_ns.class_("DallasTemperatureSensor", sensor.Sensor) | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = cv.All(sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 1).extend({ | CONFIG_SCHEMA = cv.All( | ||||||
|     cv.GenerateID(): cv.declare_id(DallasTemperatureSensor), |     sensor.sensor_schema(UNIT_CELSIUS, ICON_EMPTY, 1, DEVICE_CLASS_TEMPERATURE).extend( | ||||||
|     cv.GenerateID(CONF_DALLAS_ID): cv.use_id(DallasComponent), |         { | ||||||
|  |             cv.GenerateID(): cv.declare_id(DallasTemperatureSensor), | ||||||
|     cv.Optional(CONF_ADDRESS): cv.hex_int, |             cv.GenerateID(CONF_DALLAS_ID): cv.use_id(DallasComponent), | ||||||
|     cv.Optional(CONF_INDEX): cv.positive_int, |             cv.Optional(CONF_ADDRESS): cv.hex_int, | ||||||
|     cv.Optional(CONF_RESOLUTION, default=12): cv.int_range(min=9, max=12), |             cv.Optional(CONF_INDEX): cv.positive_int, | ||||||
| }), cv.has_exactly_one_key(CONF_ADDRESS, CONF_INDEX)) |             cv.Optional(CONF_RESOLUTION, default=12): cv.int_range(min=9, max=12), | ||||||
|  |         } | ||||||
|  |     ), | ||||||
|  |     cv.has_exactly_one_key(CONF_ADDRESS, CONF_INDEX), | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
| def to_code(config): | def to_code(config): | ||||||
|   | |||||||
| @@ -2,14 +2,16 @@ import esphome.config_validation as cv | |||||||
| import esphome.codegen as cg | import esphome.codegen as cg | ||||||
| from esphome.const import CONF_ID | from esphome.const import CONF_ID | ||||||
|  |  | ||||||
| CODEOWNERS = ['@OttoWinter'] | CODEOWNERS = ["@OttoWinter"] | ||||||
| DEPENDENCIES = ['logger'] | DEPENDENCIES = ["logger"] | ||||||
|  |  | ||||||
| debug_ns = cg.esphome_ns.namespace('debug') | debug_ns = cg.esphome_ns.namespace("debug") | ||||||
| DebugComponent = debug_ns.class_('DebugComponent', cg.Component) | DebugComponent = debug_ns.class_("DebugComponent", cg.Component) | ||||||
| CONFIG_SCHEMA = cv.Schema({ | CONFIG_SCHEMA = cv.Schema( | ||||||
|     cv.GenerateID(): cv.declare_id(DebugComponent), |     { | ||||||
| }).extend(cv.COMPONENT_SCHEMA) |         cv.GenerateID(): cv.declare_id(DebugComponent), | ||||||
|  |     } | ||||||
|  | ).extend(cv.COMPONENT_SCHEMA) | ||||||
|  |  | ||||||
|  |  | ||||||
| def to_code(config): | def to_code(config): | ||||||
|   | |||||||
| @@ -1,58 +1,81 @@ | |||||||
| import esphome.codegen as cg | import esphome.codegen as cg | ||||||
| import esphome.config_validation as cv | import esphome.config_validation as cv | ||||||
| from esphome import pins, automation | from esphome import pins, automation | ||||||
| from esphome.const import CONF_ID, CONF_MODE, CONF_NUMBER, CONF_PINS, CONF_RUN_CYCLES, \ | from esphome.const import ( | ||||||
|     CONF_RUN_DURATION, CONF_SLEEP_DURATION, CONF_WAKEUP_PIN |     CONF_ID, | ||||||
|  |     CONF_MODE, | ||||||
|  |     CONF_NUMBER, | ||||||
|  |     CONF_PINS, | ||||||
|  |     CONF_RUN_CYCLES, | ||||||
|  |     CONF_RUN_DURATION, | ||||||
|  |     CONF_SLEEP_DURATION, | ||||||
|  |     CONF_WAKEUP_PIN, | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
| def validate_pin_number(value): | def validate_pin_number(value): | ||||||
|     valid_pins = [0, 2, 4, 12, 13, 14, 15, 25, 26, 27, 32, 33, 34, 35, 36, 37, 38, 39] |     valid_pins = [0, 2, 4, 12, 13, 14, 15, 25, 26, 27, 32, 33, 34, 35, 36, 37, 38, 39] | ||||||
|     if value[CONF_NUMBER] not in valid_pins: |     if value[CONF_NUMBER] not in valid_pins: | ||||||
|         raise cv.Invalid("Only pins {} support wakeup" |         raise cv.Invalid( | ||||||
|                          "".format(', '.join(str(x) for x in valid_pins))) |             "Only pins {} support wakeup" | ||||||
|  |             "".format(", ".join(str(x) for x in valid_pins)) | ||||||
|  |         ) | ||||||
|     return value |     return value | ||||||
|  |  | ||||||
|  |  | ||||||
| deep_sleep_ns = cg.esphome_ns.namespace('deep_sleep') | deep_sleep_ns = cg.esphome_ns.namespace("deep_sleep") | ||||||
| DeepSleepComponent = deep_sleep_ns.class_('DeepSleepComponent', cg.Component) | DeepSleepComponent = deep_sleep_ns.class_("DeepSleepComponent", cg.Component) | ||||||
| EnterDeepSleepAction = deep_sleep_ns.class_('EnterDeepSleepAction', automation.Action) | EnterDeepSleepAction = deep_sleep_ns.class_("EnterDeepSleepAction", automation.Action) | ||||||
| PreventDeepSleepAction = deep_sleep_ns.class_('PreventDeepSleepAction', automation.Action) | PreventDeepSleepAction = deep_sleep_ns.class_( | ||||||
|  |     "PreventDeepSleepAction", automation.Action | ||||||
|  | ) | ||||||
|  |  | ||||||
| WakeupPinMode = deep_sleep_ns.enum('WakeupPinMode') | WakeupPinMode = deep_sleep_ns.enum("WakeupPinMode") | ||||||
| WAKEUP_PIN_MODES = { | WAKEUP_PIN_MODES = { | ||||||
|     'IGNORE': WakeupPinMode.WAKEUP_PIN_MODE_IGNORE, |     "IGNORE": WakeupPinMode.WAKEUP_PIN_MODE_IGNORE, | ||||||
|     'KEEP_AWAKE': WakeupPinMode.WAKEUP_PIN_MODE_KEEP_AWAKE, |     "KEEP_AWAKE": WakeupPinMode.WAKEUP_PIN_MODE_KEEP_AWAKE, | ||||||
|     'INVERT_WAKEUP': WakeupPinMode.WAKEUP_PIN_MODE_INVERT_WAKEUP, |     "INVERT_WAKEUP": WakeupPinMode.WAKEUP_PIN_MODE_INVERT_WAKEUP, | ||||||
| } | } | ||||||
|  |  | ||||||
| esp_sleep_ext1_wakeup_mode_t = cg.global_ns.enum('esp_sleep_ext1_wakeup_mode_t') | esp_sleep_ext1_wakeup_mode_t = cg.global_ns.enum("esp_sleep_ext1_wakeup_mode_t") | ||||||
| Ext1Wakeup = deep_sleep_ns.struct('Ext1Wakeup') | Ext1Wakeup = deep_sleep_ns.struct("Ext1Wakeup") | ||||||
| EXT1_WAKEUP_MODES = { | EXT1_WAKEUP_MODES = { | ||||||
|     'ALL_LOW': esp_sleep_ext1_wakeup_mode_t.ESP_EXT1_WAKEUP_ALL_LOW, |     "ALL_LOW": esp_sleep_ext1_wakeup_mode_t.ESP_EXT1_WAKEUP_ALL_LOW, | ||||||
|     'ANY_HIGH': esp_sleep_ext1_wakeup_mode_t.ESP_EXT1_WAKEUP_ANY_HIGH, |     "ANY_HIGH": esp_sleep_ext1_wakeup_mode_t.ESP_EXT1_WAKEUP_ANY_HIGH, | ||||||
| } | } | ||||||
|  |  | ||||||
| CONF_WAKEUP_PIN_MODE = 'wakeup_pin_mode' | CONF_WAKEUP_PIN_MODE = "wakeup_pin_mode" | ||||||
| CONF_ESP32_EXT1_WAKEUP = 'esp32_ext1_wakeup' | CONF_ESP32_EXT1_WAKEUP = "esp32_ext1_wakeup" | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = cv.Schema({ | CONFIG_SCHEMA = cv.Schema( | ||||||
|     cv.GenerateID(): cv.declare_id(DeepSleepComponent), |     { | ||||||
|     cv.Optional(CONF_RUN_DURATION): cv.positive_time_period_milliseconds, |         cv.GenerateID(): cv.declare_id(DeepSleepComponent), | ||||||
|  |         cv.Optional(CONF_RUN_DURATION): cv.positive_time_period_milliseconds, | ||||||
|     cv.Optional(CONF_SLEEP_DURATION): cv.positive_time_period_milliseconds, |         cv.Optional(CONF_SLEEP_DURATION): cv.positive_time_period_milliseconds, | ||||||
|     cv.Optional(CONF_WAKEUP_PIN): cv.All(cv.only_on_esp32, pins.internal_gpio_input_pin_schema, |         cv.Optional(CONF_WAKEUP_PIN): cv.All( | ||||||
|                                          validate_pin_number), |             cv.only_on_esp32, pins.internal_gpio_input_pin_schema, validate_pin_number | ||||||
|     cv.Optional(CONF_WAKEUP_PIN_MODE): cv.All(cv.only_on_esp32, |         ), | ||||||
|                                               cv.enum(WAKEUP_PIN_MODES), upper=True), |         cv.Optional(CONF_WAKEUP_PIN_MODE): cv.All( | ||||||
|     cv.Optional(CONF_ESP32_EXT1_WAKEUP): cv.All(cv.only_on_esp32, cv.Schema({ |             cv.only_on_esp32, cv.enum(WAKEUP_PIN_MODES), upper=True | ||||||
|         cv.Required(CONF_PINS): cv.ensure_list(pins.shorthand_input_pin, validate_pin_number), |         ), | ||||||
|         cv.Required(CONF_MODE): cv.enum(EXT1_WAKEUP_MODES, upper=True), |         cv.Optional(CONF_ESP32_EXT1_WAKEUP): cv.All( | ||||||
|     })), |             cv.only_on_esp32, | ||||||
|  |             cv.Schema( | ||||||
|     cv.Optional(CONF_RUN_CYCLES): cv.invalid("The run_cycles option has been removed in 1.11.0 as " |                 { | ||||||
|                                              "it was essentially the same as a run_duration of 0s." |                     cv.Required(CONF_PINS): cv.ensure_list( | ||||||
|                                              "Please use run_duration now.") |                         pins.shorthand_input_pin, validate_pin_number | ||||||
| }).extend(cv.COMPONENT_SCHEMA) |                     ), | ||||||
|  |                     cv.Required(CONF_MODE): cv.enum(EXT1_WAKEUP_MODES, upper=True), | ||||||
|  |                 } | ||||||
|  |             ), | ||||||
|  |         ), | ||||||
|  |         cv.Optional(CONF_RUN_CYCLES): cv.invalid( | ||||||
|  |             "The run_cycles option has been removed in 1.11.0 as " | ||||||
|  |             "it was essentially the same as a run_duration of 0s." | ||||||
|  |             "Please use run_duration now." | ||||||
|  |         ), | ||||||
|  |     } | ||||||
|  | ).extend(cv.COMPONENT_SCHEMA) | ||||||
|  |  | ||||||
|  |  | ||||||
| def to_code(config): | def to_code(config): | ||||||
| @@ -75,27 +98,43 @@ def to_code(config): | |||||||
|         for pin in conf[CONF_PINS]: |         for pin in conf[CONF_PINS]: | ||||||
|             mask |= 1 << pin[CONF_NUMBER] |             mask |= 1 << pin[CONF_NUMBER] | ||||||
|         struct = cg.StructInitializer( |         struct = cg.StructInitializer( | ||||||
|             Ext1Wakeup, |             Ext1Wakeup, ("mask", mask), ("wakeup_mode", conf[CONF_MODE]) | ||||||
|             ('mask', mask), |  | ||||||
|             ('wakeup_mode', conf[CONF_MODE]) |  | ||||||
|         ) |         ) | ||||||
|         cg.add(var.set_ext1_wakeup(struct)) |         cg.add(var.set_ext1_wakeup(struct)) | ||||||
|  |  | ||||||
|     cg.add_define('USE_DEEP_SLEEP') |     cg.add_define("USE_DEEP_SLEEP") | ||||||
|  |  | ||||||
|  |  | ||||||
| DEEP_SLEEP_ACTION_SCHEMA = automation.maybe_simple_id({ | DEEP_SLEEP_ENTER_SCHEMA = automation.maybe_simple_id( | ||||||
|     cv.GenerateID(): cv.use_id(DeepSleepComponent), |     { | ||||||
| }) |         cv.GenerateID(): cv.use_id(DeepSleepComponent), | ||||||
|  |         cv.Optional(CONF_SLEEP_DURATION): cv.positive_time_period_milliseconds, | ||||||
|  |     } | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
| @automation.register_action('deep_sleep.enter', EnterDeepSleepAction, DEEP_SLEEP_ACTION_SCHEMA) | DEEP_SLEEP_PREVENT_SCHEMA = automation.maybe_simple_id( | ||||||
|  |     { | ||||||
|  |         cv.GenerateID(): cv.use_id(DeepSleepComponent), | ||||||
|  |     } | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @automation.register_action( | ||||||
|  |     "deep_sleep.enter", EnterDeepSleepAction, DEEP_SLEEP_ENTER_SCHEMA | ||||||
|  | ) | ||||||
| def deep_sleep_enter_to_code(config, action_id, template_arg, args): | def deep_sleep_enter_to_code(config, action_id, template_arg, args): | ||||||
|     paren = yield cg.get_variable(config[CONF_ID]) |     paren = yield cg.get_variable(config[CONF_ID]) | ||||||
|     yield cg.new_Pvariable(action_id, template_arg, paren) |     var = cg.new_Pvariable(action_id, template_arg, paren) | ||||||
|  |     if CONF_SLEEP_DURATION in config: | ||||||
|  |         template_ = yield cg.templatable(config[CONF_SLEEP_DURATION], args, cg.int32) | ||||||
|  |         cg.add(var.set_sleep_duration(template_)) | ||||||
|  |     yield var | ||||||
|  |  | ||||||
|  |  | ||||||
| @automation.register_action('deep_sleep.prevent', PreventDeepSleepAction, DEEP_SLEEP_ACTION_SCHEMA) | @automation.register_action( | ||||||
|  |     "deep_sleep.prevent", PreventDeepSleepAction, DEEP_SLEEP_PREVENT_SCHEMA | ||||||
|  | ) | ||||||
| def deep_sleep_prevent_to_code(config, action_id, template_arg, args): | def deep_sleep_prevent_to_code(config, action_id, template_arg, args): | ||||||
|     paren = yield cg.get_variable(config[CONF_ID]) |     paren = yield cg.get_variable(config[CONF_ID]) | ||||||
|     yield cg.new_Pvariable(action_id, template_arg, paren) |     yield cg.new_Pvariable(action_id, template_arg, paren) | ||||||
|   | |||||||
| @@ -84,8 +84,14 @@ extern bool global_has_deep_sleep; | |||||||
| template<typename... Ts> class EnterDeepSleepAction : public Action<Ts...> { | template<typename... Ts> class EnterDeepSleepAction : public Action<Ts...> { | ||||||
|  public: |  public: | ||||||
|   EnterDeepSleepAction(DeepSleepComponent *deep_sleep) : deep_sleep_(deep_sleep) {} |   EnterDeepSleepAction(DeepSleepComponent *deep_sleep) : deep_sleep_(deep_sleep) {} | ||||||
|  |   TEMPLATABLE_VALUE(uint32_t, sleep_duration); | ||||||
|  |  | ||||||
|   void play(Ts... x) override { this->deep_sleep_->begin_sleep(true); } |   void play(Ts... x) override { | ||||||
|  |     if (this->sleep_duration_.has_value()) { | ||||||
|  |       this->deep_sleep_->set_sleep_duration(this->sleep_duration_.value(x...)); | ||||||
|  |     } | ||||||
|  |     this->deep_sleep_->begin_sleep(true); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   DeepSleepComponent *deep_sleep_; |   DeepSleepComponent *deep_sleep_; | ||||||
|   | |||||||
| @@ -4,57 +4,68 @@ from esphome import automation | |||||||
| from esphome.const import CONF_ID, CONF_TRIGGER_ID, CONF_FILE, CONF_DEVICE | from esphome.const import CONF_ID, CONF_TRIGGER_ID, CONF_FILE, CONF_DEVICE | ||||||
| from esphome.components import uart | from esphome.components import uart | ||||||
|  |  | ||||||
| DEPENDENCIES = ['uart'] | DEPENDENCIES = ["uart"] | ||||||
| CODEOWNERS = ['@glmnet'] | CODEOWNERS = ["@glmnet"] | ||||||
|  |  | ||||||
| dfplayer_ns = cg.esphome_ns.namespace('dfplayer') | dfplayer_ns = cg.esphome_ns.namespace("dfplayer") | ||||||
| DFPlayer = dfplayer_ns.class_('DFPlayer', cg.Component) | DFPlayer = dfplayer_ns.class_("DFPlayer", cg.Component) | ||||||
| DFPlayerFinishedPlaybackTrigger = dfplayer_ns.class_('DFPlayerFinishedPlaybackTrigger', | DFPlayerFinishedPlaybackTrigger = dfplayer_ns.class_( | ||||||
|                                                      automation.Trigger.template()) |     "DFPlayerFinishedPlaybackTrigger", automation.Trigger.template() | ||||||
| DFPlayerIsPlayingCondition = dfplayer_ns.class_('DFPlayerIsPlayingCondition', automation.Condition) | ) | ||||||
|  | DFPlayerIsPlayingCondition = dfplayer_ns.class_( | ||||||
|  |     "DFPlayerIsPlayingCondition", automation.Condition | ||||||
|  | ) | ||||||
|  |  | ||||||
| MULTI_CONF = True | MULTI_CONF = True | ||||||
| CONF_FOLDER = 'folder' | CONF_FOLDER = "folder" | ||||||
| CONF_LOOP = 'loop' | CONF_LOOP = "loop" | ||||||
| CONF_VOLUME = 'volume' | CONF_VOLUME = "volume" | ||||||
| CONF_EQ_PRESET = 'eq_preset' | CONF_EQ_PRESET = "eq_preset" | ||||||
| CONF_ON_FINISHED_PLAYBACK = 'on_finished_playback' | CONF_ON_FINISHED_PLAYBACK = "on_finished_playback" | ||||||
|  |  | ||||||
| EqPreset = dfplayer_ns.enum("EqPreset") | EqPreset = dfplayer_ns.enum("EqPreset") | ||||||
| EQ_PRESET = { | EQ_PRESET = { | ||||||
|     'NORMAL': EqPreset.NORMAL, |     "NORMAL": EqPreset.NORMAL, | ||||||
|     'POP': EqPreset.POP, |     "POP": EqPreset.POP, | ||||||
|     'ROCK': EqPreset.ROCK, |     "ROCK": EqPreset.ROCK, | ||||||
|     'JAZZ': EqPreset.JAZZ, |     "JAZZ": EqPreset.JAZZ, | ||||||
|     'CLASSIC': EqPreset.CLASSIC, |     "CLASSIC": EqPreset.CLASSIC, | ||||||
|     'BASS': EqPreset.BASS, |     "BASS": EqPreset.BASS, | ||||||
| } | } | ||||||
| Device = dfplayer_ns.enum("Device") | Device = dfplayer_ns.enum("Device") | ||||||
| DEVICE = { | DEVICE = { | ||||||
|     'USB': Device.USB, |     "USB": Device.USB, | ||||||
|     'TF_CARD': Device.TF_CARD, |     "TF_CARD": Device.TF_CARD, | ||||||
| } | } | ||||||
|  |  | ||||||
| NextAction = dfplayer_ns.class_('NextAction', automation.Action) | NextAction = dfplayer_ns.class_("NextAction", automation.Action) | ||||||
| PreviousAction = dfplayer_ns.class_('PreviousAction', automation.Action) | PreviousAction = dfplayer_ns.class_("PreviousAction", automation.Action) | ||||||
| PlayFileAction = dfplayer_ns.class_('PlayFileAction', automation.Action) | PlayFileAction = dfplayer_ns.class_("PlayFileAction", automation.Action) | ||||||
| PlayFolderAction = dfplayer_ns.class_('PlayFolderAction', automation.Action) | PlayFolderAction = dfplayer_ns.class_("PlayFolderAction", automation.Action) | ||||||
| SetVolumeAction = dfplayer_ns.class_('SetVolumeAction', automation.Action) | SetVolumeAction = dfplayer_ns.class_("SetVolumeAction", automation.Action) | ||||||
| SetEqAction = dfplayer_ns.class_('SetEqAction', automation.Action) | SetEqAction = dfplayer_ns.class_("SetEqAction", automation.Action) | ||||||
| SleepAction = dfplayer_ns.class_('SleepAction', automation.Action) | SleepAction = dfplayer_ns.class_("SleepAction", automation.Action) | ||||||
| ResetAction = dfplayer_ns.class_('ResetAction', automation.Action) | ResetAction = dfplayer_ns.class_("ResetAction", automation.Action) | ||||||
| StartAction = dfplayer_ns.class_('StartAction', automation.Action) | StartAction = dfplayer_ns.class_("StartAction", automation.Action) | ||||||
| PauseAction = dfplayer_ns.class_('PauseAction', automation.Action) | PauseAction = dfplayer_ns.class_("PauseAction", automation.Action) | ||||||
| StopAction = dfplayer_ns.class_('StopAction', automation.Action) | StopAction = dfplayer_ns.class_("StopAction", automation.Action) | ||||||
| RandomAction = dfplayer_ns.class_('RandomAction', automation.Action) | RandomAction = dfplayer_ns.class_("RandomAction", automation.Action) | ||||||
| SetDeviceAction = dfplayer_ns.class_('SetDeviceAction', automation.Action) | SetDeviceAction = dfplayer_ns.class_("SetDeviceAction", automation.Action) | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = cv.All(cv.Schema({ | CONFIG_SCHEMA = cv.All( | ||||||
|     cv.GenerateID(): cv.declare_id(DFPlayer), |     cv.Schema( | ||||||
|     cv.Optional(CONF_ON_FINISHED_PLAYBACK): automation.validate_automation({ |         { | ||||||
|         cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(DFPlayerFinishedPlaybackTrigger), |             cv.GenerateID(): cv.declare_id(DFPlayer), | ||||||
|     }), |             cv.Optional(CONF_ON_FINISHED_PLAYBACK): automation.validate_automation( | ||||||
| }).extend(uart.UART_DEVICE_SCHEMA)) |                 { | ||||||
|  |                     cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( | ||||||
|  |                         DFPlayerFinishedPlaybackTrigger | ||||||
|  |                     ), | ||||||
|  |                 } | ||||||
|  |             ), | ||||||
|  |         } | ||||||
|  |     ).extend(uart.UART_DEVICE_SCHEMA) | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
| def to_code(config): | def to_code(config): | ||||||
| @@ -67,29 +78,48 @@ def to_code(config): | |||||||
|         yield automation.build_automation(trigger, [], conf) |         yield automation.build_automation(trigger, [], conf) | ||||||
|  |  | ||||||
|  |  | ||||||
| @automation.register_action('dfplayer.play_next', NextAction, cv.Schema({ | @automation.register_action( | ||||||
|     cv.GenerateID(): cv.use_id(DFPlayer), |     "dfplayer.play_next", | ||||||
| })) |     NextAction, | ||||||
|  |     cv.Schema( | ||||||
|  |         { | ||||||
|  |             cv.GenerateID(): cv.use_id(DFPlayer), | ||||||
|  |         } | ||||||
|  |     ), | ||||||
|  | ) | ||||||
| def dfplayer_next_to_code(config, action_id, template_arg, args): | def dfplayer_next_to_code(config, action_id, template_arg, args): | ||||||
|     var = cg.new_Pvariable(action_id, template_arg) |     var = cg.new_Pvariable(action_id, template_arg) | ||||||
|     yield cg.register_parented(var, config[CONF_ID]) |     yield cg.register_parented(var, config[CONF_ID]) | ||||||
|     yield var |     yield var | ||||||
|  |  | ||||||
|  |  | ||||||
| @automation.register_action('dfplayer.play_previous', PreviousAction, cv.Schema({ | @automation.register_action( | ||||||
|     cv.GenerateID(): cv.use_id(DFPlayer), |     "dfplayer.play_previous", | ||||||
| })) |     PreviousAction, | ||||||
|  |     cv.Schema( | ||||||
|  |         { | ||||||
|  |             cv.GenerateID(): cv.use_id(DFPlayer), | ||||||
|  |         } | ||||||
|  |     ), | ||||||
|  | ) | ||||||
| def dfplayer_previous_to_code(config, action_id, template_arg, args): | def dfplayer_previous_to_code(config, action_id, template_arg, args): | ||||||
|     var = cg.new_Pvariable(action_id, template_arg) |     var = cg.new_Pvariable(action_id, template_arg) | ||||||
|     yield cg.register_parented(var, config[CONF_ID]) |     yield cg.register_parented(var, config[CONF_ID]) | ||||||
|     yield var |     yield var | ||||||
|  |  | ||||||
|  |  | ||||||
| @automation.register_action('dfplayer.play', PlayFileAction, cv.maybe_simple_value({ | @automation.register_action( | ||||||
|     cv.GenerateID(): cv.use_id(DFPlayer), |     "dfplayer.play", | ||||||
|     cv.Required(CONF_FILE): cv.templatable(cv.int_), |     PlayFileAction, | ||||||
|     cv.Optional(CONF_LOOP): cv.templatable(cv.boolean), |     cv.maybe_simple_value( | ||||||
| }, key=CONF_FILE)) |         { | ||||||
|  |             cv.GenerateID(): cv.use_id(DFPlayer), | ||||||
|  |             cv.Required(CONF_FILE): cv.templatable(cv.int_), | ||||||
|  |             cv.Optional(CONF_LOOP): cv.templatable(cv.boolean), | ||||||
|  |         }, | ||||||
|  |         key=CONF_FILE, | ||||||
|  |     ), | ||||||
|  | ) | ||||||
| def dfplayer_play_to_code(config, action_id, template_arg, args): | def dfplayer_play_to_code(config, action_id, template_arg, args): | ||||||
|     var = cg.new_Pvariable(action_id, template_arg) |     var = cg.new_Pvariable(action_id, template_arg) | ||||||
|     yield cg.register_parented(var, config[CONF_ID]) |     yield cg.register_parented(var, config[CONF_ID]) | ||||||
| @@ -101,12 +131,18 @@ def dfplayer_play_to_code(config, action_id, template_arg, args): | |||||||
|     yield var |     yield var | ||||||
|  |  | ||||||
|  |  | ||||||
| @automation.register_action('dfplayer.play_folder', PlayFolderAction, cv.Schema({ | @automation.register_action( | ||||||
|     cv.GenerateID(): cv.use_id(DFPlayer), |     "dfplayer.play_folder", | ||||||
|     cv.Required(CONF_FOLDER): cv.templatable(cv.int_), |     PlayFolderAction, | ||||||
|     cv.Optional(CONF_FILE): cv.templatable(cv.int_), |     cv.Schema( | ||||||
|     cv.Optional(CONF_LOOP): cv.templatable(cv.boolean), |         { | ||||||
| })) |             cv.GenerateID(): cv.use_id(DFPlayer), | ||||||
|  |             cv.Required(CONF_FOLDER): cv.templatable(cv.int_), | ||||||
|  |             cv.Optional(CONF_FILE): cv.templatable(cv.int_), | ||||||
|  |             cv.Optional(CONF_LOOP): cv.templatable(cv.boolean), | ||||||
|  |         } | ||||||
|  |     ), | ||||||
|  | ) | ||||||
| def dfplayer_play_folder_to_code(config, action_id, template_arg, args): | def dfplayer_play_folder_to_code(config, action_id, template_arg, args): | ||||||
|     var = cg.new_Pvariable(action_id, template_arg) |     var = cg.new_Pvariable(action_id, template_arg) | ||||||
|     yield cg.register_parented(var, config[CONF_ID]) |     yield cg.register_parented(var, config[CONF_ID]) | ||||||
| @@ -121,10 +157,17 @@ def dfplayer_play_folder_to_code(config, action_id, template_arg, args): | |||||||
|     yield var |     yield var | ||||||
|  |  | ||||||
|  |  | ||||||
| @automation.register_action('dfplayer.set_device', SetDeviceAction, cv.maybe_simple_value({ | @automation.register_action( | ||||||
|     cv.GenerateID(): cv.use_id(DFPlayer), |     "dfplayer.set_device", | ||||||
|     cv.Required(CONF_DEVICE): cv.enum(DEVICE, upper=True), |     SetDeviceAction, | ||||||
| }, key=CONF_DEVICE)) |     cv.maybe_simple_value( | ||||||
|  |         { | ||||||
|  |             cv.GenerateID(): cv.use_id(DFPlayer), | ||||||
|  |             cv.Required(CONF_DEVICE): cv.enum(DEVICE, upper=True), | ||||||
|  |         }, | ||||||
|  |         key=CONF_DEVICE, | ||||||
|  |     ), | ||||||
|  | ) | ||||||
| def dfplayer_set_device_to_code(config, action_id, template_arg, args): | def dfplayer_set_device_to_code(config, action_id, template_arg, args): | ||||||
|     var = cg.new_Pvariable(action_id, template_arg) |     var = cg.new_Pvariable(action_id, template_arg) | ||||||
|     yield cg.register_parented(var, config[CONF_ID]) |     yield cg.register_parented(var, config[CONF_ID]) | ||||||
| @@ -133,10 +176,17 @@ def dfplayer_set_device_to_code(config, action_id, template_arg, args): | |||||||
|     yield var |     yield var | ||||||
|  |  | ||||||
|  |  | ||||||
| @automation.register_action('dfplayer.set_volume', SetVolumeAction, cv.maybe_simple_value({ | @automation.register_action( | ||||||
|     cv.GenerateID(): cv.use_id(DFPlayer), |     "dfplayer.set_volume", | ||||||
|     cv.Required(CONF_VOLUME): cv.templatable(cv.int_), |     SetVolumeAction, | ||||||
| }, key=CONF_VOLUME)) |     cv.maybe_simple_value( | ||||||
|  |         { | ||||||
|  |             cv.GenerateID(): cv.use_id(DFPlayer), | ||||||
|  |             cv.Required(CONF_VOLUME): cv.templatable(cv.int_), | ||||||
|  |         }, | ||||||
|  |         key=CONF_VOLUME, | ||||||
|  |     ), | ||||||
|  | ) | ||||||
| def dfplayer_set_volume_to_code(config, action_id, template_arg, args): | def dfplayer_set_volume_to_code(config, action_id, template_arg, args): | ||||||
|     var = cg.new_Pvariable(action_id, template_arg) |     var = cg.new_Pvariable(action_id, template_arg) | ||||||
|     yield cg.register_parented(var, config[CONF_ID]) |     yield cg.register_parented(var, config[CONF_ID]) | ||||||
| @@ -145,10 +195,17 @@ def dfplayer_set_volume_to_code(config, action_id, template_arg, args): | |||||||
|     yield var |     yield var | ||||||
|  |  | ||||||
|  |  | ||||||
| @automation.register_action('dfplayer.set_eq', SetEqAction, cv.maybe_simple_value({ | @automation.register_action( | ||||||
|     cv.GenerateID(): cv.use_id(DFPlayer), |     "dfplayer.set_eq", | ||||||
|     cv.Required(CONF_EQ_PRESET): cv.templatable(cv.enum(EQ_PRESET, upper=True)), |     SetEqAction, | ||||||
| }, key=CONF_EQ_PRESET)) |     cv.maybe_simple_value( | ||||||
|  |         { | ||||||
|  |             cv.GenerateID(): cv.use_id(DFPlayer), | ||||||
|  |             cv.Required(CONF_EQ_PRESET): cv.templatable(cv.enum(EQ_PRESET, upper=True)), | ||||||
|  |         }, | ||||||
|  |         key=CONF_EQ_PRESET, | ||||||
|  |     ), | ||||||
|  | ) | ||||||
| def dfplayer_set_eq_to_code(config, action_id, template_arg, args): | def dfplayer_set_eq_to_code(config, action_id, template_arg, args): | ||||||
|     var = cg.new_Pvariable(action_id, template_arg) |     var = cg.new_Pvariable(action_id, template_arg) | ||||||
|     yield cg.register_parented(var, config[CONF_ID]) |     yield cg.register_parented(var, config[CONF_ID]) | ||||||
| @@ -157,63 +214,105 @@ def dfplayer_set_eq_to_code(config, action_id, template_arg, args): | |||||||
|     yield var |     yield var | ||||||
|  |  | ||||||
|  |  | ||||||
| @automation.register_action('dfplayer.sleep', SleepAction, cv.Schema({ | @automation.register_action( | ||||||
|     cv.GenerateID(): cv.use_id(DFPlayer), |     "dfplayer.sleep", | ||||||
| })) |     SleepAction, | ||||||
|  |     cv.Schema( | ||||||
|  |         { | ||||||
|  |             cv.GenerateID(): cv.use_id(DFPlayer), | ||||||
|  |         } | ||||||
|  |     ), | ||||||
|  | ) | ||||||
| def dfplayer_sleep_to_code(config, action_id, template_arg, args): | def dfplayer_sleep_to_code(config, action_id, template_arg, args): | ||||||
|     var = cg.new_Pvariable(action_id, template_arg) |     var = cg.new_Pvariable(action_id, template_arg) | ||||||
|     yield cg.register_parented(var, config[CONF_ID]) |     yield cg.register_parented(var, config[CONF_ID]) | ||||||
|     yield var |     yield var | ||||||
|  |  | ||||||
|  |  | ||||||
| @automation.register_action('dfplayer.reset', ResetAction, cv.Schema({ | @automation.register_action( | ||||||
|     cv.GenerateID(): cv.use_id(DFPlayer), |     "dfplayer.reset", | ||||||
| })) |     ResetAction, | ||||||
|  |     cv.Schema( | ||||||
|  |         { | ||||||
|  |             cv.GenerateID(): cv.use_id(DFPlayer), | ||||||
|  |         } | ||||||
|  |     ), | ||||||
|  | ) | ||||||
| def dfplayer_reset_to_code(config, action_id, template_arg, args): | def dfplayer_reset_to_code(config, action_id, template_arg, args): | ||||||
|     var = cg.new_Pvariable(action_id, template_arg) |     var = cg.new_Pvariable(action_id, template_arg) | ||||||
|     yield cg.register_parented(var, config[CONF_ID]) |     yield cg.register_parented(var, config[CONF_ID]) | ||||||
|     yield var |     yield var | ||||||
|  |  | ||||||
|  |  | ||||||
| @automation.register_action('dfplayer.start', StartAction, cv.Schema({ | @automation.register_action( | ||||||
|     cv.GenerateID(): cv.use_id(DFPlayer), |     "dfplayer.start", | ||||||
| })) |     StartAction, | ||||||
|  |     cv.Schema( | ||||||
|  |         { | ||||||
|  |             cv.GenerateID(): cv.use_id(DFPlayer), | ||||||
|  |         } | ||||||
|  |     ), | ||||||
|  | ) | ||||||
| def dfplayer_start_to_code(config, action_id, template_arg, args): | def dfplayer_start_to_code(config, action_id, template_arg, args): | ||||||
|     var = cg.new_Pvariable(action_id, template_arg) |     var = cg.new_Pvariable(action_id, template_arg) | ||||||
|     yield cg.register_parented(var, config[CONF_ID]) |     yield cg.register_parented(var, config[CONF_ID]) | ||||||
|     yield var |     yield var | ||||||
|  |  | ||||||
|  |  | ||||||
| @automation.register_action('dfplayer.pause', PauseAction, cv.Schema({ | @automation.register_action( | ||||||
|     cv.GenerateID(): cv.use_id(DFPlayer), |     "dfplayer.pause", | ||||||
| })) |     PauseAction, | ||||||
|  |     cv.Schema( | ||||||
|  |         { | ||||||
|  |             cv.GenerateID(): cv.use_id(DFPlayer), | ||||||
|  |         } | ||||||
|  |     ), | ||||||
|  | ) | ||||||
| def dfplayer_pause_to_code(config, action_id, template_arg, args): | def dfplayer_pause_to_code(config, action_id, template_arg, args): | ||||||
|     var = cg.new_Pvariable(action_id, template_arg) |     var = cg.new_Pvariable(action_id, template_arg) | ||||||
|     yield cg.register_parented(var, config[CONF_ID]) |     yield cg.register_parented(var, config[CONF_ID]) | ||||||
|     yield var |     yield var | ||||||
|  |  | ||||||
|  |  | ||||||
| @automation.register_action('dfplayer.stop', StopAction, cv.Schema({ | @automation.register_action( | ||||||
|     cv.GenerateID(): cv.use_id(DFPlayer), |     "dfplayer.stop", | ||||||
| })) |     StopAction, | ||||||
|  |     cv.Schema( | ||||||
|  |         { | ||||||
|  |             cv.GenerateID(): cv.use_id(DFPlayer), | ||||||
|  |         } | ||||||
|  |     ), | ||||||
|  | ) | ||||||
| def dfplayer_stop_to_code(config, action_id, template_arg, args): | def dfplayer_stop_to_code(config, action_id, template_arg, args): | ||||||
|     var = cg.new_Pvariable(action_id, template_arg) |     var = cg.new_Pvariable(action_id, template_arg) | ||||||
|     yield cg.register_parented(var, config[CONF_ID]) |     yield cg.register_parented(var, config[CONF_ID]) | ||||||
|     yield var |     yield var | ||||||
|  |  | ||||||
|  |  | ||||||
| @automation.register_action('dfplayer.random', RandomAction, cv.Schema({ | @automation.register_action( | ||||||
|     cv.GenerateID(): cv.use_id(DFPlayer), |     "dfplayer.random", | ||||||
| })) |     RandomAction, | ||||||
|  |     cv.Schema( | ||||||
|  |         { | ||||||
|  |             cv.GenerateID(): cv.use_id(DFPlayer), | ||||||
|  |         } | ||||||
|  |     ), | ||||||
|  | ) | ||||||
| def dfplayer_random_to_code(config, action_id, template_arg, args): | def dfplayer_random_to_code(config, action_id, template_arg, args): | ||||||
|     var = cg.new_Pvariable(action_id, template_arg) |     var = cg.new_Pvariable(action_id, template_arg) | ||||||
|     yield cg.register_parented(var, config[CONF_ID]) |     yield cg.register_parented(var, config[CONF_ID]) | ||||||
|     yield var |     yield var | ||||||
|  |  | ||||||
|  |  | ||||||
| @automation.register_condition('dfplayer.is_playing', DFPlayerIsPlayingCondition, cv.Schema({ | @automation.register_condition( | ||||||
|     cv.GenerateID(): cv.use_id(DFPlayer), |     "dfplayer.is_playing", | ||||||
| })) |     DFPlayerIsPlayingCondition, | ||||||
|  |     cv.Schema( | ||||||
|  |         { | ||||||
|  |             cv.GenerateID(): cv.use_id(DFPlayer), | ||||||
|  |         } | ||||||
|  |     ), | ||||||
|  | ) | ||||||
| def dfplyaer_is_playing_to_code(config, condition_id, template_arg, args): | def dfplyaer_is_playing_to_code(config, condition_id, template_arg, args): | ||||||
|     var = cg.new_Pvariable(condition_id, template_arg) |     var = cg.new_Pvariable(condition_id, template_arg) | ||||||
|     yield cg.register_parented(var, config[CONF_ID]) |     yield cg.register_parented(var, config[CONF_ID]) | ||||||
|   | |||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user