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:
		
							
								
								
									
										38
									
								
								.github/PULL_REQUEST_TEMPLATE.md
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										38
									
								
								.github/PULL_REQUEST_TEMPLATE.md
									
									
									
									
										vendored
									
									
								
							| @@ -1,10 +1,44 @@ | ||||
| ## Description: | ||||
| # What does this implement/fix?  | ||||
|  | ||||
| Quick description  | ||||
|  | ||||
| **Related issue (if applicable):** fixes <link to issue> | ||||
| ## Types of changes | ||||
|  | ||||
| - [ ] Bugfix (non-breaking change which fixes an issue) | ||||
| - [ ] New feature (non-breaking change which adds functionality) | ||||
| - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) | ||||
| - [ ] Configuration change (this will require users to update their yaml configuration files to keep working) | ||||
|  | ||||
| **Related issue or feature (if applicable):** fixes <link to issue> | ||||
|  | ||||
| **Pull request in [esphome-docs](https://github.com/esphome/esphome-docs) with documentation (if applicable):** esphome/esphome-docs#<esphome-docs PR number goes here> | ||||
|    | ||||
| # Test Environment | ||||
|  | ||||
| - [ ] ESP32 | ||||
| - [ ] ESP8266 | ||||
| - [ ] Windows | ||||
| - [ ] Mac OS | ||||
| - [ ] Linux | ||||
|  | ||||
| ## Example entry for `config.yaml`: | ||||
| <!-- | ||||
|   Supplying a configuration snippet, makes it easier for a maintainer to test | ||||
|   your PR. Furthermore, for new integrations, it gives an impression of how | ||||
|   the configuration would look like. | ||||
|   Note: Remove this section if this PR does not have an example entry. | ||||
| --> | ||||
|  | ||||
| ```yaml | ||||
| # Example config.yaml | ||||
|  | ||||
| ``` | ||||
|  | ||||
| # Explain your changes | ||||
|  | ||||
| Describe your changes here to communicate to the maintainers **why we should accept this pull request**. | ||||
| Very important to fill if no issue linked | ||||
|  | ||||
| ## Checklist: | ||||
|   - [ ] The code change is tested and works locally. | ||||
|   - [ ] Tests have been added to verify that the new code works (under `tests/` folder). | ||||
|   | ||||
							
								
								
									
										4
									
								
								.github/workflows/ci-docker.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/workflows/ci-docker.yml
									
									
									
									
										vendored
									
									
								
							| @@ -26,7 +26,7 @@ jobs: | ||||
|       - uses: actions/checkout@v2 | ||||
|       - name: Set up env variables | ||||
|         run: | | ||||
|           base_version="2.6.0" | ||||
|           base_version="3.0.0" | ||||
|  | ||||
|           if [[ "${{ matrix.build_type }}" == "hassio" ]]; then | ||||
|             build_from="esphome/esphome-hassio-base-${{ matrix.arch }}:${base_version}" | ||||
| @@ -45,7 +45,7 @@ jobs: | ||||
|         run: | | ||||
|           docker pull "${BUILD_TO}:dev" || true | ||||
|       - name: Register QEMU binfmt | ||||
|         run: docker run --rm --privileged multiarch/qemu-user-static:5.0.0-2 --reset -p yes | ||||
|         run: docker run --rm --privileged multiarch/qemu-user-static:5.2.0-2 --reset -p yes | ||||
|       - run: | | ||||
|           docker build \ | ||||
|             --build-arg "BUILD_FROM=${BUILD_FROM}" \ | ||||
|   | ||||
							
								
								
									
										18
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										18
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							| @@ -18,15 +18,6 @@ jobs: | ||||
|     container: esphome/esphome-lint:latest | ||||
|     steps: | ||||
|       - uses: actions/checkout@v2 | ||||
|       # Cache platformio intermediary files (like libraries etc) | ||||
|       # Note: platformio platform versions should be cached via the esphome-lint image | ||||
|       - name: Cache Platformio | ||||
|         uses: actions/cache@v1 | ||||
|         with: | ||||
|           path: .pio | ||||
|           key: lint-cpp-pio-${{ hashFiles('platformio.ini') }} | ||||
|           restore-keys: | | ||||
|             lint-cpp-pio- | ||||
|       # Set up the pio project so that the cpp checks know how files are compiled | ||||
|       # (build flags, libraries etc) | ||||
|       - name: Set up platformio environment | ||||
| @@ -49,15 +40,6 @@ jobs: | ||||
|         split: [1, 2, 3, 4] | ||||
|     steps: | ||||
|       - uses: actions/checkout@v2 | ||||
|       # Cache platformio intermediary files (like libraries etc) | ||||
|       # Note: platformio platform versions should be cached via the esphome-lint image | ||||
|       - name: Cache Platformio | ||||
|         uses: actions/cache@v1 | ||||
|         with: | ||||
|           path: .pio | ||||
|           key: lint-cpp-pio-${{ hashFiles('platformio.ini') }} | ||||
|           restore-keys: | | ||||
|             lint-cpp-pio- | ||||
|       # Set up the pio project so that the cpp checks know how files are compiled | ||||
|       # (build flags, libraries etc) | ||||
|       - name: Set up platformio environment | ||||
|   | ||||
							
								
								
									
										22
									
								
								.github/workflows/release-dev.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										22
									
								
								.github/workflows/release-dev.yml
									
									
									
									
										vendored
									
									
								
							| @@ -15,15 +15,6 @@ jobs: | ||||
|     container: esphome/esphome-lint:latest | ||||
|     steps: | ||||
|       - uses: actions/checkout@v2 | ||||
|       # Cache platformio intermediary files (like libraries etc) | ||||
|       # Note: platformio platform versions should be cached via the esphome-lint image | ||||
|       - name: Cache Platformio | ||||
|         uses: actions/cache@v1 | ||||
|         with: | ||||
|           path: .pio | ||||
|           key: lint-cpp-pio-${{ hashFiles('platformio.ini') }} | ||||
|           restore-keys: | | ||||
|             lint-cpp-pio- | ||||
|       # Set up the pio project so that the cpp checks know how files are compiled | ||||
|       # (build flags, libraries etc) | ||||
|       - name: Set up platformio environment | ||||
| @@ -46,15 +37,6 @@ jobs: | ||||
|         split: [1, 2, 3, 4] | ||||
|     steps: | ||||
|       - uses: actions/checkout@v2 | ||||
|       # Cache platformio intermediary files (like libraries etc) | ||||
|       # Note: platformio platform versions should be cached via the esphome-lint image | ||||
|       - name: Cache Platformio | ||||
|         uses: actions/cache@v1 | ||||
|         with: | ||||
|           path: .pio | ||||
|           key: lint-cpp-pio-${{ hashFiles('platformio.ini') }} | ||||
|           restore-keys: | | ||||
|             lint-cpp-pio- | ||||
|       # Set up the pio project so that the cpp checks know how files are compiled | ||||
|       # (build flags, libraries etc) | ||||
|       - name: Set up platformio environment | ||||
| @@ -192,7 +174,7 @@ jobs: | ||||
|           echo "TAG=${TAG}" >> $GITHUB_ENV | ||||
|       - name: Set up env variables | ||||
|         run: | | ||||
|           base_version="2.6.0" | ||||
|           base_version="3.0.0" | ||||
|  | ||||
|           if [[ "${{ matrix.build_type }}" == "hassio" ]]; then | ||||
|             build_from="esphome/esphome-hassio-base-${{ matrix.arch }}:${base_version}" | ||||
| @@ -211,7 +193,7 @@ jobs: | ||||
|         run: | | ||||
|           docker pull "${BUILD_TO}:dev" || true | ||||
|       - name: Register QEMU binfmt | ||||
|         run: docker run --rm --privileged multiarch/qemu-user-static:5.0.0-2 --reset -p yes | ||||
|         run: docker run --rm --privileged multiarch/qemu-user-static:5.2.0-2 --reset -p yes | ||||
|       - run: | | ||||
|           docker build \ | ||||
|             --build-arg "BUILD_FROM=${BUILD_FROM}" \ | ||||
|   | ||||
							
								
								
									
										22
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										22
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							| @@ -14,15 +14,6 @@ jobs: | ||||
|     container: esphome/esphome-lint:latest | ||||
|     steps: | ||||
|       - uses: actions/checkout@v2 | ||||
|       # Cache platformio intermediary files (like libraries etc) | ||||
|       # Note: platformio platform versions should be cached via the esphome-lint image | ||||
|       - name: Cache Platformio | ||||
|         uses: actions/cache@v1 | ||||
|         with: | ||||
|           path: .pio | ||||
|           key: lint-cpp-pio-${{ hashFiles('platformio.ini') }} | ||||
|           restore-keys: | | ||||
|             lint-cpp-pio- | ||||
|       # Set up the pio project so that the cpp checks know how files are compiled | ||||
|       # (build flags, libraries etc) | ||||
|       - name: Set up platformio environment | ||||
| @@ -45,15 +36,6 @@ jobs: | ||||
|         split: [1, 2, 3, 4] | ||||
|     steps: | ||||
|       - uses: actions/checkout@v2 | ||||
|       # Cache platformio intermediary files (like libraries etc) | ||||
|       # Note: platformio platform versions should be cached via the esphome-lint image | ||||
|       - name: Cache Platformio | ||||
|         uses: actions/cache@v1 | ||||
|         with: | ||||
|           path: .pio | ||||
|           key: lint-cpp-pio-${{ hashFiles('platformio.ini') }} | ||||
|           restore-keys: | | ||||
|             lint-cpp-pio- | ||||
|       # Set up the pio project so that the cpp checks know how files are compiled | ||||
|       # (build flags, libraries etc) | ||||
|       - name: Set up platformio environment | ||||
| @@ -212,7 +194,7 @@ jobs: | ||||
|           echo "TAG=${TAG}" >> $GITHUB_ENV | ||||
|       - name: Set up env variables | ||||
|         run: | | ||||
|           base_version="2.6.0" | ||||
|           base_version="3.0.0" | ||||
|  | ||||
|           if [[ "${{ matrix.build_type }}" == "hassio" ]]; then | ||||
|             build_from="esphome/esphome-hassio-base-${{ matrix.arch }}:${base_version}" | ||||
| @@ -239,7 +221,7 @@ jobs: | ||||
|         run: | | ||||
|           docker pull "${BUILD_TO}:${CACHE_TAG}" || true | ||||
|       - name: Register QEMU binfmt | ||||
|         run: docker run --rm --privileged multiarch/qemu-user-static:5.0.0-2 --reset -p yes | ||||
|         run: docker run --rm --privileged multiarch/qemu-user-static:5.2.0-2 --reset -p yes | ||||
|       - run: | | ||||
|           docker build \ | ||||
|             --build-arg "BUILD_FROM=${BUILD_FROM}" \ | ||||
|   | ||||
| @@ -1,11 +1,27 @@ | ||||
| # See https://pre-commit.com for more information | ||||
| # See https://pre-commit.com/hooks.html for more hooks | ||||
| repos: | ||||
| -   repo: https://github.com/pre-commit/pre-commit-hooks | ||||
|     rev: v2.4.0 | ||||
|   - repo: https://github.com/ambv/black | ||||
|     rev: 20.8b1 | ||||
|     hooks: | ||||
|     - id: black | ||||
|       args: | ||||
|         - --safe | ||||
|         - --quiet | ||||
|       files: ^((esphome|script|tests)/.+)?[^/]+\.py$ | ||||
|   - repo: https://gitlab.com/pycqa/flake8 | ||||
|     rev: 3.8.4 | ||||
|     hooks: | ||||
|     -   id: trailing-whitespace | ||||
|     -   id: end-of-file-fixer | ||||
|     -   id: check-yaml | ||||
|     -   id: check-added-large-files | ||||
|       - 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 | ||||
| esphome/components/ac_dimmer/* @glmnet | ||||
| esphome/components/adc/* @esphome/core | ||||
| esphome/components/addressable_light/* @justfalter | ||||
| esphome/components/animation/* @syndlex | ||||
| esphome/components/api/* @OttoWinter | ||||
| esphome/components/async_tcp/* @OttoWinter | ||||
| @@ -37,6 +38,7 @@ esphome/components/globals/* @esphome/core | ||||
| esphome/components/gpio/* @esphome/core | ||||
| esphome/components/homeassistant/* @OttoWinter | ||||
| esphome/components/i2c/* @esphome/core | ||||
| esphome/components/inkbird_ibsth1_mini/* @fkirill | ||||
| esphome/components/inkplate6/* @jesserockz | ||||
| esphome/components/integration/* @OttoWinter | ||||
| esphome/components/interval/* @esphome/core | ||||
| @@ -44,10 +46,18 @@ esphome/components/json/* @OttoWinter | ||||
| esphome/components/ledc/* @OttoWinter | ||||
| esphome/components/light/* @esphome/core | ||||
| esphome/components/logger/* @esphome/core | ||||
| esphome/components/mcp23s08/* @SenexCrenshaw | ||||
| esphome/components/mcp23s17/* @SenexCrenshaw | ||||
| esphome/components/max7219digit/* @rspaargaren | ||||
| esphome/components/mcp23008/* @jesserockz | ||||
| esphome/components/mcp23017/* @jesserockz | ||||
| esphome/components/mcp23s08/* @SenexCrenshaw @jesserockz | ||||
| esphome/components/mcp23s17/* @SenexCrenshaw @jesserockz | ||||
| esphome/components/mcp23x08_base/* @jesserockz | ||||
| esphome/components/mcp23x17_base/* @jesserockz | ||||
| esphome/components/mcp23xxx_base/* @jesserockz | ||||
| esphome/components/mcp2515/* @danielschramm @mvturnho | ||||
| esphome/components/mcp9808/* @k7hpn | ||||
| esphome/components/midea_ac/* @dudanov | ||||
| esphome/components/midea_dongle/* @dudanov | ||||
| esphome/components/network/* @esphome/core | ||||
| esphome/components/nfc/* @jesserockz | ||||
| esphome/components/ota/* @esphome/core | ||||
| @@ -57,6 +67,7 @@ esphome/components/pn532/* @OttoWinter @jesserockz | ||||
| esphome/components/pn532_i2c/* @OttoWinter @jesserockz | ||||
| esphome/components/pn532_spi/* @OttoWinter @jesserockz | ||||
| esphome/components/power_supply/* @esphome/core | ||||
| esphome/components/pulse_meter/* @stevebaxter | ||||
| esphome/components/rc522/* @glmnet | ||||
| esphome/components/rc522_i2c/* @glmnet | ||||
| esphome/components/rc522_spi/* @glmnet | ||||
|   | ||||
| @@ -1,9 +1,11 @@ | ||||
| ARG BUILD_FROM=esphome/esphome-base-amd64:2.6.0 | ||||
| ARG BUILD_FROM=esphome/esphome-base-amd64:3.0.0 | ||||
| FROM ${BUILD_FROM} | ||||
|  | ||||
| # First install requirements to leverage caching when requirements don't change | ||||
| COPY requirements.txt / | ||||
| RUN pip3 install --no-cache-dir -r /requirements.txt | ||||
| COPY requirements.txt docker/platformio_install_deps.py platformio.ini / | ||||
| RUN \ | ||||
|     pip3 install --no-cache-dir -r /requirements.txt \ | ||||
|     && /platformio_install_deps.py /platformio.ini | ||||
|  | ||||
| # Then copy esphome and install | ||||
| COPY . . | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| FROM esphome/esphome-base-amd64:2.6.0 | ||||
| FROM esphome/esphome-base-amd64:3.0.0 | ||||
|  | ||||
| COPY . . | ||||
|  | ||||
|   | ||||
| @@ -2,8 +2,10 @@ ARG BUILD_FROM | ||||
| FROM ${BUILD_FROM} | ||||
|  | ||||
| # First install requirements to leverage caching when requirements don't change | ||||
| COPY requirements.txt / | ||||
| RUN pip3 install --no-cache-dir -r /requirements.txt | ||||
| COPY requirements.txt docker/platformio_install_deps.py platformio.ini / | ||||
| RUN \ | ||||
|     pip3 install --no-cache-dir -r /requirements.txt \ | ||||
|     && /platformio_install_deps.py /platformio.ini | ||||
|  | ||||
| # Copy root filesystem | ||||
| COPY docker/rootfs/ / | ||||
|   | ||||
| @@ -1,7 +1,9 @@ | ||||
| FROM esphome/esphome-lint-base:2.6.0 | ||||
| FROM esphome/esphome-lint-base:3.0.0 | ||||
|  | ||||
| COPY requirements.txt requirements_test.txt / | ||||
| RUN pip3 install --no-cache-dir -r /requirements.txt -r /requirements_test.txt | ||||
| COPY requirements.txt requirements_test.txt docker/platformio_install_deps.py  platformio.ini / | ||||
| RUN \ | ||||
|     pip3 install --no-cache-dir -r /requirements.txt -r /requirements_test.txt \ | ||||
|     && /platformio_install_deps.py /platformio.ini | ||||
|  | ||||
| VOLUME ["/esphome"] | ||||
| WORKDIR /esphome | ||||
|   | ||||
							
								
								
									
										20
									
								
								docker/platformio_install_deps.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										20
									
								
								docker/platformio_install_deps.py
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,20 @@ | ||||
| #!/usr/bin/env python3 | ||||
| # This script is used in the docker containers to preinstall | ||||
| # all platformio libraries in the global storage | ||||
|  | ||||
| import configparser | ||||
| import re | ||||
| import subprocess | ||||
| import sys | ||||
|  | ||||
| config = configparser.ConfigParser() | ||||
| config.read(sys.argv[1]) | ||||
| libs = [] | ||||
| for line in config['common']['lib_deps'].splitlines(): | ||||
|     # Format: '1655@1.0.2  ; TinyGPSPlus (has name conflict)' (includes comment) | ||||
|     m = re.search(r'([a-zA-Z0-9-_/]+@[0-9\.]+)', line) | ||||
|     if m is None: | ||||
|         continue | ||||
|     libs.append(m.group(1)) | ||||
|  | ||||
| subprocess.check_call(['platformio', 'lib', '-g', 'install', *libs]) | ||||
| @@ -8,21 +8,36 @@ from datetime import datetime | ||||
| from esphome import const, writer, yaml_util | ||||
| import esphome.codegen as cg | ||||
| from esphome.config import iter_components, read_config, strip_default_ids | ||||
| from esphome.const import CONF_BAUD_RATE, CONF_BROKER, CONF_LOGGER, CONF_OTA, \ | ||||
|     CONF_PASSWORD, CONF_PORT, CONF_ESPHOME, CONF_PLATFORMIO_OPTIONS | ||||
| from esphome.const import ( | ||||
|     CONF_BAUD_RATE, | ||||
|     CONF_BROKER, | ||||
|     CONF_LOGGER, | ||||
|     CONF_OTA, | ||||
|     CONF_PASSWORD, | ||||
|     CONF_PORT, | ||||
|     CONF_ESPHOME, | ||||
|     CONF_PLATFORMIO_OPTIONS, | ||||
| ) | ||||
| from esphome.core import CORE, EsphomeError, coroutine, coroutine_with_priority | ||||
| from esphome.helpers import color, indent | ||||
| from esphome.util import run_external_command, run_external_process, safe_print, list_yaml_files, \ | ||||
|     get_serial_ports | ||||
| from esphome.util import ( | ||||
|     run_external_command, | ||||
|     run_external_process, | ||||
|     safe_print, | ||||
|     list_yaml_files, | ||||
|     get_serial_ports, | ||||
| ) | ||||
|  | ||||
| _LOGGER = logging.getLogger(__name__) | ||||
|  | ||||
|  | ||||
| def choose_prompt(options): | ||||
|     if not options: | ||||
|         raise EsphomeError("Found no valid options for upload/logging, please make sure relevant " | ||||
|         raise EsphomeError( | ||||
|             "Found no valid options for upload/logging, please make sure relevant " | ||||
|             "sections (ota, api, mqtt, ...) are in your configuration and/or the " | ||||
|                            "device is plugged in.") | ||||
|             "device is plugged in." | ||||
|         ) | ||||
|  | ||||
|     if len(options) == 1: | ||||
|         return options[0][1] | ||||
| @@ -32,7 +47,7 @@ def choose_prompt(options): | ||||
|         safe_print(f"  [{i+1}] {desc}") | ||||
|  | ||||
|     while True: | ||||
|         opt = input('(number): ') | ||||
|         opt = input("(number): ") | ||||
|         if opt in options: | ||||
|             opt = options.index(opt) | ||||
|             break | ||||
| @@ -42,7 +57,7 @@ def choose_prompt(options): | ||||
|                 raise ValueError | ||||
|             break | ||||
|         except ValueError: | ||||
|             safe_print(color('red', f"Invalid option: '{opt}'")) | ||||
|             safe_print(color("red", f"Invalid option: '{opt}'")) | ||||
|     return options[opt - 1][1] | ||||
|  | ||||
|  | ||||
| @@ -50,14 +65,14 @@ def choose_upload_log_host(default, check_default, show_ota, show_mqtt, show_api | ||||
|     options = [] | ||||
|     for port in get_serial_ports(): | ||||
|         options.append((f"{port.path} ({port.description})", port.path)) | ||||
|     if (show_ota and 'ota' in CORE.config) or (show_api and 'api' in CORE.config): | ||||
|     if (show_ota and "ota" in CORE.config) or (show_api and "api" in CORE.config): | ||||
|         options.append((f"Over The Air ({CORE.address})", CORE.address)) | ||||
|         if default == 'OTA': | ||||
|         if default == "OTA": | ||||
|             return CORE.address | ||||
|     if show_mqtt and 'mqtt' in CORE.config: | ||||
|         options.append(("MQTT ({})".format(CORE.config['mqtt'][CONF_BROKER]), 'MQTT')) | ||||
|         if default == 'OTA': | ||||
|             return 'MQTT' | ||||
|     if show_mqtt and "mqtt" in CORE.config: | ||||
|         options.append(("MQTT ({})".format(CORE.config["mqtt"][CONF_BROKER]), "MQTT")) | ||||
|         if default == "OTA": | ||||
|             return "MQTT" | ||||
|     if default is not None: | ||||
|         return default | ||||
|     if check_default is not None and check_default in [opt[1] for opt in options]: | ||||
| @@ -66,11 +81,11 @@ def choose_upload_log_host(default, check_default, show_ota, show_mqtt, show_api | ||||
|  | ||||
|  | ||||
| def get_port_type(port): | ||||
|     if port.startswith('/') or port.startswith('COM'): | ||||
|         return 'SERIAL' | ||||
|     if port == 'MQTT': | ||||
|         return 'MQTT' | ||||
|     return 'NETWORK' | ||||
|     if port.startswith("/") or port.startswith("COM"): | ||||
|         return "SERIAL" | ||||
|     if port == "MQTT": | ||||
|         return "MQTT" | ||||
|     return "NETWORK" | ||||
|  | ||||
|  | ||||
| def run_miniterm(config, port): | ||||
| @@ -80,7 +95,7 @@ def run_miniterm(config, port): | ||||
|     if CONF_LOGGER not in config: | ||||
|         _LOGGER.info("Logger is not enabled. Not starting UART logs.") | ||||
|         return | ||||
|     baud_rate = config['logger'][CONF_BAUD_RATE] | ||||
|     baud_rate = config["logger"][CONF_BAUD_RATE] | ||||
|     if baud_rate == 0: | ||||
|         _LOGGER.info("UART logging is disabled (baud_rate=0). Not starting UART logs.") | ||||
|     _LOGGER.info("Starting log output from %s with baud rate %s", port, baud_rate) | ||||
| @@ -93,13 +108,18 @@ def run_miniterm(config, port): | ||||
|             except serial.SerialException: | ||||
|                 _LOGGER.error("Serial port closed!") | ||||
|                 return | ||||
|             line = raw.replace(b'\r', b'').replace(b'\n', b'').decode('utf8', 'backslashreplace') | ||||
|             time = datetime.now().time().strftime('[%H:%M:%S]') | ||||
|             line = ( | ||||
|                 raw.replace(b"\r", b"") | ||||
|                 .replace(b"\n", b"") | ||||
|                 .decode("utf8", "backslashreplace") | ||||
|             ) | ||||
|             time = datetime.now().time().strftime("[%H:%M:%S]") | ||||
|             message = time + line | ||||
|             safe_print(message) | ||||
|  | ||||
|             backtrace_state = platformio_api.process_stacktrace( | ||||
|                 config, line, backtrace_state=backtrace_state) | ||||
|                 config, line, backtrace_state=backtrace_state | ||||
|             ) | ||||
|  | ||||
|  | ||||
| def wrap_to_code(name, comp): | ||||
| @@ -111,7 +131,7 @@ def wrap_to_code(name, comp): | ||||
|         cg.add(cg.LineComment(f"{name}:")) | ||||
|         if comp.config_schema is not None: | ||||
|             conf_str = yaml_util.dump(conf) | ||||
|             conf_str = conf_str.replace('//', '') | ||||
|             conf_str = conf_str.replace("//", "") | ||||
|             cg.add(cg.LineComment(indent(conf_str))) | ||||
|         yield coro(conf) | ||||
|  | ||||
| @@ -151,15 +171,31 @@ def compile_program(args, config): | ||||
|  | ||||
| def upload_using_esptool(config, port): | ||||
|     path = CORE.firmware_bin | ||||
|     first_baudrate = config[CONF_ESPHOME][CONF_PLATFORMIO_OPTIONS].get('upload_speed', 460800) | ||||
|     first_baudrate = config[CONF_ESPHOME][CONF_PLATFORMIO_OPTIONS].get( | ||||
|         "upload_speed", 460800 | ||||
|     ) | ||||
|  | ||||
|     def run_esptool(baud_rate): | ||||
|         cmd = ['esptool.py', '--before', 'default_reset', '--after', 'hard_reset', | ||||
|                '--baud', str(baud_rate), | ||||
|                '--chip', 'esp8266', '--port', port, 'write_flash', '0x0', path] | ||||
|         cmd = [ | ||||
|             "esptool.py", | ||||
|             "--before", | ||||
|             "default_reset", | ||||
|             "--after", | ||||
|             "hard_reset", | ||||
|             "--baud", | ||||
|             str(baud_rate), | ||||
|             "--chip", | ||||
|             "esp8266", | ||||
|             "--port", | ||||
|             port, | ||||
|             "write_flash", | ||||
|             "0x0", | ||||
|             path, | ||||
|         ] | ||||
|  | ||||
|         if os.environ.get('ESPHOME_USE_SUBPROCESS') is None: | ||||
|         if os.environ.get("ESPHOME_USE_SUBPROCESS") is None: | ||||
|             import esptool | ||||
|  | ||||
|             # pylint: disable=protected-access | ||||
|             return run_external_command(esptool._main, *cmd) | ||||
|  | ||||
| @@ -169,14 +205,16 @@ def upload_using_esptool(config, port): | ||||
|     if rc == 0 or first_baudrate == 115200: | ||||
|         return rc | ||||
|     # Try with 115200 baud rate, with some serial chips the faster baud rates do not work well | ||||
|     _LOGGER.info("Upload with baud rate %s failed. Trying again with baud rate 115200.", | ||||
|                  first_baudrate) | ||||
|     _LOGGER.info( | ||||
|         "Upload with baud rate %s failed. Trying again with baud rate 115200.", | ||||
|         first_baudrate, | ||||
|     ) | ||||
|     return run_esptool(115200) | ||||
|  | ||||
|  | ||||
| def upload_program(config, args, host): | ||||
|     # if upload is to a serial port use platformio, otherwise assume ota | ||||
|     if get_port_type(host) == 'SERIAL': | ||||
|     if get_port_type(host) == "SERIAL": | ||||
|         from esphome import platformio_api | ||||
|  | ||||
|         if CORE.is_esp8266: | ||||
| @@ -186,8 +224,10 @@ def upload_program(config, args, host): | ||||
|     from esphome import espota2 | ||||
|  | ||||
|     if CONF_OTA not in config: | ||||
|         raise EsphomeError("Cannot upload Over the Air as the config does not include the ota: " | ||||
|                            "component") | ||||
|         raise EsphomeError( | ||||
|             "Cannot upload Over the Air as the config does not include the ota: " | ||||
|             "component" | ||||
|         ) | ||||
|  | ||||
|     ota_conf = config[CONF_OTA] | ||||
|     remote_port = ota_conf[CONF_PORT] | ||||
| @@ -196,19 +236,21 @@ def upload_program(config, args, host): | ||||
|  | ||||
|  | ||||
| def show_logs(config, args, port): | ||||
|     if 'logger' not in config: | ||||
|     if "logger" not in config: | ||||
|         raise EsphomeError("Logger is not configured!") | ||||
|     if get_port_type(port) == 'SERIAL': | ||||
|     if get_port_type(port) == "SERIAL": | ||||
|         run_miniterm(config, port) | ||||
|         return 0 | ||||
|     if get_port_type(port) == 'NETWORK' and 'api' in config: | ||||
|     if get_port_type(port) == "NETWORK" and "api" in config: | ||||
|         from esphome.api.client import run_logs | ||||
|  | ||||
|         return run_logs(config, port) | ||||
|     if get_port_type(port) == 'MQTT' and 'mqtt' in config: | ||||
|     if get_port_type(port) == "MQTT" and "mqtt" in config: | ||||
|         from esphome import mqtt | ||||
|  | ||||
|         return mqtt.show_logs(config, args.topic, args.username, args.password, args.client_id) | ||||
|         return mqtt.show_logs( | ||||
|             config, args.topic, args.username, args.password, args.client_id | ||||
|         ) | ||||
|  | ||||
|     raise EsphomeError("No remote or local logging method configured (api/mqtt/logger)") | ||||
|  | ||||
| @@ -216,7 +258,9 @@ def show_logs(config, args, port): | ||||
| def clean_mqtt(config, args): | ||||
|     from esphome import mqtt | ||||
|  | ||||
|     return mqtt.clear_topic(config, args.topic, args.username, args.password, args.client_id) | ||||
|     return mqtt.clear_topic( | ||||
|         config, args.topic, args.username, args.password, args.client_id | ||||
|     ) | ||||
|  | ||||
|  | ||||
| def setup_log(debug=False, quiet=False): | ||||
| @@ -230,27 +274,31 @@ def setup_log(debug=False, quiet=False): | ||||
|     logging.basicConfig(level=log_level) | ||||
|     fmt = "%(levelname)s %(message)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: | ||||
|         import colorama | ||||
|  | ||||
|         colorama.init(strip=True) | ||||
|  | ||||
|         from colorlog import ColoredFormatter | ||||
|         logging.getLogger().handlers[0].setFormatter(ColoredFormatter( | ||||
|  | ||||
|         logging.getLogger().handlers[0].setFormatter( | ||||
|             ColoredFormatter( | ||||
|                 colorfmt, | ||||
|                 datefmt=datefmt, | ||||
|                 reset=True, | ||||
|                 log_colors={ | ||||
|                 'DEBUG': 'cyan', | ||||
|                 'INFO': 'green', | ||||
|                 'WARNING': 'yellow', | ||||
|                 'ERROR': 'red', | ||||
|                 'CRITICAL': 'red', | ||||
|             } | ||||
|         )) | ||||
|                     "DEBUG": "cyan", | ||||
|                     "INFO": "green", | ||||
|                     "WARNING": "yellow", | ||||
|                     "ERROR": "red", | ||||
|                     "CRITICAL": "red", | ||||
|                 }, | ||||
|             ) | ||||
|         ) | ||||
|     except ImportError: | ||||
|         pass | ||||
|  | ||||
| @@ -272,6 +320,8 @@ def command_config(args, config): | ||||
| def command_vscode(args): | ||||
|     from esphome import vscode | ||||
|  | ||||
|     logging.disable(logging.INFO) | ||||
|     logging.disable(logging.WARNING) | ||||
|     CORE.config_path = args.configuration[0] | ||||
|     vscode.read_config(args) | ||||
|  | ||||
| @@ -291,8 +341,13 @@ def command_compile(args, config): | ||||
|  | ||||
|  | ||||
| def command_upload(args, config): | ||||
|     port = choose_upload_log_host(default=args.upload_port, check_default=None, | ||||
|                                   show_ota=True, show_mqtt=False, show_api=False) | ||||
|     port = choose_upload_log_host( | ||||
|         default=args.upload_port, | ||||
|         check_default=None, | ||||
|         show_ota=True, | ||||
|         show_mqtt=False, | ||||
|         show_api=False, | ||||
|     ) | ||||
|     exit_code = upload_program(config, args, port) | ||||
|     if exit_code != 0: | ||||
|         return exit_code | ||||
| @@ -301,8 +356,13 @@ def command_upload(args, config): | ||||
|  | ||||
|  | ||||
| def command_logs(args, config): | ||||
|     port = choose_upload_log_host(default=args.serial_port, check_default=None, | ||||
|                                   show_ota=False, show_mqtt=True, show_api=True) | ||||
|     port = choose_upload_log_host( | ||||
|         default=args.serial_port, | ||||
|         check_default=None, | ||||
|         show_ota=False, | ||||
|         show_mqtt=True, | ||||
|         show_api=True, | ||||
|     ) | ||||
|     return show_logs(config, args, port) | ||||
|  | ||||
|  | ||||
| @@ -314,16 +374,26 @@ def command_run(args, config): | ||||
|     if exit_code != 0: | ||||
|         return exit_code | ||||
|     _LOGGER.info("Successfully compiled program.") | ||||
|     port = choose_upload_log_host(default=args.upload_port, check_default=None, | ||||
|                                   show_ota=True, show_mqtt=False, show_api=True) | ||||
|     port = choose_upload_log_host( | ||||
|         default=args.upload_port, | ||||
|         check_default=None, | ||||
|         show_ota=True, | ||||
|         show_mqtt=False, | ||||
|         show_api=True, | ||||
|     ) | ||||
|     exit_code = upload_program(config, args, port) | ||||
|     if exit_code != 0: | ||||
|         return exit_code | ||||
|     _LOGGER.info("Successfully uploaded program.") | ||||
|     if args.no_logs: | ||||
|         return 0 | ||||
|     port = choose_upload_log_host(default=args.upload_port, check_default=port, | ||||
|                                   show_ota=False, show_mqtt=True, show_api=True) | ||||
|     port = choose_upload_log_host( | ||||
|         default=args.upload_port, | ||||
|         check_default=port, | ||||
|         show_ota=False, | ||||
|         show_mqtt=True, | ||||
|         show_api=True, | ||||
|     ) | ||||
|     return show_logs(config, args, port) | ||||
|  | ||||
|  | ||||
| @@ -372,137 +442,189 @@ def command_update_all(args): | ||||
|         click.echo(f"{half_line}{middle_text}{half_line}") | ||||
|  | ||||
|     for f in files: | ||||
|         print("Updating {}".format(color('cyan', f))) | ||||
|         print('-' * twidth) | ||||
|         print("Updating {}".format(color("cyan", f))) | ||||
|         print("-" * twidth) | ||||
|         print() | ||||
|         rc = run_external_process('esphome', '--dashboard', f, 'run', '--no-logs', '--upload-port', | ||||
|                                   'OTA') | ||||
|         rc = run_external_process( | ||||
|             "esphome", "--dashboard", f, "run", "--no-logs", "--upload-port", "OTA" | ||||
|         ) | ||||
|         if rc == 0: | ||||
|             print_bar("[{}] {}".format(color('bold_green', 'SUCCESS'), f)) | ||||
|             print_bar("[{}] {}".format(color("bold_green", "SUCCESS"), f)) | ||||
|             success[f] = True | ||||
|         else: | ||||
|             print_bar("[{}] {}".format(color('bold_red', 'ERROR'), f)) | ||||
|             print_bar("[{}] {}".format(color("bold_red", "ERROR"), f)) | ||||
|             success[f] = False | ||||
|  | ||||
|         print() | ||||
|         print() | ||||
|         print() | ||||
|  | ||||
|     print_bar('[{}]'.format(color('bold_white', 'SUMMARY'))) | ||||
|     print_bar("[{}]".format(color("bold_white", "SUMMARY"))) | ||||
|     failed = 0 | ||||
|     for f in files: | ||||
|         if success[f]: | ||||
|             print("  - {}: {}".format(f, color('green', 'SUCCESS'))) | ||||
|             print("  - {}: {}".format(f, color("green", "SUCCESS"))) | ||||
|         else: | ||||
|             print("  - {}: {}".format(f, color('bold_red', 'FAILED'))) | ||||
|             print("  - {}: {}".format(f, color("bold_red", "FAILED"))) | ||||
|             failed += 1 | ||||
|     return failed | ||||
|  | ||||
|  | ||||
| PRE_CONFIG_ACTIONS = { | ||||
|     'wizard': command_wizard, | ||||
|     'version': command_version, | ||||
|     'dashboard': command_dashboard, | ||||
|     'vscode': command_vscode, | ||||
|     'update-all': command_update_all, | ||||
|     "wizard": command_wizard, | ||||
|     "version": command_version, | ||||
|     "dashboard": command_dashboard, | ||||
|     "vscode": command_vscode, | ||||
|     "update-all": command_update_all, | ||||
| } | ||||
|  | ||||
| POST_CONFIG_ACTIONS = { | ||||
|     'config': command_config, | ||||
|     'compile': command_compile, | ||||
|     'upload': command_upload, | ||||
|     'logs': command_logs, | ||||
|     'run': command_run, | ||||
|     'clean-mqtt': command_clean_mqtt, | ||||
|     'mqtt-fingerprint': command_mqtt_fingerprint, | ||||
|     'clean': command_clean, | ||||
|     "config": command_config, | ||||
|     "compile": command_compile, | ||||
|     "upload": command_upload, | ||||
|     "logs": command_logs, | ||||
|     "run": command_run, | ||||
|     "clean-mqtt": command_clean_mqtt, | ||||
|     "mqtt-fingerprint": command_mqtt_fingerprint, | ||||
|     "clean": command_clean, | ||||
| } | ||||
|  | ||||
|  | ||||
| def parse_args(argv): | ||||
|     parser = argparse.ArgumentParser(description=f'ESPHome v{const.__version__}') | ||||
|     parser.add_argument('-v', '--verbose', help="Enable verbose esphome logs.", | ||||
|                         action='store_true') | ||||
|     parser.add_argument('-q', '--quiet', help="Disable all esphome logs.", | ||||
|                         action='store_true') | ||||
|     parser.add_argument('--dashboard', help=argparse.SUPPRESS, action='store_true') | ||||
|     parser.add_argument('-s', '--substitution', nargs=2, action='append', | ||||
|                         help='Add a substitution', metavar=('key', 'value')) | ||||
|     parser.add_argument('configuration', help='Your YAML configuration file.', nargs='*') | ||||
|     parser = argparse.ArgumentParser(description=f"ESPHome v{const.__version__}") | ||||
|     parser.add_argument( | ||||
|         "-v", "--verbose", help="Enable verbose esphome logs.", action="store_true" | ||||
|     ) | ||||
|     parser.add_argument( | ||||
|         "-q", "--quiet", help="Disable all esphome logs.", action="store_true" | ||||
|     ) | ||||
|     parser.add_argument("--dashboard", help=argparse.SUPPRESS, action="store_true") | ||||
|     parser.add_argument( | ||||
|         "-s", | ||||
|         "--substitution", | ||||
|         nargs=2, | ||||
|         action="append", | ||||
|         help="Add a substitution", | ||||
|         metavar=("key", "value"), | ||||
|     ) | ||||
|     parser.add_argument( | ||||
|         "configuration", help="Your YAML configuration file.", nargs="*" | ||||
|     ) | ||||
|  | ||||
|     subparsers = parser.add_subparsers(help='Commands', dest='command') | ||||
|     subparsers = parser.add_subparsers(help="Commands", dest="command") | ||||
|     subparsers.required = True | ||||
|     subparsers.add_parser('config', help='Validate the configuration and spit it out.') | ||||
|     subparsers.add_parser("config", help="Validate the configuration and spit it out.") | ||||
|  | ||||
|     parser_compile = subparsers.add_parser('compile', | ||||
|                                            help='Read the configuration and compile a program.') | ||||
|     parser_compile.add_argument('--only-generate', | ||||
|     parser_compile = subparsers.add_parser( | ||||
|         "compile", help="Read the configuration and compile a program." | ||||
|     ) | ||||
|     parser_compile.add_argument( | ||||
|         "--only-generate", | ||||
|         help="Only generate source code, do not compile.", | ||||
|                                 action='store_true') | ||||
|         action="store_true", | ||||
|     ) | ||||
|  | ||||
|     parser_upload = subparsers.add_parser('upload', help='Validate the configuration ' | ||||
|                                                          'and upload the latest binary.') | ||||
|     parser_upload.add_argument('--upload-port', help="Manually specify the upload port to use. " | ||||
|                                                      "For example /dev/cu.SLAB_USBtoUART.") | ||||
|     parser_upload = subparsers.add_parser( | ||||
|         "upload", help="Validate the configuration " "and upload the latest binary." | ||||
|     ) | ||||
|     parser_upload.add_argument( | ||||
|         "--upload-port", | ||||
|         help="Manually specify the upload port to use. " | ||||
|         "For example /dev/cu.SLAB_USBtoUART.", | ||||
|     ) | ||||
|  | ||||
|     parser_logs = subparsers.add_parser('logs', help='Validate the configuration ' | ||||
|                                                      'and show all MQTT logs.') | ||||
|     parser_logs.add_argument('--topic', help='Manually set the topic to subscribe to.') | ||||
|     parser_logs.add_argument('--username', help='Manually set the username.') | ||||
|     parser_logs.add_argument('--password', help='Manually set the password.') | ||||
|     parser_logs.add_argument('--client-id', help='Manually set the client id.') | ||||
|     parser_logs.add_argument('--serial-port', help="Manually specify a serial port to use" | ||||
|                                                    "For example /dev/cu.SLAB_USBtoUART.") | ||||
|     parser_logs = subparsers.add_parser( | ||||
|         "logs", help="Validate the configuration " "and show all MQTT logs." | ||||
|     ) | ||||
|     parser_logs.add_argument("--topic", help="Manually set the topic to subscribe to.") | ||||
|     parser_logs.add_argument("--username", help="Manually set the username.") | ||||
|     parser_logs.add_argument("--password", help="Manually set the password.") | ||||
|     parser_logs.add_argument("--client-id", help="Manually set the client id.") | ||||
|     parser_logs.add_argument( | ||||
|         "--serial-port", | ||||
|         help="Manually specify a serial port to use" | ||||
|         "For example /dev/cu.SLAB_USBtoUART.", | ||||
|     ) | ||||
|  | ||||
|     parser_run = subparsers.add_parser('run', help='Validate the configuration, create a binary, ' | ||||
|                                                    'upload it, and start MQTT logs.') | ||||
|     parser_run.add_argument('--upload-port', help="Manually specify the upload port/ip to use. " | ||||
|                                                   "For example /dev/cu.SLAB_USBtoUART.") | ||||
|     parser_run.add_argument('--no-logs', help='Disable starting MQTT logs.', | ||||
|                             action='store_true') | ||||
|     parser_run.add_argument('--topic', help='Manually set the topic to subscribe to for logs.') | ||||
|     parser_run.add_argument('--username', help='Manually set the MQTT username for logs.') | ||||
|     parser_run.add_argument('--password', help='Manually set the MQTT password for logs.') | ||||
|     parser_run.add_argument('--client-id', help='Manually set the client id for logs.') | ||||
|     parser_run = subparsers.add_parser( | ||||
|         "run", | ||||
|         help="Validate the configuration, create a binary, " | ||||
|         "upload it, and start MQTT logs.", | ||||
|     ) | ||||
|     parser_run.add_argument( | ||||
|         "--upload-port", | ||||
|         help="Manually specify the upload port/ip to use. " | ||||
|         "For example /dev/cu.SLAB_USBtoUART.", | ||||
|     ) | ||||
|     parser_run.add_argument( | ||||
|         "--no-logs", help="Disable starting MQTT logs.", action="store_true" | ||||
|     ) | ||||
|     parser_run.add_argument( | ||||
|         "--topic", help="Manually set the topic to subscribe to for logs." | ||||
|     ) | ||||
|     parser_run.add_argument( | ||||
|         "--username", help="Manually set the MQTT username for logs." | ||||
|     ) | ||||
|     parser_run.add_argument( | ||||
|         "--password", help="Manually set the MQTT password for logs." | ||||
|     ) | ||||
|     parser_run.add_argument("--client-id", help="Manually set the client id for logs.") | ||||
|  | ||||
|     parser_clean = subparsers.add_parser('clean-mqtt', help="Helper to clear an MQTT topic from " | ||||
|                                                             "retain messages.") | ||||
|     parser_clean.add_argument('--topic', help='Manually set the topic to subscribe to.') | ||||
|     parser_clean.add_argument('--username', help='Manually set the username.') | ||||
|     parser_clean.add_argument('--password', help='Manually set the password.') | ||||
|     parser_clean.add_argument('--client-id', help='Manually set the client id.') | ||||
|     parser_clean = subparsers.add_parser( | ||||
|         "clean-mqtt", help="Helper to clear an MQTT topic from " "retain messages." | ||||
|     ) | ||||
|     parser_clean.add_argument("--topic", help="Manually set the topic to subscribe to.") | ||||
|     parser_clean.add_argument("--username", help="Manually set the username.") | ||||
|     parser_clean.add_argument("--password", help="Manually set the password.") | ||||
|     parser_clean.add_argument("--client-id", help="Manually set the client id.") | ||||
|  | ||||
|     subparsers.add_parser('wizard', help="A helpful setup wizard that will guide " | ||||
|                                          "you through setting up esphome.") | ||||
|     subparsers.add_parser( | ||||
|         "wizard", | ||||
|         help="A helpful setup wizard that will guide " | ||||
|         "you through setting up esphome.", | ||||
|     ) | ||||
|  | ||||
|     subparsers.add_parser('mqtt-fingerprint', help="Get the SSL fingerprint from a MQTT broker.") | ||||
|     subparsers.add_parser( | ||||
|         "mqtt-fingerprint", help="Get the SSL fingerprint from a MQTT broker." | ||||
|     ) | ||||
|  | ||||
|     subparsers.add_parser('version', help="Print the esphome version and exit.") | ||||
|     subparsers.add_parser("version", help="Print the esphome version and exit.") | ||||
|  | ||||
|     subparsers.add_parser('clean', help="Delete all temporary build files.") | ||||
|     subparsers.add_parser("clean", help="Delete all temporary build files.") | ||||
|  | ||||
|     dashboard = subparsers.add_parser('dashboard', | ||||
|                                       help="Create a simple web server for a dashboard.") | ||||
|     dashboard.add_argument("--port", help="The HTTP port to open connections on. Defaults to 6052.", | ||||
|                            type=int, default=6052) | ||||
|     dashboard.add_argument("--username", help="The optional username to require " | ||||
|                                               "for authentication.", | ||||
|                            type=str, default='') | ||||
|     dashboard.add_argument("--password", help="The optional password to require " | ||||
|                                               "for authentication.", | ||||
|                            type=str, default='') | ||||
|     dashboard.add_argument("--open-ui", help="Open the dashboard UI in a browser.", | ||||
|                            action='store_true') | ||||
|     dashboard.add_argument("--hassio", | ||||
|                            help=argparse.SUPPRESS, | ||||
|                            action="store_true") | ||||
|     dashboard.add_argument("--socket", | ||||
|                            help="Make the dashboard serve under a unix socket", type=str) | ||||
|     dashboard = subparsers.add_parser( | ||||
|         "dashboard", help="Create a simple web server for a dashboard." | ||||
|     ) | ||||
|     dashboard.add_argument( | ||||
|         "--port", | ||||
|         help="The HTTP port to open connections on. Defaults to 6052.", | ||||
|         type=int, | ||||
|         default=6052, | ||||
|     ) | ||||
|     dashboard.add_argument( | ||||
|         "--username", | ||||
|         help="The optional username to require " "for authentication.", | ||||
|         type=str, | ||||
|         default="", | ||||
|     ) | ||||
|     dashboard.add_argument( | ||||
|         "--password", | ||||
|         help="The optional password to require " "for authentication.", | ||||
|         type=str, | ||||
|         default="", | ||||
|     ) | ||||
|     dashboard.add_argument( | ||||
|         "--open-ui", help="Open the dashboard UI in a browser.", action="store_true" | ||||
|     ) | ||||
|     dashboard.add_argument("--hassio", help=argparse.SUPPRESS, action="store_true") | ||||
|     dashboard.add_argument( | ||||
|         "--socket", help="Make the dashboard serve under a unix socket", type=str | ||||
|     ) | ||||
|  | ||||
|     vscode = subparsers.add_parser('vscode', help=argparse.SUPPRESS) | ||||
|     vscode.add_argument('--ace', action='store_true') | ||||
|     vscode = subparsers.add_parser("vscode", help=argparse.SUPPRESS) | ||||
|     vscode.add_argument("--ace", action="store_true") | ||||
|  | ||||
|     subparsers.add_parser('update-all', help=argparse.SUPPRESS) | ||||
|     subparsers.add_parser("update-all", help=argparse.SUPPRESS) | ||||
|  | ||||
|     return parser.parse_args(argv[1:]) | ||||
|  | ||||
| @@ -512,13 +634,15 @@ def run_esphome(argv): | ||||
|     CORE.dashboard = args.dashboard | ||||
|  | ||||
|     setup_log(args.verbose, args.quiet) | ||||
|     if args.command != 'version' and not args.configuration: | ||||
|     if args.command != "version" and not args.configuration: | ||||
|         _LOGGER.error("Missing configuration parameter, see esphome --help.") | ||||
|         return 1 | ||||
|  | ||||
|     if sys.version_info < (3, 6, 0): | ||||
|         _LOGGER.error("You're running ESPHome with Python <3.6. ESPHome is no longer compatible " | ||||
|                       "with this Python version. Please reinstall ESPHome with Python 3.6+") | ||||
|         _LOGGER.error( | ||||
|             "You're running ESPHome with Python <3.6. ESPHome is no longer compatible " | ||||
|             "with this Python version. Please reinstall ESPHome with Python 3.6+" | ||||
|         ) | ||||
|         return 1 | ||||
|  | ||||
|     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: | ||||
|             ip = resolve_ip_address(self._address) | ||||
|         except EsphomeError as err: | ||||
|             _LOGGER.warning("Error resolving IP address of %s. Is it connected to WiFi?", | ||||
|                             self._address) | ||||
|             _LOGGER.warning("(If this error persists, please set a static IP address: " | ||||
|                             "https://esphome.io/components/wifi.html#manual-ips)") | ||||
|             _LOGGER.warning( | ||||
|                 "Error resolving IP address of %s. Is it connected to WiFi?", | ||||
|                 self._address, | ||||
|             ) | ||||
|             _LOGGER.warning( | ||||
|                 "(If this error persists, please set a static IP address: " | ||||
|                 "https://esphome.io/components/wifi.html#manual-ips)" | ||||
|             ) | ||||
|             raise APIConnectionError(err) from err | ||||
|  | ||||
|         _LOGGER.info("Connecting to %s:%s (%s)", self._address, self._port, ip) | ||||
| @@ -198,14 +202,19 @@ class APIClient(threading.Thread): | ||||
|         self._socket_open_event.set() | ||||
|  | ||||
|         hello = pb.HelloRequest() | ||||
|         hello.client_info = f'ESPHome v{const.__version__}' | ||||
|         hello.client_info = f"ESPHome v{const.__version__}" | ||||
|         try: | ||||
|             resp = self._send_message_await_response(hello, pb.HelloResponse) | ||||
|         except APIConnectionError as err: | ||||
|             self._fatal_error(err) | ||||
|             raise err | ||||
|         _LOGGER.debug("Successfully connected to %s ('%s' API=%s.%s)", self._address, | ||||
|                       resp.server_info, resp.api_version_major, resp.api_version_minor) | ||||
|         _LOGGER.debug( | ||||
|             "Successfully connected to %s ('%s' API=%s.%s)", | ||||
|             self._address, | ||||
|             resp.server_info, | ||||
|             resp.api_version_major, | ||||
|             resp.api_version_minor, | ||||
|         ) | ||||
|         self._connected = True | ||||
|         self._refresh_ping() | ||||
|         if self.on_connect is not None: | ||||
| @@ -270,7 +279,9 @@ class APIClient(threading.Thread): | ||||
|         req += encoded | ||||
|         self._write(req) | ||||
|  | ||||
|     def _send_message_await_response_complex(self, send_msg, do_append, do_stop, timeout=5): | ||||
|     def _send_message_await_response_complex( | ||||
|         self, send_msg, do_append, do_stop, timeout=5 | ||||
|     ): | ||||
|         event = threading.Event() | ||||
|         responses = [] | ||||
|  | ||||
| @@ -295,12 +306,15 @@ class APIClient(threading.Thread): | ||||
|         def is_response(msg): | ||||
|             return isinstance(msg, response_type) | ||||
|  | ||||
|         return self._send_message_await_response_complex(send_msg, is_response, is_response, | ||||
|                                                          timeout)[0] | ||||
|         return self._send_message_await_response_complex( | ||||
|             send_msg, is_response, is_response, timeout | ||||
|         )[0] | ||||
|  | ||||
|     def device_info(self): | ||||
|         self._check_connected() | ||||
|         return self._send_message_await_response(pb.DeviceInfoRequest(), pb.DeviceInfoResponse) | ||||
|         return self._send_message_await_response( | ||||
|             pb.DeviceInfoRequest(), pb.DeviceInfoResponse | ||||
|         ) | ||||
|  | ||||
|     def ping(self): | ||||
|         self._check_connected() | ||||
| @@ -310,7 +324,9 @@ class APIClient(threading.Thread): | ||||
|         self._check_connected() | ||||
|  | ||||
|         try: | ||||
|             self._send_message_await_response(pb.DisconnectRequest(), pb.DisconnectResponse) | ||||
|             self._send_message_await_response( | ||||
|                 pb.DisconnectRequest(), pb.DisconnectResponse | ||||
|             ) | ||||
|         except APIConnectionError: | ||||
|             pass | ||||
|         self._close_socket() | ||||
| @@ -415,7 +431,7 @@ class APIClient(threading.Thread): | ||||
|  | ||||
|  | ||||
| def run_logs(config, address): | ||||
|     conf = config['api'] | ||||
|     conf = config["api"] | ||||
|     port = conf[CONF_PORT] | ||||
|     password = conf[CONF_PASSWORD] | ||||
|     _LOGGER.info("Starting log output from %s using esphome API", address) | ||||
| @@ -447,24 +463,35 @@ def run_logs(config, address): | ||||
|             _LOGGER.info("Successfully connected to %s", address) | ||||
|             return | ||||
|  | ||||
|         wait_time = int(min(1.5**min(tries, 100), 30)) | ||||
|         wait_time = int(min(1.5 ** min(tries, 100), 30)) | ||||
|         if not has_connects: | ||||
|             _LOGGER.warning("Initial connection failed. The ESP might not be connected " | ||||
|             _LOGGER.warning( | ||||
|                 "Initial connection failed. The ESP might not be connected " | ||||
|                 "to WiFi yet (%s). Re-Trying in %s seconds", | ||||
|                             error, wait_time) | ||||
|                 error, | ||||
|                 wait_time, | ||||
|             ) | ||||
|         else: | ||||
|             _LOGGER.warning("Couldn't connect to API (%s). Trying to reconnect in %s seconds", | ||||
|                             error, wait_time) | ||||
|         timer = threading.Timer(wait_time, functools.partial(try_connect, None, tries + 1)) | ||||
|             _LOGGER.warning( | ||||
|                 "Couldn't connect to API (%s). Trying to reconnect in %s seconds", | ||||
|                 error, | ||||
|                 wait_time, | ||||
|             ) | ||||
|         timer = threading.Timer( | ||||
|             wait_time, functools.partial(try_connect, None, tries + 1) | ||||
|         ) | ||||
|         timer.start() | ||||
|         retry_timer.append(timer) | ||||
|  | ||||
|     def on_log(msg): | ||||
|         time_ = datetime.now().time().strftime('[%H:%M:%S]') | ||||
|         time_ = datetime.now().time().strftime("[%H:%M:%S]") | ||||
|         text = msg.message | ||||
|         if msg.send_failed: | ||||
|             text = color('white', '(Message skipped because it was too big to fit in ' | ||||
|                                   'TCP buffer - This is only cosmetic)') | ||||
|             text = color( | ||||
|                 "white", | ||||
|                 "(Message skipped because it was too big to fit in " | ||||
|                 "TCP buffer - This is only cosmetic)", | ||||
|             ) | ||||
|         safe_print(time_ + text) | ||||
|  | ||||
|     def on_login(): | ||||
|   | ||||
| @@ -1,8 +1,17 @@ | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome.const import CONF_AUTOMATION_ID, CONF_CONDITION, CONF_ELSE, CONF_ID, CONF_THEN, \ | ||||
|     CONF_TRIGGER_ID, CONF_TYPE_ID, CONF_TIME | ||||
| from esphome.const import ( | ||||
|     CONF_AUTOMATION_ID, | ||||
|     CONF_CONDITION, | ||||
|     CONF_ELSE, | ||||
|     CONF_ID, | ||||
|     CONF_THEN, | ||||
|     CONF_TRIGGER_ID, | ||||
|     CONF_TYPE_ID, | ||||
|     CONF_TIME, | ||||
| ) | ||||
| from esphome.core import coroutine | ||||
| from esphome.jsonschema import jschema_extractor | ||||
| from esphome.util import Registry | ||||
|  | ||||
|  | ||||
| @@ -13,7 +22,12 @@ def maybe_simple_id(*validators): | ||||
| def maybe_conf(conf, *validators): | ||||
|     validator = cv.All(*validators) | ||||
|  | ||||
|     @jschema_extractor("maybe") | ||||
|     def validate(value): | ||||
|         # pylint: disable=comparison-with-callable | ||||
|         if value == jschema_extractor: | ||||
|             return validator | ||||
|  | ||||
|         if isinstance(value, dict): | ||||
|             return validator(value) | ||||
|         with cv.remove_prepend_path([conf]): | ||||
| @@ -30,36 +44,34 @@ def register_condition(name, condition_type, schema): | ||||
|     return CONDITION_REGISTRY.register(name, condition_type, schema) | ||||
|  | ||||
|  | ||||
| Action = cg.esphome_ns.class_('Action') | ||||
| Trigger = cg.esphome_ns.class_('Trigger') | ||||
| Action = cg.esphome_ns.class_("Action") | ||||
| Trigger = cg.esphome_ns.class_("Trigger") | ||||
| ACTION_REGISTRY = Registry() | ||||
| Condition = cg.esphome_ns.class_('Condition') | ||||
| Condition = cg.esphome_ns.class_("Condition") | ||||
| CONDITION_REGISTRY = Registry() | ||||
| validate_action = cv.validate_registry_entry('action', ACTION_REGISTRY) | ||||
| validate_action_list = cv.validate_registry('action', ACTION_REGISTRY) | ||||
| validate_condition = cv.validate_registry_entry('condition', CONDITION_REGISTRY) | ||||
| validate_condition_list = cv.validate_registry('condition', CONDITION_REGISTRY) | ||||
| validate_action = cv.validate_registry_entry("action", ACTION_REGISTRY) | ||||
| validate_action_list = cv.validate_registry("action", ACTION_REGISTRY) | ||||
| validate_condition = cv.validate_registry_entry("condition", CONDITION_REGISTRY) | ||||
| validate_condition_list = cv.validate_registry("condition", CONDITION_REGISTRY) | ||||
|  | ||||
|  | ||||
| def validate_potentially_and_condition(value): | ||||
|     if isinstance(value, list): | ||||
|         with cv.remove_prepend_path(['and']): | ||||
|             return validate_condition({ | ||||
|                 'and': value | ||||
|             }) | ||||
|         with cv.remove_prepend_path(["and"]): | ||||
|             return validate_condition({"and": value}) | ||||
|     return validate_condition(value) | ||||
|  | ||||
|  | ||||
| DelayAction = cg.esphome_ns.class_('DelayAction', Action, cg.Component) | ||||
| LambdaAction = cg.esphome_ns.class_('LambdaAction', Action) | ||||
| IfAction = cg.esphome_ns.class_('IfAction', Action) | ||||
| WhileAction = cg.esphome_ns.class_('WhileAction', Action) | ||||
| WaitUntilAction = cg.esphome_ns.class_('WaitUntilAction', Action, cg.Component) | ||||
| UpdateComponentAction = cg.esphome_ns.class_('UpdateComponentAction', Action) | ||||
| Automation = cg.esphome_ns.class_('Automation') | ||||
| DelayAction = cg.esphome_ns.class_("DelayAction", Action, cg.Component) | ||||
| LambdaAction = cg.esphome_ns.class_("LambdaAction", Action) | ||||
| IfAction = cg.esphome_ns.class_("IfAction", Action) | ||||
| WhileAction = cg.esphome_ns.class_("WhileAction", Action) | ||||
| WaitUntilAction = cg.esphome_ns.class_("WaitUntilAction", Action, cg.Component) | ||||
| UpdateComponentAction = cg.esphome_ns.class_("UpdateComponentAction", Action) | ||||
| Automation = cg.esphome_ns.class_("Automation") | ||||
|  | ||||
| LambdaCondition = cg.esphome_ns.class_('LambdaCondition', Condition) | ||||
| ForCondition = cg.esphome_ns.class_('ForCondition', Condition, cg.Component) | ||||
| LambdaCondition = cg.esphome_ns.class_("LambdaCondition", Condition) | ||||
| ForCondition = cg.esphome_ns.class_("ForCondition", Condition, cg.Component) | ||||
|  | ||||
|  | ||||
| def validate_automation(extra_schema=None, extra_validators=None, single=False): | ||||
| @@ -83,10 +95,10 @@ def validate_automation(extra_schema=None, extra_validators=None, single=False): | ||||
|                 try: | ||||
|                     return cv.Schema([schema])(value) | ||||
|                 except cv.Invalid as err2: | ||||
|                     if 'extra keys not allowed' in str(err2) and len(err2.path) == 2: | ||||
|                     if "extra keys not allowed" in str(err2) and len(err2.path) == 2: | ||||
|                         # pylint: disable=raise-missing-from | ||||
|                         raise err | ||||
|                     if 'Unable to find action' in str(err): | ||||
|                     if "Unable to find action" in str(err): | ||||
|                         raise err2 | ||||
|                     raise cv.MultipleInvalid([err, err2]) | ||||
|         elif isinstance(value, dict): | ||||
| @@ -97,7 +109,13 @@ def validate_automation(extra_schema=None, extra_validators=None, single=False): | ||||
|         # This should only happen with invalid configs, but let's have a nice error message. | ||||
|         return [schema(value)] | ||||
|  | ||||
|     @jschema_extractor("automation") | ||||
|     def validator(value): | ||||
|         # hack to get the schema | ||||
|         # pylint: disable=comparison-with-callable | ||||
|         if value == jschema_extractor: | ||||
|             return schema | ||||
|  | ||||
|         value = validator_(value) | ||||
|         if extra_validators is not None: | ||||
|             value = cv.Schema([extra_validators])(value) | ||||
| @@ -110,47 +128,59 @@ def validate_automation(extra_schema=None, extra_validators=None, single=False): | ||||
|     return validator | ||||
|  | ||||
|  | ||||
| AUTOMATION_SCHEMA = cv.Schema({ | ||||
| AUTOMATION_SCHEMA = cv.Schema( | ||||
|     { | ||||
|         cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(Trigger), | ||||
|         cv.GenerateID(CONF_AUTOMATION_ID): cv.declare_id(Automation), | ||||
|         cv.Required(CONF_THEN): validate_action_list, | ||||
| }) | ||||
|     } | ||||
| ) | ||||
|  | ||||
| AndCondition = cg.esphome_ns.class_('AndCondition', Condition) | ||||
| OrCondition = cg.esphome_ns.class_('OrCondition', Condition) | ||||
| NotCondition = cg.esphome_ns.class_('NotCondition', Condition) | ||||
| AndCondition = cg.esphome_ns.class_("AndCondition", Condition) | ||||
| OrCondition = cg.esphome_ns.class_("OrCondition", Condition) | ||||
| NotCondition = cg.esphome_ns.class_("NotCondition", Condition) | ||||
|  | ||||
|  | ||||
| @register_condition('and', AndCondition, validate_condition_list) | ||||
| @register_condition("and", AndCondition, validate_condition_list) | ||||
| def and_condition_to_code(config, condition_id, template_arg, args): | ||||
|     conditions = yield build_condition_list(config, template_arg, args) | ||||
|     yield cg.new_Pvariable(condition_id, template_arg, conditions) | ||||
|  | ||||
|  | ||||
| @register_condition('or', OrCondition, validate_condition_list) | ||||
| @register_condition("or", OrCondition, validate_condition_list) | ||||
| def or_condition_to_code(config, condition_id, template_arg, args): | ||||
|     conditions = yield build_condition_list(config, template_arg, args) | ||||
|     yield cg.new_Pvariable(condition_id, template_arg, conditions) | ||||
|  | ||||
|  | ||||
| @register_condition('not', NotCondition, validate_potentially_and_condition) | ||||
| @register_condition("not", NotCondition, validate_potentially_and_condition) | ||||
| def not_condition_to_code(config, condition_id, template_arg, args): | ||||
|     condition = yield build_condition(config, template_arg, args) | ||||
|     yield cg.new_Pvariable(condition_id, template_arg, condition) | ||||
|  | ||||
|  | ||||
| @register_condition('lambda', LambdaCondition, cv.lambda_) | ||||
| @register_condition("lambda", LambdaCondition, cv.lambda_) | ||||
| def lambda_condition_to_code(config, condition_id, template_arg, args): | ||||
|     lambda_ = yield cg.process_lambda(config, args, return_type=bool) | ||||
|     yield cg.new_Pvariable(condition_id, template_arg, lambda_) | ||||
|  | ||||
|  | ||||
| @register_condition('for', ForCondition, cv.Schema({ | ||||
|     cv.Required(CONF_TIME): cv.templatable(cv.positive_time_period_milliseconds), | ||||
| @register_condition( | ||||
|     "for", | ||||
|     ForCondition, | ||||
|     cv.Schema( | ||||
|         { | ||||
|             cv.Required(CONF_TIME): cv.templatable( | ||||
|                 cv.positive_time_period_milliseconds | ||||
|             ), | ||||
|             cv.Required(CONF_CONDITION): validate_potentially_and_condition, | ||||
| }).extend(cv.COMPONENT_SCHEMA)) | ||||
|         } | ||||
|     ).extend(cv.COMPONENT_SCHEMA), | ||||
| ) | ||||
| def for_condition_to_code(config, condition_id, template_arg, args): | ||||
|     condition = yield build_condition(config[CONF_CONDITION], cg.TemplateArguments(), []) | ||||
|     condition = yield build_condition( | ||||
|         config[CONF_CONDITION], cg.TemplateArguments(), [] | ||||
|     ) | ||||
|     var = cg.new_Pvariable(condition_id, template_arg, condition) | ||||
|     yield cg.register_component(var, config) | ||||
|     templ = yield cg.templatable(config[CONF_TIME], args, cg.uint32) | ||||
| @@ -158,7 +188,9 @@ def for_condition_to_code(config, condition_id, template_arg, args): | ||||
|     yield var | ||||
|  | ||||
|  | ||||
| @register_action('delay', DelayAction, cv.templatable(cv.positive_time_period_milliseconds)) | ||||
| @register_action( | ||||
|     "delay", DelayAction, cv.templatable(cv.positive_time_period_milliseconds) | ||||
| ) | ||||
| def delay_action_to_code(config, action_id, template_arg, args): | ||||
|     var = cg.new_Pvariable(action_id, template_arg) | ||||
|     yield cg.register_component(var, {}) | ||||
| @@ -167,11 +199,18 @@ def delay_action_to_code(config, action_id, template_arg, args): | ||||
|     yield var | ||||
|  | ||||
|  | ||||
| @register_action('if', IfAction, cv.All({ | ||||
| @register_action( | ||||
|     "if", | ||||
|     IfAction, | ||||
|     cv.All( | ||||
|         { | ||||
|             cv.Required(CONF_CONDITION): validate_potentially_and_condition, | ||||
|             cv.Optional(CONF_THEN): validate_action_list, | ||||
|             cv.Optional(CONF_ELSE): validate_action_list, | ||||
| }, cv.has_at_least_one_key(CONF_THEN, CONF_ELSE))) | ||||
|         }, | ||||
|         cv.has_at_least_one_key(CONF_THEN, CONF_ELSE), | ||||
|     ), | ||||
| ) | ||||
| def if_action_to_code(config, action_id, template_arg, args): | ||||
|     conditions = yield build_condition(config[CONF_CONDITION], template_arg, args) | ||||
|     var = cg.new_Pvariable(action_id, template_arg, conditions) | ||||
| @@ -184,10 +223,16 @@ def if_action_to_code(config, action_id, template_arg, args): | ||||
|     yield var | ||||
|  | ||||
|  | ||||
| @register_action('while', WhileAction, cv.Schema({ | ||||
| @register_action( | ||||
|     "while", | ||||
|     WhileAction, | ||||
|     cv.Schema( | ||||
|         { | ||||
|             cv.Required(CONF_CONDITION): validate_potentially_and_condition, | ||||
|             cv.Required(CONF_THEN): validate_action_list, | ||||
| })) | ||||
|         } | ||||
|     ), | ||||
| ) | ||||
| def while_action_to_code(config, action_id, template_arg, args): | ||||
|     conditions = yield build_condition(config[CONF_CONDITION], template_arg, args) | ||||
|     var = cg.new_Pvariable(action_id, template_arg, conditions) | ||||
| @@ -197,15 +242,17 @@ def while_action_to_code(config, action_id, template_arg, args): | ||||
|  | ||||
|  | ||||
| def validate_wait_until(value): | ||||
|     schema = cv.Schema({ | ||||
|     schema = cv.Schema( | ||||
|         { | ||||
|             cv.Required(CONF_CONDITION): validate_potentially_and_condition, | ||||
|     }) | ||||
|         } | ||||
|     ) | ||||
|     if isinstance(value, dict) and CONF_CONDITION in value: | ||||
|         return schema(value) | ||||
|     return validate_wait_until({CONF_CONDITION: value}) | ||||
|  | ||||
|  | ||||
| @register_action('wait_until', WaitUntilAction, validate_wait_until) | ||||
| @register_action("wait_until", WaitUntilAction, validate_wait_until) | ||||
| def wait_until_action_to_code(config, action_id, template_arg, args): | ||||
|     conditions = yield build_condition(config[CONF_CONDITION], template_arg, args) | ||||
|     var = cg.new_Pvariable(action_id, template_arg, conditions) | ||||
| @@ -213,15 +260,21 @@ def wait_until_action_to_code(config, action_id, template_arg, args): | ||||
|     yield var | ||||
|  | ||||
|  | ||||
| @register_action('lambda', LambdaAction, cv.lambda_) | ||||
| @register_action("lambda", LambdaAction, cv.lambda_) | ||||
| def lambda_action_to_code(config, action_id, template_arg, args): | ||||
|     lambda_ = yield cg.process_lambda(config, args, return_type=cg.void) | ||||
|     yield cg.new_Pvariable(action_id, template_arg, lambda_) | ||||
|  | ||||
|  | ||||
| @register_action('component.update', UpdateComponentAction, maybe_simple_id({ | ||||
| @register_action( | ||||
|     "component.update", | ||||
|     UpdateComponentAction, | ||||
|     maybe_simple_id( | ||||
|         { | ||||
|             cv.Required(CONF_ID): cv.use_id(cg.PollingComponent), | ||||
| })) | ||||
|         } | ||||
|     ), | ||||
| ) | ||||
| def component_update_action_to_code(config, action_id, template_arg, args): | ||||
|     comp = yield cg.get_variable(config[CONF_ID]) | ||||
|     yield cg.new_Pvariable(action_id, template_arg, comp) | ||||
| @@ -229,7 +282,9 @@ def component_update_action_to_code(config, action_id, template_arg, args): | ||||
|  | ||||
| @coroutine | ||||
| def build_action(full_config, template_arg, args): | ||||
|     registry_entry, config = cg.extract_registry_entry_config(ACTION_REGISTRY, full_config) | ||||
|     registry_entry, config = cg.extract_registry_entry_config( | ||||
|         ACTION_REGISTRY, full_config | ||||
|     ) | ||||
|     action_id = full_config[CONF_TYPE_ID] | ||||
|     builder = registry_entry.coroutine_fun | ||||
|     yield builder(config, action_id, template_arg, args) | ||||
| @@ -246,7 +301,9 @@ def build_action_list(config, templ, arg_type): | ||||
|  | ||||
| @coroutine | ||||
| def build_condition(full_config, template_arg, args): | ||||
|     registry_entry, config = cg.extract_registry_entry_config(CONDITION_REGISTRY, full_config) | ||||
|     registry_entry, config = cg.extract_registry_entry_config( | ||||
|         CONDITION_REGISTRY, full_config | ||||
|     ) | ||||
|     action_id = full_config[CONF_TYPE_ID] | ||||
|     builder = registry_entry.coroutine_fun | ||||
|     yield builder(config, action_id, template_arg, args) | ||||
|   | ||||
| @@ -9,18 +9,71 @@ | ||||
|  | ||||
| # pylint: disable=unused-import | ||||
| from esphome.cpp_generator import (  # noqa | ||||
|     Expression, RawExpression, RawStatement, TemplateArguments, | ||||
|     StructInitializer, ArrayInitializer, safe_exp, Statement, LineComment, | ||||
|     progmem_array, statement, variable, Pvariable, new_Pvariable, | ||||
|     add, add_global, add_library, add_build_flag, add_define, | ||||
|     get_variable, get_variable_with_full_id, process_lambda, is_template, templatable, MockObj, | ||||
|     MockObjClass) | ||||
|     Expression, | ||||
|     RawExpression, | ||||
|     RawStatement, | ||||
|     TemplateArguments, | ||||
|     StructInitializer, | ||||
|     ArrayInitializer, | ||||
|     safe_exp, | ||||
|     Statement, | ||||
|     LineComment, | ||||
|     progmem_array, | ||||
|     statement, | ||||
|     variable, | ||||
|     new_variable, | ||||
|     Pvariable, | ||||
|     new_Pvariable, | ||||
|     add, | ||||
|     add_global, | ||||
|     add_library, | ||||
|     add_build_flag, | ||||
|     add_define, | ||||
|     get_variable, | ||||
|     get_variable_with_full_id, | ||||
|     process_lambda, | ||||
|     is_template, | ||||
|     templatable, | ||||
|     MockObj, | ||||
|     MockObjClass, | ||||
| ) | ||||
| from esphome.cpp_helpers import (  # noqa | ||||
|     gpio_pin_expression, register_component, build_registry_entry, | ||||
|     build_registry_list, extract_registry_entry_config, register_parented) | ||||
|     gpio_pin_expression, | ||||
|     register_component, | ||||
|     build_registry_entry, | ||||
|     build_registry_list, | ||||
|     extract_registry_entry_config, | ||||
|     register_parented, | ||||
| ) | ||||
| from esphome.cpp_types import (  # noqa | ||||
|     global_ns, void, nullptr, float_, double, bool_, int_, std_ns, std_string, | ||||
|     std_vector, uint8, uint16, uint32, int32, const_char_ptr, NAN, | ||||
|     esphome_ns, App, Nameable, Component, ComponentPtr, | ||||
|     PollingComponent, Application, optional, arduino_json_ns, JsonObject, | ||||
|     JsonObjectRef, JsonObjectConstRef, Controller, GPIOPin) | ||||
|     global_ns, | ||||
|     void, | ||||
|     nullptr, | ||||
|     float_, | ||||
|     double, | ||||
|     bool_, | ||||
|     int_, | ||||
|     std_ns, | ||||
|     std_string, | ||||
|     std_vector, | ||||
|     uint8, | ||||
|     uint16, | ||||
|     uint32, | ||||
|     int32, | ||||
|     const_char_ptr, | ||||
|     NAN, | ||||
|     esphome_ns, | ||||
|     App, | ||||
|     Nameable, | ||||
|     Component, | ||||
|     ComponentPtr, | ||||
|     PollingComponent, | ||||
|     Application, | ||||
|     optional, | ||||
|     arduino_json_ns, | ||||
|     JsonObject, | ||||
|     JsonObjectRef, | ||||
|     JsonObjectConstRef, | ||||
|     Controller, | ||||
|     GPIOPin, | ||||
| ) | ||||
|   | ||||
| @@ -11,6 +11,7 @@ void A4988::setup() { | ||||
|   if (this->sleep_pin_ != nullptr) { | ||||
|     this->sleep_pin_->setup(); | ||||
|     this->sleep_pin_->digital_write(false); | ||||
|     this->sleep_pin_state_ = false; | ||||
|   } | ||||
|   this->step_pin_->setup(); | ||||
|   this->step_pin_->digital_write(false); | ||||
| @@ -27,7 +28,12 @@ void A4988::dump_config() { | ||||
| void A4988::loop() { | ||||
|   bool at_target = this->has_reached_target(); | ||||
|   if (this->sleep_pin_ != nullptr) { | ||||
|     bool sleep_rising_edge = !sleep_pin_state_ & !at_target; | ||||
|     this->sleep_pin_->digital_write(!at_target); | ||||
|     this->sleep_pin_state_ = !at_target; | ||||
|     if (sleep_rising_edge) { | ||||
|       delayMicroseconds(1000); | ||||
|     } | ||||
|   } | ||||
|   if (at_target) { | ||||
|     this->high_freq_.stop(); | ||||
|   | ||||
| @@ -21,6 +21,7 @@ class A4988 : public stepper::Stepper, public Component { | ||||
|   GPIOPin *step_pin_; | ||||
|   GPIOPin *dir_pin_; | ||||
|   GPIOPin *sleep_pin_{nullptr}; | ||||
|   bool sleep_pin_state_; | ||||
|   HighFrequencyLoopRequester high_freq_; | ||||
| }; | ||||
|  | ||||
|   | ||||
| @@ -5,15 +5,17 @@ import esphome.codegen as cg | ||||
| from esphome.const import CONF_DIR_PIN, CONF_ID, CONF_SLEEP_PIN, CONF_STEP_PIN | ||||
|  | ||||
|  | ||||
| a4988_ns = cg.esphome_ns.namespace('a4988') | ||||
| A4988 = a4988_ns.class_('A4988', stepper.Stepper, cg.Component) | ||||
| a4988_ns = cg.esphome_ns.namespace("a4988") | ||||
| A4988 = a4988_ns.class_("A4988", stepper.Stepper, cg.Component) | ||||
|  | ||||
| CONFIG_SCHEMA = stepper.STEPPER_SCHEMA.extend({ | ||||
| CONFIG_SCHEMA = stepper.STEPPER_SCHEMA.extend( | ||||
|     { | ||||
|         cv.Required(CONF_ID): cv.declare_id(A4988), | ||||
|         cv.Required(CONF_STEP_PIN): pins.gpio_output_pin_schema, | ||||
|         cv.Required(CONF_DIR_PIN): pins.gpio_output_pin_schema, | ||||
|         cv.Optional(CONF_SLEEP_PIN): pins.gpio_output_pin_schema, | ||||
| }).extend(cv.COMPONENT_SCHEMA) | ||||
|     } | ||||
| ).extend(cv.COMPONENT_SCHEMA) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
|   | ||||
| @@ -4,28 +4,32 @@ from esphome import pins | ||||
| from esphome.components import output | ||||
| from esphome.const import CONF_ID, CONF_MIN_POWER, CONF_METHOD | ||||
|  | ||||
| CODEOWNERS = ['@glmnet'] | ||||
| CODEOWNERS = ["@glmnet"] | ||||
|  | ||||
| ac_dimmer_ns = cg.esphome_ns.namespace('ac_dimmer') | ||||
| AcDimmer = ac_dimmer_ns.class_('AcDimmer', output.FloatOutput, cg.Component) | ||||
| ac_dimmer_ns = cg.esphome_ns.namespace("ac_dimmer") | ||||
| AcDimmer = ac_dimmer_ns.class_("AcDimmer", output.FloatOutput, cg.Component) | ||||
|  | ||||
| DimMethod = ac_dimmer_ns.enum('DimMethod') | ||||
| DimMethod = ac_dimmer_ns.enum("DimMethod") | ||||
| DIM_METHODS = { | ||||
|     'LEADING_PULSE': DimMethod.DIM_METHOD_LEADING_PULSE, | ||||
|     'LEADING': DimMethod.DIM_METHOD_LEADING, | ||||
|     'TRAILING': DimMethod.DIM_METHOD_TRAILING, | ||||
|     "LEADING_PULSE": DimMethod.DIM_METHOD_LEADING_PULSE, | ||||
|     "LEADING": DimMethod.DIM_METHOD_LEADING, | ||||
|     "TRAILING": DimMethod.DIM_METHOD_TRAILING, | ||||
| } | ||||
|  | ||||
| CONF_GATE_PIN = 'gate_pin' | ||||
| CONF_ZERO_CROSS_PIN = 'zero_cross_pin' | ||||
| CONF_INIT_WITH_HALF_CYCLE = 'init_with_half_cycle' | ||||
| CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend({ | ||||
| CONF_GATE_PIN = "gate_pin" | ||||
| CONF_ZERO_CROSS_PIN = "zero_cross_pin" | ||||
| CONF_INIT_WITH_HALF_CYCLE = "init_with_half_cycle" | ||||
| CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend( | ||||
|     { | ||||
|         cv.Required(CONF_ID): cv.declare_id(AcDimmer), | ||||
|         cv.Required(CONF_GATE_PIN): pins.internal_gpio_output_pin_schema, | ||||
|         cv.Required(CONF_ZERO_CROSS_PIN): pins.internal_gpio_input_pin_schema, | ||||
|         cv.Optional(CONF_INIT_WITH_HALF_CYCLE, default=True): cv.boolean, | ||||
|     cv.Optional(CONF_METHOD, default='leading pulse'): cv.enum(DIM_METHODS, upper=True, space='_'), | ||||
| }).extend(cv.COMPONENT_SCHEMA) | ||||
|         cv.Optional(CONF_METHOD, default="leading pulse"): cv.enum( | ||||
|             DIM_METHODS, upper=True, space="_" | ||||
|         ), | ||||
|     } | ||||
| ).extend(cv.COMPONENT_SCHEMA) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
|   | ||||
| @@ -5,18 +5,22 @@ from esphome.components.light.types import AddressableLightEffect | ||||
| from esphome.components.light.effects import register_addressable_effect | ||||
| from esphome.const import CONF_NAME, CONF_UART_ID | ||||
|  | ||||
| DEPENDENCIES = ['uart'] | ||||
| DEPENDENCIES = ["uart"] | ||||
|  | ||||
| adalight_ns = cg.esphome_ns.namespace('adalight') | ||||
| adalight_ns = cg.esphome_ns.namespace("adalight") | ||||
| AdalightLightEffect = adalight_ns.class_( | ||||
|     'AdalightLightEffect', uart.UARTDevice, AddressableLightEffect) | ||||
|     "AdalightLightEffect", uart.UARTDevice, AddressableLightEffect | ||||
| ) | ||||
|  | ||||
| CONFIG_SCHEMA = cv.Schema({}) | ||||
|  | ||||
|  | ||||
| @register_addressable_effect('adalight', AdalightLightEffect, "Adalight", { | ||||
|     cv.GenerateID(CONF_UART_ID): cv.use_id(uart.UARTComponent) | ||||
| }) | ||||
| @register_addressable_effect( | ||||
|     "adalight", | ||||
|     AdalightLightEffect, | ||||
|     "Adalight", | ||||
|     {cv.GenerateID(CONF_UART_ID): cv.use_id(uart.UARTComponent)}, | ||||
| ) | ||||
| def adalight_light_effect_to_code(config, effect_id): | ||||
|     effect = cg.new_Pvariable(effect_id, config[CONF_NAME]) | ||||
|     yield uart.register_uart_device(effect, config) | ||||
|   | ||||
| @@ -42,11 +42,11 @@ void AdalightLightEffect::reset_frame_(light::AddressableLight &it) { | ||||
|  | ||||
| void AdalightLightEffect::blank_all_leds_(light::AddressableLight &it) { | ||||
|   for (int led = it.size(); led-- > 0;) { | ||||
|     it[led].set(light::ESPColor::BLACK); | ||||
|     it[led].set(COLOR_BLACK); | ||||
|   } | ||||
| } | ||||
|  | ||||
| void AdalightLightEffect::apply(light::AddressableLight &it, const light::ESPColor ¤t_color) { | ||||
| void AdalightLightEffect::apply(light::AddressableLight &it, const Color ¤t_color) { | ||||
|   const uint32_t now = millis(); | ||||
|  | ||||
|   if (now - this->last_ack_ >= ADALIGHT_ACK_INTERVAL) { | ||||
| @@ -130,7 +130,7 @@ AdalightLightEffect::Frame AdalightLightEffect::parse_frame_(light::AddressableL | ||||
|   for (int led = 0; led < accepted_led_count; led++, led_data += 3) { | ||||
|     auto white = std::min(std::min(led_data[0], led_data[1]), led_data[2]); | ||||
|  | ||||
|     it[led].set(light::ESPColor(led_data[0], led_data[1], led_data[2], white)); | ||||
|     it[led].set(Color(led_data[0], led_data[1], led_data[2], white)); | ||||
|   } | ||||
|  | ||||
|   return CONSUMED; | ||||
|   | ||||
| @@ -16,7 +16,7 @@ class AdalightLightEffect : public light::AddressableLightEffect, public uart::U | ||||
|  public: | ||||
|   void start() override; | ||||
|   void stop() override; | ||||
|   void apply(light::AddressableLight &it, const light::ESPColor ¤t_color) override; | ||||
|   void apply(light::AddressableLight &it, const Color ¤t_color) override; | ||||
|  | ||||
|  protected: | ||||
|   enum Frame { | ||||
|   | ||||
| @@ -1 +1 @@ | ||||
| CODEOWNERS = ['@esphome/core'] | ||||
| CODEOWNERS = ["@esphome/core"] | ||||
|   | ||||
| @@ -16,7 +16,9 @@ void ADCSensor::set_attenuation(adc_attenuation_t attenuation) { this->attenuati | ||||
|  | ||||
| void ADCSensor::setup() { | ||||
|   ESP_LOGCONFIG(TAG, "Setting up ADC '%s'...", this->get_name().c_str()); | ||||
| #ifndef USE_ADC_SENSOR_VCC | ||||
|   GPIOPin(this->pin_, INPUT).setup(); | ||||
| #endif | ||||
|  | ||||
| #ifdef ARDUINO_ARCH_ESP32 | ||||
|   analogSetPinAttenuation(this->pin_, this->attenuation_); | ||||
|   | ||||
| @@ -2,36 +2,51 @@ import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome import pins | ||||
| from esphome.components import sensor, voltage_sampler | ||||
| from esphome.const import CONF_ATTENUATION, CONF_ID, CONF_PIN, ICON_FLASH, UNIT_VOLT | ||||
| from esphome.const import ( | ||||
|     CONF_ATTENUATION, | ||||
|     CONF_ID, | ||||
|     CONF_PIN, | ||||
|     DEVICE_CLASS_VOLTAGE, | ||||
|     ICON_EMPTY, | ||||
|     UNIT_VOLT, | ||||
| ) | ||||
|  | ||||
|  | ||||
| AUTO_LOAD = ['voltage_sampler'] | ||||
| AUTO_LOAD = ["voltage_sampler"] | ||||
|  | ||||
| ATTENUATION_MODES = { | ||||
|     '0db': cg.global_ns.ADC_0db, | ||||
|     '2.5db': cg.global_ns.ADC_2_5db, | ||||
|     '6db': cg.global_ns.ADC_6db, | ||||
|     '11db': cg.global_ns.ADC_11db, | ||||
|     "0db": cg.global_ns.ADC_0db, | ||||
|     "2.5db": cg.global_ns.ADC_2_5db, | ||||
|     "6db": cg.global_ns.ADC_6db, | ||||
|     "11db": cg.global_ns.ADC_11db, | ||||
| } | ||||
|  | ||||
|  | ||||
| def validate_adc_pin(value): | ||||
|     vcc = str(value).upper() | ||||
|     if vcc == 'VCC': | ||||
|     if vcc == "VCC": | ||||
|         return cv.only_on_esp8266(vcc) | ||||
|     return pins.analog_pin(value) | ||||
|  | ||||
|  | ||||
| adc_ns = cg.esphome_ns.namespace('adc') | ||||
| ADCSensor = adc_ns.class_('ADCSensor', sensor.Sensor, cg.PollingComponent, | ||||
|                           voltage_sampler.VoltageSampler) | ||||
| adc_ns = cg.esphome_ns.namespace("adc") | ||||
| ADCSensor = adc_ns.class_( | ||||
|     "ADCSensor", sensor.Sensor, cg.PollingComponent, voltage_sampler.VoltageSampler | ||||
| ) | ||||
|  | ||||
| CONFIG_SCHEMA = sensor.sensor_schema(UNIT_VOLT, ICON_FLASH, 2).extend({ | ||||
| CONFIG_SCHEMA = ( | ||||
|     sensor.sensor_schema(UNIT_VOLT, ICON_EMPTY, 2, DEVICE_CLASS_VOLTAGE) | ||||
|     .extend( | ||||
|         { | ||||
|             cv.GenerateID(): cv.declare_id(ADCSensor), | ||||
|             cv.Required(CONF_PIN): validate_adc_pin, | ||||
|     cv.SplitDefault(CONF_ATTENUATION, esp32='0db'): | ||||
|         cv.All(cv.only_on_esp32, cv.enum(ATTENUATION_MODES, lower=True)), | ||||
| }).extend(cv.polling_component_schema('60s')) | ||||
|             cv.SplitDefault(CONF_ATTENUATION, esp32="0db"): cv.All( | ||||
|                 cv.only_on_esp32, cv.enum(ATTENUATION_MODES, lower=True) | ||||
|             ), | ||||
|         } | ||||
|     ) | ||||
|     .extend(cv.polling_component_schema("60s")) | ||||
| ) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
| @@ -39,8 +54,8 @@ def to_code(config): | ||||
|     yield cg.register_component(var, config) | ||||
|     yield sensor.register_sensor(var, config) | ||||
|  | ||||
|     if config[CONF_PIN] == 'VCC': | ||||
|         cg.add_define('USE_ADC_SENSOR_VCC') | ||||
|     if config[CONF_PIN] == "VCC": | ||||
|         cg.add_define("USE_ADC_SENSOR_VCC") | ||||
|     else: | ||||
|         cg.add(var.set_pin(config[CONF_PIN])) | ||||
|  | ||||
|   | ||||
							
								
								
									
										0
									
								
								esphome/components/addressable_light/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								esphome/components/addressable_light/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,67 @@ | ||||
| #include "addressable_light_display.h" | ||||
| #include "esphome/core/log.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace addressable_light { | ||||
|  | ||||
| static const char* TAG = "addressable_light.display"; | ||||
|  | ||||
| int AddressableLightDisplay::get_width_internal() { return this->width_; } | ||||
| int AddressableLightDisplay::get_height_internal() { return this->height_; } | ||||
|  | ||||
| void AddressableLightDisplay::setup() { | ||||
|   this->addressable_light_buffer_.resize(this->width_ * this->height_, {0, 0, 0, 0}); | ||||
| } | ||||
|  | ||||
| void AddressableLightDisplay::update() { | ||||
|   if (!this->enabled_) | ||||
|     return; | ||||
|  | ||||
|   this->do_update_(); | ||||
|   this->display(); | ||||
| } | ||||
|  | ||||
| void AddressableLightDisplay::display() { | ||||
|   bool dirty = false; | ||||
|   uint8_t old_r, old_g, old_b, old_w; | ||||
|   Color* c; | ||||
|  | ||||
|   for (uint32_t offset = 0; offset < this->addressable_light_buffer_.size(); offset++) { | ||||
|     c = &(this->addressable_light_buffer_[offset]); | ||||
|  | ||||
|     light::ESPColorView pixel = (*this->light_)[offset]; | ||||
|  | ||||
|     // Track the original values for the pixel view. If it has changed updating, then | ||||
|     // we trigger a redraw. Avoiding redraws == avoiding flicker! | ||||
|     old_r = pixel.get_red(); | ||||
|     old_g = pixel.get_green(); | ||||
|     old_b = pixel.get_blue(); | ||||
|     old_w = pixel.get_white(); | ||||
|  | ||||
|     pixel.set_rgbw(c->r, c->g, c->b, c->w); | ||||
|  | ||||
|     // If the actual value of the pixel changed, then schedule a redraw. | ||||
|     if (pixel.get_red() != old_r || pixel.get_green() != old_g || pixel.get_blue() != old_b || | ||||
|         pixel.get_white() != old_w) { | ||||
|       dirty = true; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   if (dirty) { | ||||
|     this->light_->schedule_show(); | ||||
|   } | ||||
| } | ||||
|  | ||||
| void HOT AddressableLightDisplay::draw_absolute_pixel_internal(int x, int y, Color color) { | ||||
|   if (x >= this->get_width_internal() || x < 0 || y >= this->get_height_internal() || y < 0) | ||||
|     return; | ||||
|  | ||||
|   if (this->pixel_mapper_f_.has_value()) { | ||||
|     // Params are passed by reference, so they may be modified in call. | ||||
|     this->addressable_light_buffer_[(*this->pixel_mapper_f_)(x, y)] = color; | ||||
|   } else { | ||||
|     this->addressable_light_buffer_[y * this->get_width_internal() + x] = color; | ||||
|   } | ||||
| } | ||||
| }  // namespace addressable_light | ||||
| }  // namespace esphome | ||||
| @@ -0,0 +1,59 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/core/color.h" | ||||
| #include "esphome/components/display/display_buffer.h" | ||||
| #include "esphome/components/light/addressable_light.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace addressable_light { | ||||
|  | ||||
| class AddressableLightDisplay : public display::DisplayBuffer, public PollingComponent { | ||||
|  public: | ||||
|   light::AddressableLight *get_light() const { return this->light_; } | ||||
|  | ||||
|   void set_width(int32_t width) { width_ = width; } | ||||
|   void set_height(int32_t height) { height_ = height; } | ||||
|   void set_light(light::LightState *state) { | ||||
|     light_state_ = state; | ||||
|     light_ = static_cast<light::AddressableLight *>(state->get_output()); | ||||
|   } | ||||
|   void set_enabled(bool enabled) { | ||||
|     if (light_state_) { | ||||
|       if (enabled_ && !enabled) {  // enabled -> disabled | ||||
|         // - Tell the parent light to refresh, effectively wiping the display. Also | ||||
|         //   restores the previous effect (if any). | ||||
|         light_state_->make_call().set_effect(this->last_effect_).perform(); | ||||
|  | ||||
|       } else if (!enabled_ && enabled) {  // disabled -> enabled | ||||
|         // - Save the current effect. | ||||
|         this->last_effect_ = light_state_->get_effect_name(); | ||||
|         // - Disable any current effect. | ||||
|         light_state_->make_call().set_effect(0).perform(); | ||||
|       } | ||||
|     } | ||||
|     enabled_ = enabled; | ||||
|   } | ||||
|   bool get_enabled() { return enabled_; } | ||||
|  | ||||
|   void set_pixel_mapper(std::function<int(int, int)> &&pixel_mapper_f) { this->pixel_mapper_f_ = pixel_mapper_f; } | ||||
|   void setup() override; | ||||
|   void display(); | ||||
|  | ||||
|  protected: | ||||
|   int get_width_internal() override; | ||||
|   int get_height_internal() override; | ||||
|   void draw_absolute_pixel_internal(int x, int y, Color color) override; | ||||
|   void update() override; | ||||
|  | ||||
|   light::LightState *light_state_; | ||||
|   light::AddressableLight *light_; | ||||
|   bool enabled_{true}; | ||||
|   int32_t width_; | ||||
|   int32_t height_; | ||||
|   std::vector<Color> addressable_light_buffer_; | ||||
|   optional<std::string> last_effect_; | ||||
|   optional<std::function<int(int, int)>> pixel_mapper_f_; | ||||
| }; | ||||
| }  // namespace addressable_light | ||||
| }  // namespace esphome | ||||
							
								
								
									
										63
									
								
								esphome/components/addressable_light/display.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								esphome/components/addressable_light/display.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,63 @@ | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome.components import display, light | ||||
| from esphome.const import ( | ||||
|     CONF_ID, | ||||
|     CONF_LAMBDA, | ||||
|     CONF_PAGES, | ||||
|     CONF_ADDRESSABLE_LIGHT_ID, | ||||
|     CONF_HEIGHT, | ||||
|     CONF_WIDTH, | ||||
|     CONF_UPDATE_INTERVAL, | ||||
|     CONF_PIXEL_MAPPER, | ||||
| ) | ||||
|  | ||||
| CODEOWNERS = ["@justfalter"] | ||||
|  | ||||
| addressable_light_ns = cg.esphome_ns.namespace("addressable_light") | ||||
| AddressableLightDisplay = addressable_light_ns.class_( | ||||
|     "AddressableLightDisplay", display.DisplayBuffer, cg.PollingComponent | ||||
| ) | ||||
|  | ||||
| CONFIG_SCHEMA = cv.All( | ||||
|     display.FULL_DISPLAY_SCHEMA.extend( | ||||
|         { | ||||
|             cv.GenerateID(): cv.declare_id(AddressableLightDisplay), | ||||
|             cv.Required(CONF_ADDRESSABLE_LIGHT_ID): cv.use_id( | ||||
|                 light.AddressableLightState | ||||
|             ), | ||||
|             cv.Required(CONF_WIDTH): cv.positive_int, | ||||
|             cv.Required(CONF_HEIGHT): cv.positive_int, | ||||
|             cv.Optional( | ||||
|                 CONF_UPDATE_INTERVAL, default="16ms" | ||||
|             ): cv.positive_time_period_milliseconds, | ||||
|             cv.Optional(CONF_PIXEL_MAPPER): cv.returning_lambda, | ||||
|         } | ||||
|     ), | ||||
|     cv.has_at_most_one_key(CONF_PAGES, CONF_LAMBDA), | ||||
| ) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
|     var = cg.new_Pvariable(config[CONF_ID]) | ||||
|     wrapped_light = yield cg.get_variable(config[CONF_ADDRESSABLE_LIGHT_ID]) | ||||
|     cg.add(var.set_width(config[CONF_WIDTH])) | ||||
|     cg.add(var.set_height(config[CONF_HEIGHT])) | ||||
|     cg.add(var.set_light(wrapped_light)) | ||||
|  | ||||
|     yield cg.register_component(var, config) | ||||
|     yield display.register_display(var, config) | ||||
|  | ||||
|     if CONF_PIXEL_MAPPER in config: | ||||
|         pixel_mapper_template_ = yield cg.process_lambda( | ||||
|             config[CONF_PIXEL_MAPPER], | ||||
|             [(int, "x"), (int, "y")], | ||||
|             return_type=cg.int_, | ||||
|         ) | ||||
|         cg.add(var.set_pixel_mapper(pixel_mapper_template_)) | ||||
|  | ||||
|     if CONF_LAMBDA in config: | ||||
|         lambda_ = yield cg.process_lambda( | ||||
|             config[CONF_LAMBDA], [(display.DisplayBufferRef, "it")], return_type=cg.void | ||||
|         ) | ||||
|         cg.add(var.set_writer(lambda_)) | ||||
| @@ -2,29 +2,54 @@ import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome.components import sensor, i2c | ||||
| from esphome import pins | ||||
| from esphome.const import CONF_ID, CONF_VOLTAGE, \ | ||||
|     UNIT_VOLT, ICON_FLASH, UNIT_AMPERE, UNIT_WATT | ||||
| from esphome.const import ( | ||||
|     CONF_ID, | ||||
|     CONF_VOLTAGE, | ||||
|     DEVICE_CLASS_CURRENT, | ||||
|     DEVICE_CLASS_POWER, | ||||
|     DEVICE_CLASS_VOLTAGE, | ||||
|     ICON_EMPTY, | ||||
|     UNIT_VOLT, | ||||
|     UNIT_AMPERE, | ||||
|     UNIT_WATT, | ||||
| ) | ||||
|  | ||||
| DEPENDENCIES = ['i2c'] | ||||
| DEPENDENCIES = ["i2c"] | ||||
|  | ||||
| ade7953_ns = cg.esphome_ns.namespace('ade7953') | ||||
| ADE7953 = ade7953_ns.class_('ADE7953', cg.PollingComponent, i2c.I2CDevice) | ||||
| ade7953_ns = cg.esphome_ns.namespace("ade7953") | ||||
| ADE7953 = ade7953_ns.class_("ADE7953", cg.PollingComponent, i2c.I2CDevice) | ||||
|  | ||||
| CONF_IRQ_PIN = 'irq_pin' | ||||
| CONF_CURRENT_A = 'current_a' | ||||
| CONF_CURRENT_B = 'current_b' | ||||
| CONF_ACTIVE_POWER_A = 'active_power_a' | ||||
| CONF_ACTIVE_POWER_B = 'active_power_b' | ||||
| CONF_IRQ_PIN = "irq_pin" | ||||
| CONF_CURRENT_A = "current_a" | ||||
| CONF_CURRENT_B = "current_b" | ||||
| CONF_ACTIVE_POWER_A = "active_power_a" | ||||
| CONF_ACTIVE_POWER_B = "active_power_b" | ||||
|  | ||||
| CONFIG_SCHEMA = cv.Schema({ | ||||
| CONFIG_SCHEMA = ( | ||||
|     cv.Schema( | ||||
|         { | ||||
|             cv.GenerateID(): cv.declare_id(ADE7953), | ||||
|             cv.Optional(CONF_IRQ_PIN): pins.input_pin, | ||||
|     cv.Optional(CONF_VOLTAGE): sensor.sensor_schema(UNIT_VOLT, ICON_FLASH, 1), | ||||
|     cv.Optional(CONF_CURRENT_A): sensor.sensor_schema(UNIT_AMPERE, ICON_FLASH, 2), | ||||
|     cv.Optional(CONF_CURRENT_B): sensor.sensor_schema(UNIT_AMPERE, ICON_FLASH, 2), | ||||
|     cv.Optional(CONF_ACTIVE_POWER_A): sensor.sensor_schema(UNIT_WATT, ICON_FLASH, 1), | ||||
|     cv.Optional(CONF_ACTIVE_POWER_B): sensor.sensor_schema(UNIT_WATT, ICON_FLASH, 1), | ||||
| }).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x38)) | ||||
|             cv.Optional(CONF_VOLTAGE): sensor.sensor_schema( | ||||
|                 UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE | ||||
|             ), | ||||
|             cv.Optional(CONF_CURRENT_A): sensor.sensor_schema( | ||||
|                 UNIT_AMPERE, ICON_EMPTY, 2, DEVICE_CLASS_CURRENT | ||||
|             ), | ||||
|             cv.Optional(CONF_CURRENT_B): sensor.sensor_schema( | ||||
|                 UNIT_AMPERE, ICON_EMPTY, 2, DEVICE_CLASS_CURRENT | ||||
|             ), | ||||
|             cv.Optional(CONF_ACTIVE_POWER_A): sensor.sensor_schema( | ||||
|                 UNIT_WATT, ICON_EMPTY, 1, DEVICE_CLASS_POWER | ||||
|             ), | ||||
|             cv.Optional(CONF_ACTIVE_POWER_B): sensor.sensor_schema( | ||||
|                 UNIT_WATT, ICON_EMPTY, 1, DEVICE_CLASS_POWER | ||||
|             ), | ||||
|         } | ||||
|     ) | ||||
|     .extend(cv.polling_component_schema("60s")) | ||||
|     .extend(i2c.i2c_device_schema(0x38)) | ||||
| ) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
| @@ -35,10 +60,15 @@ def to_code(config): | ||||
|     if CONF_IRQ_PIN in config: | ||||
|         cg.add(var.set_irq_pin(config[CONF_IRQ_PIN])) | ||||
|  | ||||
|     for key in [CONF_VOLTAGE, CONF_CURRENT_A, CONF_CURRENT_B, CONF_ACTIVE_POWER_A, | ||||
|                 CONF_ACTIVE_POWER_B]: | ||||
|     for key in [ | ||||
|         CONF_VOLTAGE, | ||||
|         CONF_CURRENT_A, | ||||
|         CONF_CURRENT_B, | ||||
|         CONF_ACTIVE_POWER_A, | ||||
|         CONF_ACTIVE_POWER_B, | ||||
|     ]: | ||||
|         if key not in config: | ||||
|             continue | ||||
|         conf = config[key] | ||||
|         sens = yield sensor.new_sensor(conf) | ||||
|         cg.add(getattr(var, f'set_{key}_sensor')(sens)) | ||||
|         cg.add(getattr(var, f"set_{key}_sensor")(sens)) | ||||
|   | ||||
| @@ -3,18 +3,24 @@ import esphome.config_validation as cv | ||||
| from esphome.components import i2c | ||||
| from esphome.const import CONF_ID | ||||
|  | ||||
| DEPENDENCIES = ['i2c'] | ||||
| AUTO_LOAD = ['sensor', 'voltage_sampler'] | ||||
| DEPENDENCIES = ["i2c"] | ||||
| AUTO_LOAD = ["sensor", "voltage_sampler"] | ||||
| MULTI_CONF = True | ||||
|  | ||||
| ads1115_ns = cg.esphome_ns.namespace('ads1115') | ||||
| ADS1115Component = ads1115_ns.class_('ADS1115Component', cg.Component, i2c.I2CDevice) | ||||
| ads1115_ns = cg.esphome_ns.namespace("ads1115") | ||||
| ADS1115Component = ads1115_ns.class_("ADS1115Component", cg.Component, i2c.I2CDevice) | ||||
|  | ||||
| CONF_CONTINUOUS_MODE = 'continuous_mode' | ||||
| CONFIG_SCHEMA = cv.Schema({ | ||||
| CONF_CONTINUOUS_MODE = "continuous_mode" | ||||
| CONFIG_SCHEMA = ( | ||||
|     cv.Schema( | ||||
|         { | ||||
|             cv.GenerateID(): cv.declare_id(ADS1115Component), | ||||
|             cv.Optional(CONF_CONTINUOUS_MODE, default=False): cv.boolean, | ||||
| }).extend(cv.COMPONENT_SCHEMA).extend(i2c.i2c_device_schema(None)) | ||||
|         } | ||||
|     ) | ||||
|     .extend(cv.COMPONENT_SCHEMA) | ||||
|     .extend(i2c.i2c_device_schema(None)) | ||||
| ) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
|   | ||||
| @@ -1,53 +1,67 @@ | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome.components import sensor, voltage_sampler | ||||
| from esphome.const import CONF_GAIN, CONF_MULTIPLEXER, ICON_FLASH, UNIT_VOLT, CONF_ID | ||||
| from esphome.const import ( | ||||
|     CONF_GAIN, | ||||
|     CONF_MULTIPLEXER, | ||||
|     DEVICE_CLASS_VOLTAGE, | ||||
|     ICON_EMPTY, | ||||
|     UNIT_VOLT, | ||||
|     CONF_ID, | ||||
| ) | ||||
| from . import ads1115_ns, ADS1115Component | ||||
|  | ||||
| DEPENDENCIES = ['ads1115'] | ||||
| DEPENDENCIES = ["ads1115"] | ||||
|  | ||||
| ADS1115Multiplexer = ads1115_ns.enum('ADS1115Multiplexer') | ||||
| ADS1115Multiplexer = ads1115_ns.enum("ADS1115Multiplexer") | ||||
| MUX = { | ||||
|     'A0_A1': ADS1115Multiplexer.ADS1115_MULTIPLEXER_P0_N1, | ||||
|     'A0_A3': ADS1115Multiplexer.ADS1115_MULTIPLEXER_P0_N3, | ||||
|     'A1_A3': ADS1115Multiplexer.ADS1115_MULTIPLEXER_P1_N3, | ||||
|     'A2_A3': ADS1115Multiplexer.ADS1115_MULTIPLEXER_P2_N3, | ||||
|     'A0_GND': ADS1115Multiplexer.ADS1115_MULTIPLEXER_P0_NG, | ||||
|     'A1_GND': ADS1115Multiplexer.ADS1115_MULTIPLEXER_P1_NG, | ||||
|     'A2_GND': ADS1115Multiplexer.ADS1115_MULTIPLEXER_P2_NG, | ||||
|     'A3_GND': ADS1115Multiplexer.ADS1115_MULTIPLEXER_P3_NG, | ||||
|     "A0_A1": ADS1115Multiplexer.ADS1115_MULTIPLEXER_P0_N1, | ||||
|     "A0_A3": ADS1115Multiplexer.ADS1115_MULTIPLEXER_P0_N3, | ||||
|     "A1_A3": ADS1115Multiplexer.ADS1115_MULTIPLEXER_P1_N3, | ||||
|     "A2_A3": ADS1115Multiplexer.ADS1115_MULTIPLEXER_P2_N3, | ||||
|     "A0_GND": ADS1115Multiplexer.ADS1115_MULTIPLEXER_P0_NG, | ||||
|     "A1_GND": ADS1115Multiplexer.ADS1115_MULTIPLEXER_P1_NG, | ||||
|     "A2_GND": ADS1115Multiplexer.ADS1115_MULTIPLEXER_P2_NG, | ||||
|     "A3_GND": ADS1115Multiplexer.ADS1115_MULTIPLEXER_P3_NG, | ||||
| } | ||||
|  | ||||
| ADS1115Gain = ads1115_ns.enum('ADS1115Gain') | ||||
| ADS1115Gain = ads1115_ns.enum("ADS1115Gain") | ||||
| GAIN = { | ||||
|     '6.144': ADS1115Gain.ADS1115_GAIN_6P144, | ||||
|     '4.096': ADS1115Gain.ADS1115_GAIN_4P096, | ||||
|     '2.048': ADS1115Gain.ADS1115_GAIN_2P048, | ||||
|     '1.024': ADS1115Gain.ADS1115_GAIN_1P024, | ||||
|     '0.512': ADS1115Gain.ADS1115_GAIN_0P512, | ||||
|     '0.256': ADS1115Gain.ADS1115_GAIN_0P256, | ||||
|     "6.144": ADS1115Gain.ADS1115_GAIN_6P144, | ||||
|     "4.096": ADS1115Gain.ADS1115_GAIN_4P096, | ||||
|     "2.048": ADS1115Gain.ADS1115_GAIN_2P048, | ||||
|     "1.024": ADS1115Gain.ADS1115_GAIN_1P024, | ||||
|     "0.512": ADS1115Gain.ADS1115_GAIN_0P512, | ||||
|     "0.256": ADS1115Gain.ADS1115_GAIN_0P256, | ||||
| } | ||||
|  | ||||
|  | ||||
| def validate_gain(value): | ||||
|     if isinstance(value, float): | ||||
|         value = f'{value:0.03f}' | ||||
|         value = f"{value:0.03f}" | ||||
|     elif not isinstance(value, str): | ||||
|         raise cv.Invalid(f'invalid gain "{value}"') | ||||
|  | ||||
|     return cv.enum(GAIN)(value) | ||||
|  | ||||
|  | ||||
| ADS1115Sensor = ads1115_ns.class_('ADS1115Sensor', sensor.Sensor, cg.PollingComponent, | ||||
|                                   voltage_sampler.VoltageSampler) | ||||
| ADS1115Sensor = ads1115_ns.class_( | ||||
|     "ADS1115Sensor", sensor.Sensor, cg.PollingComponent, voltage_sampler.VoltageSampler | ||||
| ) | ||||
|  | ||||
| CONF_ADS1115_ID = 'ads1115_id' | ||||
| CONFIG_SCHEMA = sensor.sensor_schema(UNIT_VOLT, ICON_FLASH, 3).extend({ | ||||
| CONF_ADS1115_ID = "ads1115_id" | ||||
| CONFIG_SCHEMA = ( | ||||
|     sensor.sensor_schema(UNIT_VOLT, ICON_EMPTY, 3, DEVICE_CLASS_VOLTAGE) | ||||
|     .extend( | ||||
|         { | ||||
|             cv.GenerateID(): cv.declare_id(ADS1115Sensor), | ||||
|             cv.GenerateID(CONF_ADS1115_ID): cv.use_id(ADS1115Component), | ||||
|     cv.Required(CONF_MULTIPLEXER): cv.enum(MUX, upper=True, space='_'), | ||||
|             cv.Required(CONF_MULTIPLEXER): cv.enum(MUX, upper=True, space="_"), | ||||
|             cv.Required(CONF_GAIN): validate_gain, | ||||
| }).extend(cv.polling_component_schema('60s')) | ||||
|         } | ||||
|     ) | ||||
|     .extend(cv.polling_component_schema("60s")) | ||||
| ) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
|   | ||||
| @@ -1,19 +1,37 @@ | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome.components import i2c, sensor | ||||
| from esphome.const import CONF_HUMIDITY, CONF_ID, CONF_TEMPERATURE, \ | ||||
|     UNIT_CELSIUS, ICON_THERMOMETER, ICON_WATER_PERCENT, UNIT_PERCENT | ||||
| from esphome.const import ( | ||||
|     CONF_HUMIDITY, | ||||
|     CONF_ID, | ||||
|     CONF_TEMPERATURE, | ||||
|     DEVICE_CLASS_HUMIDITY, | ||||
|     DEVICE_CLASS_TEMPERATURE, | ||||
|     ICON_EMPTY, | ||||
|     UNIT_CELSIUS, | ||||
|     UNIT_PERCENT, | ||||
| ) | ||||
|  | ||||
| DEPENDENCIES = ['i2c'] | ||||
| DEPENDENCIES = ["i2c"] | ||||
|  | ||||
| aht10_ns = cg.esphome_ns.namespace('aht10') | ||||
| AHT10Component = aht10_ns.class_('AHT10Component', cg.PollingComponent, i2c.I2CDevice) | ||||
| aht10_ns = cg.esphome_ns.namespace("aht10") | ||||
| AHT10Component = aht10_ns.class_("AHT10Component", cg.PollingComponent, i2c.I2CDevice) | ||||
|  | ||||
| CONFIG_SCHEMA = cv.Schema({ | ||||
| CONFIG_SCHEMA = ( | ||||
|     cv.Schema( | ||||
|         { | ||||
|             cv.GenerateID(): cv.declare_id(AHT10Component), | ||||
|     cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 2), | ||||
|     cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(UNIT_PERCENT, ICON_WATER_PERCENT, 2), | ||||
| }).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x38)) | ||||
|             cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( | ||||
|                 UNIT_CELSIUS, ICON_EMPTY, 2, DEVICE_CLASS_TEMPERATURE | ||||
|             ), | ||||
|             cv.Optional(CONF_HUMIDITY): sensor.sensor_schema( | ||||
|                 UNIT_PERCENT, ICON_EMPTY, 2, DEVICE_CLASS_HUMIDITY | ||||
|             ), | ||||
|         } | ||||
|     ) | ||||
|     .extend(cv.polling_component_schema("60s")) | ||||
|     .extend(i2c.i2c_device_schema(0x38)) | ||||
| ) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
|   | ||||
| @@ -1,19 +1,39 @@ | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome.components import i2c, sensor | ||||
| from esphome.const import CONF_HUMIDITY, CONF_ID, CONF_TEMPERATURE, \ | ||||
|     UNIT_CELSIUS, ICON_THERMOMETER, ICON_WATER_PERCENT, UNIT_PERCENT | ||||
| from esphome.const import ( | ||||
|     CONF_HUMIDITY, | ||||
|     CONF_ID, | ||||
|     CONF_TEMPERATURE, | ||||
|     DEVICE_CLASS_HUMIDITY, | ||||
|     DEVICE_CLASS_TEMPERATURE, | ||||
|     UNIT_CELSIUS, | ||||
|     ICON_EMPTY, | ||||
|     UNIT_PERCENT, | ||||
| ) | ||||
|  | ||||
| DEPENDENCIES = ['i2c'] | ||||
| DEPENDENCIES = ["i2c"] | ||||
|  | ||||
| am2320_ns = cg.esphome_ns.namespace('am2320') | ||||
| AM2320Component = am2320_ns.class_('AM2320Component', cg.PollingComponent, i2c.I2CDevice) | ||||
| am2320_ns = cg.esphome_ns.namespace("am2320") | ||||
| AM2320Component = am2320_ns.class_( | ||||
|     "AM2320Component", cg.PollingComponent, i2c.I2CDevice | ||||
| ) | ||||
|  | ||||
| CONFIG_SCHEMA = cv.Schema({ | ||||
| CONFIG_SCHEMA = ( | ||||
|     cv.Schema( | ||||
|         { | ||||
|             cv.GenerateID(): cv.declare_id(AM2320Component), | ||||
|     cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 1), | ||||
|     cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(UNIT_PERCENT, ICON_WATER_PERCENT, 1), | ||||
| }).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x5C)) | ||||
|             cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( | ||||
|                 UNIT_CELSIUS, ICON_EMPTY, 1, DEVICE_CLASS_TEMPERATURE | ||||
|             ), | ||||
|             cv.Optional(CONF_HUMIDITY): sensor.sensor_schema( | ||||
|                 UNIT_PERCENT, ICON_EMPTY, 1, DEVICE_CLASS_HUMIDITY | ||||
|             ), | ||||
|         } | ||||
|     ) | ||||
|     .extend(cv.polling_component_schema("60s")) | ||||
|     .extend(i2c.i2c_device_schema(0x5C)) | ||||
| ) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
|   | ||||
| @@ -10,24 +10,28 @@ from esphome.core import CORE, HexInt | ||||
|  | ||||
| _LOGGER = logging.getLogger(__name__) | ||||
|  | ||||
| DEPENDENCIES = ['display'] | ||||
| DEPENDENCIES = ["display"] | ||||
| MULTI_CONF = True | ||||
|  | ||||
| Animation_ = display.display_ns.class_('Animation') | ||||
| Animation_ = display.display_ns.class_("Animation") | ||||
|  | ||||
| CONF_RAW_DATA_ID = 'raw_data_id' | ||||
| CONF_RAW_DATA_ID = "raw_data_id" | ||||
|  | ||||
| ANIMATION_SCHEMA = cv.Schema({ | ||||
| ANIMATION_SCHEMA = cv.Schema( | ||||
|     { | ||||
|         cv.Required(CONF_ID): cv.declare_id(Animation_), | ||||
|         cv.Required(CONF_FILE): cv.file_, | ||||
|         cv.Optional(CONF_RESIZE): cv.dimensions, | ||||
|     cv.Optional(CONF_TYPE, default='BINARY'): cv.enum(espImage.IMAGE_TYPE, upper=True), | ||||
|         cv.Optional(CONF_TYPE, default="BINARY"): cv.enum( | ||||
|             espImage.IMAGE_TYPE, upper=True | ||||
|         ), | ||||
|         cv.GenerateID(CONF_RAW_DATA_ID): cv.declare_id(cg.uint8), | ||||
| }) | ||||
|     } | ||||
| ) | ||||
|  | ||||
| CONFIG_SCHEMA = cv.All(font.validate_pillow_installed, ANIMATION_SCHEMA) | ||||
|  | ||||
| CODEOWNERS = ['@syndlex'] | ||||
| CODEOWNERS = ["@syndlex"] | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
| @@ -46,26 +50,28 @@ def to_code(config): | ||||
|         width, height = image.size | ||||
|     else: | ||||
|         if width > 500 or height > 500: | ||||
|             _LOGGER.warning("The image you requested is very big. Please consider using" | ||||
|                             " the resize parameter.") | ||||
|             _LOGGER.warning( | ||||
|                 "The image you requested is very big. Please consider using" | ||||
|                 " the resize parameter." | ||||
|             ) | ||||
|  | ||||
|     if config[CONF_TYPE] == 'GRAYSCALE': | ||||
|     if config[CONF_TYPE] == "GRAYSCALE": | ||||
|         data = [0 for _ in range(height * width * frames)] | ||||
|         pos = 0 | ||||
|         for frameIndex in range(frames): | ||||
|             image.seek(frameIndex) | ||||
|             frame = image.convert('L', dither=Image.NONE) | ||||
|             frame = image.convert("L", dither=Image.NONE) | ||||
|             pixels = list(frame.getdata()) | ||||
|             for pix in pixels: | ||||
|                 data[pos] = pix | ||||
|                 pos += 1 | ||||
|  | ||||
|     elif config[CONF_TYPE] == 'RGB24': | ||||
|     elif config[CONF_TYPE] == "RGB24": | ||||
|         data = [0 for _ in range(height * width * 3 * frames)] | ||||
|         pos = 0 | ||||
|         for frameIndex in range(frames): | ||||
|             image.seek(frameIndex) | ||||
|             frame = image.convert('RGB') | ||||
|             frame = image.convert("RGB") | ||||
|             pixels = list(frame.getdata()) | ||||
|             for pix in pixels: | ||||
|                 data[pos] = pix[0] | ||||
| @@ -75,12 +81,12 @@ def to_code(config): | ||||
|                 data[pos] = pix[2] | ||||
|                 pos += 1 | ||||
|  | ||||
|     elif config[CONF_TYPE] == 'BINARY': | ||||
|     elif config[CONF_TYPE] == "BINARY": | ||||
|         width8 = ((width + 7) // 8) * 8 | ||||
|         data = [0 for _ in range((height * width8 // 8) * frames)] | ||||
|         for frameIndex in range(frames): | ||||
|             image.seek(frameIndex) | ||||
|             frame = image.convert('1', dither=Image.NONE) | ||||
|             frame = image.convert("1", dither=Image.NONE) | ||||
|             for y in range(height): | ||||
|                 for x in range(width): | ||||
|                     if frame.getpixel((x, y)): | ||||
| @@ -90,5 +96,11 @@ def to_code(config): | ||||
|  | ||||
|     rhs = [HexInt(x) for x in data] | ||||
|     prog_arr = cg.progmem_array(config[CONF_RAW_DATA_ID], rhs) | ||||
|     cg.new_Pvariable(config[CONF_ID], prog_arr, width, height, frames, | ||||
|                      espImage.IMAGE_TYPE[config[CONF_TYPE]]) | ||||
|     cg.new_Pvariable( | ||||
|         config[CONF_ID], | ||||
|         prog_arr, | ||||
|         width, | ||||
|         height, | ||||
|         frames, | ||||
|         espImage.IMAGE_TYPE[config[CONF_TYPE]], | ||||
|     ) | ||||
|   | ||||
| @@ -3,18 +3,24 @@ import esphome.config_validation as cv | ||||
| from esphome.components import i2c | ||||
| from esphome.const import CONF_ID | ||||
|  | ||||
| DEPENDENCIES = ['i2c'] | ||||
| AUTO_LOAD = ['sensor', 'binary_sensor'] | ||||
| DEPENDENCIES = ["i2c"] | ||||
| AUTO_LOAD = ["sensor", "binary_sensor"] | ||||
| MULTI_CONF = True | ||||
|  | ||||
| CONF_APDS9960_ID = 'apds9960_id' | ||||
| CONF_APDS9960_ID = "apds9960_id" | ||||
|  | ||||
| apds9960_nds = cg.esphome_ns.namespace('apds9960') | ||||
| APDS9960 = apds9960_nds.class_('APDS9960', cg.PollingComponent, i2c.I2CDevice) | ||||
| apds9960_nds = cg.esphome_ns.namespace("apds9960") | ||||
| APDS9960 = apds9960_nds.class_("APDS9960", cg.PollingComponent, i2c.I2CDevice) | ||||
|  | ||||
| CONFIG_SCHEMA = cv.Schema({ | ||||
| CONFIG_SCHEMA = ( | ||||
|     cv.Schema( | ||||
|         { | ||||
|             cv.GenerateID(): cv.declare_id(APDS9960), | ||||
| }).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x39)) | ||||
|         } | ||||
|     ) | ||||
|     .extend(cv.polling_component_schema("60s")) | ||||
|     .extend(i2c.i2c_device_schema(0x39)) | ||||
| ) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
|   | ||||
| @@ -4,20 +4,24 @@ from esphome.components import binary_sensor | ||||
| from esphome.const import CONF_DIRECTION, CONF_DEVICE_CLASS, DEVICE_CLASS_MOVING | ||||
| from . import APDS9960, CONF_APDS9960_ID | ||||
|  | ||||
| DEPENDENCIES = ['apds9960'] | ||||
| DEPENDENCIES = ["apds9960"] | ||||
|  | ||||
| DIRECTIONS = { | ||||
|     'UP': 'set_up_direction', | ||||
|     'DOWN': 'set_down_direction', | ||||
|     'LEFT': 'set_left_direction', | ||||
|     'RIGHT': 'set_right_direction', | ||||
|     "UP": "set_up_direction", | ||||
|     "DOWN": "set_down_direction", | ||||
|     "LEFT": "set_left_direction", | ||||
|     "RIGHT": "set_right_direction", | ||||
| } | ||||
|  | ||||
| CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend({ | ||||
| CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend( | ||||
|     { | ||||
|         cv.Required(CONF_DIRECTION): cv.one_of(*DIRECTIONS, upper=True), | ||||
|         cv.GenerateID(CONF_APDS9960_ID): cv.use_id(APDS9960), | ||||
|     cv.Optional(CONF_DEVICE_CLASS, default=DEVICE_CLASS_MOVING): binary_sensor.device_class, | ||||
| }) | ||||
|         cv.Optional( | ||||
|             CONF_DEVICE_CLASS, default=DEVICE_CLASS_MOVING | ||||
|         ): binary_sensor.device_class, | ||||
|     } | ||||
| ) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
|   | ||||
| @@ -1,23 +1,27 @@ | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome.components import sensor | ||||
| from esphome.const import CONF_TYPE, UNIT_PERCENT, ICON_LIGHTBULB | ||||
| from esphome.const import CONF_TYPE, DEVICE_CLASS_EMPTY, UNIT_PERCENT, ICON_LIGHTBULB | ||||
| from . import APDS9960, CONF_APDS9960_ID | ||||
|  | ||||
| DEPENDENCIES = ['apds9960'] | ||||
| DEPENDENCIES = ["apds9960"] | ||||
|  | ||||
| TYPES = { | ||||
|     'CLEAR': 'set_clear_channel', | ||||
|     'RED': 'set_red_channel', | ||||
|     'GREEN': 'set_green_channel', | ||||
|     'BLUE': 'set_blue_channel', | ||||
|     'PROXIMITY': 'set_proximity', | ||||
|     "CLEAR": "set_clear_channel", | ||||
|     "RED": "set_red_channel", | ||||
|     "GREEN": "set_green_channel", | ||||
|     "BLUE": "set_blue_channel", | ||||
|     "PROXIMITY": "set_proximity", | ||||
| } | ||||
|  | ||||
| CONFIG_SCHEMA = sensor.sensor_schema(UNIT_PERCENT, ICON_LIGHTBULB, 1).extend({ | ||||
| CONFIG_SCHEMA = sensor.sensor_schema( | ||||
|     UNIT_PERCENT, ICON_LIGHTBULB, 1, DEVICE_CLASS_EMPTY | ||||
| ).extend( | ||||
|     { | ||||
|         cv.Required(CONF_TYPE): cv.one_of(*TYPES, upper=True), | ||||
|         cv.GenerateID(CONF_APDS9960_ID): cv.use_id(APDS9960), | ||||
| }) | ||||
|     } | ||||
| ) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
|   | ||||
| @@ -2,46 +2,69 @@ import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome import automation | ||||
| from esphome.automation import Condition | ||||
| from esphome.const import CONF_DATA, CONF_DATA_TEMPLATE, CONF_ID, CONF_PASSWORD, CONF_PORT, \ | ||||
|     CONF_REBOOT_TIMEOUT, CONF_SERVICE, CONF_VARIABLES, CONF_SERVICES, CONF_TRIGGER_ID, CONF_EVENT, \ | ||||
|     CONF_TAG | ||||
| from esphome.const import ( | ||||
|     CONF_DATA, | ||||
|     CONF_DATA_TEMPLATE, | ||||
|     CONF_ID, | ||||
|     CONF_PASSWORD, | ||||
|     CONF_PORT, | ||||
|     CONF_REBOOT_TIMEOUT, | ||||
|     CONF_SERVICE, | ||||
|     CONF_VARIABLES, | ||||
|     CONF_SERVICES, | ||||
|     CONF_TRIGGER_ID, | ||||
|     CONF_EVENT, | ||||
|     CONF_TAG, | ||||
| ) | ||||
| from esphome.core import coroutine_with_priority | ||||
|  | ||||
| DEPENDENCIES = ['network'] | ||||
| AUTO_LOAD = ['async_tcp'] | ||||
| CODEOWNERS = ['@OttoWinter'] | ||||
| DEPENDENCIES = ["network"] | ||||
| AUTO_LOAD = ["async_tcp"] | ||||
| CODEOWNERS = ["@OttoWinter"] | ||||
|  | ||||
| api_ns = cg.esphome_ns.namespace('api') | ||||
| APIServer = api_ns.class_('APIServer', cg.Component, cg.Controller) | ||||
| HomeAssistantServiceCallAction = api_ns.class_('HomeAssistantServiceCallAction', automation.Action) | ||||
| APIConnectedCondition = api_ns.class_('APIConnectedCondition', Condition) | ||||
| api_ns = cg.esphome_ns.namespace("api") | ||||
| APIServer = api_ns.class_("APIServer", cg.Component, cg.Controller) | ||||
| HomeAssistantServiceCallAction = api_ns.class_( | ||||
|     "HomeAssistantServiceCallAction", automation.Action | ||||
| ) | ||||
| APIConnectedCondition = api_ns.class_("APIConnectedCondition", Condition) | ||||
|  | ||||
| UserServiceTrigger = api_ns.class_('UserServiceTrigger', automation.Trigger) | ||||
| ListEntitiesServicesArgument = api_ns.class_('ListEntitiesServicesArgument') | ||||
| UserServiceTrigger = api_ns.class_("UserServiceTrigger", automation.Trigger) | ||||
| ListEntitiesServicesArgument = api_ns.class_("ListEntitiesServicesArgument") | ||||
| SERVICE_ARG_NATIVE_TYPES = { | ||||
|     'bool': bool, | ||||
|     'int': cg.int32, | ||||
|     'float': float, | ||||
|     'string': cg.std_string, | ||||
|     'bool[]': cg.std_vector.template(bool), | ||||
|     'int[]': cg.std_vector.template(cg.int32), | ||||
|     'float[]': cg.std_vector.template(float), | ||||
|     'string[]': cg.std_vector.template(cg.std_string), | ||||
|     "bool": bool, | ||||
|     "int": cg.int32, | ||||
|     "float": float, | ||||
|     "string": cg.std_string, | ||||
|     "bool[]": cg.std_vector.template(bool), | ||||
|     "int[]": cg.std_vector.template(cg.int32), | ||||
|     "float[]": cg.std_vector.template(float), | ||||
|     "string[]": cg.std_vector.template(cg.std_string), | ||||
| } | ||||
|  | ||||
| CONFIG_SCHEMA = cv.Schema({ | ||||
| CONFIG_SCHEMA = cv.Schema( | ||||
|     { | ||||
|         cv.GenerateID(): cv.declare_id(APIServer), | ||||
|         cv.Optional(CONF_PORT, default=6053): cv.port, | ||||
|     cv.Optional(CONF_PASSWORD, default=''): cv.string_strict, | ||||
|     cv.Optional(CONF_REBOOT_TIMEOUT, default='15min'): cv.positive_time_period_milliseconds, | ||||
|     cv.Optional(CONF_SERVICES): automation.validate_automation({ | ||||
|         cv.Optional(CONF_PASSWORD, default=""): cv.string_strict, | ||||
|         cv.Optional( | ||||
|             CONF_REBOOT_TIMEOUT, default="15min" | ||||
|         ): cv.positive_time_period_milliseconds, | ||||
|         cv.Optional(CONF_SERVICES): automation.validate_automation( | ||||
|             { | ||||
|                 cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(UserServiceTrigger), | ||||
|                 cv.Required(CONF_SERVICE): cv.valid_name, | ||||
|         cv.Optional(CONF_VARIABLES, default={}): cv.Schema({ | ||||
|             cv.validate_id_name: cv.one_of(*SERVICE_ARG_NATIVE_TYPES, lower=True), | ||||
|         }), | ||||
|     }), | ||||
| }).extend(cv.COMPONENT_SCHEMA) | ||||
|                 cv.Optional(CONF_VARIABLES, default={}): cv.Schema( | ||||
|                     { | ||||
|                         cv.validate_id_name: cv.one_of( | ||||
|                             *SERVICE_ARG_NATIVE_TYPES, lower=True | ||||
|                         ), | ||||
|                     } | ||||
|                 ), | ||||
|             } | ||||
|         ), | ||||
|     } | ||||
| ).extend(cv.COMPONENT_SCHEMA) | ||||
|  | ||||
|  | ||||
| @coroutine_with_priority(40.0) | ||||
| @@ -63,28 +86,36 @@ def to_code(config): | ||||
|             func_args.append((native, name)) | ||||
|             service_arg_names.append(name) | ||||
|         templ = cg.TemplateArguments(*template_args) | ||||
|         trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], templ, | ||||
|                                    conf[CONF_SERVICE], service_arg_names) | ||||
|         trigger = cg.new_Pvariable( | ||||
|             conf[CONF_TRIGGER_ID], templ, conf[CONF_SERVICE], service_arg_names | ||||
|         ) | ||||
|         cg.add(var.register_user_service(trigger)) | ||||
|         yield automation.build_automation(trigger, func_args, conf) | ||||
|  | ||||
|     cg.add_define('USE_API') | ||||
|     cg.add_define("USE_API") | ||||
|     cg.add_global(api_ns.using) | ||||
|  | ||||
|  | ||||
| KEY_VALUE_SCHEMA = cv.Schema({cv.string: cv.templatable(cv.string_strict)}) | ||||
|  | ||||
| HOMEASSISTANT_SERVICE_ACTION_SCHEMA = cv.Schema({ | ||||
| HOMEASSISTANT_SERVICE_ACTION_SCHEMA = cv.Schema( | ||||
|     { | ||||
|         cv.GenerateID(): cv.use_id(APIServer), | ||||
|         cv.Required(CONF_SERVICE): cv.templatable(cv.string), | ||||
|         cv.Optional(CONF_DATA, default={}): KEY_VALUE_SCHEMA, | ||||
|         cv.Optional(CONF_DATA_TEMPLATE, default={}): KEY_VALUE_SCHEMA, | ||||
|     cv.Optional(CONF_VARIABLES, default={}): cv.Schema({cv.string: cv.returning_lambda}), | ||||
| }) | ||||
|         cv.Optional(CONF_VARIABLES, default={}): cv.Schema( | ||||
|             {cv.string: cv.returning_lambda} | ||||
|         ), | ||||
|     } | ||||
| ) | ||||
|  | ||||
|  | ||||
| @automation.register_action('homeassistant.service', HomeAssistantServiceCallAction, | ||||
|                             HOMEASSISTANT_SERVICE_ACTION_SCHEMA) | ||||
| @automation.register_action( | ||||
|     "homeassistant.service", | ||||
|     HomeAssistantServiceCallAction, | ||||
|     HOMEASSISTANT_SERVICE_ACTION_SCHEMA, | ||||
| ) | ||||
| def homeassistant_service_to_code(config, action_id, template_arg, args): | ||||
|     serv = yield cg.get_variable(config[CONF_ID]) | ||||
|     var = cg.new_Pvariable(action_id, template_arg, serv, False) | ||||
| @@ -104,23 +135,30 @@ def homeassistant_service_to_code(config, action_id, template_arg, args): | ||||
|  | ||||
| def validate_homeassistant_event(value): | ||||
|     value = cv.string(value) | ||||
|     if not value.startswith('esphome.'): | ||||
|         raise cv.Invalid("ESPHome can only generate Home Assistant events that begin with " | ||||
|                          "esphome. For example 'esphome.xyz'") | ||||
|     if not value.startswith("esphome."): | ||||
|         raise cv.Invalid( | ||||
|             "ESPHome can only generate Home Assistant events that begin with " | ||||
|             "esphome. For example 'esphome.xyz'" | ||||
|         ) | ||||
|     return value | ||||
|  | ||||
|  | ||||
| HOMEASSISTANT_EVENT_ACTION_SCHEMA = cv.Schema({ | ||||
| HOMEASSISTANT_EVENT_ACTION_SCHEMA = cv.Schema( | ||||
|     { | ||||
|         cv.GenerateID(): cv.use_id(APIServer), | ||||
|         cv.Required(CONF_EVENT): validate_homeassistant_event, | ||||
|         cv.Optional(CONF_DATA, default={}): KEY_VALUE_SCHEMA, | ||||
|         cv.Optional(CONF_DATA_TEMPLATE, default={}): KEY_VALUE_SCHEMA, | ||||
|         cv.Optional(CONF_VARIABLES, default={}): KEY_VALUE_SCHEMA, | ||||
| }) | ||||
|     } | ||||
| ) | ||||
|  | ||||
|  | ||||
| @automation.register_action('homeassistant.event', HomeAssistantServiceCallAction, | ||||
|                             HOMEASSISTANT_EVENT_ACTION_SCHEMA) | ||||
| @automation.register_action( | ||||
|     "homeassistant.event", | ||||
|     HomeAssistantServiceCallAction, | ||||
|     HOMEASSISTANT_EVENT_ACTION_SCHEMA, | ||||
| ) | ||||
| def homeassistant_event_to_code(config, action_id, template_arg, args): | ||||
|     serv = yield cg.get_variable(config[CONF_ID]) | ||||
|     var = cg.new_Pvariable(action_id, template_arg, serv, True) | ||||
| @@ -138,23 +176,29 @@ def homeassistant_event_to_code(config, action_id, template_arg, args): | ||||
|     yield var | ||||
|  | ||||
|  | ||||
| HOMEASSISTANT_TAG_SCANNED_ACTION_SCHEMA = cv.maybe_simple_value({ | ||||
| HOMEASSISTANT_TAG_SCANNED_ACTION_SCHEMA = cv.maybe_simple_value( | ||||
|     { | ||||
|         cv.GenerateID(): cv.use_id(APIServer), | ||||
|         cv.Required(CONF_TAG): cv.templatable(cv.string_strict), | ||||
| }, key=CONF_TAG) | ||||
|     }, | ||||
|     key=CONF_TAG, | ||||
| ) | ||||
|  | ||||
|  | ||||
| @automation.register_action('homeassistant.tag_scanned', HomeAssistantServiceCallAction, | ||||
|                             HOMEASSISTANT_TAG_SCANNED_ACTION_SCHEMA) | ||||
| @automation.register_action( | ||||
|     "homeassistant.tag_scanned", | ||||
|     HomeAssistantServiceCallAction, | ||||
|     HOMEASSISTANT_TAG_SCANNED_ACTION_SCHEMA, | ||||
| ) | ||||
| def homeassistant_tag_scanned_to_code(config, action_id, template_arg, args): | ||||
|     serv = yield cg.get_variable(config[CONF_ID]) | ||||
|     var = cg.new_Pvariable(action_id, template_arg, serv, True) | ||||
|     cg.add(var.set_service('esphome.tag_scanned')) | ||||
|     cg.add(var.set_service("esphome.tag_scanned")) | ||||
|     templ = yield cg.templatable(config[CONF_TAG], args, cg.std_string) | ||||
|     cg.add(var.add_data('tag_id', templ)) | ||||
|     cg.add(var.add_data("tag_id", templ)) | ||||
|     yield var | ||||
|  | ||||
|  | ||||
| @automation.register_condition('api.connected', APIConnectedCondition, {}) | ||||
| @automation.register_condition("api.connected", APIConnectedCondition, {}) | ||||
| def api_connected_to_code(config, condition_id, template_arg, args): | ||||
|     yield cg.new_Pvariable(condition_id, template_arg) | ||||
|   | ||||
| @@ -46,6 +46,7 @@ service APIConnection { | ||||
| // The Home Assistant protocol is structured as a simple | ||||
| // TCP socket with short binary messages encoded in the protocol buffers format | ||||
| // First, a message in this protocol has a specific format: | ||||
| //  * A zero byte. | ||||
| //  * VarInt denoting the size of the message object. (type is not part of this) | ||||
| //  * VarInt denoting the type of message. | ||||
| //  * The message object encoded as a ProtoBuf message | ||||
| @@ -302,6 +303,7 @@ message ListEntitiesFanResponse { | ||||
|   bool supports_oscillation = 5; | ||||
|   bool supports_speed = 6; | ||||
|   bool supports_direction = 7; | ||||
|   int32 supported_speed_count = 8; | ||||
| } | ||||
| enum FanSpeed { | ||||
|   FAN_SPEED_LOW = 0; | ||||
| @@ -321,8 +323,9 @@ message FanStateResponse { | ||||
|   fixed32 key = 1; | ||||
|   bool state = 2; | ||||
|   bool oscillating = 3; | ||||
|   FanSpeed speed = 4; | ||||
|   FanSpeed speed = 4 [deprecated = true]; | ||||
|   FanDirection direction = 5; | ||||
|   int32 speed_level = 6; | ||||
| } | ||||
| message FanCommandRequest { | ||||
|   option (id) = 31; | ||||
| @@ -333,12 +336,14 @@ message FanCommandRequest { | ||||
|   fixed32 key = 1; | ||||
|   bool has_state = 2; | ||||
|   bool state = 3; | ||||
|   bool has_speed = 4; | ||||
|   FanSpeed speed = 5; | ||||
|   bool has_speed = 4 [deprecated = true]; | ||||
|   FanSpeed speed = 5 [deprecated = true]; | ||||
|   bool has_oscillating = 6; | ||||
|   bool oscillating = 7; | ||||
|   bool has_direction = 8; | ||||
|   FanDirection direction = 9; | ||||
|   bool has_speed_level = 10; | ||||
|   int32 speed_level = 11; | ||||
| } | ||||
|  | ||||
| // ==================== LIGHT ==================== | ||||
| @@ -418,6 +423,7 @@ message ListEntitiesSensorResponse { | ||||
|   string unit_of_measurement = 6; | ||||
|   int32 accuracy_decimals = 7; | ||||
|   bool force_update = 8; | ||||
|   string device_class = 9; | ||||
| } | ||||
| message SensorStateResponse { | ||||
|   option (id) = 25; | ||||
|   | ||||
| @@ -9,6 +9,9 @@ | ||||
| #ifdef USE_HOMEASSISTANT_TIME | ||||
| #include "esphome/components/homeassistant/time/homeassistant_time.h" | ||||
| #endif | ||||
| #ifdef USE_FAN | ||||
| #include "esphome/components/fan/fan_helpers.h" | ||||
| #endif | ||||
|  | ||||
| namespace esphome { | ||||
| namespace api { | ||||
| @@ -246,8 +249,10 @@ bool APIConnection::send_fan_state(fan::FanState *fan) { | ||||
|   resp.state = fan->state; | ||||
|   if (traits.supports_oscillation()) | ||||
|     resp.oscillating = fan->oscillating; | ||||
|   if (traits.supports_speed()) | ||||
|     resp.speed = static_cast<enums::FanSpeed>(fan->speed); | ||||
|   if (traits.supports_speed()) { | ||||
|     resp.speed_level = fan->speed; | ||||
|     resp.speed = static_cast<enums::FanSpeed>(fan::speed_level_to_enum(fan->speed, traits.supported_speed_count())); | ||||
|   } | ||||
|   if (traits.supports_direction()) | ||||
|     resp.direction = static_cast<enums::FanDirection>(fan->direction); | ||||
|   return this->send_fan_state_response(resp); | ||||
| @@ -262,6 +267,7 @@ bool APIConnection::send_fan_info(fan::FanState *fan) { | ||||
|   msg.supports_oscillation = traits.supports_oscillation(); | ||||
|   msg.supports_speed = traits.supports_speed(); | ||||
|   msg.supports_direction = traits.supports_direction(); | ||||
|   msg.supported_speed_count = traits.supported_speed_count(); | ||||
|   return this->send_list_entities_fan_response(msg); | ||||
| } | ||||
| void APIConnection::fan_command(const FanCommandRequest &msg) { | ||||
| @@ -269,13 +275,19 @@ void APIConnection::fan_command(const FanCommandRequest &msg) { | ||||
|   if (fan == nullptr) | ||||
|     return; | ||||
|  | ||||
|   auto traits = fan->get_traits(); | ||||
|  | ||||
|   auto call = fan->make_call(); | ||||
|   if (msg.has_state) | ||||
|     call.set_state(msg.state); | ||||
|   if (msg.has_oscillating) | ||||
|     call.set_oscillating(msg.oscillating); | ||||
|   if (msg.has_speed) | ||||
|     call.set_speed(static_cast<fan::FanSpeed>(msg.speed)); | ||||
|   if (msg.has_speed_level) { | ||||
|     // Prefer level | ||||
|     call.set_speed(msg.speed_level); | ||||
|   } else if (msg.has_speed) { | ||||
|     call.set_speed(fan::speed_enum_to_level(static_cast<fan::FanSpeed>(msg.speed), traits.supported_speed_count())); | ||||
|   } | ||||
|   if (msg.has_direction) | ||||
|     call.set_direction(static_cast<fan::FanDirection>(msg.direction)); | ||||
|   call.perform(); | ||||
| @@ -382,6 +394,7 @@ bool APIConnection::send_sensor_info(sensor::Sensor *sensor) { | ||||
|   msg.unit_of_measurement = sensor->get_unit_of_measurement(); | ||||
|   msg.accuracy_decimals = sensor->get_accuracy_decimals(); | ||||
|   msg.force_update = sensor->get_force_update(); | ||||
|   msg.device_class = sensor->get_device_class(); | ||||
|   return this->send_list_entities_sensor_response(msg); | ||||
| } | ||||
| #endif | ||||
| @@ -589,7 +602,7 @@ HelloResponse APIConnection::hello(const HelloRequest &msg) { | ||||
|  | ||||
|   HelloResponse resp; | ||||
|   resp.api_version_major = 1; | ||||
|   resp.api_version_minor = 3; | ||||
|   resp.api_version_minor = 4; | ||||
|   resp.server_info = App.get_name() + " (esphome v" ESPHOME_VERSION ")"; | ||||
|   this->connection_state_ = ConnectionState::CONNECTED; | ||||
|   return resp; | ||||
|   | ||||
| @@ -774,6 +774,10 @@ bool ListEntitiesFanResponse::decode_varint(uint32_t field_id, ProtoVarInt value | ||||
|       this->supports_direction = value.as_bool(); | ||||
|       return true; | ||||
|     } | ||||
|     case 8: { | ||||
|       this->supported_speed_count = value.as_int32(); | ||||
|       return true; | ||||
|     } | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| @@ -814,6 +818,7 @@ void ListEntitiesFanResponse::encode(ProtoWriteBuffer buffer) const { | ||||
|   buffer.encode_bool(5, this->supports_oscillation); | ||||
|   buffer.encode_bool(6, this->supports_speed); | ||||
|   buffer.encode_bool(7, this->supports_direction); | ||||
|   buffer.encode_int32(8, this->supported_speed_count); | ||||
| } | ||||
| void ListEntitiesFanResponse::dump_to(std::string &out) const { | ||||
|   char buffer[64]; | ||||
| @@ -846,6 +851,11 @@ void ListEntitiesFanResponse::dump_to(std::string &out) const { | ||||
|   out.append("  supports_direction: "); | ||||
|   out.append(YESNO(this->supports_direction)); | ||||
|   out.append("\n"); | ||||
|  | ||||
|   out.append("  supported_speed_count: "); | ||||
|   sprintf(buffer, "%d", this->supported_speed_count); | ||||
|   out.append(buffer); | ||||
|   out.append("\n"); | ||||
|   out.append("}"); | ||||
| } | ||||
| bool FanStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { | ||||
| @@ -866,6 +876,10 @@ bool FanStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { | ||||
|       this->direction = value.as_enum<enums::FanDirection>(); | ||||
|       return true; | ||||
|     } | ||||
|     case 6: { | ||||
|       this->speed_level = value.as_int32(); | ||||
|       return true; | ||||
|     } | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| @@ -886,6 +900,7 @@ void FanStateResponse::encode(ProtoWriteBuffer buffer) const { | ||||
|   buffer.encode_bool(3, this->oscillating); | ||||
|   buffer.encode_enum<enums::FanSpeed>(4, this->speed); | ||||
|   buffer.encode_enum<enums::FanDirection>(5, this->direction); | ||||
|   buffer.encode_int32(6, this->speed_level); | ||||
| } | ||||
| void FanStateResponse::dump_to(std::string &out) const { | ||||
|   char buffer[64]; | ||||
| @@ -910,6 +925,11 @@ void FanStateResponse::dump_to(std::string &out) const { | ||||
|   out.append("  direction: "); | ||||
|   out.append(proto_enum_to_string<enums::FanDirection>(this->direction)); | ||||
|   out.append("\n"); | ||||
|  | ||||
|   out.append("  speed_level: "); | ||||
|   sprintf(buffer, "%d", this->speed_level); | ||||
|   out.append(buffer); | ||||
|   out.append("\n"); | ||||
|   out.append("}"); | ||||
| } | ||||
| bool FanCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { | ||||
| @@ -946,6 +966,14 @@ bool FanCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { | ||||
|       this->direction = value.as_enum<enums::FanDirection>(); | ||||
|       return true; | ||||
|     } | ||||
|     case 10: { | ||||
|       this->has_speed_level = value.as_bool(); | ||||
|       return true; | ||||
|     } | ||||
|     case 11: { | ||||
|       this->speed_level = value.as_int32(); | ||||
|       return true; | ||||
|     } | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| @@ -970,6 +998,8 @@ void FanCommandRequest::encode(ProtoWriteBuffer buffer) const { | ||||
|   buffer.encode_bool(7, this->oscillating); | ||||
|   buffer.encode_bool(8, this->has_direction); | ||||
|   buffer.encode_enum<enums::FanDirection>(9, this->direction); | ||||
|   buffer.encode_bool(10, this->has_speed_level); | ||||
|   buffer.encode_int32(11, this->speed_level); | ||||
| } | ||||
| void FanCommandRequest::dump_to(std::string &out) const { | ||||
|   char buffer[64]; | ||||
| @@ -1010,6 +1040,15 @@ void FanCommandRequest::dump_to(std::string &out) const { | ||||
|   out.append("  direction: "); | ||||
|   out.append(proto_enum_to_string<enums::FanDirection>(this->direction)); | ||||
|   out.append("\n"); | ||||
|  | ||||
|   out.append("  has_speed_level: "); | ||||
|   out.append(YESNO(this->has_speed_level)); | ||||
|   out.append("\n"); | ||||
|  | ||||
|   out.append("  speed_level: "); | ||||
|   sprintf(buffer, "%d", this->speed_level); | ||||
|   out.append(buffer); | ||||
|   out.append("\n"); | ||||
|   out.append("}"); | ||||
| } | ||||
| bool ListEntitiesLightResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { | ||||
| @@ -1494,6 +1533,10 @@ bool ListEntitiesSensorResponse::decode_length(uint32_t field_id, ProtoLengthDel | ||||
|       this->unit_of_measurement = value.as_string(); | ||||
|       return true; | ||||
|     } | ||||
|     case 9: { | ||||
|       this->device_class = value.as_string(); | ||||
|       return true; | ||||
|     } | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| @@ -1517,6 +1560,7 @@ void ListEntitiesSensorResponse::encode(ProtoWriteBuffer buffer) const { | ||||
|   buffer.encode_string(6, this->unit_of_measurement); | ||||
|   buffer.encode_int32(7, this->accuracy_decimals); | ||||
|   buffer.encode_bool(8, this->force_update); | ||||
|   buffer.encode_string(9, this->device_class); | ||||
| } | ||||
| void ListEntitiesSensorResponse::dump_to(std::string &out) const { | ||||
|   char buffer[64]; | ||||
| @@ -1554,6 +1598,10 @@ void ListEntitiesSensorResponse::dump_to(std::string &out) const { | ||||
|   out.append("  force_update: "); | ||||
|   out.append(YESNO(this->force_update)); | ||||
|   out.append("\n"); | ||||
|  | ||||
|   out.append("  device_class: "); | ||||
|   out.append("'").append(this->device_class).append("'"); | ||||
|   out.append("\n"); | ||||
|   out.append("}"); | ||||
| } | ||||
| bool SensorStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { | ||||
|   | ||||
| @@ -284,6 +284,7 @@ class ListEntitiesFanResponse : public ProtoMessage { | ||||
|   bool supports_oscillation{false};  // NOLINT | ||||
|   bool supports_speed{false};        // NOLINT | ||||
|   bool supports_direction{false};    // NOLINT | ||||
|   int32_t supported_speed_count{0};  // NOLINT | ||||
|   void encode(ProtoWriteBuffer buffer) const override; | ||||
|   void dump_to(std::string &out) const override; | ||||
|  | ||||
| @@ -299,6 +300,7 @@ class FanStateResponse : public ProtoMessage { | ||||
|   bool oscillating{false};          // NOLINT | ||||
|   enums::FanSpeed speed{};          // NOLINT | ||||
|   enums::FanDirection direction{};  // NOLINT | ||||
|   int32_t speed_level{0};           // NOLINT | ||||
|   void encode(ProtoWriteBuffer buffer) const override; | ||||
|   void dump_to(std::string &out) const override; | ||||
|  | ||||
| @@ -317,6 +319,8 @@ class FanCommandRequest : public ProtoMessage { | ||||
|   bool oscillating{false};          // NOLINT | ||||
|   bool has_direction{false};        // NOLINT | ||||
|   enums::FanDirection direction{};  // NOLINT | ||||
|   bool has_speed_level{false};      // NOLINT | ||||
|   int32_t speed_level{0};           // NOLINT | ||||
|   void encode(ProtoWriteBuffer buffer) const override; | ||||
|   void dump_to(std::string &out) const override; | ||||
|  | ||||
| @@ -403,6 +407,7 @@ class ListEntitiesSensorResponse : public ProtoMessage { | ||||
|   std::string unit_of_measurement{};  // NOLINT | ||||
|   int32_t accuracy_decimals{0};       // NOLINT | ||||
|   bool force_update{false};           // NOLINT | ||||
|   std::string device_class{};         // NOLINT | ||||
|   void encode(ProtoWriteBuffer buffer) const override; | ||||
|   void dump_to(std::string &out) const override; | ||||
|  | ||||
|   | ||||
| @@ -1,33 +1,43 @@ | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome import pins | ||||
| from esphome.const import CONF_INDOOR, CONF_WATCHDOG_THRESHOLD, \ | ||||
|     CONF_NOISE_LEVEL, CONF_SPIKE_REJECTION, CONF_LIGHTNING_THRESHOLD, \ | ||||
|     CONF_MASK_DISTURBER, CONF_DIV_RATIO, CONF_CAPACITANCE | ||||
| from esphome.const import ( | ||||
|     CONF_INDOOR, | ||||
|     CONF_WATCHDOG_THRESHOLD, | ||||
|     CONF_NOISE_LEVEL, | ||||
|     CONF_SPIKE_REJECTION, | ||||
|     CONF_LIGHTNING_THRESHOLD, | ||||
|     CONF_MASK_DISTURBER, | ||||
|     CONF_DIV_RATIO, | ||||
|     CONF_CAPACITANCE, | ||||
| ) | ||||
| from esphome.core import coroutine | ||||
|  | ||||
| AUTO_LOAD = ['sensor', 'binary_sensor'] | ||||
| AUTO_LOAD = ["sensor", "binary_sensor"] | ||||
| MULTI_CONF = True | ||||
|  | ||||
| CONF_AS3935_ID = 'as3935_id' | ||||
| CONF_AS3935_ID = "as3935_id" | ||||
|  | ||||
| as3935_ns = cg.esphome_ns.namespace('as3935') | ||||
| AS3935 = as3935_ns.class_('AS3935Component', cg.Component) | ||||
| as3935_ns = cg.esphome_ns.namespace("as3935") | ||||
| AS3935 = as3935_ns.class_("AS3935Component", cg.Component) | ||||
|  | ||||
| CONF_IRQ_PIN = 'irq_pin' | ||||
| AS3935_SCHEMA = cv.Schema({ | ||||
| CONF_IRQ_PIN = "irq_pin" | ||||
| AS3935_SCHEMA = cv.Schema( | ||||
|     { | ||||
|         cv.GenerateID(): cv.declare_id(AS3935), | ||||
|         cv.Required(CONF_IRQ_PIN): pins.gpio_input_pin_schema, | ||||
|  | ||||
|         cv.Optional(CONF_INDOOR, default=True): cv.boolean, | ||||
|         cv.Optional(CONF_NOISE_LEVEL, default=2): cv.int_range(min=1, max=7), | ||||
|         cv.Optional(CONF_WATCHDOG_THRESHOLD, default=2): cv.int_range(min=1, max=10), | ||||
|         cv.Optional(CONF_SPIKE_REJECTION, default=2): cv.int_range(min=1, max=11), | ||||
|     cv.Optional(CONF_LIGHTNING_THRESHOLD, default=1): cv.one_of(1, 5, 9, 16, int=True), | ||||
|         cv.Optional(CONF_LIGHTNING_THRESHOLD, default=1): cv.one_of( | ||||
|             1, 5, 9, 16, int=True | ||||
|         ), | ||||
|         cv.Optional(CONF_MASK_DISTURBER, default=False): cv.boolean, | ||||
|         cv.Optional(CONF_DIV_RATIO, default=0): cv.one_of(0, 16, 32, 64, 128, int=True), | ||||
|         cv.Optional(CONF_CAPACITANCE, default=0): cv.int_range(min=0, max=15), | ||||
| }) | ||||
|     } | ||||
| ) | ||||
|  | ||||
|  | ||||
| @coroutine | ||||
|   | ||||
| @@ -3,11 +3,13 @@ import esphome.config_validation as cv | ||||
| from esphome.components import binary_sensor | ||||
| from . import AS3935, CONF_AS3935_ID | ||||
|  | ||||
| DEPENDENCIES = ['as3935'] | ||||
| DEPENDENCIES = ["as3935"] | ||||
|  | ||||
| CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend({ | ||||
| CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend( | ||||
|     { | ||||
|         cv.GenerateID(CONF_AS3935_ID): cv.use_id(AS3935), | ||||
| }) | ||||
|     } | ||||
| ) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
|   | ||||
| @@ -1,19 +1,30 @@ | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome.components import sensor | ||||
| from esphome.const import CONF_DISTANCE, CONF_LIGHTNING_ENERGY, \ | ||||
|     UNIT_KILOMETER, UNIT_EMPTY, ICON_SIGNAL_DISTANCE_VARIANT, ICON_FLASH | ||||
| from esphome.const import ( | ||||
|     CONF_DISTANCE, | ||||
|     CONF_LIGHTNING_ENERGY, | ||||
|     DEVICE_CLASS_EMPTY, | ||||
|     UNIT_KILOMETER, | ||||
|     UNIT_EMPTY, | ||||
|     ICON_SIGNAL_DISTANCE_VARIANT, | ||||
|     ICON_FLASH, | ||||
| ) | ||||
| from . import AS3935, CONF_AS3935_ID | ||||
|  | ||||
| DEPENDENCIES = ['as3935'] | ||||
| DEPENDENCIES = ["as3935"] | ||||
|  | ||||
| CONFIG_SCHEMA = cv.Schema({ | ||||
| CONFIG_SCHEMA = cv.Schema( | ||||
|     { | ||||
|         cv.GenerateID(CONF_AS3935_ID): cv.use_id(AS3935), | ||||
|     cv.Optional(CONF_DISTANCE): | ||||
|         sensor.sensor_schema(UNIT_KILOMETER, ICON_SIGNAL_DISTANCE_VARIANT, 1), | ||||
|     cv.Optional(CONF_LIGHTNING_ENERGY): | ||||
|         sensor.sensor_schema(UNIT_EMPTY, ICON_FLASH, 1), | ||||
| }).extend(cv.COMPONENT_SCHEMA) | ||||
|         cv.Optional(CONF_DISTANCE): sensor.sensor_schema( | ||||
|             UNIT_KILOMETER, ICON_SIGNAL_DISTANCE_VARIANT, 1, DEVICE_CLASS_EMPTY | ||||
|         ), | ||||
|         cv.Optional(CONF_LIGHTNING_ENERGY): sensor.sensor_schema( | ||||
|             UNIT_EMPTY, ICON_FLASH, 1, DEVICE_CLASS_EMPTY | ||||
|         ), | ||||
|     } | ||||
| ).extend(cv.COMPONENT_SCHEMA) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
|   | ||||
| @@ -3,15 +3,21 @@ import esphome.config_validation as cv | ||||
| from esphome.components import as3935, i2c | ||||
| from esphome.const import CONF_ID | ||||
|  | ||||
| AUTO_LOAD = ['as3935'] | ||||
| DEPENDENCIES = ['i2c'] | ||||
| AUTO_LOAD = ["as3935"] | ||||
| DEPENDENCIES = ["i2c"] | ||||
|  | ||||
| as3935_i2c_ns = cg.esphome_ns.namespace('as3935_i2c') | ||||
| I2CAS3935 = as3935_i2c_ns.class_('I2CAS3935Component', as3935.AS3935, i2c.I2CDevice) | ||||
| as3935_i2c_ns = cg.esphome_ns.namespace("as3935_i2c") | ||||
| I2CAS3935 = as3935_i2c_ns.class_("I2CAS3935Component", as3935.AS3935, i2c.I2CDevice) | ||||
|  | ||||
| CONFIG_SCHEMA = cv.All(as3935.AS3935_SCHEMA.extend({ | ||||
| CONFIG_SCHEMA = cv.All( | ||||
|     as3935.AS3935_SCHEMA.extend( | ||||
|         { | ||||
|             cv.GenerateID(): cv.declare_id(I2CAS3935), | ||||
| }).extend(cv.COMPONENT_SCHEMA).extend(i2c.i2c_device_schema(0x03))) | ||||
|         } | ||||
|     ) | ||||
|     .extend(cv.COMPONENT_SCHEMA) | ||||
|     .extend(i2c.i2c_device_schema(0x03)) | ||||
| ) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
|   | ||||
| @@ -3,15 +3,21 @@ import esphome.config_validation as cv | ||||
| from esphome.components import as3935, spi | ||||
| from esphome.const import CONF_ID | ||||
|  | ||||
| AUTO_LOAD = ['as3935'] | ||||
| DEPENDENCIES = ['spi'] | ||||
| AUTO_LOAD = ["as3935"] | ||||
| DEPENDENCIES = ["spi"] | ||||
|  | ||||
| as3935_spi_ns = cg.esphome_ns.namespace('as3935_spi') | ||||
| SPIAS3935 = as3935_spi_ns.class_('SPIAS3935Component', as3935.AS3935, spi.SPIDevice) | ||||
| as3935_spi_ns = cg.esphome_ns.namespace("as3935_spi") | ||||
| SPIAS3935 = as3935_spi_ns.class_("SPIAS3935Component", as3935.AS3935, spi.SPIDevice) | ||||
|  | ||||
| CONFIG_SCHEMA = cv.All(as3935.AS3935_SCHEMA.extend({ | ||||
| CONFIG_SCHEMA = cv.All( | ||||
|     as3935.AS3935_SCHEMA.extend( | ||||
|         { | ||||
|             cv.GenerateID(): cv.declare_id(SPIAS3935), | ||||
| }).extend(cv.COMPONENT_SCHEMA).extend(spi.spi_device_schema(cs_pin_required=True))) | ||||
|         } | ||||
|     ) | ||||
|     .extend(cv.COMPONENT_SCHEMA) | ||||
|     .extend(spi.spi_device_schema(cs_pin_required=True)) | ||||
| ) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
|   | ||||
| @@ -2,14 +2,14 @@ | ||||
| import esphome.codegen as cg | ||||
| from esphome.core import CORE, coroutine_with_priority | ||||
|  | ||||
| CODEOWNERS = ['@OttoWinter'] | ||||
| CODEOWNERS = ["@OttoWinter"] | ||||
|  | ||||
|  | ||||
| @coroutine_with_priority(200.0) | ||||
| def to_code(config): | ||||
|     if CORE.is_esp32: | ||||
|         # https://github.com/OttoWinter/AsyncTCP/blob/master/library.json | ||||
|         cg.add_library('AsyncTCP-esphome', '1.1.1') | ||||
|         cg.add_library("AsyncTCP-esphome", "1.1.1") | ||||
|     elif CORE.is_esp8266: | ||||
|         # https://github.com/OttoWinter/ESPAsyncTCP | ||||
|         cg.add_library('ESPAsyncTCP-esphome', '1.2.3') | ||||
|         cg.add_library("ESPAsyncTCP-esphome", "1.2.3") | ||||
|   | ||||
| @@ -1,27 +1,54 @@ | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome.components import sensor, esp32_ble_tracker | ||||
| from esphome.const import CONF_BATTERY_LEVEL, CONF_BATTERY_VOLTAGE, CONF_MAC_ADDRESS, \ | ||||
|     CONF_HUMIDITY, CONF_TEMPERATURE, CONF_ID, UNIT_CELSIUS, UNIT_PERCENT, UNIT_VOLT, \ | ||||
|     ICON_BATTERY, ICON_THERMOMETER, ICON_WATER_PERCENT | ||||
| from esphome.const import ( | ||||
|     CONF_BATTERY_LEVEL, | ||||
|     CONF_BATTERY_VOLTAGE, | ||||
|     CONF_MAC_ADDRESS, | ||||
|     CONF_HUMIDITY, | ||||
|     CONF_TEMPERATURE, | ||||
|     CONF_ID, | ||||
|     DEVICE_CLASS_BATTERY, | ||||
|     DEVICE_CLASS_HUMIDITY, | ||||
|     DEVICE_CLASS_TEMPERATURE, | ||||
|     DEVICE_CLASS_VOLTAGE, | ||||
|     ICON_EMPTY, | ||||
|     UNIT_CELSIUS, | ||||
|     UNIT_PERCENT, | ||||
|     UNIT_VOLT, | ||||
| ) | ||||
|  | ||||
| CODEOWNERS = ['@ahpohl'] | ||||
| CODEOWNERS = ["@ahpohl"] | ||||
|  | ||||
| DEPENDENCIES = ['esp32_ble_tracker'] | ||||
| DEPENDENCIES = ["esp32_ble_tracker"] | ||||
|  | ||||
| atc_mithermometer_ns = cg.esphome_ns.namespace('atc_mithermometer') | ||||
| ATCMiThermometer = atc_mithermometer_ns.class_('ATCMiThermometer', | ||||
|                                                esp32_ble_tracker.ESPBTDeviceListener, | ||||
|                                                cg.Component) | ||||
| atc_mithermometer_ns = cg.esphome_ns.namespace("atc_mithermometer") | ||||
| ATCMiThermometer = atc_mithermometer_ns.class_( | ||||
|     "ATCMiThermometer", esp32_ble_tracker.ESPBTDeviceListener, cg.Component | ||||
| ) | ||||
|  | ||||
| CONFIG_SCHEMA = cv.Schema({ | ||||
| CONFIG_SCHEMA = ( | ||||
|     cv.Schema( | ||||
|         { | ||||
|             cv.GenerateID(): cv.declare_id(ATCMiThermometer), | ||||
|             cv.Required(CONF_MAC_ADDRESS): cv.mac_address, | ||||
|     cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 1), | ||||
|     cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(UNIT_PERCENT, ICON_WATER_PERCENT, 0), | ||||
|     cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema(UNIT_PERCENT, ICON_BATTERY, 0), | ||||
|     cv.Optional(CONF_BATTERY_VOLTAGE): sensor.sensor_schema(UNIT_VOLT, ICON_BATTERY, 3), | ||||
| }).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA).extend(cv.COMPONENT_SCHEMA) | ||||
|             cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( | ||||
|                 UNIT_CELSIUS, ICON_EMPTY, 1, DEVICE_CLASS_TEMPERATURE | ||||
|             ), | ||||
|             cv.Optional(CONF_HUMIDITY): sensor.sensor_schema( | ||||
|                 UNIT_PERCENT, ICON_EMPTY, 0, DEVICE_CLASS_HUMIDITY | ||||
|             ), | ||||
|             cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema( | ||||
|                 UNIT_PERCENT, ICON_EMPTY, 0, DEVICE_CLASS_BATTERY | ||||
|             ), | ||||
|             cv.Optional(CONF_BATTERY_VOLTAGE): sensor.sensor_schema( | ||||
|                 UNIT_VOLT, ICON_EMPTY, 3, DEVICE_CLASS_VOLTAGE | ||||
|             ), | ||||
|         } | ||||
|     ) | ||||
|     .extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA) | ||||
|     .extend(cv.COMPONENT_SCHEMA) | ||||
| ) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
|   | ||||
| @@ -1,61 +1,106 @@ | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome.components import sensor, spi | ||||
| from esphome.const import \ | ||||
|     CONF_ID, CONF_VOLTAGE, CONF_CURRENT, CONF_POWER, CONF_POWER_FACTOR, CONF_FREQUENCY, \ | ||||
|     ICON_FLASH, ICON_LIGHTBULB, ICON_CURRENT_AC, ICON_THERMOMETER, \ | ||||
|     UNIT_HERTZ, UNIT_VOLT, UNIT_AMPERE, UNIT_WATT, UNIT_EMPTY, UNIT_CELSIUS, UNIT_VOLT_AMPS_REACTIVE | ||||
| from esphome.const import ( | ||||
|     CONF_ID, | ||||
|     CONF_VOLTAGE, | ||||
|     CONF_CURRENT, | ||||
|     CONF_POWER, | ||||
|     CONF_POWER_FACTOR, | ||||
|     CONF_FREQUENCY, | ||||
|     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_B = 'phase_b' | ||||
| CONF_PHASE_C = 'phase_c' | ||||
| CONF_PHASE_A = "phase_a" | ||||
| CONF_PHASE_B = "phase_b" | ||||
| CONF_PHASE_C = "phase_c" | ||||
|  | ||||
| CONF_REACTIVE_POWER = 'reactive_power' | ||||
| CONF_LINE_FREQUENCY = 'line_frequency' | ||||
| CONF_CHIP_TEMPERATURE = 'chip_temperature' | ||||
| CONF_GAIN_PGA = 'gain_pga' | ||||
| CONF_CURRENT_PHASES = 'current_phases' | ||||
| CONF_GAIN_VOLTAGE = 'gain_voltage' | ||||
| CONF_GAIN_CT = 'gain_ct' | ||||
| CONF_REACTIVE_POWER = "reactive_power" | ||||
| CONF_LINE_FREQUENCY = "line_frequency" | ||||
| CONF_CHIP_TEMPERATURE = "chip_temperature" | ||||
| CONF_GAIN_PGA = "gain_pga" | ||||
| CONF_CURRENT_PHASES = "current_phases" | ||||
| CONF_GAIN_VOLTAGE = "gain_voltage" | ||||
| CONF_GAIN_CT = "gain_ct" | ||||
| LINE_FREQS = { | ||||
|     '50HZ': 50, | ||||
|     '60HZ': 60, | ||||
|     "50HZ": 50, | ||||
|     "60HZ": 60, | ||||
| } | ||||
| CURRENT_PHASES = { | ||||
|     '2': 2, | ||||
|     '3': 3, | ||||
|     "2": 2, | ||||
|     "3": 3, | ||||
| } | ||||
| PGA_GAINS = { | ||||
|     '1X': 0x0, | ||||
|     '2X': 0x15, | ||||
|     '4X': 0x2A, | ||||
|     "1X": 0x0, | ||||
|     "2X": 0x15, | ||||
|     "4X": 0x2A, | ||||
| } | ||||
|  | ||||
| atm90e32_ns = cg.esphome_ns.namespace('atm90e32') | ||||
| ATM90E32Component = atm90e32_ns.class_('ATM90E32Component', cg.PollingComponent, spi.SPIDevice) | ||||
| atm90e32_ns = cg.esphome_ns.namespace("atm90e32") | ||||
| ATM90E32Component = atm90e32_ns.class_( | ||||
|     "ATM90E32Component", cg.PollingComponent, spi.SPIDevice | ||||
| ) | ||||
|  | ||||
| ATM90E32_PHASE_SCHEMA = cv.Schema({ | ||||
|     cv.Optional(CONF_VOLTAGE): sensor.sensor_schema(UNIT_VOLT, ICON_FLASH, 2), | ||||
|     cv.Optional(CONF_CURRENT): sensor.sensor_schema(UNIT_AMPERE, ICON_CURRENT_AC, 2), | ||||
|     cv.Optional(CONF_POWER): sensor.sensor_schema(UNIT_WATT, ICON_FLASH, 2), | ||||
|     cv.Optional(CONF_REACTIVE_POWER): sensor.sensor_schema(UNIT_VOLT_AMPS_REACTIVE, | ||||
|                                                            ICON_LIGHTBULB, 2), | ||||
|     cv.Optional(CONF_POWER_FACTOR): sensor.sensor_schema(UNIT_EMPTY, ICON_FLASH, 2), | ||||
| ATM90E32_PHASE_SCHEMA = cv.Schema( | ||||
|     { | ||||
|         cv.Optional(CONF_VOLTAGE): sensor.sensor_schema( | ||||
|             UNIT_VOLT, ICON_EMPTY, 2, DEVICE_CLASS_VOLTAGE | ||||
|         ), | ||||
|         cv.Optional(CONF_CURRENT): sensor.sensor_schema( | ||||
|             UNIT_AMPERE, ICON_EMPTY, 2, DEVICE_CLASS_CURRENT | ||||
|         ), | ||||
|         cv.Optional(CONF_POWER): sensor.sensor_schema( | ||||
|             UNIT_WATT, ICON_EMPTY, 2, DEVICE_CLASS_POWER | ||||
|         ), | ||||
|         cv.Optional(CONF_REACTIVE_POWER): sensor.sensor_schema( | ||||
|             UNIT_VOLT_AMPS_REACTIVE, ICON_LIGHTBULB, 2, DEVICE_CLASS_EMPTY | ||||
|         ), | ||||
|         cv.Optional(CONF_POWER_FACTOR): sensor.sensor_schema( | ||||
|             UNIT_EMPTY, ICON_EMPTY, 2, DEVICE_CLASS_POWER_FACTOR | ||||
|         ), | ||||
|         cv.Optional(CONF_GAIN_VOLTAGE, default=7305): cv.uint16_t, | ||||
|         cv.Optional(CONF_GAIN_CT, default=27961): cv.uint16_t, | ||||
| }) | ||||
|     } | ||||
| ) | ||||
|  | ||||
| CONFIG_SCHEMA = cv.Schema({ | ||||
| CONFIG_SCHEMA = ( | ||||
|     cv.Schema( | ||||
|         { | ||||
|             cv.GenerateID(): cv.declare_id(ATM90E32Component), | ||||
|             cv.Optional(CONF_PHASE_A): ATM90E32_PHASE_SCHEMA, | ||||
|             cv.Optional(CONF_PHASE_B): ATM90E32_PHASE_SCHEMA, | ||||
|             cv.Optional(CONF_PHASE_C): ATM90E32_PHASE_SCHEMA, | ||||
|     cv.Optional(CONF_FREQUENCY): sensor.sensor_schema(UNIT_HERTZ, ICON_CURRENT_AC, 1), | ||||
|     cv.Optional(CONF_CHIP_TEMPERATURE): sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 1), | ||||
|             cv.Optional(CONF_FREQUENCY): sensor.sensor_schema( | ||||
|                 UNIT_HERTZ, ICON_CURRENT_AC, 1, DEVICE_CLASS_EMPTY | ||||
|             ), | ||||
|             cv.Optional(CONF_CHIP_TEMPERATURE): sensor.sensor_schema( | ||||
|                 UNIT_CELSIUS, ICON_EMPTY, 1, DEVICE_CLASS_TEMPERATURE | ||||
|             ), | ||||
|             cv.Required(CONF_LINE_FREQUENCY): cv.enum(LINE_FREQS, upper=True), | ||||
|     cv.Optional(CONF_CURRENT_PHASES, default='3'): cv.enum(CURRENT_PHASES, upper=True), | ||||
|     cv.Optional(CONF_GAIN_PGA, default='2X'): cv.enum(PGA_GAINS, upper=True), | ||||
| }).extend(cv.polling_component_schema('60s')).extend(spi.spi_device_schema()) | ||||
|             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): | ||||
|   | ||||
| @@ -1 +1 @@ | ||||
| CODEOWNERS = ['@OttoWinter'] | ||||
| CODEOWNERS = ["@OttoWinter"] | ||||
|   | ||||
| @@ -2,15 +2,24 @@ import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome import automation | ||||
| from esphome.components import climate, sensor | ||||
| from esphome.const import CONF_AWAY_CONFIG, CONF_COOL_ACTION, \ | ||||
|     CONF_DEFAULT_TARGET_TEMPERATURE_HIGH, CONF_DEFAULT_TARGET_TEMPERATURE_LOW, CONF_HEAT_ACTION, \ | ||||
|     CONF_ID, CONF_IDLE_ACTION, CONF_SENSOR | ||||
| from esphome.const import ( | ||||
|     CONF_AWAY_CONFIG, | ||||
|     CONF_COOL_ACTION, | ||||
|     CONF_DEFAULT_TARGET_TEMPERATURE_HIGH, | ||||
|     CONF_DEFAULT_TARGET_TEMPERATURE_LOW, | ||||
|     CONF_HEAT_ACTION, | ||||
|     CONF_ID, | ||||
|     CONF_IDLE_ACTION, | ||||
|     CONF_SENSOR, | ||||
| ) | ||||
|  | ||||
| bang_bang_ns = cg.esphome_ns.namespace('bang_bang') | ||||
| BangBangClimate = bang_bang_ns.class_('BangBangClimate', climate.Climate, cg.Component) | ||||
| BangBangClimateTargetTempConfig = bang_bang_ns.struct('BangBangClimateTargetTempConfig') | ||||
| bang_bang_ns = cg.esphome_ns.namespace("bang_bang") | ||||
| BangBangClimate = bang_bang_ns.class_("BangBangClimate", climate.Climate, cg.Component) | ||||
| BangBangClimateTargetTempConfig = bang_bang_ns.struct("BangBangClimateTargetTempConfig") | ||||
|  | ||||
| CONFIG_SCHEMA = cv.All(climate.CLIMATE_SCHEMA.extend({ | ||||
| CONFIG_SCHEMA = cv.All( | ||||
|     climate.CLIMATE_SCHEMA.extend( | ||||
|         { | ||||
|             cv.GenerateID(): cv.declare_id(BangBangClimate), | ||||
|             cv.Required(CONF_SENSOR): cv.use_id(sensor.Sensor), | ||||
|             cv.Required(CONF_DEFAULT_TARGET_TEMPERATURE_LOW): cv.temperature, | ||||
| @@ -18,11 +27,16 @@ CONFIG_SCHEMA = cv.All(climate.CLIMATE_SCHEMA.extend({ | ||||
|             cv.Required(CONF_IDLE_ACTION): automation.validate_automation(single=True), | ||||
|             cv.Optional(CONF_COOL_ACTION): automation.validate_automation(single=True), | ||||
|             cv.Optional(CONF_HEAT_ACTION): automation.validate_automation(single=True), | ||||
|     cv.Optional(CONF_AWAY_CONFIG): cv.Schema({ | ||||
|             cv.Optional(CONF_AWAY_CONFIG): cv.Schema( | ||||
|                 { | ||||
|                     cv.Required(CONF_DEFAULT_TARGET_TEMPERATURE_LOW): cv.temperature, | ||||
|                     cv.Required(CONF_DEFAULT_TARGET_TEMPERATURE_HIGH): cv.temperature, | ||||
|     }), | ||||
| }).extend(cv.COMPONENT_SCHEMA), cv.has_at_least_one_key(CONF_COOL_ACTION, CONF_HEAT_ACTION)) | ||||
|                 } | ||||
|             ), | ||||
|         } | ||||
|     ).extend(cv.COMPONENT_SCHEMA), | ||||
|     cv.has_at_least_one_key(CONF_COOL_ACTION, CONF_HEAT_ACTION), | ||||
| ) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
| @@ -35,23 +49,29 @@ def to_code(config): | ||||
|  | ||||
|     normal_config = BangBangClimateTargetTempConfig( | ||||
|         config[CONF_DEFAULT_TARGET_TEMPERATURE_LOW], | ||||
|         config[CONF_DEFAULT_TARGET_TEMPERATURE_HIGH] | ||||
|         config[CONF_DEFAULT_TARGET_TEMPERATURE_HIGH], | ||||
|     ) | ||||
|     cg.add(var.set_normal_config(normal_config)) | ||||
|  | ||||
|     yield automation.build_automation(var.get_idle_trigger(), [], config[CONF_IDLE_ACTION]) | ||||
|     yield automation.build_automation( | ||||
|         var.get_idle_trigger(), [], config[CONF_IDLE_ACTION] | ||||
|     ) | ||||
|  | ||||
|     if CONF_COOL_ACTION in config: | ||||
|         yield automation.build_automation(var.get_cool_trigger(), [], config[CONF_COOL_ACTION]) | ||||
|         yield automation.build_automation( | ||||
|             var.get_cool_trigger(), [], config[CONF_COOL_ACTION] | ||||
|         ) | ||||
|         cg.add(var.set_supports_cool(True)) | ||||
|     if CONF_HEAT_ACTION in config: | ||||
|         yield automation.build_automation(var.get_heat_trigger(), [], config[CONF_HEAT_ACTION]) | ||||
|         yield automation.build_automation( | ||||
|             var.get_heat_trigger(), [], config[CONF_HEAT_ACTION] | ||||
|         ) | ||||
|         cg.add(var.set_supports_heat(True)) | ||||
|  | ||||
|     if CONF_AWAY_CONFIG in config: | ||||
|         away = config[CONF_AWAY_CONFIG] | ||||
|         away_config = BangBangClimateTargetTempConfig( | ||||
|             away[CONF_DEFAULT_TARGET_TEMPERATURE_LOW], | ||||
|             away[CONF_DEFAULT_TARGET_TEMPERATURE_HIGH] | ||||
|             away[CONF_DEFAULT_TARGET_TEMPERATURE_HIGH], | ||||
|         ) | ||||
|         cg.add(var.set_away_config(away_config)) | ||||
|   | ||||
| @@ -1,26 +1,45 @@ | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome.components import i2c, sensor | ||||
| from esphome.const import CONF_ID, CONF_RESOLUTION, UNIT_LUX, ICON_BRIGHTNESS_5 | ||||
| from esphome.const import ( | ||||
|     CONF_ID, | ||||
|     CONF_RESOLUTION, | ||||
|     DEVICE_CLASS_ILLUMINANCE, | ||||
|     ICON_EMPTY, | ||||
|     UNIT_LUX, | ||||
| ) | ||||
|  | ||||
| DEPENDENCIES = ['i2c'] | ||||
| DEPENDENCIES = ["i2c"] | ||||
|  | ||||
| bh1750_ns = cg.esphome_ns.namespace('bh1750') | ||||
| BH1750Resolution = bh1750_ns.enum('BH1750Resolution') | ||||
| bh1750_ns = cg.esphome_ns.namespace("bh1750") | ||||
| BH1750Resolution = bh1750_ns.enum("BH1750Resolution") | ||||
| BH1750_RESOLUTIONS = { | ||||
|     4.0: BH1750Resolution.BH1750_RESOLUTION_4P0_LX, | ||||
|     1.0: BH1750Resolution.BH1750_RESOLUTION_1P0_LX, | ||||
|     0.5: BH1750Resolution.BH1750_RESOLUTION_0P5_LX, | ||||
| } | ||||
|  | ||||
| BH1750Sensor = bh1750_ns.class_('BH1750Sensor', sensor.Sensor, cg.PollingComponent, i2c.I2CDevice) | ||||
| BH1750Sensor = bh1750_ns.class_( | ||||
|     "BH1750Sensor", sensor.Sensor, cg.PollingComponent, i2c.I2CDevice | ||||
| ) | ||||
|  | ||||
| CONF_MEASUREMENT_TIME = 'measurement_time' | ||||
| CONFIG_SCHEMA = sensor.sensor_schema(UNIT_LUX, ICON_BRIGHTNESS_5, 1).extend({ | ||||
| CONF_MEASUREMENT_TIME = "measurement_time" | ||||
| CONFIG_SCHEMA = ( | ||||
|     sensor.sensor_schema(UNIT_LUX, ICON_EMPTY, 1, DEVICE_CLASS_ILLUMINANCE) | ||||
|     .extend( | ||||
|         { | ||||
|             cv.GenerateID(): cv.declare_id(BH1750Sensor), | ||||
|     cv.Optional(CONF_RESOLUTION, default=0.5): cv.enum(BH1750_RESOLUTIONS, float=True), | ||||
|     cv.Optional(CONF_MEASUREMENT_TIME, default=69): cv.int_range(min=31, max=254), | ||||
| }).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x23)) | ||||
|             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): | ||||
|   | ||||
| @@ -1,3 +1,3 @@ | ||||
| import esphome.codegen as cg | ||||
|  | ||||
| binary_ns = cg.esphome_ns.namespace('binary') | ||||
| binary_ns = cg.esphome_ns.namespace("binary") | ||||
|   | ||||
| @@ -1,18 +1,24 @@ | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome.components import fan, output | ||||
| from esphome.const import CONF_DIRECTION_OUTPUT, CONF_OSCILLATION_OUTPUT, \ | ||||
|     CONF_OUTPUT, CONF_OUTPUT_ID | ||||
| from esphome.const import ( | ||||
|     CONF_DIRECTION_OUTPUT, | ||||
|     CONF_OSCILLATION_OUTPUT, | ||||
|     CONF_OUTPUT, | ||||
|     CONF_OUTPUT_ID, | ||||
| ) | ||||
| from .. import binary_ns | ||||
|  | ||||
| BinaryFan = binary_ns.class_('BinaryFan', cg.Component) | ||||
| BinaryFan = binary_ns.class_("BinaryFan", cg.Component) | ||||
|  | ||||
| CONFIG_SCHEMA = fan.FAN_SCHEMA.extend({ | ||||
| CONFIG_SCHEMA = fan.FAN_SCHEMA.extend( | ||||
|     { | ||||
|         cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(BinaryFan), | ||||
|         cv.Required(CONF_OUTPUT): cv.use_id(output.BinaryOutput), | ||||
|         cv.Optional(CONF_DIRECTION_OUTPUT): cv.use_id(output.BinaryOutput), | ||||
|         cv.Optional(CONF_OSCILLATION_OUTPUT): cv.use_id(output.BinaryOutput), | ||||
| }).extend(cv.COMPONENT_SCHEMA) | ||||
|     } | ||||
| ).extend(cv.COMPONENT_SCHEMA) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
|   | ||||
| @@ -16,7 +16,7 @@ void binary::BinaryFan::dump_config() { | ||||
|   } | ||||
| } | ||||
| void BinaryFan::setup() { | ||||
|   auto traits = fan::FanTraits(this->oscillating_ != nullptr, false, this->direction_ != nullptr); | ||||
|   auto traits = fan::FanTraits(this->oscillating_ != nullptr, false, this->direction_ != nullptr, 0); | ||||
|   this->fan_->set_traits(traits); | ||||
|   this->fan_->add_on_state_callback([this]() { this->next_update_ = true; }); | ||||
| } | ||||
|   | ||||
| @@ -4,12 +4,14 @@ from esphome.components import light, output | ||||
| from esphome.const import CONF_OUTPUT_ID, CONF_OUTPUT | ||||
| from .. import binary_ns | ||||
|  | ||||
| BinaryLightOutput = binary_ns.class_('BinaryLightOutput', light.LightOutput) | ||||
| BinaryLightOutput = binary_ns.class_("BinaryLightOutput", light.LightOutput) | ||||
|  | ||||
| CONFIG_SCHEMA = light.BINARY_LIGHT_SCHEMA.extend({ | ||||
| CONFIG_SCHEMA = light.BINARY_LIGHT_SCHEMA.extend( | ||||
|     { | ||||
|         cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(BinaryLightOutput), | ||||
|         cv.Required(CONF_OUTPUT): cv.use_id(output.BinaryOutput), | ||||
| }) | ||||
|     } | ||||
| ) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
|   | ||||
| @@ -3,131 +3,214 @@ import esphome.config_validation as cv | ||||
| from esphome import automation, core | ||||
| from esphome.automation import Condition, maybe_simple_id | ||||
| from esphome.components import mqtt | ||||
| from esphome.const import CONF_DEVICE_CLASS, CONF_FILTERS, \ | ||||
|     CONF_ID, CONF_INTERNAL, CONF_INVALID_COOLDOWN, CONF_INVERTED, \ | ||||
|     CONF_MAX_LENGTH, CONF_MIN_LENGTH, CONF_ON_CLICK, \ | ||||
|     CONF_ON_DOUBLE_CLICK, CONF_ON_MULTI_CLICK, CONF_ON_PRESS, CONF_ON_RELEASE, CONF_ON_STATE, \ | ||||
|     CONF_STATE, CONF_TIMING, CONF_TRIGGER_ID, CONF_FOR, CONF_NAME, CONF_MQTT_ID | ||||
| from esphome.const import ( | ||||
|     CONF_DEVICE_CLASS, | ||||
|     CONF_FILTERS, | ||||
|     CONF_ID, | ||||
|     CONF_INTERNAL, | ||||
|     CONF_INVALID_COOLDOWN, | ||||
|     CONF_INVERTED, | ||||
|     CONF_MAX_LENGTH, | ||||
|     CONF_MIN_LENGTH, | ||||
|     CONF_ON_CLICK, | ||||
|     CONF_ON_DOUBLE_CLICK, | ||||
|     CONF_ON_MULTI_CLICK, | ||||
|     CONF_ON_PRESS, | ||||
|     CONF_ON_RELEASE, | ||||
|     CONF_ON_STATE, | ||||
|     CONF_STATE, | ||||
|     CONF_TIMING, | ||||
|     CONF_TRIGGER_ID, | ||||
|     CONF_FOR, | ||||
|     CONF_NAME, | ||||
|     CONF_MQTT_ID, | ||||
|     DEVICE_CLASS_EMPTY, | ||||
|     DEVICE_CLASS_BATTERY, | ||||
|     DEVICE_CLASS_BATTERY_CHARGING, | ||||
|     DEVICE_CLASS_COLD, | ||||
|     DEVICE_CLASS_CONNECTIVITY, | ||||
|     DEVICE_CLASS_DOOR, | ||||
|     DEVICE_CLASS_GARAGE_DOOR, | ||||
|     DEVICE_CLASS_GAS, | ||||
|     DEVICE_CLASS_HEAT, | ||||
|     DEVICE_CLASS_LIGHT, | ||||
|     DEVICE_CLASS_LOCK, | ||||
|     DEVICE_CLASS_MOISTURE, | ||||
|     DEVICE_CLASS_MOTION, | ||||
|     DEVICE_CLASS_MOVING, | ||||
|     DEVICE_CLASS_OCCUPANCY, | ||||
|     DEVICE_CLASS_OPENING, | ||||
|     DEVICE_CLASS_PLUG, | ||||
|     DEVICE_CLASS_POWER, | ||||
|     DEVICE_CLASS_PRESENCE, | ||||
|     DEVICE_CLASS_PROBLEM, | ||||
|     DEVICE_CLASS_SAFETY, | ||||
|     DEVICE_CLASS_SMOKE, | ||||
|     DEVICE_CLASS_SOUND, | ||||
|     DEVICE_CLASS_VIBRATION, | ||||
|     DEVICE_CLASS_WINDOW, | ||||
| ) | ||||
| from esphome.core import CORE, coroutine, coroutine_with_priority | ||||
| from esphome.util import Registry | ||||
|  | ||||
| CODEOWNERS = ['@esphome/core'] | ||||
| CODEOWNERS = ["@esphome/core"] | ||||
| DEVICE_CLASSES = [ | ||||
|     '', 'battery', 'cold', 'connectivity', 'door', 'garage_door', 'gas', | ||||
|     'heat', 'light', 'lock', 'moisture', 'motion', 'moving', 'occupancy', | ||||
|     'opening', 'plug', 'power', 'presence', 'problem', 'safety', 'smoke', | ||||
|     'sound', 'vibration', 'window' | ||||
|     DEVICE_CLASS_EMPTY, | ||||
|     DEVICE_CLASS_BATTERY, | ||||
|     DEVICE_CLASS_BATTERY_CHARGING, | ||||
|     DEVICE_CLASS_COLD, | ||||
|     DEVICE_CLASS_CONNECTIVITY, | ||||
|     DEVICE_CLASS_DOOR, | ||||
|     DEVICE_CLASS_GARAGE_DOOR, | ||||
|     DEVICE_CLASS_GAS, | ||||
|     DEVICE_CLASS_HEAT, | ||||
|     DEVICE_CLASS_LIGHT, | ||||
|     DEVICE_CLASS_LOCK, | ||||
|     DEVICE_CLASS_MOISTURE, | ||||
|     DEVICE_CLASS_MOTION, | ||||
|     DEVICE_CLASS_MOVING, | ||||
|     DEVICE_CLASS_OCCUPANCY, | ||||
|     DEVICE_CLASS_OPENING, | ||||
|     DEVICE_CLASS_PLUG, | ||||
|     DEVICE_CLASS_POWER, | ||||
|     DEVICE_CLASS_PRESENCE, | ||||
|     DEVICE_CLASS_PROBLEM, | ||||
|     DEVICE_CLASS_SAFETY, | ||||
|     DEVICE_CLASS_SMOKE, | ||||
|     DEVICE_CLASS_SOUND, | ||||
|     DEVICE_CLASS_VIBRATION, | ||||
|     DEVICE_CLASS_WINDOW, | ||||
| ] | ||||
|  | ||||
| IS_PLATFORM_COMPONENT = True | ||||
|  | ||||
| binary_sensor_ns = cg.esphome_ns.namespace('binary_sensor') | ||||
| BinarySensor = binary_sensor_ns.class_('BinarySensor', cg.Nameable) | ||||
| BinarySensorInitiallyOff = binary_sensor_ns.class_('BinarySensorInitiallyOff', BinarySensor) | ||||
| BinarySensorPtr = BinarySensor.operator('ptr') | ||||
| binary_sensor_ns = cg.esphome_ns.namespace("binary_sensor") | ||||
| BinarySensor = binary_sensor_ns.class_("BinarySensor", cg.Nameable) | ||||
| BinarySensorInitiallyOff = binary_sensor_ns.class_( | ||||
|     "BinarySensorInitiallyOff", BinarySensor | ||||
| ) | ||||
| BinarySensorPtr = BinarySensor.operator("ptr") | ||||
|  | ||||
| # Triggers | ||||
| PressTrigger = binary_sensor_ns.class_('PressTrigger', automation.Trigger.template()) | ||||
| ReleaseTrigger = binary_sensor_ns.class_('ReleaseTrigger', automation.Trigger.template()) | ||||
| ClickTrigger = binary_sensor_ns.class_('ClickTrigger', automation.Trigger.template()) | ||||
| DoubleClickTrigger = binary_sensor_ns.class_('DoubleClickTrigger', automation.Trigger.template()) | ||||
| MultiClickTrigger = binary_sensor_ns.class_('MultiClickTrigger', automation.Trigger.template(), | ||||
|                                             cg.Component) | ||||
| MultiClickTriggerEvent = binary_sensor_ns.struct('MultiClickTriggerEvent') | ||||
| StateTrigger = binary_sensor_ns.class_('StateTrigger', automation.Trigger.template(bool)) | ||||
| BinarySensorPublishAction = binary_sensor_ns.class_('BinarySensorPublishAction', automation.Action) | ||||
| PressTrigger = binary_sensor_ns.class_("PressTrigger", automation.Trigger.template()) | ||||
| ReleaseTrigger = binary_sensor_ns.class_( | ||||
|     "ReleaseTrigger", automation.Trigger.template() | ||||
| ) | ||||
| ClickTrigger = binary_sensor_ns.class_("ClickTrigger", automation.Trigger.template()) | ||||
| DoubleClickTrigger = binary_sensor_ns.class_( | ||||
|     "DoubleClickTrigger", automation.Trigger.template() | ||||
| ) | ||||
| MultiClickTrigger = binary_sensor_ns.class_( | ||||
|     "MultiClickTrigger", automation.Trigger.template(), cg.Component | ||||
| ) | ||||
| MultiClickTriggerEvent = binary_sensor_ns.struct("MultiClickTriggerEvent") | ||||
| StateTrigger = binary_sensor_ns.class_( | ||||
|     "StateTrigger", automation.Trigger.template(bool) | ||||
| ) | ||||
| BinarySensorPublishAction = binary_sensor_ns.class_( | ||||
|     "BinarySensorPublishAction", automation.Action | ||||
| ) | ||||
|  | ||||
| # Condition | ||||
| BinarySensorCondition = binary_sensor_ns.class_('BinarySensorCondition', Condition) | ||||
| BinarySensorCondition = binary_sensor_ns.class_("BinarySensorCondition", Condition) | ||||
|  | ||||
| # Filters | ||||
| Filter = binary_sensor_ns.class_('Filter') | ||||
| DelayedOnOffFilter = binary_sensor_ns.class_('DelayedOnOffFilter', Filter, cg.Component) | ||||
| DelayedOnFilter = binary_sensor_ns.class_('DelayedOnFilter', Filter, cg.Component) | ||||
| DelayedOffFilter = binary_sensor_ns.class_('DelayedOffFilter', Filter, cg.Component) | ||||
| InvertFilter = binary_sensor_ns.class_('InvertFilter', Filter) | ||||
| LambdaFilter = binary_sensor_ns.class_('LambdaFilter', Filter) | ||||
| Filter = binary_sensor_ns.class_("Filter") | ||||
| DelayedOnOffFilter = binary_sensor_ns.class_("DelayedOnOffFilter", Filter, cg.Component) | ||||
| DelayedOnFilter = binary_sensor_ns.class_("DelayedOnFilter", Filter, cg.Component) | ||||
| DelayedOffFilter = binary_sensor_ns.class_("DelayedOffFilter", Filter, cg.Component) | ||||
| InvertFilter = binary_sensor_ns.class_("InvertFilter", Filter) | ||||
| LambdaFilter = binary_sensor_ns.class_("LambdaFilter", Filter) | ||||
|  | ||||
| FILTER_REGISTRY = Registry() | ||||
| validate_filters = cv.validate_registry('filter', FILTER_REGISTRY) | ||||
| validate_filters = cv.validate_registry("filter", FILTER_REGISTRY) | ||||
|  | ||||
|  | ||||
| @FILTER_REGISTRY.register('invert', InvertFilter, {}) | ||||
| @FILTER_REGISTRY.register("invert", InvertFilter, {}) | ||||
| def invert_filter_to_code(config, filter_id): | ||||
|     yield cg.new_Pvariable(filter_id) | ||||
|  | ||||
|  | ||||
| @FILTER_REGISTRY.register('delayed_on_off', DelayedOnOffFilter, | ||||
|                           cv.positive_time_period_milliseconds) | ||||
| @FILTER_REGISTRY.register( | ||||
|     "delayed_on_off", DelayedOnOffFilter, cv.positive_time_period_milliseconds | ||||
| ) | ||||
| def delayed_on_off_filter_to_code(config, filter_id): | ||||
|     var = cg.new_Pvariable(filter_id, config) | ||||
|     yield cg.register_component(var, {}) | ||||
|     yield var | ||||
|  | ||||
|  | ||||
| @FILTER_REGISTRY.register('delayed_on', DelayedOnFilter, | ||||
|                           cv.positive_time_period_milliseconds) | ||||
| @FILTER_REGISTRY.register( | ||||
|     "delayed_on", DelayedOnFilter, cv.positive_time_period_milliseconds | ||||
| ) | ||||
| def delayed_on_filter_to_code(config, filter_id): | ||||
|     var = cg.new_Pvariable(filter_id, config) | ||||
|     yield cg.register_component(var, {}) | ||||
|     yield var | ||||
|  | ||||
|  | ||||
| @FILTER_REGISTRY.register('delayed_off', DelayedOffFilter, cv.positive_time_period_milliseconds) | ||||
| @FILTER_REGISTRY.register( | ||||
|     "delayed_off", DelayedOffFilter, cv.positive_time_period_milliseconds | ||||
| ) | ||||
| def delayed_off_filter_to_code(config, filter_id): | ||||
|     var = cg.new_Pvariable(filter_id, config) | ||||
|     yield cg.register_component(var, {}) | ||||
|     yield var | ||||
|  | ||||
|  | ||||
| @FILTER_REGISTRY.register('lambda', LambdaFilter, cv.returning_lambda) | ||||
| @FILTER_REGISTRY.register("lambda", LambdaFilter, cv.returning_lambda) | ||||
| def lambda_filter_to_code(config, filter_id): | ||||
|     lambda_ = yield cg.process_lambda(config, [(bool, 'x')], return_type=cg.optional.template(bool)) | ||||
|     lambda_ = yield cg.process_lambda( | ||||
|         config, [(bool, "x")], return_type=cg.optional.template(bool) | ||||
|     ) | ||||
|     yield cg.new_Pvariable(filter_id, lambda_) | ||||
|  | ||||
|  | ||||
| MULTI_CLICK_TIMING_SCHEMA = cv.Schema({ | ||||
| MULTI_CLICK_TIMING_SCHEMA = cv.Schema( | ||||
|     { | ||||
|         cv.Optional(CONF_STATE): cv.boolean, | ||||
|         cv.Optional(CONF_MIN_LENGTH): cv.positive_time_period_milliseconds, | ||||
|         cv.Optional(CONF_MAX_LENGTH): cv.positive_time_period_milliseconds, | ||||
| }) | ||||
|     } | ||||
| ) | ||||
|  | ||||
|  | ||||
| def parse_multi_click_timing_str(value): | ||||
|     if not isinstance(value, str): | ||||
|         return value | ||||
|  | ||||
|     parts = value.lower().split(' ') | ||||
|     parts = value.lower().split(" ") | ||||
|     if len(parts) != 5: | ||||
|         raise cv.Invalid("Multi click timing grammar consists of exactly 5 words, not {}" | ||||
|                          "".format(len(parts))) | ||||
|         raise cv.Invalid( | ||||
|             "Multi click timing grammar consists of exactly 5 words, not {}" | ||||
|             "".format(len(parts)) | ||||
|         ) | ||||
|     try: | ||||
|         state = cv.boolean(parts[0]) | ||||
|     except cv.Invalid: | ||||
|         # pylint: disable=raise-missing-from | ||||
|         raise cv.Invalid("First word must either be ON or OFF, not {}".format(parts[0])) | ||||
|  | ||||
|     if parts[1] != 'for': | ||||
|     if parts[1] != "for": | ||||
|         raise cv.Invalid("Second word must be 'for', got {}".format(parts[1])) | ||||
|  | ||||
|     if parts[2] == 'at': | ||||
|         if parts[3] == 'least': | ||||
|     if parts[2] == "at": | ||||
|         if parts[3] == "least": | ||||
|             key = CONF_MIN_LENGTH | ||||
|         elif parts[3] == 'most': | ||||
|         elif parts[3] == "most": | ||||
|             key = CONF_MAX_LENGTH | ||||
|         else: | ||||
|             raise cv.Invalid("Third word after at must either be 'least' or 'most', got {}" | ||||
|                              "".format(parts[3])) | ||||
|             raise cv.Invalid( | ||||
|                 "Third word after at must either be 'least' or 'most', got {}" | ||||
|                 "".format(parts[3]) | ||||
|             ) | ||||
|         try: | ||||
|             length = cv.positive_time_period_milliseconds(parts[4]) | ||||
|         except cv.Invalid as err: | ||||
|             raise cv.Invalid(f"Multi Click Grammar Parsing length failed: {err}") | ||||
|         return { | ||||
|             CONF_STATE: state, | ||||
|             key: str(length) | ||||
|         } | ||||
|         return {CONF_STATE: state, key: str(length)} | ||||
|  | ||||
|     if parts[3] != 'to': | ||||
|     if parts[3] != "to": | ||||
|         raise cv.Invalid("Multi click grammar: 4th word must be 'to'") | ||||
|  | ||||
|     try: | ||||
| @@ -143,7 +226,7 @@ def parse_multi_click_timing_str(value): | ||||
|     return { | ||||
|         CONF_STATE: state, | ||||
|         CONF_MIN_LENGTH: str(min_length), | ||||
|         CONF_MAX_LENGTH: str(max_length) | ||||
|         CONF_MAX_LENGTH: str(max_length), | ||||
|     } | ||||
|  | ||||
|  | ||||
| @@ -163,11 +246,15 @@ def validate_multi_click_timing(value): | ||||
|  | ||||
|         new_state = v_.get(CONF_STATE, not state) | ||||
|         if new_state == state: | ||||
|             raise cv.Invalid("Timings must have alternating state. Indices {} and {} have " | ||||
|                              "the same state {}".format(i, i + 1, state)) | ||||
|             raise cv.Invalid( | ||||
|                 "Timings must have alternating state. Indices {} and {} have " | ||||
|                 "the same state {}".format(i, i + 1, state) | ||||
|             ) | ||||
|         if max_length is not None and max_length < min_length: | ||||
|             raise cv.Invalid("Max length ({}) must be larger than min length ({})." | ||||
|                              "".format(max_length, min_length)) | ||||
|             raise cv.Invalid( | ||||
|                 "Max length ({}) must be larger than min length ({})." | ||||
|                 "".format(max_length, min_length) | ||||
|             ) | ||||
|  | ||||
|         state = new_state | ||||
|         tim = { | ||||
| @@ -180,46 +267,71 @@ def validate_multi_click_timing(value): | ||||
|     return timings | ||||
|  | ||||
|  | ||||
| device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space='_') | ||||
| device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space="_") | ||||
|  | ||||
| BINARY_SENSOR_SCHEMA = cv.MQTT_COMPONENT_SCHEMA.extend({ | ||||
| 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.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id( | ||||
|             mqtt.MQTTBinarySensorComponent | ||||
|         ), | ||||
|         cv.Optional(CONF_DEVICE_CLASS): device_class, | ||||
|         cv.Optional(CONF_FILTERS): validate_filters, | ||||
|     cv.Optional(CONF_ON_PRESS): automation.validate_automation({ | ||||
|         cv.Optional(CONF_ON_PRESS): automation.validate_automation( | ||||
|             { | ||||
|                 cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(PressTrigger), | ||||
|     }), | ||||
|     cv.Optional(CONF_ON_RELEASE): automation.validate_automation({ | ||||
|             } | ||||
|         ), | ||||
|         cv.Optional(CONF_ON_RELEASE): automation.validate_automation( | ||||
|             { | ||||
|                 cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ReleaseTrigger), | ||||
|     }), | ||||
|     cv.Optional(CONF_ON_CLICK): automation.validate_automation({ | ||||
|             } | ||||
|         ), | ||||
|         cv.Optional(CONF_ON_CLICK): automation.validate_automation( | ||||
|             { | ||||
|                 cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ClickTrigger), | ||||
|         cv.Optional(CONF_MIN_LENGTH, default='50ms'): cv.positive_time_period_milliseconds, | ||||
|         cv.Optional(CONF_MAX_LENGTH, default='350ms'): cv.positive_time_period_milliseconds, | ||||
|     }), | ||||
|     cv.Optional(CONF_ON_DOUBLE_CLICK): automation.validate_automation({ | ||||
|                 cv.Optional( | ||||
|                     CONF_MIN_LENGTH, default="50ms" | ||||
|                 ): cv.positive_time_period_milliseconds, | ||||
|                 cv.Optional( | ||||
|                     CONF_MAX_LENGTH, default="350ms" | ||||
|                 ): cv.positive_time_period_milliseconds, | ||||
|             } | ||||
|         ), | ||||
|         cv.Optional(CONF_ON_DOUBLE_CLICK): automation.validate_automation( | ||||
|             { | ||||
|                 cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(DoubleClickTrigger), | ||||
|         cv.Optional(CONF_MIN_LENGTH, default='50ms'): cv.positive_time_period_milliseconds, | ||||
|         cv.Optional(CONF_MAX_LENGTH, default='350ms'): cv.positive_time_period_milliseconds, | ||||
|     }), | ||||
|     cv.Optional(CONF_ON_MULTI_CLICK): automation.validate_automation({ | ||||
|                 cv.Optional( | ||||
|                     CONF_MIN_LENGTH, default="50ms" | ||||
|                 ): cv.positive_time_period_milliseconds, | ||||
|                 cv.Optional( | ||||
|                     CONF_MAX_LENGTH, default="350ms" | ||||
|                 ): cv.positive_time_period_milliseconds, | ||||
|             } | ||||
|         ), | ||||
|         cv.Optional(CONF_ON_MULTI_CLICK): automation.validate_automation( | ||||
|             { | ||||
|                 cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(MultiClickTrigger), | ||||
|         cv.Required(CONF_TIMING): cv.All([parse_multi_click_timing_str], | ||||
|                                          validate_multi_click_timing), | ||||
|         cv.Optional(CONF_INVALID_COOLDOWN, default='1s'): cv.positive_time_period_milliseconds, | ||||
|     }), | ||||
|     cv.Optional(CONF_ON_STATE): automation.validate_automation({ | ||||
|                 cv.Required(CONF_TIMING): cv.All( | ||||
|                     [parse_multi_click_timing_str], validate_multi_click_timing | ||||
|                 ), | ||||
|                 cv.Optional( | ||||
|                     CONF_INVALID_COOLDOWN, default="1s" | ||||
|                 ): cv.positive_time_period_milliseconds, | ||||
|             } | ||||
|         ), | ||||
|         cv.Optional(CONF_ON_STATE): automation.validate_automation( | ||||
|             { | ||||
|                 cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(StateTrigger), | ||||
|     }), | ||||
|  | ||||
|             } | ||||
|         ), | ||||
|         cv.Optional(CONF_INVERTED): cv.invalid( | ||||
|             "The inverted binary_sensor property has been replaced by the " | ||||
|             "new 'invert' binary  sensor filter. Please see " | ||||
|             "https://esphome.io/components/binary_sensor/index.html." | ||||
|         ), | ||||
| }) | ||||
|     } | ||||
| ) | ||||
|  | ||||
|  | ||||
| @coroutine | ||||
| @@ -244,24 +356,28 @@ def setup_binary_sensor_core_(var, config): | ||||
|         yield automation.build_automation(trigger, [], conf) | ||||
|  | ||||
|     for conf in config.get(CONF_ON_CLICK, []): | ||||
|         trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var, | ||||
|                                    conf[CONF_MIN_LENGTH], conf[CONF_MAX_LENGTH]) | ||||
|         trigger = cg.new_Pvariable( | ||||
|             conf[CONF_TRIGGER_ID], var, conf[CONF_MIN_LENGTH], conf[CONF_MAX_LENGTH] | ||||
|         ) | ||||
|         yield automation.build_automation(trigger, [], conf) | ||||
|  | ||||
|     for conf in config.get(CONF_ON_DOUBLE_CLICK, []): | ||||
|         trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var, | ||||
|                                    conf[CONF_MIN_LENGTH], conf[CONF_MAX_LENGTH]) | ||||
|         trigger = cg.new_Pvariable( | ||||
|             conf[CONF_TRIGGER_ID], var, conf[CONF_MIN_LENGTH], conf[CONF_MAX_LENGTH] | ||||
|         ) | ||||
|         yield automation.build_automation(trigger, [], conf) | ||||
|  | ||||
|     for conf in config.get(CONF_ON_MULTI_CLICK, []): | ||||
|         timings = [] | ||||
|         for tim in conf[CONF_TIMING]: | ||||
|             timings.append(cg.StructInitializer( | ||||
|             timings.append( | ||||
|                 cg.StructInitializer( | ||||
|                     MultiClickTriggerEvent, | ||||
|                 ('state', tim[CONF_STATE]), | ||||
|                 ('min_length', tim[CONF_MIN_LENGTH]), | ||||
|                 ('max_length', tim.get(CONF_MAX_LENGTH, 4294967294)), | ||||
|             )) | ||||
|                     ("state", tim[CONF_STATE]), | ||||
|                     ("min_length", tim[CONF_MIN_LENGTH]), | ||||
|                     ("max_length", tim.get(CONF_MAX_LENGTH, 4294967294)), | ||||
|                 ) | ||||
|             ) | ||||
|         trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var, timings) | ||||
|         if CONF_INVALID_COOLDOWN in conf: | ||||
|             cg.add(trigger.set_invalid_cooldown(conf[CONF_INVALID_COOLDOWN])) | ||||
| @@ -270,7 +386,7 @@ def setup_binary_sensor_core_(var, config): | ||||
|  | ||||
|     for conf in config.get(CONF_ON_STATE, []): | ||||
|         trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) | ||||
|         yield automation.build_automation(trigger, [(bool, 'x')], conf) | ||||
|         yield automation.build_automation(trigger, [(bool, "x")], conf) | ||||
|  | ||||
|     if CONF_MQTT_ID in config: | ||||
|         mqtt_ = cg.new_Pvariable(config[CONF_MQTT_ID], var) | ||||
| @@ -292,22 +408,28 @@ def new_binary_sensor(config): | ||||
|     yield var | ||||
|  | ||||
|  | ||||
| BINARY_SENSOR_CONDITION_SCHEMA = maybe_simple_id({ | ||||
| BINARY_SENSOR_CONDITION_SCHEMA = maybe_simple_id( | ||||
|     { | ||||
|         cv.Required(CONF_ID): cv.use_id(BinarySensor), | ||||
|     cv.Optional(CONF_FOR): cv.invalid("This option has been removed in 1.13, please use the " | ||||
|                                       "'for' condition instead."), | ||||
| }) | ||||
|         cv.Optional(CONF_FOR): cv.invalid( | ||||
|             "This option has been removed in 1.13, please use the " | ||||
|             "'for' condition instead." | ||||
|         ), | ||||
|     } | ||||
| ) | ||||
|  | ||||
|  | ||||
| @automation.register_condition('binary_sensor.is_on', BinarySensorCondition, | ||||
|                                BINARY_SENSOR_CONDITION_SCHEMA) | ||||
| @automation.register_condition( | ||||
|     "binary_sensor.is_on", BinarySensorCondition, BINARY_SENSOR_CONDITION_SCHEMA | ||||
| ) | ||||
| def binary_sensor_is_on_to_code(config, condition_id, template_arg, args): | ||||
|     paren = yield cg.get_variable(config[CONF_ID]) | ||||
|     yield cg.new_Pvariable(condition_id, template_arg, paren, True) | ||||
|  | ||||
|  | ||||
| @automation.register_condition('binary_sensor.is_off', BinarySensorCondition, | ||||
|                                BINARY_SENSOR_CONDITION_SCHEMA) | ||||
| @automation.register_condition( | ||||
|     "binary_sensor.is_off", BinarySensorCondition, BINARY_SENSOR_CONDITION_SCHEMA | ||||
| ) | ||||
| def binary_sensor_is_off_to_code(config, condition_id, template_arg, args): | ||||
|     paren = yield cg.get_variable(config[CONF_ID]) | ||||
|     yield cg.new_Pvariable(condition_id, template_arg, paren, False) | ||||
| @@ -315,5 +437,5 @@ def binary_sensor_is_off_to_code(config, condition_id, template_arg, args): | ||||
|  | ||||
| @coroutine_with_priority(100.0) | ||||
| def to_code(config): | ||||
|     cg.add_define('USE_BINARY_SENSOR') | ||||
|     cg.add_define("USE_BINARY_SENSOR") | ||||
|     cg.add_global(binary_sensor_ns.using) | ||||
|   | ||||
| @@ -2,14 +2,25 @@ import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
|  | ||||
| from esphome.components import sensor, binary_sensor | ||||
| from esphome.const import CONF_ID, CONF_CHANNELS, CONF_VALUE, CONF_TYPE, UNIT_EMPTY, \ | ||||
|     ICON_CHECK_CIRCLE_OUTLINE, CONF_BINARY_SENSOR, CONF_GROUP | ||||
| from esphome.const import ( | ||||
|     CONF_ID, | ||||
|     CONF_CHANNELS, | ||||
|     CONF_VALUE, | ||||
|     CONF_TYPE, | ||||
|     DEVICE_CLASS_EMPTY, | ||||
|     UNIT_EMPTY, | ||||
|     ICON_CHECK_CIRCLE_OUTLINE, | ||||
|     CONF_BINARY_SENSOR, | ||||
|     CONF_GROUP, | ||||
| ) | ||||
|  | ||||
| DEPENDENCIES = ['binary_sensor'] | ||||
| DEPENDENCIES = ["binary_sensor"] | ||||
|  | ||||
| binary_sensor_map_ns = cg.esphome_ns.namespace('binary_sensor_map') | ||||
| BinarySensorMap = binary_sensor_map_ns.class_('BinarySensorMap', cg.Component, sensor.Sensor) | ||||
| SensorMapType = binary_sensor_map_ns.enum('SensorMapType') | ||||
| binary_sensor_map_ns = cg.esphome_ns.namespace("binary_sensor_map") | ||||
| BinarySensorMap = binary_sensor_map_ns.class_( | ||||
|     "BinarySensorMap", cg.Component, sensor.Sensor | ||||
| ) | ||||
| SensorMapType = binary_sensor_map_ns.enum("SensorMapType") | ||||
|  | ||||
| SENSOR_MAP_TYPES = { | ||||
|     CONF_GROUP: SensorMapType.BINARY_SENSOR_MAP_TYPE_GROUP, | ||||
| @@ -20,12 +31,21 @@ entry = { | ||||
|     cv.Required(CONF_VALUE): cv.float_, | ||||
| } | ||||
|  | ||||
| CONFIG_SCHEMA = cv.typed_schema({ | ||||
|     CONF_GROUP: sensor.sensor_schema(UNIT_EMPTY, ICON_CHECK_CIRCLE_OUTLINE, 0).extend({ | ||||
| CONFIG_SCHEMA = cv.typed_schema( | ||||
|     { | ||||
|         CONF_GROUP: sensor.sensor_schema( | ||||
|             UNIT_EMPTY, ICON_CHECK_CIRCLE_OUTLINE, 0, DEVICE_CLASS_EMPTY | ||||
|         ).extend( | ||||
|             { | ||||
|                 cv.GenerateID(): cv.declare_id(BinarySensorMap), | ||||
|         cv.Required(CONF_CHANNELS): cv.All(cv.ensure_list(entry), cv.Length(min=1)), | ||||
|     }), | ||||
| }, lower=True) | ||||
|                 cv.Required(CONF_CHANNELS): cv.All( | ||||
|                     cv.ensure_list(entry), cv.Length(min=1) | ||||
|                 ), | ||||
|             } | ||||
|         ), | ||||
|     }, | ||||
|     lower=True, | ||||
| ) | ||||
|  | ||||
|  | ||||
| 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.const import CONF_MAC_ADDRESS, CONF_SERVICE_UUID, CONF_ID | ||||
|  | ||||
| DEPENDENCIES = ['esp32_ble_tracker'] | ||||
| DEPENDENCIES = ["esp32_ble_tracker"] | ||||
|  | ||||
| ble_presence_ns = cg.esphome_ns.namespace('ble_presence') | ||||
| BLEPresenceDevice = ble_presence_ns.class_('BLEPresenceDevice', binary_sensor.BinarySensor, | ||||
|                                            cg.Component, esp32_ble_tracker.ESPBTDeviceListener) | ||||
| ble_presence_ns = cg.esphome_ns.namespace("ble_presence") | ||||
| BLEPresenceDevice = ble_presence_ns.class_( | ||||
|     "BLEPresenceDevice", | ||||
|     binary_sensor.BinarySensor, | ||||
|     cg.Component, | ||||
|     esp32_ble_tracker.ESPBTDeviceListener, | ||||
| ) | ||||
|  | ||||
| CONFIG_SCHEMA = cv.All(binary_sensor.BINARY_SENSOR_SCHEMA.extend({ | ||||
| CONFIG_SCHEMA = cv.All( | ||||
|     binary_sensor.BINARY_SENSOR_SCHEMA.extend( | ||||
|         { | ||||
|             cv.GenerateID(): cv.declare_id(BLEPresenceDevice), | ||||
|             cv.Optional(CONF_MAC_ADDRESS): cv.mac_address, | ||||
|             cv.Optional(CONF_SERVICE_UUID): esp32_ble_tracker.bt_uuid, | ||||
| }).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA).extend( | ||||
|     cv.COMPONENT_SCHEMA), cv.has_exactly_one_key(CONF_MAC_ADDRESS, CONF_SERVICE_UUID)) | ||||
|         } | ||||
|     ) | ||||
|     .extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA) | ||||
|     .extend(cv.COMPONENT_SCHEMA), | ||||
|     cv.has_exactly_one_key(CONF_MAC_ADDRESS, CONF_SERVICE_UUID), | ||||
| ) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
| @@ -28,9 +38,17 @@ def to_code(config): | ||||
|  | ||||
|     if CONF_SERVICE_UUID in config: | ||||
|         if len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid16_format): | ||||
|             cg.add(var.set_service_uuid16(esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID]))) | ||||
|             cg.add( | ||||
|                 var.set_service_uuid16( | ||||
|                     esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID]) | ||||
|                 ) | ||||
|             ) | ||||
|         elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid32_format): | ||||
|             cg.add(var.set_service_uuid32(esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID]))) | ||||
|             cg.add( | ||||
|                 var.set_service_uuid32( | ||||
|                     esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID]) | ||||
|                 ) | ||||
|             ) | ||||
|         elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid128_format): | ||||
|             uuid128 = esp32_ble_tracker.as_hex_array(config[CONF_SERVICE_UUID]) | ||||
|             cg.add(var.set_service_uuid128(uuid128)) | ||||
|   | ||||
| @@ -1,20 +1,35 @@ | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome.components import sensor, esp32_ble_tracker | ||||
| from esphome.const import CONF_SERVICE_UUID, CONF_MAC_ADDRESS, CONF_ID, UNIT_DECIBEL, ICON_SIGNAL | ||||
| from esphome.const import ( | ||||
|     CONF_SERVICE_UUID, | ||||
|     CONF_MAC_ADDRESS, | ||||
|     CONF_ID, | ||||
|     DEVICE_CLASS_SIGNAL_STRENGTH, | ||||
|     UNIT_DECIBEL, | ||||
|     ICON_EMPTY, | ||||
| ) | ||||
|  | ||||
| DEPENDENCIES = ['esp32_ble_tracker'] | ||||
| DEPENDENCIES = ["esp32_ble_tracker"] | ||||
|  | ||||
| ble_rssi_ns = cg.esphome_ns.namespace('ble_rssi') | ||||
| BLERSSISensor = ble_rssi_ns.class_('BLERSSISensor', sensor.Sensor, cg.Component, | ||||
|                                    esp32_ble_tracker.ESPBTDeviceListener) | ||||
| ble_rssi_ns = cg.esphome_ns.namespace("ble_rssi") | ||||
| BLERSSISensor = ble_rssi_ns.class_( | ||||
|     "BLERSSISensor", sensor.Sensor, cg.Component, esp32_ble_tracker.ESPBTDeviceListener | ||||
| ) | ||||
|  | ||||
| CONFIG_SCHEMA = cv.All(sensor.sensor_schema(UNIT_DECIBEL, ICON_SIGNAL, 0).extend({ | ||||
| CONFIG_SCHEMA = cv.All( | ||||
|     sensor.sensor_schema(UNIT_DECIBEL, ICON_EMPTY, 0, DEVICE_CLASS_SIGNAL_STRENGTH) | ||||
|     .extend( | ||||
|         { | ||||
|             cv.GenerateID(): cv.declare_id(BLERSSISensor), | ||||
|             cv.Optional(CONF_MAC_ADDRESS): cv.mac_address, | ||||
|             cv.Optional(CONF_SERVICE_UUID): esp32_ble_tracker.bt_uuid, | ||||
| }).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA).extend( | ||||
|     cv.COMPONENT_SCHEMA), cv.has_exactly_one_key(CONF_MAC_ADDRESS, CONF_SERVICE_UUID)) | ||||
|         } | ||||
|     ) | ||||
|     .extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA) | ||||
|     .extend(cv.COMPONENT_SCHEMA), | ||||
|     cv.has_exactly_one_key(CONF_MAC_ADDRESS, CONF_SERVICE_UUID), | ||||
| ) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
| @@ -28,9 +43,17 @@ def to_code(config): | ||||
|  | ||||
|     if CONF_SERVICE_UUID in config: | ||||
|         if len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid16_format): | ||||
|             cg.add(var.set_service_uuid16(esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID]))) | ||||
|             cg.add( | ||||
|                 var.set_service_uuid16( | ||||
|                     esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID]) | ||||
|                 ) | ||||
|             ) | ||||
|         elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid32_format): | ||||
|             cg.add(var.set_service_uuid32(esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID]))) | ||||
|             cg.add( | ||||
|                 var.set_service_uuid32( | ||||
|                     esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID]) | ||||
|                 ) | ||||
|             ) | ||||
|         elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid128_format): | ||||
|             uuid128 = esp32_ble_tracker.as_hex_array(config[CONF_SERVICE_UUID]) | ||||
|             cg.add(var.set_service_uuid128(uuid128)) | ||||
|   | ||||
| @@ -3,16 +3,25 @@ import esphome.config_validation as cv | ||||
| from esphome.components import text_sensor, esp32_ble_tracker | ||||
| from esphome.const import CONF_ID | ||||
|  | ||||
| DEPENDENCIES = ['esp32_ble_tracker'] | ||||
| DEPENDENCIES = ["esp32_ble_tracker"] | ||||
|  | ||||
| ble_scanner_ns = cg.esphome_ns.namespace('ble_scanner') | ||||
| BLEScanner = ble_scanner_ns.class_('BLEScanner', text_sensor.TextSensor, cg.Component, | ||||
|                                    esp32_ble_tracker.ESPBTDeviceListener) | ||||
| ble_scanner_ns = cg.esphome_ns.namespace("ble_scanner") | ||||
| BLEScanner = ble_scanner_ns.class_( | ||||
|     "BLEScanner", | ||||
|     text_sensor.TextSensor, | ||||
|     cg.Component, | ||||
|     esp32_ble_tracker.ESPBTDeviceListener, | ||||
| ) | ||||
|  | ||||
| CONFIG_SCHEMA = cv.All(text_sensor.TEXT_SENSOR_SCHEMA.extend({ | ||||
| CONFIG_SCHEMA = cv.All( | ||||
|     text_sensor.TEXT_SENSOR_SCHEMA.extend( | ||||
|         { | ||||
|             cv.GenerateID(): cv.declare_id(BLEScanner), | ||||
| }).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA).extend( | ||||
|     cv.COMPONENT_SCHEMA)) | ||||
|         } | ||||
|     ) | ||||
|     .extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA) | ||||
|     .extend(cv.COMPONENT_SCHEMA) | ||||
| ) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
|   | ||||
| @@ -1,53 +1,87 @@ | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome.components import i2c, sensor | ||||
| from esphome.const import CONF_HUMIDITY, CONF_ID, CONF_IIR_FILTER, CONF_OVERSAMPLING, \ | ||||
|     CONF_PRESSURE, CONF_TEMPERATURE, ICON_THERMOMETER, \ | ||||
|     UNIT_CELSIUS, UNIT_HECTOPASCAL, ICON_GAUGE, ICON_WATER_PERCENT, UNIT_PERCENT | ||||
| from esphome.const import ( | ||||
|     CONF_HUMIDITY, | ||||
|     CONF_ID, | ||||
|     CONF_IIR_FILTER, | ||||
|     CONF_OVERSAMPLING, | ||||
|     CONF_PRESSURE, | ||||
|     CONF_TEMPERATURE, | ||||
|     DEVICE_CLASS_HUMIDITY, | ||||
|     DEVICE_CLASS_PRESSURE, | ||||
|     DEVICE_CLASS_TEMPERATURE, | ||||
|     ICON_EMPTY, | ||||
|     UNIT_CELSIUS, | ||||
|     UNIT_HECTOPASCAL, | ||||
|     UNIT_PERCENT, | ||||
| ) | ||||
|  | ||||
| DEPENDENCIES = ['i2c'] | ||||
| DEPENDENCIES = ["i2c"] | ||||
|  | ||||
| bme280_ns = cg.esphome_ns.namespace('bme280') | ||||
| BME280Oversampling = bme280_ns.enum('BME280Oversampling') | ||||
| bme280_ns = cg.esphome_ns.namespace("bme280") | ||||
| BME280Oversampling = bme280_ns.enum("BME280Oversampling") | ||||
| OVERSAMPLING_OPTIONS = { | ||||
|     'NONE': BME280Oversampling.BME280_OVERSAMPLING_NONE, | ||||
|     '1X': BME280Oversampling.BME280_OVERSAMPLING_1X, | ||||
|     '2X': BME280Oversampling.BME280_OVERSAMPLING_2X, | ||||
|     '4X': BME280Oversampling.BME280_OVERSAMPLING_4X, | ||||
|     '8X': BME280Oversampling.BME280_OVERSAMPLING_8X, | ||||
|     '16X': BME280Oversampling.BME280_OVERSAMPLING_16X, | ||||
|     "NONE": BME280Oversampling.BME280_OVERSAMPLING_NONE, | ||||
|     "1X": BME280Oversampling.BME280_OVERSAMPLING_1X, | ||||
|     "2X": BME280Oversampling.BME280_OVERSAMPLING_2X, | ||||
|     "4X": BME280Oversampling.BME280_OVERSAMPLING_4X, | ||||
|     "8X": BME280Oversampling.BME280_OVERSAMPLING_8X, | ||||
|     "16X": BME280Oversampling.BME280_OVERSAMPLING_16X, | ||||
| } | ||||
|  | ||||
| BME280IIRFilter = bme280_ns.enum('BME280IIRFilter') | ||||
| BME280IIRFilter = bme280_ns.enum("BME280IIRFilter") | ||||
| IIR_FILTER_OPTIONS = { | ||||
|     'OFF': BME280IIRFilter.BME280_IIR_FILTER_OFF, | ||||
|     '2X': BME280IIRFilter.BME280_IIR_FILTER_2X, | ||||
|     '4X': BME280IIRFilter.BME280_IIR_FILTER_4X, | ||||
|     '8X': BME280IIRFilter.BME280_IIR_FILTER_8X, | ||||
|     '16X': BME280IIRFilter.BME280_IIR_FILTER_16X, | ||||
|     "OFF": BME280IIRFilter.BME280_IIR_FILTER_OFF, | ||||
|     "2X": BME280IIRFilter.BME280_IIR_FILTER_2X, | ||||
|     "4X": BME280IIRFilter.BME280_IIR_FILTER_4X, | ||||
|     "8X": BME280IIRFilter.BME280_IIR_FILTER_8X, | ||||
|     "16X": BME280IIRFilter.BME280_IIR_FILTER_16X, | ||||
| } | ||||
|  | ||||
| BME280Component = bme280_ns.class_('BME280Component', cg.PollingComponent, i2c.I2CDevice) | ||||
| BME280Component = bme280_ns.class_( | ||||
|     "BME280Component", cg.PollingComponent, i2c.I2CDevice | ||||
| ) | ||||
|  | ||||
| CONFIG_SCHEMA = cv.Schema({ | ||||
| CONFIG_SCHEMA = ( | ||||
|     cv.Schema( | ||||
|         { | ||||
|             cv.GenerateID(): cv.declare_id(BME280Component), | ||||
|     cv.Optional(CONF_TEMPERATURE): | ||||
|         sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 1).extend({ | ||||
|             cv.Optional(CONF_OVERSAMPLING, default='16X'): | ||||
|                 cv.enum(OVERSAMPLING_OPTIONS, upper=True), | ||||
|         }), | ||||
|     cv.Optional(CONF_PRESSURE): | ||||
|         sensor.sensor_schema(UNIT_HECTOPASCAL, ICON_GAUGE, 1).extend({ | ||||
|             cv.Optional(CONF_OVERSAMPLING, default='16X'): | ||||
|                 cv.enum(OVERSAMPLING_OPTIONS, upper=True), | ||||
|         }), | ||||
|     cv.Optional(CONF_HUMIDITY): | ||||
|         sensor.sensor_schema(UNIT_PERCENT, ICON_WATER_PERCENT, 1).extend({ | ||||
|             cv.Optional(CONF_OVERSAMPLING, default='16X'): | ||||
|                 cv.enum(OVERSAMPLING_OPTIONS, upper=True), | ||||
|         }), | ||||
|     cv.Optional(CONF_IIR_FILTER, default='OFF'): cv.enum(IIR_FILTER_OPTIONS, upper=True), | ||||
| }).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x77)) | ||||
|             cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( | ||||
|                 UNIT_CELSIUS, ICON_EMPTY, 1, DEVICE_CLASS_TEMPERATURE | ||||
|             ).extend( | ||||
|                 { | ||||
|                     cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum( | ||||
|                         OVERSAMPLING_OPTIONS, upper=True | ||||
|                     ), | ||||
|                 } | ||||
|             ), | ||||
|             cv.Optional(CONF_PRESSURE): sensor.sensor_schema( | ||||
|                 UNIT_HECTOPASCAL, ICON_EMPTY, 1, DEVICE_CLASS_PRESSURE | ||||
|             ).extend( | ||||
|                 { | ||||
|                     cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum( | ||||
|                         OVERSAMPLING_OPTIONS, upper=True | ||||
|                     ), | ||||
|                 } | ||||
|             ), | ||||
|             cv.Optional(CONF_HUMIDITY): sensor.sensor_schema( | ||||
|                 UNIT_PERCENT, ICON_EMPTY, 1, DEVICE_CLASS_HUMIDITY | ||||
|             ).extend( | ||||
|                 { | ||||
|                     cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum( | ||||
|                         OVERSAMPLING_OPTIONS, upper=True | ||||
|                     ), | ||||
|                 } | ||||
|             ), | ||||
|             cv.Optional(CONF_IIR_FILTER, default="OFF"): cv.enum( | ||||
|                 IIR_FILTER_OPTIONS, upper=True | ||||
|             ), | ||||
|         } | ||||
|     ) | ||||
|     .extend(cv.polling_component_schema("60s")) | ||||
|     .extend(i2c.i2c_device_schema(0x77)) | ||||
| ) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
|   | ||||
| @@ -2,64 +2,116 @@ import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome import core | ||||
| from esphome.components import i2c, sensor | ||||
| from esphome.const import CONF_DURATION, CONF_GAS_RESISTANCE, CONF_HEATER, \ | ||||
|     CONF_HUMIDITY, CONF_ID, CONF_IIR_FILTER, CONF_OVERSAMPLING, CONF_PRESSURE, \ | ||||
|     CONF_TEMPERATURE, UNIT_OHM, ICON_GAS_CYLINDER, UNIT_CELSIUS, \ | ||||
|     ICON_THERMOMETER, UNIT_HECTOPASCAL, ICON_GAUGE, ICON_WATER_PERCENT, UNIT_PERCENT | ||||
| from esphome.const import ( | ||||
|     CONF_DURATION, | ||||
|     CONF_GAS_RESISTANCE, | ||||
|     CONF_HEATER, | ||||
|     CONF_HUMIDITY, | ||||
|     CONF_ID, | ||||
|     CONF_IIR_FILTER, | ||||
|     CONF_OVERSAMPLING, | ||||
|     CONF_PRESSURE, | ||||
|     CONF_TEMPERATURE, | ||||
|     DEVICE_CLASS_EMPTY, | ||||
|     DEVICE_CLASS_HUMIDITY, | ||||
|     DEVICE_CLASS_PRESSURE, | ||||
|     DEVICE_CLASS_TEMPERATURE, | ||||
|     UNIT_OHM, | ||||
|     ICON_GAS_CYLINDER, | ||||
|     UNIT_CELSIUS, | ||||
|     ICON_EMPTY, | ||||
|     UNIT_HECTOPASCAL, | ||||
|     UNIT_PERCENT, | ||||
| ) | ||||
|  | ||||
| DEPENDENCIES = ['i2c'] | ||||
| DEPENDENCIES = ["i2c"] | ||||
|  | ||||
| bme680_ns = cg.esphome_ns.namespace('bme680') | ||||
| BME680Oversampling = bme680_ns.enum('BME680Oversampling') | ||||
| bme680_ns = cg.esphome_ns.namespace("bme680") | ||||
| BME680Oversampling = bme680_ns.enum("BME680Oversampling") | ||||
| OVERSAMPLING_OPTIONS = { | ||||
|     'NONE': BME680Oversampling.BME680_OVERSAMPLING_NONE, | ||||
|     '1X': BME680Oversampling.BME680_OVERSAMPLING_1X, | ||||
|     '2X': BME680Oversampling.BME680_OVERSAMPLING_2X, | ||||
|     '4X': BME680Oversampling.BME680_OVERSAMPLING_4X, | ||||
|     '8X': BME680Oversampling.BME680_OVERSAMPLING_8X, | ||||
|     '16X': BME680Oversampling.BME680_OVERSAMPLING_16X, | ||||
|     "NONE": BME680Oversampling.BME680_OVERSAMPLING_NONE, | ||||
|     "1X": BME680Oversampling.BME680_OVERSAMPLING_1X, | ||||
|     "2X": BME680Oversampling.BME680_OVERSAMPLING_2X, | ||||
|     "4X": BME680Oversampling.BME680_OVERSAMPLING_4X, | ||||
|     "8X": BME680Oversampling.BME680_OVERSAMPLING_8X, | ||||
|     "16X": BME680Oversampling.BME680_OVERSAMPLING_16X, | ||||
| } | ||||
|  | ||||
| BME680IIRFilter = bme680_ns.enum('BME680IIRFilter') | ||||
| BME680IIRFilter = bme680_ns.enum("BME680IIRFilter") | ||||
| IIR_FILTER_OPTIONS = { | ||||
|     'OFF': BME680IIRFilter.BME680_IIR_FILTER_OFF, | ||||
|     '1X': BME680IIRFilter.BME680_IIR_FILTER_1X, | ||||
|     '3X': BME680IIRFilter.BME680_IIR_FILTER_3X, | ||||
|     '7X': BME680IIRFilter.BME680_IIR_FILTER_7X, | ||||
|     '15X': BME680IIRFilter.BME680_IIR_FILTER_15X, | ||||
|     '31X': BME680IIRFilter.BME680_IIR_FILTER_31X, | ||||
|     '63X': BME680IIRFilter.BME680_IIR_FILTER_63X, | ||||
|     '127X': BME680IIRFilter.BME680_IIR_FILTER_127X, | ||||
|     "OFF": BME680IIRFilter.BME680_IIR_FILTER_OFF, | ||||
|     "1X": BME680IIRFilter.BME680_IIR_FILTER_1X, | ||||
|     "3X": BME680IIRFilter.BME680_IIR_FILTER_3X, | ||||
|     "7X": BME680IIRFilter.BME680_IIR_FILTER_7X, | ||||
|     "15X": BME680IIRFilter.BME680_IIR_FILTER_15X, | ||||
|     "31X": BME680IIRFilter.BME680_IIR_FILTER_31X, | ||||
|     "63X": BME680IIRFilter.BME680_IIR_FILTER_63X, | ||||
|     "127X": BME680IIRFilter.BME680_IIR_FILTER_127X, | ||||
| } | ||||
|  | ||||
| BME680Component = bme680_ns.class_('BME680Component', cg.PollingComponent, i2c.I2CDevice) | ||||
| BME680Component = bme680_ns.class_( | ||||
|     "BME680Component", cg.PollingComponent, i2c.I2CDevice | ||||
| ) | ||||
|  | ||||
| CONFIG_SCHEMA = cv.Schema({ | ||||
| CONFIG_SCHEMA = ( | ||||
|     cv.Schema( | ||||
|         { | ||||
|             cv.GenerateID(): cv.declare_id(BME680Component), | ||||
|     cv.Optional(CONF_TEMPERATURE): | ||||
|         sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 1).extend({ | ||||
|             cv.Optional(CONF_OVERSAMPLING, default='16X'): | ||||
|                 cv.enum(OVERSAMPLING_OPTIONS, upper=True), | ||||
|         }), | ||||
|     cv.Optional(CONF_PRESSURE): | ||||
|         sensor.sensor_schema(UNIT_HECTOPASCAL, ICON_GAUGE, 1).extend({ | ||||
|             cv.Optional(CONF_OVERSAMPLING, default='16X'): | ||||
|                 cv.enum(OVERSAMPLING_OPTIONS, upper=True), | ||||
|         }), | ||||
|     cv.Optional(CONF_HUMIDITY): | ||||
|         sensor.sensor_schema(UNIT_PERCENT, ICON_WATER_PERCENT, 1).extend({ | ||||
|             cv.Optional(CONF_OVERSAMPLING, default='16X'): | ||||
|                 cv.enum(OVERSAMPLING_OPTIONS, upper=True), | ||||
|         }), | ||||
|     cv.Optional(CONF_GAS_RESISTANCE): | ||||
|         sensor.sensor_schema(UNIT_OHM, ICON_GAS_CYLINDER, 1), | ||||
|     cv.Optional(CONF_IIR_FILTER, default='OFF'): cv.enum(IIR_FILTER_OPTIONS, upper=True), | ||||
|     cv.Optional(CONF_HEATER): cv.Any(None, cv.All(cv.Schema({ | ||||
|         cv.Optional(CONF_TEMPERATURE, default=320): cv.int_range(min=200, max=400), | ||||
|         cv.Optional(CONF_DURATION, default='150ms'): cv.All( | ||||
|             cv.positive_time_period_milliseconds, cv.Range(max=core.TimePeriod(milliseconds=4032))) | ||||
|     }), cv.has_at_least_one_key(CONF_TEMPERATURE, CONF_DURATION))), | ||||
| }).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x76)) | ||||
|             cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( | ||||
|                 UNIT_CELSIUS, ICON_EMPTY, 1, DEVICE_CLASS_TEMPERATURE | ||||
|             ).extend( | ||||
|                 { | ||||
|                     cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum( | ||||
|                         OVERSAMPLING_OPTIONS, upper=True | ||||
|                     ), | ||||
|                 } | ||||
|             ), | ||||
|             cv.Optional(CONF_PRESSURE): sensor.sensor_schema( | ||||
|                 UNIT_HECTOPASCAL, ICON_EMPTY, 1, DEVICE_CLASS_PRESSURE | ||||
|             ).extend( | ||||
|                 { | ||||
|                     cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum( | ||||
|                         OVERSAMPLING_OPTIONS, upper=True | ||||
|                     ), | ||||
|                 } | ||||
|             ), | ||||
|             cv.Optional(CONF_HUMIDITY): sensor.sensor_schema( | ||||
|                 UNIT_PERCENT, ICON_EMPTY, 1, DEVICE_CLASS_HUMIDITY | ||||
|             ).extend( | ||||
|                 { | ||||
|                     cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum( | ||||
|                         OVERSAMPLING_OPTIONS, upper=True | ||||
|                     ), | ||||
|                 } | ||||
|             ), | ||||
|             cv.Optional(CONF_GAS_RESISTANCE): sensor.sensor_schema( | ||||
|                 UNIT_OHM, ICON_GAS_CYLINDER, 1, DEVICE_CLASS_EMPTY | ||||
|             ), | ||||
|             cv.Optional(CONF_IIR_FILTER, default="OFF"): cv.enum( | ||||
|                 IIR_FILTER_OPTIONS, upper=True | ||||
|             ), | ||||
|             cv.Optional(CONF_HEATER): cv.Any( | ||||
|                 None, | ||||
|                 cv.All( | ||||
|                     cv.Schema( | ||||
|                         { | ||||
|                             cv.Optional(CONF_TEMPERATURE, default=320): cv.int_range( | ||||
|                                 min=200, max=400 | ||||
|                             ), | ||||
|                             cv.Optional(CONF_DURATION, default="150ms"): cv.All( | ||||
|                                 cv.positive_time_period_milliseconds, | ||||
|                                 cv.Range(max=core.TimePeriod(milliseconds=4032)), | ||||
|                             ), | ||||
|                         } | ||||
|                     ), | ||||
|                     cv.has_at_least_one_key(CONF_TEMPERATURE, CONF_DURATION), | ||||
|                 ), | ||||
|             ), | ||||
|         } | ||||
|     ) | ||||
|     .extend(cv.polling_component_schema("60s")) | ||||
|     .extend(i2c.i2c_device_schema(0x76)) | ||||
| ) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
|   | ||||
| @@ -1,19 +1,39 @@ | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome.components import i2c, sensor | ||||
| from esphome.const import CONF_ID, CONF_PRESSURE, CONF_TEMPERATURE, \ | ||||
|     UNIT_CELSIUS, ICON_THERMOMETER, ICON_GAUGE, UNIT_HECTOPASCAL | ||||
| from esphome.const import ( | ||||
|     CONF_ID, | ||||
|     CONF_PRESSURE, | ||||
|     CONF_TEMPERATURE, | ||||
|     DEVICE_CLASS_PRESSURE, | ||||
|     DEVICE_CLASS_TEMPERATURE, | ||||
|     UNIT_CELSIUS, | ||||
|     ICON_EMPTY, | ||||
|     UNIT_HECTOPASCAL, | ||||
| ) | ||||
|  | ||||
| DEPENDENCIES = ['i2c'] | ||||
| DEPENDENCIES = ["i2c"] | ||||
|  | ||||
| bmp085_ns = cg.esphome_ns.namespace('bmp085') | ||||
| BMP085Component = bmp085_ns.class_('BMP085Component', cg.PollingComponent, i2c.I2CDevice) | ||||
| bmp085_ns = cg.esphome_ns.namespace("bmp085") | ||||
| BMP085Component = bmp085_ns.class_( | ||||
|     "BMP085Component", cg.PollingComponent, i2c.I2CDevice | ||||
| ) | ||||
|  | ||||
| CONFIG_SCHEMA = cv.Schema({ | ||||
| CONFIG_SCHEMA = ( | ||||
|     cv.Schema( | ||||
|         { | ||||
|             cv.GenerateID(): cv.declare_id(BMP085Component), | ||||
|     cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 1), | ||||
|     cv.Optional(CONF_PRESSURE): sensor.sensor_schema(UNIT_HECTOPASCAL, ICON_GAUGE, 1), | ||||
| }).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x77)) | ||||
|             cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( | ||||
|                 UNIT_CELSIUS, ICON_EMPTY, 1, DEVICE_CLASS_TEMPERATURE | ||||
|             ), | ||||
|             cv.Optional(CONF_PRESSURE): sensor.sensor_schema( | ||||
|                 UNIT_HECTOPASCAL, ICON_EMPTY, 1, DEVICE_CLASS_PRESSURE | ||||
|             ), | ||||
|         } | ||||
|     ) | ||||
|     .extend(cv.polling_component_schema("60s")) | ||||
|     .extend(i2c.i2c_device_schema(0x77)) | ||||
| ) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
|   | ||||
| @@ -1,44 +1,75 @@ | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome.components import i2c, sensor | ||||
| from esphome.const import CONF_ID, CONF_PRESSURE, CONF_TEMPERATURE, \ | ||||
|     UNIT_CELSIUS, ICON_THERMOMETER, ICON_GAUGE, UNIT_HECTOPASCAL, \ | ||||
|     CONF_IIR_FILTER, CONF_OVERSAMPLING | ||||
| from esphome.const import ( | ||||
|     CONF_ID, | ||||
|     CONF_PRESSURE, | ||||
|     CONF_TEMPERATURE, | ||||
|     DEVICE_CLASS_PRESSURE, | ||||
|     DEVICE_CLASS_TEMPERATURE, | ||||
|     UNIT_CELSIUS, | ||||
|     ICON_EMPTY, | ||||
|     UNIT_HECTOPASCAL, | ||||
|     CONF_IIR_FILTER, | ||||
|     CONF_OVERSAMPLING, | ||||
| ) | ||||
|  | ||||
| DEPENDENCIES = ['i2c'] | ||||
| DEPENDENCIES = ["i2c"] | ||||
|  | ||||
| bmp280_ns = cg.esphome_ns.namespace('bmp280') | ||||
| BMP280Oversampling = bmp280_ns.enum('BMP280Oversampling') | ||||
| bmp280_ns = cg.esphome_ns.namespace("bmp280") | ||||
| BMP280Oversampling = bmp280_ns.enum("BMP280Oversampling") | ||||
| OVERSAMPLING_OPTIONS = { | ||||
|     'NONE': BMP280Oversampling.BMP280_OVERSAMPLING_NONE, | ||||
|     '1X': BMP280Oversampling.BMP280_OVERSAMPLING_1X, | ||||
|     '2X': BMP280Oversampling.BMP280_OVERSAMPLING_2X, | ||||
|     '4X': BMP280Oversampling.BMP280_OVERSAMPLING_4X, | ||||
|     '8X': BMP280Oversampling.BMP280_OVERSAMPLING_8X, | ||||
|     '16X': BMP280Oversampling.BMP280_OVERSAMPLING_16X, | ||||
|     "NONE": BMP280Oversampling.BMP280_OVERSAMPLING_NONE, | ||||
|     "1X": BMP280Oversampling.BMP280_OVERSAMPLING_1X, | ||||
|     "2X": BMP280Oversampling.BMP280_OVERSAMPLING_2X, | ||||
|     "4X": BMP280Oversampling.BMP280_OVERSAMPLING_4X, | ||||
|     "8X": BMP280Oversampling.BMP280_OVERSAMPLING_8X, | ||||
|     "16X": BMP280Oversampling.BMP280_OVERSAMPLING_16X, | ||||
| } | ||||
|  | ||||
| BMP280IIRFilter = bmp280_ns.enum('BMP280IIRFilter') | ||||
| BMP280IIRFilter = bmp280_ns.enum("BMP280IIRFilter") | ||||
| IIR_FILTER_OPTIONS = { | ||||
|     'OFF': BMP280IIRFilter.BMP280_IIR_FILTER_OFF, | ||||
|     '2X': BMP280IIRFilter.BMP280_IIR_FILTER_2X, | ||||
|     '4X': BMP280IIRFilter.BMP280_IIR_FILTER_4X, | ||||
|     '8X': BMP280IIRFilter.BMP280_IIR_FILTER_8X, | ||||
|     '16X': BMP280IIRFilter.BMP280_IIR_FILTER_16X, | ||||
|     "OFF": BMP280IIRFilter.BMP280_IIR_FILTER_OFF, | ||||
|     "2X": BMP280IIRFilter.BMP280_IIR_FILTER_2X, | ||||
|     "4X": BMP280IIRFilter.BMP280_IIR_FILTER_4X, | ||||
|     "8X": BMP280IIRFilter.BMP280_IIR_FILTER_8X, | ||||
|     "16X": BMP280IIRFilter.BMP280_IIR_FILTER_16X, | ||||
| } | ||||
|  | ||||
| BMP280Component = bmp280_ns.class_('BMP280Component', cg.PollingComponent, i2c.I2CDevice) | ||||
| BMP280Component = bmp280_ns.class_( | ||||
|     "BMP280Component", cg.PollingComponent, i2c.I2CDevice | ||||
| ) | ||||
|  | ||||
| CONFIG_SCHEMA = cv.Schema({ | ||||
| CONFIG_SCHEMA = ( | ||||
|     cv.Schema( | ||||
|         { | ||||
|             cv.GenerateID(): cv.declare_id(BMP280Component), | ||||
|     cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 1).extend({ | ||||
|         cv.Optional(CONF_OVERSAMPLING, default='16X'): cv.enum(OVERSAMPLING_OPTIONS, upper=True), | ||||
|     }), | ||||
|     cv.Optional(CONF_PRESSURE): sensor.sensor_schema(UNIT_HECTOPASCAL, ICON_GAUGE, 1).extend({ | ||||
|         cv.Optional(CONF_OVERSAMPLING, default='16X'): cv.enum(OVERSAMPLING_OPTIONS, upper=True), | ||||
|     }), | ||||
|     cv.Optional(CONF_IIR_FILTER, default='OFF'): cv.enum(IIR_FILTER_OPTIONS, upper=True), | ||||
| }).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x77)) | ||||
|             cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( | ||||
|                 UNIT_CELSIUS, ICON_EMPTY, 1, DEVICE_CLASS_TEMPERATURE | ||||
|             ).extend( | ||||
|                 { | ||||
|                     cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum( | ||||
|                         OVERSAMPLING_OPTIONS, upper=True | ||||
|                     ), | ||||
|                 } | ||||
|             ), | ||||
|             cv.Optional(CONF_PRESSURE): sensor.sensor_schema( | ||||
|                 UNIT_HECTOPASCAL, ICON_EMPTY, 1, DEVICE_CLASS_PRESSURE | ||||
|             ).extend( | ||||
|                 { | ||||
|                     cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum( | ||||
|                         OVERSAMPLING_OPTIONS, upper=True | ||||
|                     ), | ||||
|                 } | ||||
|             ), | ||||
|             cv.Optional(CONF_IIR_FILTER, default="OFF"): cv.enum( | ||||
|                 IIR_FILTER_OPTIONS, upper=True | ||||
|             ), | ||||
|         } | ||||
|     ) | ||||
|     .extend(cv.polling_component_schema("60s")) | ||||
|     .extend(i2c.i2c_device_schema(0x77)) | ||||
| ) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
|   | ||||
| @@ -4,68 +4,82 @@ from esphome import automation | ||||
| from esphome.core import CORE, coroutine | ||||
| from esphome.const import CONF_ID, CONF_TRIGGER_ID, CONF_DATA | ||||
|  | ||||
| CODEOWNERS = ['@mvturnho', '@danielschramm'] | ||||
| CODEOWNERS = ["@mvturnho", "@danielschramm"] | ||||
| IS_PLATFORM_COMPONENT = True | ||||
|  | ||||
| CONF_CAN_ID = 'can_id' | ||||
| CONF_USE_EXTENDED_ID = 'use_extended_id' | ||||
| CONF_CANBUS_ID = 'canbus_id' | ||||
| CONF_BIT_RATE = 'bit_rate' | ||||
| CONF_ON_FRAME = 'on_frame' | ||||
| CONF_CANBUS_SEND = 'canbus.send' | ||||
| CONF_CAN_ID = "can_id" | ||||
| CONF_USE_EXTENDED_ID = "use_extended_id" | ||||
| CONF_CANBUS_ID = "canbus_id" | ||||
| CONF_BIT_RATE = "bit_rate" | ||||
| CONF_ON_FRAME = "on_frame" | ||||
|  | ||||
|  | ||||
| def validate_id(id_value, id_ext): | ||||
|     if not id_ext: | ||||
|         if id_value > 0x7ff: | ||||
|         if id_value > 0x7FF: | ||||
|             raise cv.Invalid("Standard IDs must be 11 Bit (0x000-0x7ff / 0-2047)") | ||||
|  | ||||
|  | ||||
| def validate_raw_data(value): | ||||
|     if isinstance(value, str): | ||||
|         return value.encode('utf-8') | ||||
|         return value.encode("utf-8") | ||||
|     if isinstance(value, list): | ||||
|         return cv.Schema([cv.hex_uint8_t])(value) | ||||
|     raise cv.Invalid("data must either be a string wrapped in quotes or a list of bytes") | ||||
|     raise cv.Invalid( | ||||
|         "data must either be a string wrapped in quotes or a list of bytes" | ||||
|     ) | ||||
|  | ||||
|  | ||||
| canbus_ns = cg.esphome_ns.namespace('canbus') | ||||
| CanbusComponent = canbus_ns.class_('CanbusComponent', cg.Component) | ||||
| CanbusTrigger = canbus_ns.class_('CanbusTrigger', | ||||
| canbus_ns = cg.esphome_ns.namespace("canbus") | ||||
| CanbusComponent = canbus_ns.class_("CanbusComponent", cg.Component) | ||||
| CanbusTrigger = canbus_ns.class_( | ||||
|     "CanbusTrigger", | ||||
|     automation.Trigger.template(cg.std_vector.template(cg.uint8)), | ||||
|                                  cg.Component) | ||||
| CanSpeed = canbus_ns.enum('CAN_SPEED') | ||||
|     cg.Component, | ||||
| ) | ||||
| CanSpeed = canbus_ns.enum("CAN_SPEED") | ||||
|  | ||||
| CAN_SPEEDS = { | ||||
|     '5KBPS': CanSpeed.CAN_5KBPS, | ||||
|     '10KBPS': CanSpeed.CAN_10KBPS, | ||||
|     '20KBPS': CanSpeed.CAN_20KBPS, | ||||
|     '31K25BPS': CanSpeed.CAN_31K25BPS, | ||||
|     '33KBPS': CanSpeed.CAN_33KBPS, | ||||
|     '40KBPS': CanSpeed.CAN_40KBPS, | ||||
|     '50KBPS': CanSpeed.CAN_50KBPS, | ||||
|     '80KBPS': CanSpeed.CAN_80KBPS, | ||||
|     '83K3BPS': CanSpeed.CAN_83K3BPS, | ||||
|     '95KBPS': CanSpeed.CAN_95KBPS, | ||||
|     '100KBPS': CanSpeed.CAN_100KBPS, | ||||
|     '125KBPS': CanSpeed.CAN_125KBPS, | ||||
|     '200KBPS': CanSpeed.CAN_200KBPS, | ||||
|     '250KBPS': CanSpeed.CAN_250KBPS, | ||||
|     '500KBPS': CanSpeed.CAN_500KBPS, | ||||
|     '1000KBPS': CanSpeed.CAN_1000KBPS, | ||||
|     "5KBPS": CanSpeed.CAN_5KBPS, | ||||
|     "10KBPS": CanSpeed.CAN_10KBPS, | ||||
|     "20KBPS": CanSpeed.CAN_20KBPS, | ||||
|     "31K25BPS": CanSpeed.CAN_31K25BPS, | ||||
|     "33KBPS": CanSpeed.CAN_33KBPS, | ||||
|     "40KBPS": CanSpeed.CAN_40KBPS, | ||||
|     "50KBPS": CanSpeed.CAN_50KBPS, | ||||
|     "80KBPS": CanSpeed.CAN_80KBPS, | ||||
|     "83K3BPS": CanSpeed.CAN_83K3BPS, | ||||
|     "95KBPS": CanSpeed.CAN_95KBPS, | ||||
|     "100KBPS": CanSpeed.CAN_100KBPS, | ||||
|     "125KBPS": CanSpeed.CAN_125KBPS, | ||||
|     "200KBPS": CanSpeed.CAN_200KBPS, | ||||
|     "250KBPS": CanSpeed.CAN_250KBPS, | ||||
|     "500KBPS": CanSpeed.CAN_500KBPS, | ||||
|     "1000KBPS": CanSpeed.CAN_1000KBPS, | ||||
| } | ||||
|  | ||||
| CONFIG_SCHEMA = cv.Schema({ | ||||
| CANBUS_SCHEMA = cv.Schema( | ||||
|     { | ||||
|         cv.GenerateID(): cv.declare_id(CanbusComponent), | ||||
|     cv.Required(CONF_CAN_ID): cv.int_range(min=0, max=0x1fffffff), | ||||
|     cv.Optional(CONF_BIT_RATE, default='125KBPS'): cv.enum(CAN_SPEEDS, upper=True), | ||||
|         cv.Required(CONF_CAN_ID): cv.int_range(min=0, max=0x1FFFFFFF), | ||||
|         cv.Optional(CONF_BIT_RATE, default="125KBPS"): cv.enum(CAN_SPEEDS, upper=True), | ||||
|         cv.Optional(CONF_USE_EXTENDED_ID, default=False): cv.boolean, | ||||
|     cv.Optional(CONF_ON_FRAME): automation.validate_automation({ | ||||
|         cv.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.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) | ||||
|                 cv.Optional(CONF_ON_FRAME): automation.validate_automation( | ||||
|                     { | ||||
|                         cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(CanbusTrigger), | ||||
|                         cv.GenerateID(CONF_CAN_ID): cv.int_range(min=0, max=0x1FFFFFFF), | ||||
|                         cv.Optional(CONF_USE_EXTENDED_ID, default=False): cv.boolean, | ||||
|                     } | ||||
|                 ), | ||||
|             } | ||||
|         ), | ||||
|     } | ||||
| ).extend(cv.COMPONENT_SCHEMA) | ||||
|  | ||||
|  | ||||
| @coroutine | ||||
| @@ -82,7 +96,9 @@ def setup_canbus_core_(var, config): | ||||
|         validate_id(can_id, ext_id) | ||||
|         trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var, can_id, ext_id) | ||||
|         yield cg.register_component(trigger, conf) | ||||
|         yield automation.build_automation(trigger, [(cg.std_vector.template(cg.uint8), 'x')], conf) | ||||
|         yield automation.build_automation( | ||||
|             trigger, [(cg.std_vector.template(cg.uint8), "x")], conf | ||||
|         ) | ||||
|  | ||||
|  | ||||
| @coroutine | ||||
| @@ -93,14 +109,19 @@ def register_canbus(var, config): | ||||
|  | ||||
|  | ||||
| # Actions | ||||
| @automation.register_action(CONF_CANBUS_SEND, | ||||
|                             canbus_ns.class_('CanbusSendAction', automation.Action), | ||||
|                             cv.maybe_simple_value({ | ||||
| @automation.register_action( | ||||
|     "canbus.send", | ||||
|     canbus_ns.class_("CanbusSendAction", automation.Action), | ||||
|     cv.maybe_simple_value( | ||||
|         { | ||||
|             cv.GenerateID(CONF_CANBUS_ID): cv.use_id(CanbusComponent), | ||||
|                                 cv.Optional(CONF_CAN_ID): cv.int_range(min=0, max=0x1fffffff), | ||||
|             cv.Optional(CONF_CAN_ID): cv.int_range(min=0, max=0x1FFFFFFF), | ||||
|             cv.Optional(CONF_USE_EXTENDED_ID, default=False): cv.boolean, | ||||
|             cv.Required(CONF_DATA): cv.templatable(validate_raw_data), | ||||
|                             }, key=CONF_DATA)) | ||||
|         }, | ||||
|         key=CONF_DATA, | ||||
|     ), | ||||
| ) | ||||
| def canbus_action_to_code(config, action_id, template_arg, args): | ||||
|     validate_id(config[CONF_CAN_ID], config[CONF_USE_EXTENDED_ID]) | ||||
|     var = cg.new_Pvariable(action_id, template_arg) | ||||
| @@ -110,7 +131,9 @@ def canbus_action_to_code(config, action_id, template_arg, args): | ||||
|         can_id = yield cg.templatable(config[CONF_CAN_ID], args, cg.uint32) | ||||
|         cg.add(var.set_can_id(can_id)) | ||||
|  | ||||
|     use_extended_id = yield cg.templatable(config[CONF_USE_EXTENDED_ID], args, cg.uint32) | ||||
|     use_extended_id = yield cg.templatable( | ||||
|         config[CONF_USE_EXTENDED_ID], args, cg.uint32 | ||||
|     ) | ||||
|     cg.add(var.set_use_extended_id(use_extended_id)) | ||||
|  | ||||
|     data = config[CONF_DATA] | ||||
|   | ||||
| @@ -5,17 +5,21 @@ from esphome.components.web_server_base import CONF_WEB_SERVER_BASE_ID | ||||
| from esphome.const import CONF_ID | ||||
| from esphome.core import coroutine_with_priority | ||||
|  | ||||
| AUTO_LOAD = ['web_server_base'] | ||||
| DEPENDENCIES = ['wifi'] | ||||
| CODEOWNERS = ['@OttoWinter'] | ||||
| AUTO_LOAD = ["web_server_base"] | ||||
| DEPENDENCIES = ["wifi"] | ||||
| CODEOWNERS = ["@OttoWinter"] | ||||
|  | ||||
| captive_portal_ns = cg.esphome_ns.namespace('captive_portal') | ||||
| CaptivePortal = captive_portal_ns.class_('CaptivePortal', cg.Component) | ||||
| captive_portal_ns = cg.esphome_ns.namespace("captive_portal") | ||||
| CaptivePortal = captive_portal_ns.class_("CaptivePortal", cg.Component) | ||||
|  | ||||
| CONFIG_SCHEMA = cv.Schema({ | ||||
| CONFIG_SCHEMA = cv.Schema( | ||||
|     { | ||||
|         cv.GenerateID(): cv.declare_id(CaptivePortal), | ||||
|     cv.GenerateID(CONF_WEB_SERVER_BASE_ID): cv.use_id(web_server_base.WebServerBase), | ||||
| }).extend(cv.COMPONENT_SCHEMA) | ||||
|         cv.GenerateID(CONF_WEB_SERVER_BASE_ID): cv.use_id( | ||||
|             web_server_base.WebServerBase | ||||
|         ), | ||||
|     } | ||||
| ).extend(cv.COMPONENT_SCHEMA) | ||||
|  | ||||
|  | ||||
| @coroutine_with_priority(64.0) | ||||
| @@ -24,4 +28,4 @@ def to_code(config): | ||||
|  | ||||
|     var = cg.new_Pvariable(config[CONF_ID], paren) | ||||
|     yield cg.register_component(var, config) | ||||
|     cg.add_define('USE_CAPTIVE_PORTAL') | ||||
|     cg.add_define("USE_CAPTIVE_PORTAL") | ||||
|   | ||||
| @@ -1,28 +1,46 @@ | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome.components import i2c, sensor | ||||
| from esphome.const import CONF_ID, ICON_RADIATOR, UNIT_PARTS_PER_MILLION, \ | ||||
|     UNIT_PARTS_PER_BILLION, CONF_TEMPERATURE, CONF_HUMIDITY, ICON_MOLECULE_CO2 | ||||
| from esphome.const import ( | ||||
|     CONF_ID, | ||||
|     DEVICE_CLASS_EMPTY, | ||||
|     ICON_RADIATOR, | ||||
|     UNIT_PARTS_PER_MILLION, | ||||
|     UNIT_PARTS_PER_BILLION, | ||||
|     CONF_TEMPERATURE, | ||||
|     CONF_TVOC, | ||||
|     CONF_HUMIDITY, | ||||
|     ICON_MOLECULE_CO2, | ||||
| ) | ||||
|  | ||||
| DEPENDENCIES = ['i2c'] | ||||
| DEPENDENCIES = ["i2c"] | ||||
|  | ||||
| ccs811_ns = cg.esphome_ns.namespace('ccs811') | ||||
| CCS811Component = ccs811_ns.class_('CCS811Component', cg.PollingComponent, i2c.I2CDevice) | ||||
| ccs811_ns = cg.esphome_ns.namespace("ccs811") | ||||
| CCS811Component = ccs811_ns.class_( | ||||
|     "CCS811Component", cg.PollingComponent, i2c.I2CDevice | ||||
| ) | ||||
|  | ||||
| CONF_ECO2 = 'eco2' | ||||
| CONF_TVOC = 'tvoc' | ||||
| CONF_BASELINE = 'baseline' | ||||
| CONF_ECO2 = "eco2" | ||||
| CONF_BASELINE = "baseline" | ||||
|  | ||||
| CONFIG_SCHEMA = cv.Schema({ | ||||
| CONFIG_SCHEMA = ( | ||||
|     cv.Schema( | ||||
|         { | ||||
|             cv.GenerateID(): cv.declare_id(CCS811Component), | ||||
|     cv.Required(CONF_ECO2): sensor.sensor_schema(UNIT_PARTS_PER_MILLION, ICON_MOLECULE_CO2, | ||||
|                                                  0), | ||||
|     cv.Required(CONF_TVOC): sensor.sensor_schema(UNIT_PARTS_PER_BILLION, ICON_RADIATOR, 0), | ||||
|  | ||||
|             cv.Required(CONF_ECO2): sensor.sensor_schema( | ||||
|                 UNIT_PARTS_PER_MILLION, ICON_MOLECULE_CO2, 0, DEVICE_CLASS_EMPTY | ||||
|             ), | ||||
|             cv.Required(CONF_TVOC): sensor.sensor_schema( | ||||
|                 UNIT_PARTS_PER_BILLION, ICON_RADIATOR, 0, DEVICE_CLASS_EMPTY | ||||
|             ), | ||||
|             cv.Optional(CONF_BASELINE): cv.hex_uint16_t, | ||||
|             cv.Optional(CONF_TEMPERATURE): cv.use_id(sensor.Sensor), | ||||
|             cv.Optional(CONF_HUMIDITY): cv.use_id(sensor.Sensor), | ||||
| }).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x5A)) | ||||
|         } | ||||
|     ) | ||||
|     .extend(cv.polling_component_schema("60s")) | ||||
|     .extend(i2c.i2c_device_schema(0x5A)) | ||||
| ) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
|   | ||||
| @@ -2,70 +2,87 @@ import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome import automation | ||||
| from esphome.components import mqtt | ||||
| from esphome.const import CONF_AWAY, CONF_ID, CONF_INTERNAL, CONF_MAX_TEMPERATURE, \ | ||||
|     CONF_MIN_TEMPERATURE, CONF_MODE, CONF_TARGET_TEMPERATURE, \ | ||||
|     CONF_TARGET_TEMPERATURE_HIGH, CONF_TARGET_TEMPERATURE_LOW, CONF_TEMPERATURE_STEP, CONF_VISUAL, \ | ||||
|     CONF_MQTT_ID, CONF_NAME, CONF_FAN_MODE, CONF_SWING_MODE | ||||
| from esphome.const import ( | ||||
|     CONF_AWAY, | ||||
|     CONF_ID, | ||||
|     CONF_INTERNAL, | ||||
|     CONF_MAX_TEMPERATURE, | ||||
|     CONF_MIN_TEMPERATURE, | ||||
|     CONF_MODE, | ||||
|     CONF_TARGET_TEMPERATURE, | ||||
|     CONF_TARGET_TEMPERATURE_HIGH, | ||||
|     CONF_TARGET_TEMPERATURE_LOW, | ||||
|     CONF_TEMPERATURE_STEP, | ||||
|     CONF_VISUAL, | ||||
|     CONF_MQTT_ID, | ||||
|     CONF_NAME, | ||||
|     CONF_FAN_MODE, | ||||
|     CONF_SWING_MODE, | ||||
| ) | ||||
| from esphome.core import CORE, coroutine, coroutine_with_priority | ||||
|  | ||||
| IS_PLATFORM_COMPONENT = True | ||||
|  | ||||
| CODEOWNERS = ['@esphome/core'] | ||||
| climate_ns = cg.esphome_ns.namespace('climate') | ||||
| CODEOWNERS = ["@esphome/core"] | ||||
| climate_ns = cg.esphome_ns.namespace("climate") | ||||
|  | ||||
| Climate = climate_ns.class_('Climate', cg.Nameable) | ||||
| ClimateCall = climate_ns.class_('ClimateCall') | ||||
| ClimateTraits = climate_ns.class_('ClimateTraits') | ||||
| Climate = climate_ns.class_("Climate", cg.Nameable) | ||||
| ClimateCall = climate_ns.class_("ClimateCall") | ||||
| ClimateTraits = climate_ns.class_("ClimateTraits") | ||||
|  | ||||
| ClimateMode = climate_ns.enum('ClimateMode') | ||||
| ClimateMode = climate_ns.enum("ClimateMode") | ||||
| CLIMATE_MODES = { | ||||
|     'OFF': ClimateMode.CLIMATE_MODE_OFF, | ||||
|     'AUTO': ClimateMode.CLIMATE_MODE_AUTO, | ||||
|     'COOL': ClimateMode.CLIMATE_MODE_COOL, | ||||
|     'HEAT': ClimateMode.CLIMATE_MODE_HEAT, | ||||
|     'DRY': ClimateMode.CLIMATE_MODE_DRY, | ||||
|     'FAN_ONLY': ClimateMode.CLIMATE_MODE_FAN_ONLY, | ||||
|     "OFF": ClimateMode.CLIMATE_MODE_OFF, | ||||
|     "AUTO": ClimateMode.CLIMATE_MODE_AUTO, | ||||
|     "COOL": ClimateMode.CLIMATE_MODE_COOL, | ||||
|     "HEAT": ClimateMode.CLIMATE_MODE_HEAT, | ||||
|     "DRY": ClimateMode.CLIMATE_MODE_DRY, | ||||
|     "FAN_ONLY": ClimateMode.CLIMATE_MODE_FAN_ONLY, | ||||
| } | ||||
| validate_climate_mode = cv.enum(CLIMATE_MODES, upper=True) | ||||
|  | ||||
| ClimateFanMode = climate_ns.enum('ClimateFanMode') | ||||
| ClimateFanMode = climate_ns.enum("ClimateFanMode") | ||||
| CLIMATE_FAN_MODES = { | ||||
|     'ON': ClimateFanMode.CLIMATE_FAN_ON, | ||||
|     'OFF': ClimateFanMode.CLIMATE_FAN_OFF, | ||||
|     'AUTO': ClimateFanMode.CLIMATE_FAN_AUTO, | ||||
|     'LOW': ClimateFanMode.CLIMATE_FAN_LOW, | ||||
|     'MEDIUM': ClimateFanMode.CLIMATE_FAN_MEDIUM, | ||||
|     'HIGH': ClimateFanMode.CLIMATE_FAN_HIGH, | ||||
|     'MIDDLE': ClimateFanMode.CLIMATE_FAN_MIDDLE, | ||||
|     'FOCUS': ClimateFanMode.CLIMATE_FAN_FOCUS, | ||||
|     'DIFFUSE': ClimateFanMode.CLIMATE_FAN_DIFFUSE, | ||||
|     "ON": ClimateFanMode.CLIMATE_FAN_ON, | ||||
|     "OFF": ClimateFanMode.CLIMATE_FAN_OFF, | ||||
|     "AUTO": ClimateFanMode.CLIMATE_FAN_AUTO, | ||||
|     "LOW": ClimateFanMode.CLIMATE_FAN_LOW, | ||||
|     "MEDIUM": ClimateFanMode.CLIMATE_FAN_MEDIUM, | ||||
|     "HIGH": ClimateFanMode.CLIMATE_FAN_HIGH, | ||||
|     "MIDDLE": ClimateFanMode.CLIMATE_FAN_MIDDLE, | ||||
|     "FOCUS": ClimateFanMode.CLIMATE_FAN_FOCUS, | ||||
|     "DIFFUSE": ClimateFanMode.CLIMATE_FAN_DIFFUSE, | ||||
| } | ||||
|  | ||||
| validate_climate_fan_mode = cv.enum(CLIMATE_FAN_MODES, upper=True) | ||||
|  | ||||
| ClimateSwingMode = climate_ns.enum('ClimateSwingMode') | ||||
| ClimateSwingMode = climate_ns.enum("ClimateSwingMode") | ||||
| CLIMATE_SWING_MODES = { | ||||
|     'OFF': ClimateSwingMode.CLIMATE_SWING_OFF, | ||||
|     'BOTH': ClimateSwingMode.CLIMATE_SWING_BOTH, | ||||
|     'VERTICAL': ClimateSwingMode.CLIMATE_SWING_VERTICAL, | ||||
|     'HORIZONTAL': ClimateSwingMode.CLIMATE_SWING_HORIZONTAL, | ||||
|     "OFF": ClimateSwingMode.CLIMATE_SWING_OFF, | ||||
|     "BOTH": ClimateSwingMode.CLIMATE_SWING_BOTH, | ||||
|     "VERTICAL": ClimateSwingMode.CLIMATE_SWING_VERTICAL, | ||||
|     "HORIZONTAL": ClimateSwingMode.CLIMATE_SWING_HORIZONTAL, | ||||
| } | ||||
|  | ||||
| validate_climate_swing_mode = cv.enum(CLIMATE_SWING_MODES, upper=True) | ||||
|  | ||||
| # Actions | ||||
| ControlAction = climate_ns.class_('ControlAction', automation.Action) | ||||
| ControlAction = climate_ns.class_("ControlAction", automation.Action) | ||||
|  | ||||
| CLIMATE_SCHEMA = cv.MQTT_COMMAND_COMPONENT_SCHEMA.extend({ | ||||
| CLIMATE_SCHEMA = cv.MQTT_COMMAND_COMPONENT_SCHEMA.extend( | ||||
|     { | ||||
|         cv.GenerateID(): cv.declare_id(Climate), | ||||
|     cv.OnlyWith(CONF_MQTT_ID, 'mqtt'): cv.declare_id(mqtt.MQTTClimateComponent), | ||||
|     cv.Optional(CONF_VISUAL, default={}): cv.Schema({ | ||||
|         cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTClimateComponent), | ||||
|         cv.Optional(CONF_VISUAL, default={}): cv.Schema( | ||||
|             { | ||||
|                 cv.Optional(CONF_MIN_TEMPERATURE): cv.temperature, | ||||
|                 cv.Optional(CONF_MAX_TEMPERATURE): cv.temperature, | ||||
|                 cv.Optional(CONF_TEMPERATURE_STEP): cv.temperature, | ||||
|     }), | ||||
|             } | ||||
|         ), | ||||
|         # TODO: MQTT topic options | ||||
| }) | ||||
|     } | ||||
| ) | ||||
|  | ||||
|  | ||||
| @coroutine | ||||
| @@ -94,7 +111,8 @@ def register_climate(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.Optional(CONF_TARGET_TEMPERATURE): cv.templatable(cv.temperature), | ||||
| @@ -103,10 +121,13 @@ CLIMATE_CONTROL_ACTION_SCHEMA = cv.Schema({ | ||||
|         cv.Optional(CONF_AWAY): cv.templatable(cv.boolean), | ||||
|         cv.Optional(CONF_FAN_MODE): cv.templatable(validate_climate_fan_mode), | ||||
|         cv.Optional(CONF_SWING_MODE): cv.templatable(validate_climate_swing_mode), | ||||
| }) | ||||
|     } | ||||
| ) | ||||
|  | ||||
|  | ||||
| @automation.register_action('climate.control', ControlAction, CLIMATE_CONTROL_ACTION_SCHEMA) | ||||
| @automation.register_action( | ||||
|     "climate.control", ControlAction, CLIMATE_CONTROL_ACTION_SCHEMA | ||||
| ) | ||||
| def climate_control_to_code(config, action_id, template_arg, args): | ||||
|     paren = yield cg.get_variable(config[CONF_ID]) | ||||
|     var = cg.new_Pvariable(action_id, template_arg, paren) | ||||
| @@ -117,10 +138,14 @@ def climate_control_to_code(config, action_id, template_arg, args): | ||||
|         template_ = yield cg.templatable(config[CONF_TARGET_TEMPERATURE], args, float) | ||||
|         cg.add(var.set_target_temperature(template_)) | ||||
|     if CONF_TARGET_TEMPERATURE_LOW in config: | ||||
|         template_ = yield cg.templatable(config[CONF_TARGET_TEMPERATURE_LOW], args, float) | ||||
|         template_ = yield cg.templatable( | ||||
|             config[CONF_TARGET_TEMPERATURE_LOW], args, float | ||||
|         ) | ||||
|         cg.add(var.set_target_temperature_low(template_)) | ||||
|     if CONF_TARGET_TEMPERATURE_HIGH in config: | ||||
|         template_ = yield cg.templatable(config[CONF_TARGET_TEMPERATURE_HIGH], args, float) | ||||
|         template_ = yield cg.templatable( | ||||
|             config[CONF_TARGET_TEMPERATURE_HIGH], args, float | ||||
|         ) | ||||
|         cg.add(var.set_target_temperature_high(template_)) | ||||
|     if CONF_AWAY in config: | ||||
|         template_ = yield cg.templatable(config[CONF_AWAY], args, bool) | ||||
| @@ -129,12 +154,14 @@ def climate_control_to_code(config, action_id, template_arg, args): | ||||
|         template_ = yield cg.templatable(config[CONF_FAN_MODE], args, ClimateFanMode) | ||||
|         cg.add(var.set_fan_mode(template_)) | ||||
|     if CONF_SWING_MODE in config: | ||||
|         template_ = yield cg.templatable(config[CONF_SWING_MODE], args, ClimateSwingMode) | ||||
|         template_ = yield cg.templatable( | ||||
|             config[CONF_SWING_MODE], args, ClimateSwingMode | ||||
|         ) | ||||
|         cg.add(var.set_swing_mode(template_)) | ||||
|     yield var | ||||
|  | ||||
|  | ||||
| @coroutine_with_priority(100.0) | ||||
| def to_code(config): | ||||
|     cg.add_define('USE_CLIMATE') | ||||
|     cg.add_define("USE_CLIMATE") | ||||
|     cg.add_global(climate_ns.using) | ||||
|   | ||||
| @@ -1,27 +1,42 @@ | ||||
| import esphome.codegen as cg | ||||
| 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.const import CONF_SUPPORTS_COOL, CONF_SUPPORTS_HEAT, CONF_SENSOR | ||||
| from esphome.core import coroutine | ||||
|  | ||||
| AUTO_LOAD = ['sensor', 'remote_base'] | ||||
| CODEOWNERS = ['@glmnet'] | ||||
| AUTO_LOAD = ["sensor", "remote_base"] | ||||
| CODEOWNERS = ["@glmnet"] | ||||
|  | ||||
| climate_ir_ns = cg.esphome_ns.namespace('climate_ir') | ||||
| ClimateIR = climate_ir_ns.class_('ClimateIR', climate.Climate, cg.Component, | ||||
|                                  remote_base.RemoteReceiverListener) | ||||
| climate_ir_ns = cg.esphome_ns.namespace("climate_ir") | ||||
| ClimateIR = climate_ir_ns.class_( | ||||
|     "ClimateIR", climate.Climate, cg.Component, remote_base.RemoteReceiverListener | ||||
| ) | ||||
|  | ||||
| CLIMATE_IR_SCHEMA = climate.CLIMATE_SCHEMA.extend({ | ||||
|     cv.GenerateID(CONF_TRANSMITTER_ID): cv.use_id(remote_transmitter.RemoteTransmitterComponent), | ||||
| 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.Optional(CONF_SUPPORTS_HEAT, default=True): cv.boolean, | ||||
|         cv.Optional(CONF_SENSOR): cv.use_id(sensor.Sensor), | ||||
| }).extend(cv.COMPONENT_SCHEMA) | ||||
|     } | ||||
| ).extend(cv.COMPONENT_SCHEMA) | ||||
|  | ||||
| CLIMATE_IR_WITH_RECEIVER_SCHEMA = CLIMATE_IR_SCHEMA.extend({ | ||||
|     cv.Optional(CONF_RECEIVER_ID): cv.use_id(remote_receiver.RemoteReceiverComponent), | ||||
| }) | ||||
| CLIMATE_IR_WITH_RECEIVER_SCHEMA = CLIMATE_IR_SCHEMA.extend( | ||||
|     { | ||||
|         cv.Optional(CONF_RECEIVER_ID): cv.use_id( | ||||
|             remote_receiver.RemoteReceiverComponent | ||||
|         ), | ||||
|     } | ||||
| ) | ||||
|  | ||||
|  | ||||
| @coroutine | ||||
|   | ||||
| @@ -3,16 +3,45 @@ import esphome.config_validation as cv | ||||
| from esphome.components import climate_ir | ||||
| 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') | ||||
| LgIrClimate = climate_ir_lg_ns.class_('LgIrClimate', climate_ir.ClimateIR) | ||||
| climate_ir_lg_ns = cg.esphome_ns.namespace("climate_ir_lg") | ||||
| 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" | ||||
| 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): | ||||
|     var = cg.new_Pvariable(config[CONF_ID]) | ||||
|     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_AI = 0x03000; | ||||
| const uint32_t COMMAND_COOL = 0x08000; | ||||
| const uint32_t COMMAND_HEAT = 0x0C000; | ||||
| const uint32_t COMMAND_OFF = 0xC0000; | ||||
| const uint32_t COMMAND_SWING = 0x10000; | ||||
| // 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_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; | ||||
|  | ||||
| void LgIrClimate::transmit_state() { | ||||
| @@ -55,6 +49,9 @@ void LgIrClimate::transmit_state() { | ||||
|         case climate::CLIMATE_MODE_COOL: | ||||
|           remote_state |= COMMAND_COOL; | ||||
|           break; | ||||
|         case climate::CLIMATE_MODE_HEAT: | ||||
|           remote_state |= COMMAND_HEAT; | ||||
|           break; | ||||
|         case climate::CLIMATE_MODE_AUTO: | ||||
|           remote_state |= COMMAND_AUTO; | ||||
|           break; | ||||
| @@ -73,7 +70,8 @@ void LgIrClimate::transmit_state() { | ||||
|  | ||||
|     if (this->mode == climate::CLIMATE_MODE_OFF) { | ||||
|       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) { | ||||
|         case climate::CLIMATE_FAN_HIGH: | ||||
|           remote_state |= FAN_MAX; | ||||
| @@ -95,7 +93,7 @@ void LgIrClimate::transmit_state() { | ||||
|       this->fan_mode = climate::CLIMATE_FAN_AUTO; | ||||
|       // 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)); | ||||
|       remote_state |= ((temp - 15) << TEMP_SHIFT); | ||||
|     } | ||||
| @@ -108,13 +106,13 @@ bool LgIrClimate::on_receive(remote_base::RemoteReceiveData data) { | ||||
|   uint8_t nbits = 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; | ||||
|  | ||||
|   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; | ||||
|     } 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; | ||||
|     } else if (nbits == BITS) { | ||||
|       break; | ||||
| @@ -141,21 +139,23 @@ bool LgIrClimate::on_receive(remote_base::RemoteReceiveData data) { | ||||
|   } else { | ||||
|     if ((remote_state & COMMAND_MASK) == COMMAND_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; | ||||
|     else if ((remote_state & COMMAND_MASK) == COMMAND_HEAT) { | ||||
|       this->mode = climate::CLIMATE_MODE_HEAT; | ||||
|     } else { | ||||
|       this->mode = climate::CLIMATE_MODE_COOL; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|     // 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; | ||||
|  | ||||
|     // Fan Speed | ||||
|     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) { | ||||
|     } else if (this->mode == climate::CLIMATE_MODE_COOL || this->mode == climate::CLIMATE_MODE_HEAT || | ||||
|                this->mode == climate::CLIMATE_MODE_DRY) { | ||||
|       if ((remote_state & FAN_MASK) == FAN_AUTO) | ||||
|         this->fan_mode = climate::CLIMATE_FAN_AUTO; | ||||
|       else if ((remote_state & FAN_MASK) == FAN_MIN) | ||||
| @@ -165,6 +165,7 @@ bool LgIrClimate::on_receive(remote_base::RemoteReceiveData data) { | ||||
|       else if ((remote_state & FAN_MASK) == FAN_MAX) | ||||
|         this->fan_mode = climate::CLIMATE_FAN_HIGH; | ||||
|     } | ||||
|   } | ||||
|   this->publish_state(); | ||||
|  | ||||
|   return true; | ||||
| @@ -179,15 +180,16 @@ void LgIrClimate::transmit_(uint32_t value) { | ||||
|   data->set_carrier_frequency(38000); | ||||
|   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) { | ||||
|     if (value & mask) | ||||
|       data->item(BIT_HIGH_US, BIT_ONE_LOW_US); | ||||
|     else | ||||
|       data->item(BIT_HIGH_US, BIT_ZERO_LOW_US); | ||||
|     if (value & mask) { | ||||
|       data->item(this->bit_high_, this->bit_one_low_); | ||||
|     } else { | ||||
|       data->item(this->bit_high_, this->bit_zero_low_); | ||||
|     } | ||||
|   data->mark(BIT_HIGH_US); | ||||
|   } | ||||
|   data->mark(this->bit_high_); | ||||
|   transmit.perform(); | ||||
| } | ||||
| void LgIrClimate::calc_checksum_(uint32_t &value) { | ||||
|   | ||||
| @@ -25,6 +25,11 @@ class LgIrClimate : public climate_ir::ClimateIR { | ||||
|       this->swing_mode = climate::CLIMATE_SWING_OFF; | ||||
|     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: | ||||
|   /// 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 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}; | ||||
| }; | ||||
|  | ||||
|   | ||||
| @@ -2,22 +2,56 @@ from esphome import config_validation as cv | ||||
| from esphome import codegen as cg | ||||
| 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 | ||||
| CONFIG_SCHEMA = cv.Schema({ | ||||
|  | ||||
| CONF_RED_INT = "red_int" | ||||
| CONF_GREEN_INT = "green_int" | ||||
| CONF_BLUE_INT = "blue_int" | ||||
| CONF_WHITE_INT = "white_int" | ||||
|  | ||||
| CONFIG_SCHEMA = cv.Schema( | ||||
|     { | ||||
|         cv.Required(CONF_ID): cv.declare_id(ColorStruct), | ||||
|     cv.Optional(CONF_RED, default=0.0): cv.percentage, | ||||
|     cv.Optional(CONF_GREEN, default=0.0): cv.percentage, | ||||
|     cv.Optional(CONF_BLUE, default=0.0): cv.percentage, | ||||
|     cv.Optional(CONF_WHITE, default=0.0): cv.percentage, | ||||
| }).extend(cv.COMPONENT_SCHEMA) | ||||
|         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): | ||||
|     cg.variable(config[CONF_ID], cg.StructInitializer( | ||||
|         ColorStruct, | ||||
|         ('r', config[CONF_RED]), | ||||
|         ('g', config[CONF_GREEN]), | ||||
|         ('b', config[CONF_BLUE]), | ||||
|         ('w', config[CONF_WHITE]))) | ||||
|     r = 0 | ||||
|     if CONF_RED in config: | ||||
|         r = int(config[CONF_RED] * 255) | ||||
|     elif CONF_RED_INT in config: | ||||
|         r = config[CONF_RED_INT] | ||||
|  | ||||
|     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.const import CONF_ID | ||||
|  | ||||
| AUTO_LOAD = ['climate_ir'] | ||||
| CODEOWNERS = ['@glmnet'] | ||||
| AUTO_LOAD = ["climate_ir"] | ||||
| CODEOWNERS = ["@glmnet"] | ||||
|  | ||||
| coolix_ns = cg.esphome_ns.namespace('coolix') | ||||
| CoolixClimate = coolix_ns.class_('CoolixClimate', climate_ir.ClimateIR) | ||||
| coolix_ns = cg.esphome_ns.namespace("coolix") | ||||
| 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), | ||||
| }) | ||||
|     } | ||||
| ) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
|   | ||||
| @@ -3,54 +3,74 @@ import esphome.config_validation as cv | ||||
| from esphome import automation | ||||
| from esphome.automation import maybe_simple_id, Condition | ||||
| from esphome.components import mqtt | ||||
| from esphome.const import CONF_ID, CONF_INTERNAL, CONF_DEVICE_CLASS, CONF_STATE, \ | ||||
|     CONF_POSITION, CONF_TILT, CONF_STOP, CONF_MQTT_ID, CONF_NAME | ||||
| from esphome.const import ( | ||||
|     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 | ||||
|  | ||||
| IS_PLATFORM_COMPONENT = True | ||||
|  | ||||
| CODEOWNERS = ['@esphome/core'] | ||||
| CODEOWNERS = ["@esphome/core"] | ||||
| 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_CLOSED = cover_ns.COVER_CLOSED | ||||
|  | ||||
| COVER_STATES = { | ||||
|     'OPEN': COVER_OPEN, | ||||
|     'CLOSED': COVER_CLOSED, | ||||
|     "OPEN": COVER_OPEN, | ||||
|     "CLOSED": COVER_CLOSED, | ||||
| } | ||||
| validate_cover_state = cv.enum(COVER_STATES, upper=True) | ||||
|  | ||||
| CoverOperation = cover_ns.enum('CoverOperation') | ||||
| CoverOperation = cover_ns.enum("CoverOperation") | ||||
| COVER_OPERATIONS = { | ||||
|     'IDLE': CoverOperation.COVER_OPERATION_IDLE, | ||||
|     'OPENING': CoverOperation.COVER_OPERATION_OPENING, | ||||
|     'CLOSING': CoverOperation.COVER_OPERATION_CLOSING, | ||||
|     "IDLE": CoverOperation.COVER_OPERATION_IDLE, | ||||
|     "OPENING": CoverOperation.COVER_OPERATION_OPENING, | ||||
|     "CLOSING": CoverOperation.COVER_OPERATION_CLOSING, | ||||
| } | ||||
| validate_cover_operation = cv.enum(COVER_OPERATIONS, upper=True) | ||||
|  | ||||
| # Actions | ||||
| OpenAction = cover_ns.class_('OpenAction', automation.Action) | ||||
| CloseAction = cover_ns.class_('CloseAction', automation.Action) | ||||
| StopAction = cover_ns.class_('StopAction', automation.Action) | ||||
| ControlAction = cover_ns.class_('ControlAction', automation.Action) | ||||
| CoverPublishAction = cover_ns.class_('CoverPublishAction', automation.Action) | ||||
| CoverIsOpenCondition = cover_ns.class_('CoverIsOpenCondition', Condition) | ||||
| CoverIsClosedCondition = cover_ns.class_('CoverIsClosedCondition', Condition) | ||||
| OpenAction = cover_ns.class_("OpenAction", automation.Action) | ||||
| CloseAction = cover_ns.class_("CloseAction", automation.Action) | ||||
| StopAction = cover_ns.class_("StopAction", automation.Action) | ||||
| ControlAction = cover_ns.class_("ControlAction", automation.Action) | ||||
| CoverPublishAction = cover_ns.class_("CoverPublishAction", automation.Action) | ||||
| CoverIsOpenCondition = cover_ns.class_("CoverIsOpenCondition", Condition) | ||||
| CoverIsClosedCondition = cover_ns.class_("CoverIsClosedCondition", Condition) | ||||
|  | ||||
| COVER_SCHEMA = cv.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.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTCoverComponent), | ||||
|         cv.Optional(CONF_DEVICE_CLASS): cv.one_of(*DEVICE_CLASSES, lower=True), | ||||
|         # TODO: MQTT topic options | ||||
| }) | ||||
|     } | ||||
| ) | ||||
|  | ||||
|  | ||||
| @coroutine | ||||
| @@ -74,39 +94,43 @@ def register_cover(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), | ||||
| }) | ||||
|     } | ||||
| ) | ||||
|  | ||||
|  | ||||
| @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): | ||||
|     paren = yield cg.get_variable(config[CONF_ID]) | ||||
|     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): | ||||
|     paren = yield cg.get_variable(config[CONF_ID]) | ||||
|     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): | ||||
|     paren = yield cg.get_variable(config[CONF_ID]) | ||||
|     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.Exclusive(CONF_STATE, 'pos'): cv.templatable(validate_cover_state), | ||||
|     cv.Exclusive(CONF_POSITION, 'pos'): cv.templatable(cv.percentage), | ||||
|         cv.Exclusive(CONF_STATE, "pos"): cv.templatable(validate_cover_state), | ||||
|         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): | ||||
|     paren = yield cg.get_variable(config[CONF_ID]) | ||||
|     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) | ||||
| def to_code(config): | ||||
|     cg.add_define('USE_COVER') | ||||
|     cg.add_define("USE_COVER") | ||||
|     cg.add_global(cover_ns.using) | ||||
|   | ||||
| @@ -1,21 +1,45 @@ | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome.components import sensor, uart | ||||
| from esphome.const import CONF_CURRENT, CONF_ID, CONF_POWER, CONF_VOLTAGE, \ | ||||
|     UNIT_VOLT, ICON_FLASH, UNIT_AMPERE, UNIT_WATT | ||||
| from esphome.const import ( | ||||
|     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') | ||||
| CSE7766Component = cse7766_ns.class_('CSE7766Component', cg.PollingComponent, uart.UARTDevice) | ||||
| cse7766_ns = cg.esphome_ns.namespace("cse7766") | ||||
| CSE7766Component = cse7766_ns.class_( | ||||
|     "CSE7766Component", cg.PollingComponent, uart.UARTDevice | ||||
| ) | ||||
|  | ||||
| CONFIG_SCHEMA = cv.Schema({ | ||||
| CONFIG_SCHEMA = ( | ||||
|     cv.Schema( | ||||
|         { | ||||
|             cv.GenerateID(): cv.declare_id(CSE7766Component), | ||||
|  | ||||
|     cv.Optional(CONF_VOLTAGE): sensor.sensor_schema(UNIT_VOLT, ICON_FLASH, 1), | ||||
|     cv.Optional(CONF_CURRENT): sensor.sensor_schema(UNIT_AMPERE, ICON_FLASH, 2), | ||||
|     cv.Optional(CONF_POWER): sensor.sensor_schema(UNIT_WATT, ICON_FLASH, 1), | ||||
| }).extend(cv.polling_component_schema('60s')).extend(uart.UART_DEVICE_SCHEMA) | ||||
|             cv.Optional(CONF_VOLTAGE): sensor.sensor_schema( | ||||
|                 UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE | ||||
|             ), | ||||
|             cv.Optional(CONF_CURRENT): sensor.sensor_schema( | ||||
|                 UNIT_AMPERE, ICON_EMPTY, 2, DEVICE_CLASS_CURRENT | ||||
|             ), | ||||
|             cv.Optional(CONF_POWER): sensor.sensor_schema( | ||||
|                 UNIT_WATT, ICON_EMPTY, 1, DEVICE_CLASS_POWER | ||||
|             ), | ||||
|         } | ||||
|     ) | ||||
|     .extend(cv.polling_component_schema("60s")) | ||||
|     .extend(uart.UART_DEVICE_SCHEMA) | ||||
| ) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
|   | ||||
| @@ -1,21 +1,35 @@ | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| 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'] | ||||
| CODEOWNERS = ['@jesserockz'] | ||||
| AUTO_LOAD = ["voltage_sampler"] | ||||
| CODEOWNERS = ["@jesserockz"] | ||||
|  | ||||
| CONF_SAMPLE_DURATION = 'sample_duration' | ||||
| CONF_SAMPLE_DURATION = "sample_duration" | ||||
|  | ||||
| ct_clamp_ns = cg.esphome_ns.namespace('ct_clamp') | ||||
| CTClampSensor = ct_clamp_ns.class_('CTClampSensor', sensor.Sensor, cg.PollingComponent) | ||||
| ct_clamp_ns = cg.esphome_ns.namespace("ct_clamp") | ||||
| CTClampSensor = ct_clamp_ns.class_("CTClampSensor", sensor.Sensor, cg.PollingComponent) | ||||
|  | ||||
| CONFIG_SCHEMA = sensor.sensor_schema(UNIT_AMPERE, ICON_FLASH, 2).extend({ | ||||
| CONFIG_SCHEMA = ( | ||||
|     sensor.sensor_schema(UNIT_AMPERE, ICON_EMPTY, 2, DEVICE_CLASS_CURRENT) | ||||
|     .extend( | ||||
|         { | ||||
|             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')) | ||||
|             cv.Optional( | ||||
|                 CONF_SAMPLE_DURATION, default="200ms" | ||||
|             ): cv.positive_time_period_milliseconds, | ||||
|         } | ||||
|     ) | ||||
|     .extend(cv.polling_component_schema("60s")) | ||||
| ) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
|   | ||||
| @@ -1,3 +1,3 @@ | ||||
| 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 .. 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.Required(CONF_BINARY_SENSORS): cv.ensure_list(binary_sensor.BINARY_SENSOR_SCHEMA), | ||||
| }) | ||||
|         cv.Required(CONF_BINARY_SENSORS): cv.ensure_list( | ||||
|             binary_sensor.BINARY_SENSOR_SCHEMA | ||||
|         ), | ||||
|     } | ||||
| ) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
|     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_) | ||||
|     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 .. import custom_ns | ||||
|  | ||||
| CustomClimateConstructor = custom_ns.class_('CustomClimateConstructor') | ||||
| CONF_CLIMATES = 'climates' | ||||
| CustomClimateConstructor = custom_ns.class_("CustomClimateConstructor") | ||||
| 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.Required(CONF_CLIMATES): cv.ensure_list(climate.CLIMATE_SCHEMA), | ||||
| }) | ||||
|     } | ||||
| ) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
|     template_ = yield cg.process_lambda( | ||||
|         config[CONF_LAMBDA], [], | ||||
|         return_type=cg.std_vector.template(climate.Climate.operator('ptr'))) | ||||
|         config[CONF_LAMBDA], | ||||
|         [], | ||||
|         return_type=cg.std_vector.template(climate.Climate.operator("ptr")), | ||||
|     ) | ||||
|  | ||||
|     rhs = CustomClimateConstructor(template_) | ||||
|     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 .. import custom_ns | ||||
|  | ||||
| CustomCoverConstructor = custom_ns.class_('CustomCoverConstructor') | ||||
| CONF_COVERS = 'covers' | ||||
| CustomCoverConstructor = custom_ns.class_("CustomCoverConstructor") | ||||
| 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.Required(CONF_COVERS): cv.ensure_list(cover.COVER_SCHEMA), | ||||
| }) | ||||
|     } | ||||
| ) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
|     template_ = yield cg.process_lambda( | ||||
|         config[CONF_LAMBDA], [], | ||||
|         return_type=cg.std_vector.template(cover.Cover.operator('ptr'))) | ||||
|         config[CONF_LAMBDA], | ||||
|         [], | ||||
|         return_type=cg.std_vector.template(cover.Cover.operator("ptr")), | ||||
|     ) | ||||
|  | ||||
|     rhs = CustomCoverConstructor(template_) | ||||
|     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 .. import custom_ns | ||||
|  | ||||
| CustomLightOutputConstructor = custom_ns.class_('CustomLightOutputConstructor') | ||||
| CONF_LIGHTS = 'lights' | ||||
| CustomLightOutputConstructor = custom_ns.class_("CustomLightOutputConstructor") | ||||
| 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.Required(CONF_LIGHTS): cv.ensure_list(light.ADDRESSABLE_LIGHT_SCHEMA), | ||||
| }) | ||||
|     } | ||||
| ) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
|     template_ = yield cg.process_lambda( | ||||
|         config[CONF_LAMBDA], [], | ||||
|         return_type=cg.std_vector.template(light.LightOutput.operator('ptr'))) | ||||
|         config[CONF_LAMBDA], | ||||
|         [], | ||||
|         return_type=cg.std_vector.template(light.LightOutput.operator("ptr")), | ||||
|     ) | ||||
|  | ||||
|     rhs = CustomLightOutputConstructor(template_) | ||||
|     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 .. import custom_ns | ||||
|  | ||||
| CustomBinaryOutputConstructor = custom_ns.class_('CustomBinaryOutputConstructor') | ||||
| CustomFloatOutputConstructor = custom_ns.class_('CustomFloatOutputConstructor') | ||||
| CustomBinaryOutputConstructor = custom_ns.class_("CustomBinaryOutputConstructor") | ||||
| CustomFloatOutputConstructor = custom_ns.class_("CustomFloatOutputConstructor") | ||||
|  | ||||
| CONF_FLOAT = 'float' | ||||
| CONF_FLOAT = "float" | ||||
|  | ||||
| CONFIG_SCHEMA = cv.typed_schema({ | ||||
|     CONF_BINARY: cv.Schema({ | ||||
| CONFIG_SCHEMA = cv.typed_schema( | ||||
|     { | ||||
|         CONF_BINARY: cv.Schema( | ||||
|             { | ||||
|                 cv.GenerateID(): cv.declare_id(CustomBinaryOutputConstructor), | ||||
|                 cv.Required(CONF_LAMBDA): cv.returning_lambda, | ||||
|         cv.Required(CONF_OUTPUTS): | ||||
|             cv.ensure_list(output.BINARY_OUTPUT_SCHEMA.extend({ | ||||
|                 cv.Required(CONF_OUTPUTS): cv.ensure_list( | ||||
|                     output.BINARY_OUTPUT_SCHEMA.extend( | ||||
|                         { | ||||
|                             cv.GenerateID(): cv.declare_id(output.BinaryOutput), | ||||
|             })), | ||||
|     }), | ||||
|     CONF_FLOAT: cv.Schema({ | ||||
|                         } | ||||
|                     ) | ||||
|                 ), | ||||
|             } | ||||
|         ), | ||||
|         CONF_FLOAT: cv.Schema( | ||||
|             { | ||||
|                 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.Required(CONF_OUTPUTS): cv.ensure_list( | ||||
|                     output.FLOAT_OUTPUT_SCHEMA.extend( | ||||
|                         { | ||||
|                             cv.GenerateID(): cv.declare_id(output.FloatOutput), | ||||
|             })), | ||||
|     }) | ||||
| }, lower=True) | ||||
|                         } | ||||
|                     ) | ||||
|                 ), | ||||
|             } | ||||
|         ), | ||||
|     }, | ||||
|     lower=True, | ||||
| ) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
|     type = config[CONF_TYPE] | ||||
|     if type == 'binary': | ||||
|     if type == "binary": | ||||
|         ret_type = output.BinaryOutputPtr | ||||
|         klass = CustomBinaryOutputConstructor | ||||
|     else: | ||||
|         ret_type = output.FloatOutputPtr | ||||
|         klass = CustomFloatOutputConstructor | ||||
|     template_ = yield cg.process_lambda(config[CONF_LAMBDA], [], | ||||
|                                         return_type=cg.std_vector.template(ret_type)) | ||||
|     template_ = yield cg.process_lambda( | ||||
|         config[CONF_LAMBDA], [], return_type=cg.std_vector.template(ret_type) | ||||
|     ) | ||||
|  | ||||
|     rhs = klass(template_) | ||||
|     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 .. 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.Required(CONF_SENSORS): cv.ensure_list(sensor.SENSOR_SCHEMA), | ||||
| }) | ||||
|     } | ||||
| ) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
|     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_) | ||||
|     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 .. 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.Required(CONF_SWITCHES): | ||||
|         cv.ensure_list(switch.SWITCH_SCHEMA.extend({ | ||||
|         cv.Required(CONF_SWITCHES): cv.ensure_list( | ||||
|             switch.SWITCH_SCHEMA.extend( | ||||
|                 { | ||||
|                     cv.GenerateID(): cv.declare_id(switch.Switch), | ||||
|         })), | ||||
| }) | ||||
|                 } | ||||
|             ) | ||||
|         ), | ||||
|     } | ||||
| ) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
|     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_) | ||||
|     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 .. 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.Required(CONF_TEXT_SENSORS): | ||||
|         cv.ensure_list(text_sensor.TEXT_SENSOR_SCHEMA.extend({ | ||||
|         cv.Required(CONF_TEXT_SENSORS): cv.ensure_list( | ||||
|             text_sensor.TEXT_SENSOR_SCHEMA.extend( | ||||
|                 { | ||||
|                     cv.GenerateID(): cv.declare_id(text_sensor.TextSensor), | ||||
|         })), | ||||
| }) | ||||
|                 } | ||||
|             ) | ||||
|         ), | ||||
|     } | ||||
| ) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
|     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_) | ||||
|     var = cg.variable(config[CONF_ID], rhs) | ||||
|   | ||||
| @@ -2,22 +2,27 @@ import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome.const import CONF_COMPONENTS, CONF_ID, CONF_LAMBDA | ||||
|  | ||||
| custom_component_ns = cg.esphome_ns.namespace('custom_component') | ||||
| CustomComponentConstructor = custom_component_ns.class_('CustomComponentConstructor') | ||||
| custom_component_ns = cg.esphome_ns.namespace("custom_component") | ||||
| CustomComponentConstructor = custom_component_ns.class_("CustomComponentConstructor") | ||||
|  | ||||
| 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.Optional(CONF_COMPONENTS): cv.ensure_list(cv.Schema({ | ||||
|         cv.GenerateID(): cv.declare_id(cg.Component) | ||||
|     }).extend(cv.COMPONENT_SCHEMA)), | ||||
| }) | ||||
|         cv.Optional(CONF_COMPONENTS): cv.ensure_list( | ||||
|             cv.Schema({cv.GenerateID(): cv.declare_id(cg.Component)}).extend( | ||||
|                 cv.COMPONENT_SCHEMA | ||||
|             ) | ||||
|         ), | ||||
|     } | ||||
| ) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
|     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_) | ||||
|     var = cg.variable(config[CONF_ID], rhs) | ||||
|   | ||||
| @@ -1,22 +1,29 @@ | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome.components import light, output | ||||
| from esphome.const import CONF_OUTPUT_ID, CONF_COLD_WHITE, CONF_WARM_WHITE, \ | ||||
|     CONF_COLD_WHITE_COLOR_TEMPERATURE, CONF_WARM_WHITE_COLOR_TEMPERATURE | ||||
| from esphome.const import ( | ||||
|     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') | ||||
| CWWWLightOutput = cwww_ns.class_('CWWWLightOutput', light.LightOutput) | ||||
| cwww_ns = cg.esphome_ns.namespace("cwww") | ||||
| 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.Required(CONF_WARM_WHITE): cv.use_id(output.FloatOutput), | ||||
|         cv.Required(CONF_COLD_WHITE_COLOR_TEMPERATURE): cv.color_temperature, | ||||
|         cv.Required(CONF_WARM_WHITE_COLOR_TEMPERATURE): cv.color_temperature, | ||||
|         cv.Optional(CONF_CONSTANT_BRIGHTNESS, default=False): cv.boolean, | ||||
| }) | ||||
|     } | ||||
| ) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
|   | ||||
| @@ -3,14 +3,16 @@ import esphome.config_validation as cv | ||||
| from esphome.components import climate_ir | ||||
| from esphome.const import CONF_ID | ||||
|  | ||||
| AUTO_LOAD = ['climate_ir'] | ||||
| AUTO_LOAD = ["climate_ir"] | ||||
|  | ||||
| daikin_ns = cg.esphome_ns.namespace('daikin') | ||||
| DaikinClimate = daikin_ns.class_('DaikinClimate', climate_ir.ClimateIR) | ||||
| daikin_ns = cg.esphome_ns.namespace("daikin") | ||||
| 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), | ||||
| }) | ||||
|     } | ||||
| ) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
|   | ||||
| @@ -4,18 +4,20 @@ from esphome import pins | ||||
| from esphome.const import CONF_ID, CONF_PIN | ||||
|  | ||||
| MULTI_CONF = True | ||||
| AUTO_LOAD = ['sensor'] | ||||
| AUTO_LOAD = ["sensor"] | ||||
|  | ||||
| CONF_ONE_WIRE_ID = 'one_wire_id' | ||||
| dallas_ns = cg.esphome_ns.namespace('dallas') | ||||
| DallasComponent = dallas_ns.class_('DallasComponent', cg.PollingComponent) | ||||
| ESPOneWire = dallas_ns.class_('ESPOneWire') | ||||
| CONF_ONE_WIRE_ID = "one_wire_id" | ||||
| dallas_ns = cg.esphome_ns.namespace("dallas") | ||||
| DallasComponent = dallas_ns.class_("DallasComponent", cg.PollingComponent) | ||||
| 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.Required(CONF_PIN): pins.gpio_input_pin_schema, | ||||
| }).extend(cv.polling_component_schema('60s')) | ||||
|     } | ||||
| ).extend(cv.polling_component_schema("60s")) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
|   | ||||
| @@ -1,20 +1,32 @@ | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome.components import sensor | ||||
| from esphome.const import CONF_ADDRESS, CONF_DALLAS_ID, CONF_INDEX, CONF_RESOLUTION, UNIT_CELSIUS, \ | ||||
|     ICON_THERMOMETER, CONF_ID | ||||
| from esphome.const import ( | ||||
|     CONF_ADDRESS, | ||||
|     CONF_DALLAS_ID, | ||||
|     CONF_INDEX, | ||||
|     CONF_RESOLUTION, | ||||
|     DEVICE_CLASS_TEMPERATURE, | ||||
|     ICON_EMPTY, | ||||
|     UNIT_CELSIUS, | ||||
|     CONF_ID, | ||||
| ) | ||||
| 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( | ||||
|     sensor.sensor_schema(UNIT_CELSIUS, ICON_EMPTY, 1, DEVICE_CLASS_TEMPERATURE).extend( | ||||
|         { | ||||
|             cv.GenerateID(): cv.declare_id(DallasTemperatureSensor), | ||||
|             cv.GenerateID(CONF_DALLAS_ID): cv.use_id(DallasComponent), | ||||
|  | ||||
|             cv.Optional(CONF_ADDRESS): cv.hex_int, | ||||
|             cv.Optional(CONF_INDEX): cv.positive_int, | ||||
|             cv.Optional(CONF_RESOLUTION, default=12): cv.int_range(min=9, max=12), | ||||
| }), cv.has_exactly_one_key(CONF_ADDRESS, CONF_INDEX)) | ||||
|         } | ||||
|     ), | ||||
|     cv.has_exactly_one_key(CONF_ADDRESS, CONF_INDEX), | ||||
| ) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
|   | ||||
| @@ -2,14 +2,16 @@ import esphome.config_validation as cv | ||||
| import esphome.codegen as cg | ||||
| from esphome.const import CONF_ID | ||||
|  | ||||
| CODEOWNERS = ['@OttoWinter'] | ||||
| DEPENDENCIES = ['logger'] | ||||
| CODEOWNERS = ["@OttoWinter"] | ||||
| DEPENDENCIES = ["logger"] | ||||
|  | ||||
| debug_ns = cg.esphome_ns.namespace('debug') | ||||
| DebugComponent = debug_ns.class_('DebugComponent', cg.Component) | ||||
| CONFIG_SCHEMA = cv.Schema({ | ||||
| debug_ns = cg.esphome_ns.namespace("debug") | ||||
| DebugComponent = debug_ns.class_("DebugComponent", cg.Component) | ||||
| CONFIG_SCHEMA = cv.Schema( | ||||
|     { | ||||
|         cv.GenerateID(): cv.declare_id(DebugComponent), | ||||
| }).extend(cv.COMPONENT_SCHEMA) | ||||
|     } | ||||
| ).extend(cv.COMPONENT_SCHEMA) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
|   | ||||
| @@ -1,58 +1,81 @@ | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome import pins, automation | ||||
| from esphome.const import CONF_ID, CONF_MODE, CONF_NUMBER, CONF_PINS, CONF_RUN_CYCLES, \ | ||||
|     CONF_RUN_DURATION, CONF_SLEEP_DURATION, CONF_WAKEUP_PIN | ||||
| from esphome.const import ( | ||||
|     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): | ||||
|     valid_pins = [0, 2, 4, 12, 13, 14, 15, 25, 26, 27, 32, 33, 34, 35, 36, 37, 38, 39] | ||||
|     if value[CONF_NUMBER] not in valid_pins: | ||||
|         raise cv.Invalid("Only pins {} support wakeup" | ||||
|                          "".format(', '.join(str(x) for x in valid_pins))) | ||||
|         raise cv.Invalid( | ||||
|             "Only pins {} support wakeup" | ||||
|             "".format(", ".join(str(x) for x in valid_pins)) | ||||
|         ) | ||||
|     return value | ||||
|  | ||||
|  | ||||
| deep_sleep_ns = cg.esphome_ns.namespace('deep_sleep') | ||||
| DeepSleepComponent = deep_sleep_ns.class_('DeepSleepComponent', cg.Component) | ||||
| EnterDeepSleepAction = deep_sleep_ns.class_('EnterDeepSleepAction', automation.Action) | ||||
| PreventDeepSleepAction = deep_sleep_ns.class_('PreventDeepSleepAction', automation.Action) | ||||
| deep_sleep_ns = cg.esphome_ns.namespace("deep_sleep") | ||||
| DeepSleepComponent = deep_sleep_ns.class_("DeepSleepComponent", cg.Component) | ||||
| EnterDeepSleepAction = deep_sleep_ns.class_("EnterDeepSleepAction", 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 = { | ||||
|     'IGNORE': WakeupPinMode.WAKEUP_PIN_MODE_IGNORE, | ||||
|     'KEEP_AWAKE': WakeupPinMode.WAKEUP_PIN_MODE_KEEP_AWAKE, | ||||
|     'INVERT_WAKEUP': WakeupPinMode.WAKEUP_PIN_MODE_INVERT_WAKEUP, | ||||
|     "IGNORE": WakeupPinMode.WAKEUP_PIN_MODE_IGNORE, | ||||
|     "KEEP_AWAKE": WakeupPinMode.WAKEUP_PIN_MODE_KEEP_AWAKE, | ||||
|     "INVERT_WAKEUP": WakeupPinMode.WAKEUP_PIN_MODE_INVERT_WAKEUP, | ||||
| } | ||||
|  | ||||
| esp_sleep_ext1_wakeup_mode_t = cg.global_ns.enum('esp_sleep_ext1_wakeup_mode_t') | ||||
| Ext1Wakeup = deep_sleep_ns.struct('Ext1Wakeup') | ||||
| esp_sleep_ext1_wakeup_mode_t = cg.global_ns.enum("esp_sleep_ext1_wakeup_mode_t") | ||||
| Ext1Wakeup = deep_sleep_ns.struct("Ext1Wakeup") | ||||
| EXT1_WAKEUP_MODES = { | ||||
|     '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, | ||||
|     "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, | ||||
| } | ||||
|  | ||||
| CONF_WAKEUP_PIN_MODE = 'wakeup_pin_mode' | ||||
| CONF_ESP32_EXT1_WAKEUP = 'esp32_ext1_wakeup' | ||||
| CONF_WAKEUP_PIN_MODE = "wakeup_pin_mode" | ||||
| 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.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, | ||||
|                                          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_ESP32_EXT1_WAKEUP): cv.All(cv.only_on_esp32, cv.Schema({ | ||||
|         cv.Required(CONF_PINS): cv.ensure_list(pins.shorthand_input_pin, validate_pin_number), | ||||
|         cv.Optional(CONF_WAKEUP_PIN): cv.All( | ||||
|             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_ESP32_EXT1_WAKEUP): cv.All( | ||||
|             cv.only_on_esp32, | ||||
|             cv.Schema( | ||||
|                 { | ||||
|                     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_RUN_CYCLES): cv.invalid("The run_cycles option has been removed in 1.11.0 as " | ||||
|                 } | ||||
|             ), | ||||
|         ), | ||||
|         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) | ||||
|             "Please use run_duration now." | ||||
|         ), | ||||
|     } | ||||
| ).extend(cv.COMPONENT_SCHEMA) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
| @@ -75,27 +98,43 @@ def to_code(config): | ||||
|         for pin in conf[CONF_PINS]: | ||||
|             mask |= 1 << pin[CONF_NUMBER] | ||||
|         struct = cg.StructInitializer( | ||||
|             Ext1Wakeup, | ||||
|             ('mask', mask), | ||||
|             ('wakeup_mode', conf[CONF_MODE]) | ||||
|             Ext1Wakeup, ("mask", mask), ("wakeup_mode", conf[CONF_MODE]) | ||||
|         ) | ||||
|         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.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): | ||||
|     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): | ||||
|     paren = yield cg.get_variable(config[CONF_ID]) | ||||
|     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...> { | ||||
|  public: | ||||
|   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: | ||||
|   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.components import uart | ||||
|  | ||||
| DEPENDENCIES = ['uart'] | ||||
| CODEOWNERS = ['@glmnet'] | ||||
| DEPENDENCIES = ["uart"] | ||||
| CODEOWNERS = ["@glmnet"] | ||||
|  | ||||
| dfplayer_ns = cg.esphome_ns.namespace('dfplayer') | ||||
| DFPlayer = dfplayer_ns.class_('DFPlayer', cg.Component) | ||||
| DFPlayerFinishedPlaybackTrigger = dfplayer_ns.class_('DFPlayerFinishedPlaybackTrigger', | ||||
|                                                      automation.Trigger.template()) | ||||
| DFPlayerIsPlayingCondition = dfplayer_ns.class_('DFPlayerIsPlayingCondition', automation.Condition) | ||||
| dfplayer_ns = cg.esphome_ns.namespace("dfplayer") | ||||
| DFPlayer = dfplayer_ns.class_("DFPlayer", cg.Component) | ||||
| DFPlayerFinishedPlaybackTrigger = dfplayer_ns.class_( | ||||
|     "DFPlayerFinishedPlaybackTrigger", automation.Trigger.template() | ||||
| ) | ||||
| DFPlayerIsPlayingCondition = dfplayer_ns.class_( | ||||
|     "DFPlayerIsPlayingCondition", automation.Condition | ||||
| ) | ||||
|  | ||||
| MULTI_CONF = True | ||||
| CONF_FOLDER = 'folder' | ||||
| CONF_LOOP = 'loop' | ||||
| CONF_VOLUME = 'volume' | ||||
| CONF_EQ_PRESET = 'eq_preset' | ||||
| CONF_ON_FINISHED_PLAYBACK = 'on_finished_playback' | ||||
| CONF_FOLDER = "folder" | ||||
| CONF_LOOP = "loop" | ||||
| CONF_VOLUME = "volume" | ||||
| CONF_EQ_PRESET = "eq_preset" | ||||
| CONF_ON_FINISHED_PLAYBACK = "on_finished_playback" | ||||
|  | ||||
| EqPreset = dfplayer_ns.enum("EqPreset") | ||||
| EQ_PRESET = { | ||||
|     'NORMAL': EqPreset.NORMAL, | ||||
|     'POP': EqPreset.POP, | ||||
|     'ROCK': EqPreset.ROCK, | ||||
|     'JAZZ': EqPreset.JAZZ, | ||||
|     'CLASSIC': EqPreset.CLASSIC, | ||||
|     'BASS': EqPreset.BASS, | ||||
|     "NORMAL": EqPreset.NORMAL, | ||||
|     "POP": EqPreset.POP, | ||||
|     "ROCK": EqPreset.ROCK, | ||||
|     "JAZZ": EqPreset.JAZZ, | ||||
|     "CLASSIC": EqPreset.CLASSIC, | ||||
|     "BASS": EqPreset.BASS, | ||||
| } | ||||
| Device = dfplayer_ns.enum("Device") | ||||
| DEVICE = { | ||||
|     'USB': Device.USB, | ||||
|     'TF_CARD': Device.TF_CARD, | ||||
|     "USB": Device.USB, | ||||
|     "TF_CARD": Device.TF_CARD, | ||||
| } | ||||
|  | ||||
| NextAction = dfplayer_ns.class_('NextAction', automation.Action) | ||||
| PreviousAction = dfplayer_ns.class_('PreviousAction', automation.Action) | ||||
| PlayFileAction = dfplayer_ns.class_('PlayFileAction', automation.Action) | ||||
| PlayFolderAction = dfplayer_ns.class_('PlayFolderAction', automation.Action) | ||||
| SetVolumeAction = dfplayer_ns.class_('SetVolumeAction', automation.Action) | ||||
| SetEqAction = dfplayer_ns.class_('SetEqAction', automation.Action) | ||||
| SleepAction = dfplayer_ns.class_('SleepAction', automation.Action) | ||||
| ResetAction = dfplayer_ns.class_('ResetAction', automation.Action) | ||||
| StartAction = dfplayer_ns.class_('StartAction', automation.Action) | ||||
| PauseAction = dfplayer_ns.class_('PauseAction', automation.Action) | ||||
| StopAction = dfplayer_ns.class_('StopAction', automation.Action) | ||||
| RandomAction = dfplayer_ns.class_('RandomAction', automation.Action) | ||||
| SetDeviceAction = dfplayer_ns.class_('SetDeviceAction', automation.Action) | ||||
| NextAction = dfplayer_ns.class_("NextAction", automation.Action) | ||||
| PreviousAction = dfplayer_ns.class_("PreviousAction", automation.Action) | ||||
| PlayFileAction = dfplayer_ns.class_("PlayFileAction", automation.Action) | ||||
| PlayFolderAction = dfplayer_ns.class_("PlayFolderAction", automation.Action) | ||||
| SetVolumeAction = dfplayer_ns.class_("SetVolumeAction", automation.Action) | ||||
| SetEqAction = dfplayer_ns.class_("SetEqAction", automation.Action) | ||||
| SleepAction = dfplayer_ns.class_("SleepAction", automation.Action) | ||||
| ResetAction = dfplayer_ns.class_("ResetAction", automation.Action) | ||||
| StartAction = dfplayer_ns.class_("StartAction", automation.Action) | ||||
| PauseAction = dfplayer_ns.class_("PauseAction", automation.Action) | ||||
| StopAction = dfplayer_ns.class_("StopAction", automation.Action) | ||||
| RandomAction = dfplayer_ns.class_("RandomAction", automation.Action) | ||||
| SetDeviceAction = dfplayer_ns.class_("SetDeviceAction", automation.Action) | ||||
|  | ||||
| CONFIG_SCHEMA = cv.All(cv.Schema({ | ||||
| CONFIG_SCHEMA = cv.All( | ||||
|     cv.Schema( | ||||
|         { | ||||
|             cv.GenerateID(): cv.declare_id(DFPlayer), | ||||
|     cv.Optional(CONF_ON_FINISHED_PLAYBACK): automation.validate_automation({ | ||||
|         cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(DFPlayerFinishedPlaybackTrigger), | ||||
|     }), | ||||
| }).extend(uart.UART_DEVICE_SCHEMA)) | ||||
|             cv.Optional(CONF_ON_FINISHED_PLAYBACK): automation.validate_automation( | ||||
|                 { | ||||
|                     cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( | ||||
|                         DFPlayerFinishedPlaybackTrigger | ||||
|                     ), | ||||
|                 } | ||||
|             ), | ||||
|         } | ||||
|     ).extend(uart.UART_DEVICE_SCHEMA) | ||||
| ) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
| @@ -67,29 +78,48 @@ def to_code(config): | ||||
|         yield automation.build_automation(trigger, [], conf) | ||||
|  | ||||
|  | ||||
| @automation.register_action('dfplayer.play_next', NextAction, cv.Schema({ | ||||
| @automation.register_action( | ||||
|     "dfplayer.play_next", | ||||
|     NextAction, | ||||
|     cv.Schema( | ||||
|         { | ||||
|             cv.GenerateID(): cv.use_id(DFPlayer), | ||||
| })) | ||||
|         } | ||||
|     ), | ||||
| ) | ||||
| def dfplayer_next_to_code(config, action_id, template_arg, args): | ||||
|     var = cg.new_Pvariable(action_id, template_arg) | ||||
|     yield cg.register_parented(var, config[CONF_ID]) | ||||
|     yield var | ||||
|  | ||||
|  | ||||
| @automation.register_action('dfplayer.play_previous', PreviousAction, cv.Schema({ | ||||
| @automation.register_action( | ||||
|     "dfplayer.play_previous", | ||||
|     PreviousAction, | ||||
|     cv.Schema( | ||||
|         { | ||||
|             cv.GenerateID(): cv.use_id(DFPlayer), | ||||
| })) | ||||
|         } | ||||
|     ), | ||||
| ) | ||||
| def dfplayer_previous_to_code(config, action_id, template_arg, args): | ||||
|     var = cg.new_Pvariable(action_id, template_arg) | ||||
|     yield cg.register_parented(var, config[CONF_ID]) | ||||
|     yield var | ||||
|  | ||||
|  | ||||
| @automation.register_action('dfplayer.play', PlayFileAction, cv.maybe_simple_value({ | ||||
| @automation.register_action( | ||||
|     "dfplayer.play", | ||||
|     PlayFileAction, | ||||
|     cv.maybe_simple_value( | ||||
|         { | ||||
|             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)) | ||||
|         }, | ||||
|         key=CONF_FILE, | ||||
|     ), | ||||
| ) | ||||
| def dfplayer_play_to_code(config, action_id, template_arg, args): | ||||
|     var = cg.new_Pvariable(action_id, template_arg) | ||||
|     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 | ||||
|  | ||||
|  | ||||
| @automation.register_action('dfplayer.play_folder', PlayFolderAction, cv.Schema({ | ||||
| @automation.register_action( | ||||
|     "dfplayer.play_folder", | ||||
|     PlayFolderAction, | ||||
|     cv.Schema( | ||||
|         { | ||||
|             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): | ||||
|     var = cg.new_Pvariable(action_id, template_arg) | ||||
|     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 | ||||
|  | ||||
|  | ||||
| @automation.register_action('dfplayer.set_device', SetDeviceAction, cv.maybe_simple_value({ | ||||
| @automation.register_action( | ||||
|     "dfplayer.set_device", | ||||
|     SetDeviceAction, | ||||
|     cv.maybe_simple_value( | ||||
|         { | ||||
|             cv.GenerateID(): cv.use_id(DFPlayer), | ||||
|             cv.Required(CONF_DEVICE): cv.enum(DEVICE, upper=True), | ||||
| }, key=CONF_DEVICE)) | ||||
|         }, | ||||
|         key=CONF_DEVICE, | ||||
|     ), | ||||
| ) | ||||
| def dfplayer_set_device_to_code(config, action_id, template_arg, args): | ||||
|     var = cg.new_Pvariable(action_id, template_arg) | ||||
|     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 | ||||
|  | ||||
|  | ||||
| @automation.register_action('dfplayer.set_volume', SetVolumeAction, cv.maybe_simple_value({ | ||||
| @automation.register_action( | ||||
|     "dfplayer.set_volume", | ||||
|     SetVolumeAction, | ||||
|     cv.maybe_simple_value( | ||||
|         { | ||||
|             cv.GenerateID(): cv.use_id(DFPlayer), | ||||
|             cv.Required(CONF_VOLUME): cv.templatable(cv.int_), | ||||
| }, key=CONF_VOLUME)) | ||||
|         }, | ||||
|         key=CONF_VOLUME, | ||||
|     ), | ||||
| ) | ||||
| def dfplayer_set_volume_to_code(config, action_id, template_arg, args): | ||||
|     var = cg.new_Pvariable(action_id, template_arg) | ||||
|     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 | ||||
|  | ||||
|  | ||||
| @automation.register_action('dfplayer.set_eq', SetEqAction, cv.maybe_simple_value({ | ||||
| @automation.register_action( | ||||
|     "dfplayer.set_eq", | ||||
|     SetEqAction, | ||||
|     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)) | ||||
|         }, | ||||
|         key=CONF_EQ_PRESET, | ||||
|     ), | ||||
| ) | ||||
| def dfplayer_set_eq_to_code(config, action_id, template_arg, args): | ||||
|     var = cg.new_Pvariable(action_id, template_arg) | ||||
|     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 | ||||
|  | ||||
|  | ||||
| @automation.register_action('dfplayer.sleep', SleepAction, cv.Schema({ | ||||
| @automation.register_action( | ||||
|     "dfplayer.sleep", | ||||
|     SleepAction, | ||||
|     cv.Schema( | ||||
|         { | ||||
|             cv.GenerateID(): cv.use_id(DFPlayer), | ||||
| })) | ||||
|         } | ||||
|     ), | ||||
| ) | ||||
| def dfplayer_sleep_to_code(config, action_id, template_arg, args): | ||||
|     var = cg.new_Pvariable(action_id, template_arg) | ||||
|     yield cg.register_parented(var, config[CONF_ID]) | ||||
|     yield var | ||||
|  | ||||
|  | ||||
| @automation.register_action('dfplayer.reset', ResetAction, cv.Schema({ | ||||
| @automation.register_action( | ||||
|     "dfplayer.reset", | ||||
|     ResetAction, | ||||
|     cv.Schema( | ||||
|         { | ||||
|             cv.GenerateID(): cv.use_id(DFPlayer), | ||||
| })) | ||||
|         } | ||||
|     ), | ||||
| ) | ||||
| def dfplayer_reset_to_code(config, action_id, template_arg, args): | ||||
|     var = cg.new_Pvariable(action_id, template_arg) | ||||
|     yield cg.register_parented(var, config[CONF_ID]) | ||||
|     yield var | ||||
|  | ||||
|  | ||||
| @automation.register_action('dfplayer.start', StartAction, cv.Schema({ | ||||
| @automation.register_action( | ||||
|     "dfplayer.start", | ||||
|     StartAction, | ||||
|     cv.Schema( | ||||
|         { | ||||
|             cv.GenerateID(): cv.use_id(DFPlayer), | ||||
| })) | ||||
|         } | ||||
|     ), | ||||
| ) | ||||
| def dfplayer_start_to_code(config, action_id, template_arg, args): | ||||
|     var = cg.new_Pvariable(action_id, template_arg) | ||||
|     yield cg.register_parented(var, config[CONF_ID]) | ||||
|     yield var | ||||
|  | ||||
|  | ||||
| @automation.register_action('dfplayer.pause', PauseAction, cv.Schema({ | ||||
| @automation.register_action( | ||||
|     "dfplayer.pause", | ||||
|     PauseAction, | ||||
|     cv.Schema( | ||||
|         { | ||||
|             cv.GenerateID(): cv.use_id(DFPlayer), | ||||
| })) | ||||
|         } | ||||
|     ), | ||||
| ) | ||||
| def dfplayer_pause_to_code(config, action_id, template_arg, args): | ||||
|     var = cg.new_Pvariable(action_id, template_arg) | ||||
|     yield cg.register_parented(var, config[CONF_ID]) | ||||
|     yield var | ||||
|  | ||||
|  | ||||
| @automation.register_action('dfplayer.stop', StopAction, cv.Schema({ | ||||
| @automation.register_action( | ||||
|     "dfplayer.stop", | ||||
|     StopAction, | ||||
|     cv.Schema( | ||||
|         { | ||||
|             cv.GenerateID(): cv.use_id(DFPlayer), | ||||
| })) | ||||
|         } | ||||
|     ), | ||||
| ) | ||||
| def dfplayer_stop_to_code(config, action_id, template_arg, args): | ||||
|     var = cg.new_Pvariable(action_id, template_arg) | ||||
|     yield cg.register_parented(var, config[CONF_ID]) | ||||
|     yield var | ||||
|  | ||||
|  | ||||
| @automation.register_action('dfplayer.random', RandomAction, cv.Schema({ | ||||
| @automation.register_action( | ||||
|     "dfplayer.random", | ||||
|     RandomAction, | ||||
|     cv.Schema( | ||||
|         { | ||||
|             cv.GenerateID(): cv.use_id(DFPlayer), | ||||
| })) | ||||
|         } | ||||
|     ), | ||||
| ) | ||||
| def dfplayer_random_to_code(config, action_id, template_arg, args): | ||||
|     var = cg.new_Pvariable(action_id, template_arg) | ||||
|     yield cg.register_parented(var, config[CONF_ID]) | ||||
|     yield var | ||||
|  | ||||
|  | ||||
| @automation.register_condition('dfplayer.is_playing', DFPlayerIsPlayingCondition, cv.Schema({ | ||||
| @automation.register_condition( | ||||
|     "dfplayer.is_playing", | ||||
|     DFPlayerIsPlayingCondition, | ||||
|     cv.Schema( | ||||
|         { | ||||
|             cv.GenerateID(): cv.use_id(DFPlayer), | ||||
| })) | ||||
|         } | ||||
|     ), | ||||
| ) | ||||
| def dfplyaer_is_playing_to_code(config, condition_id, template_arg, args): | ||||
|     var = cg.new_Pvariable(condition_id, template_arg) | ||||
|     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