mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 07:03:55 +00:00 
			
		
		
		
	Merge branch 'beta' into bump-1.15.0
This commit is contained in:
		
							
								
								
									
										2
									
								
								.coveragerc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								.coveragerc
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | |||||||
|  | [run] | ||||||
|  | omit = esphome/components/* | ||||||
							
								
								
									
										32
									
								
								.devcontainer/devcontainer.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								.devcontainer/devcontainer.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,32 @@ | |||||||
|  | { | ||||||
|  |   "name": "ESPHome Dev", | ||||||
|  |   "context": "..", | ||||||
|  |   "dockerFile": "../docker/Dockerfile.dev", | ||||||
|  |   "postCreateCommand": "mkdir -p config && pip3 install -e .", | ||||||
|  |   "runArgs": ["--privileged", "-e", "ESPHOME_DASHBOARD_USE_PING=1"], | ||||||
|  |   "appPort": 6052, | ||||||
|  |   "extensions": [ | ||||||
|  |     "ms-python.python", | ||||||
|  |     "visualstudioexptteam.vscodeintellicode", | ||||||
|  |     "redhat.vscode-yaml" | ||||||
|  |   ], | ||||||
|  |   "settings": { | ||||||
|  |     "python.pythonPath": "/usr/local/bin/python", | ||||||
|  |     "python.linting.pylintEnabled": true, | ||||||
|  |     "python.linting.enabled": true, | ||||||
|  |     "python.formatting.provider": "black", | ||||||
|  |     "editor.formatOnPaste": false, | ||||||
|  |     "editor.formatOnSave": true, | ||||||
|  |     "editor.formatOnType": true, | ||||||
|  |     "files.trimTrailingWhitespace": true, | ||||||
|  |     "terminal.integrated.shell.linux": "/bin/bash", | ||||||
|  |     "yaml.customTags": [ | ||||||
|  |       "!secret scalar", | ||||||
|  |       "!lambda scalar", | ||||||
|  |       "!include_dir_named scalar", | ||||||
|  |       "!include_dir_list scalar", | ||||||
|  |       "!include_dir_merge_list scalar", | ||||||
|  |       "!include_dir_merge_named scalar" | ||||||
|  |     ] | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										12
									
								
								.github/ISSUE_TEMPLATE/config.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								.github/ISSUE_TEMPLATE/config.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | |||||||
|  | blank_issues_enabled: false | ||||||
|  | contact_links: | ||||||
|  |   - name: Issue Tracker | ||||||
|  |     url: https://github.com/esphome/issues | ||||||
|  |     about: Please create bug reports in the dedicated issue tracker. | ||||||
|  |   - name: Feature Request Tracker | ||||||
|  |     url: https://github.com/esphome/feature-requests | ||||||
|  |     about: Please create feature requests in the dedicated feature request tracker. | ||||||
|  |   - name: Frequently Asked Question | ||||||
|  |     url: https://esphome.io/guides/faq.html | ||||||
|  |     about: Please view the FAQ for common questions and what to include in a bug report. | ||||||
|  |      | ||||||
							
								
								
									
										9
									
								
								.github/dependabot.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								.github/dependabot.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | |||||||
|  | version: 2 | ||||||
|  | updates: | ||||||
|  |   - package-ecosystem: "pip" | ||||||
|  |     directory: "/" | ||||||
|  |     schedule: | ||||||
|  |       interval: "daily" | ||||||
|  |     ignore: | ||||||
|  |       # Hypotehsis is only used for testing and is updated quite often | ||||||
|  |       - dependency-name: hypothesis | ||||||
							
								
								
									
										36
									
								
								.github/lock.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								.github/lock.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,36 @@ | |||||||
|  | # Configuration for Lock Threads - https://github.com/dessant/lock-threads | ||||||
|  |  | ||||||
|  | # Number of days of inactivity before a closed issue or pull request is locked | ||||||
|  | daysUntilLock: 7 | ||||||
|  |  | ||||||
|  | # Skip issues and pull requests created before a given timestamp. Timestamp must | ||||||
|  | # follow ISO 8601 (`YYYY-MM-DD`). Set to `false` to disable | ||||||
|  | skipCreatedBefore: false | ||||||
|  |  | ||||||
|  | # Issues and pull requests with these labels will be ignored. Set to `[]` to disable | ||||||
|  | exemptLabels: | ||||||
|  |   - keep-open | ||||||
|  |  | ||||||
|  | # Label to add before locking, such as `outdated`. Set to `false` to disable | ||||||
|  | lockLabel: false | ||||||
|  |  | ||||||
|  | # Comment to post before locking. Set to `false` to disable | ||||||
|  | lockComment: false | ||||||
|  |  | ||||||
|  | # Assign `resolved` as the reason for locking. Set to `false` to disable | ||||||
|  | setLockReason: false | ||||||
|  |  | ||||||
|  | # Limit to only `issues` or `pulls` | ||||||
|  | # only: issues | ||||||
|  |  | ||||||
|  | # Optionally, specify configuration settings just for `issues` or `pulls` | ||||||
|  | # issues: | ||||||
|  | #   exemptLabels: | ||||||
|  | #     - help-wanted | ||||||
|  | #   lockLabel: outdated | ||||||
|  |  | ||||||
|  | # pulls: | ||||||
|  | #   daysUntilLock: 30 | ||||||
|  |  | ||||||
|  | # Repository to extend settings from | ||||||
|  | # _extends: repo | ||||||
							
								
								
									
										59
									
								
								.github/stale.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								.github/stale.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,59 @@ | |||||||
|  | # Configuration for probot-stale - https://github.com/probot/stale | ||||||
|  |  | ||||||
|  | # Number of days of inactivity before an Issue or Pull Request becomes stale | ||||||
|  | daysUntilStale: 60 | ||||||
|  |  | ||||||
|  | # Number of days of inactivity before an Issue or Pull Request with the stale label is closed. | ||||||
|  | # Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale. | ||||||
|  | daysUntilClose: 7 | ||||||
|  |  | ||||||
|  | # Only issues or pull requests with all of these labels are check if stale. Defaults to `[]` (disabled) | ||||||
|  | onlyLabels: [] | ||||||
|  |  | ||||||
|  | # Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable | ||||||
|  | exemptLabels: | ||||||
|  |   - not-stale | ||||||
|  |  | ||||||
|  | # Set to true to ignore issues in a project (defaults to false) | ||||||
|  | exemptProjects: false | ||||||
|  |  | ||||||
|  | # Set to true to ignore issues in a milestone (defaults to false) | ||||||
|  | exemptMilestones: true | ||||||
|  |  | ||||||
|  | # Set to true to ignore issues with an assignee (defaults to false) | ||||||
|  | exemptAssignees: false | ||||||
|  |  | ||||||
|  | # Label to use when marking as stale | ||||||
|  | staleLabel: stale | ||||||
|  |  | ||||||
|  | # Comment to post when marking as stale. Set to `false` to disable | ||||||
|  | markComment: > | ||||||
|  |   This issue has been automatically marked as stale because it has not had | ||||||
|  |   recent activity. It will be closed if no further activity occurs. Thank you | ||||||
|  |   for your contributions. | ||||||
|  |  | ||||||
|  | # Comment to post when removing the stale label. | ||||||
|  | # unmarkComment: > | ||||||
|  | #   Your comment here. | ||||||
|  |  | ||||||
|  | # Comment to post when closing a stale Issue or Pull Request. | ||||||
|  | # closeComment: > | ||||||
|  | #   Your comment here. | ||||||
|  |  | ||||||
|  | # Limit the number of actions per hour, from 1-30. Default is 30 | ||||||
|  | limitPerRun: 10 | ||||||
|  |  | ||||||
|  | # Limit to only `issues` or `pulls` | ||||||
|  | only: pulls | ||||||
|  |  | ||||||
|  | # Optionally, specify configuration settings that are specific to just 'issues' or 'pulls': | ||||||
|  | # pulls: | ||||||
|  | #   daysUntilStale: 30 | ||||||
|  | #   markComment: > | ||||||
|  | #     This pull request has been automatically marked as stale because it has not had | ||||||
|  | #     recent activity. It will be closed if no further activity occurs. Thank you | ||||||
|  | #     for your contributions. | ||||||
|  |  | ||||||
|  | # issues: | ||||||
|  | #   exemptLabels: | ||||||
|  | #     - confirmed | ||||||
							
								
								
									
										54
									
								
								.github/workflows/ci-docker.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								.github/workflows/ci-docker.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,54 @@ | |||||||
|  | name: CI for docker images | ||||||
|  |  | ||||||
|  | # Only run when docker paths change | ||||||
|  | on: | ||||||
|  |   push: | ||||||
|  |     branches: [dev, beta, master] | ||||||
|  |     paths: | ||||||
|  |       - 'docker/**' | ||||||
|  |       - '.github/workflows/**' | ||||||
|  |  | ||||||
|  |   pull_request: | ||||||
|  |     paths: | ||||||
|  |       - 'docker/**' | ||||||
|  |       - '.github/workflows/**' | ||||||
|  |  | ||||||
|  | jobs: | ||||||
|  |   check-docker: | ||||||
|  |     name: Build docker containers | ||||||
|  |     runs-on: ubuntu-latest | ||||||
|  |     strategy: | ||||||
|  |       matrix: | ||||||
|  |         arch: [amd64, armv7, aarch64] | ||||||
|  |         build_type: ["hassio", "docker"] | ||||||
|  |     steps: | ||||||
|  |       - uses: actions/checkout@v2 | ||||||
|  |       - name: Set up env variables | ||||||
|  |         run: | | ||||||
|  |           base_version="2.6.0" | ||||||
|  |  | ||||||
|  |           if [[ "${{ matrix.build_type }}" == "hassio" ]]; then | ||||||
|  |             build_from="esphome/esphome-hassio-base-${{ matrix.arch }}:${base_version}" | ||||||
|  |             build_to="esphome/esphome-hassio-${{ matrix.arch }}" | ||||||
|  |             dockerfile="docker/Dockerfile.hassio" | ||||||
|  |           else | ||||||
|  |             build_from="esphome/esphome-base-${{ matrix.arch }}:${base_version}" | ||||||
|  |             build_to="esphome/esphome-${{ matrix.arch }}" | ||||||
|  |             dockerfile="docker/Dockerfile" | ||||||
|  |           fi | ||||||
|  |  | ||||||
|  |           echo "::set-env name=BUILD_FROM::${build_from}" | ||||||
|  |           echo "::set-env name=BUILD_TO::${build_to}" | ||||||
|  |           echo "::set-env name=DOCKERFILE::${dockerfile}" | ||||||
|  |       - name: Pull for cache | ||||||
|  |         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 build \ | ||||||
|  |             --build-arg "BUILD_FROM=${BUILD_FROM}" \ | ||||||
|  |             --build-arg "BUILD_VERSION=ci" \ | ||||||
|  |             --cache-from "${BUILD_TO}:dev" \ | ||||||
|  |             --file "${DOCKERFILE}" \ | ||||||
|  |             . | ||||||
							
								
								
									
										215
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										215
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,215 @@ | |||||||
|  | # THESE JOBS ARE COPIED IN release.yml and release-dev.yml | ||||||
|  | # PLEASE ALSO UPDATE THOSE FILES WHEN CHANGING LINES HERE | ||||||
|  | name: CI | ||||||
|  |  | ||||||
|  | on: | ||||||
|  |   push: | ||||||
|  |     # On dev branch release-dev already performs CI checks | ||||||
|  |     # On other branches the `pull_request` trigger will be used | ||||||
|  |     branches: [beta, master] | ||||||
|  |  | ||||||
|  |   pull_request: | ||||||
|  |  | ||||||
|  | jobs: | ||||||
|  |   # A fast overview job that checks only changed files | ||||||
|  |   overview: | ||||||
|  |     runs-on: ubuntu-latest | ||||||
|  |     container: esphome/esphome-lint:latest | ||||||
|  |     steps: | ||||||
|  |       # Also fetch history and dev branch so that we can check which files changed | ||||||
|  |       - uses: actions/checkout@v2 | ||||||
|  |         with: | ||||||
|  |           fetch-depth: 0 | ||||||
|  |       - name: Fetch dev branch | ||||||
|  |         run: git fetch origin dev | ||||||
|  |  | ||||||
|  |       # Cache the .pio directory with (primarily) library dependencies | ||||||
|  |       - name: Cache .pio lib_deps | ||||||
|  |         uses: actions/cache@v1 | ||||||
|  |         with: | ||||||
|  |           path: .pio | ||||||
|  |           key: lint-cpp-pio-${{ hashFiles('platformio.ini') }} | ||||||
|  |           restore-keys: | | ||||||
|  |             lint-cpp-pio- | ||||||
|  |       - name: Set up python environment | ||||||
|  |         run: script/setup | ||||||
|  |       # Set up the pio project so that the cpp checks know how files are compiled | ||||||
|  |       # (build flags, libraries etc) | ||||||
|  |       - name: Set up platformio environment | ||||||
|  |         run: pio init --ide atom | ||||||
|  |  | ||||||
|  |       - name: Register problem matchers | ||||||
|  |         run: | | ||||||
|  |           echo "::add-matcher::.github/workflows/matchers/ci-custom.json" | ||||||
|  |           echo "::add-matcher::.github/workflows/matchers/clang-tidy.json" | ||||||
|  |           echo "::add-matcher::.github/workflows/matchers/gcc.json" | ||||||
|  |           echo "::add-matcher::.github/workflows/matchers/lint-python.json" | ||||||
|  |           echo "::add-matcher::.github/workflows/matchers/python.json" | ||||||
|  |       - name: Run a quick lint over all changed files | ||||||
|  |         run: script/quicklint | ||||||
|  |       - name: Suggest changes | ||||||
|  |         run: script/ci-suggest-changes | ||||||
|  |  | ||||||
|  |   lint-clang-format: | ||||||
|  |     runs-on: ubuntu-latest | ||||||
|  |     # cpp lint job runs with esphome-lint docker image so that clang-format-* | ||||||
|  |     # doesn't have to be installed | ||||||
|  |     container: esphome/esphome-lint:latest | ||||||
|  |     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 | ||||||
|  |         run: pio init --ide atom | ||||||
|  |  | ||||||
|  |       - name: Run clang-format | ||||||
|  |         run: script/clang-format -i | ||||||
|  |       - name: Suggest changes | ||||||
|  |         run: script/ci-suggest-changes | ||||||
|  |  | ||||||
|  |   lint-clang-tidy: | ||||||
|  |     runs-on: ubuntu-latest | ||||||
|  |     # cpp lint job runs with esphome-lint docker image so that clang-format-* | ||||||
|  |     # doesn't have to be installed | ||||||
|  |     container: esphome/esphome-lint:latest | ||||||
|  |     # Split clang-tidy check into 4 jobs. Each one will check 1/4th of the .cpp files | ||||||
|  |     strategy: | ||||||
|  |       matrix: | ||||||
|  |         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 | ||||||
|  |         run: pio init --ide atom | ||||||
|  |  | ||||||
|  |  | ||||||
|  |       - name: Register problem matchers | ||||||
|  |         run: | | ||||||
|  |           echo "::add-matcher::.github/workflows/matchers/clang-tidy.json" | ||||||
|  |           echo "::add-matcher::.github/workflows/matchers/gcc.json" | ||||||
|  |       - name: Run clang-tidy | ||||||
|  |         run: script/clang-tidy --all-headers --fix --split-num 4 --split-at ${{ matrix.split }} | ||||||
|  |       - name: Suggest changes | ||||||
|  |         run: script/ci-suggest-changes | ||||||
|  |  | ||||||
|  |   lint-python: | ||||||
|  |     # Don't use the esphome-lint docker image because it may contain outdated requirements. | ||||||
|  |     # This way, all dependencies are cached via the cache action. | ||||||
|  |     runs-on: ubuntu-latest | ||||||
|  |     steps: | ||||||
|  |       - uses: actions/checkout@v2 | ||||||
|  |       - name: Set up Python | ||||||
|  |         uses: actions/setup-python@v2 | ||||||
|  |         with: | ||||||
|  |           python-version: '3.7' | ||||||
|  |       - name: Cache pip modules | ||||||
|  |         uses: actions/cache@v1 | ||||||
|  |         with: | ||||||
|  |           path: ~/.cache/pip | ||||||
|  |           key: esphome-pip-3.7-${{ hashFiles('setup.py') }} | ||||||
|  |           restore-keys: | | ||||||
|  |             esphome-pip-3.7- | ||||||
|  |       - name: Set up python environment | ||||||
|  |         run: script/setup | ||||||
|  |  | ||||||
|  |       - name: Register problem matchers | ||||||
|  |         run: | | ||||||
|  |           echo "::add-matcher::.github/workflows/matchers/ci-custom.json" | ||||||
|  |           echo "::add-matcher::.github/workflows/matchers/lint-python.json" | ||||||
|  |           echo "::add-matcher::.github/workflows/matchers/python.json" | ||||||
|  |       - name: Lint Custom | ||||||
|  |         run: script/ci-custom.py | ||||||
|  |       - name: Lint Python | ||||||
|  |         run: script/lint-python | ||||||
|  |       - name: Lint CODEOWNERS | ||||||
|  |         run: script/build_codeowners.py --check | ||||||
|  |  | ||||||
|  |   test: | ||||||
|  |     runs-on: ubuntu-latest | ||||||
|  |     strategy: | ||||||
|  |       matrix: | ||||||
|  |           test: | ||||||
|  |           - test1 | ||||||
|  |           - test2 | ||||||
|  |           - test3 | ||||||
|  |           - test4 | ||||||
|  |     steps: | ||||||
|  |       - uses: actions/checkout@v2 | ||||||
|  |       - name: Set up Python | ||||||
|  |         uses: actions/setup-python@v2 | ||||||
|  |         with: | ||||||
|  |           python-version: '3.7' | ||||||
|  |       - name: Cache pip modules | ||||||
|  |         uses: actions/cache@v1 | ||||||
|  |         with: | ||||||
|  |           path: ~/.cache/pip | ||||||
|  |           key: esphome-pip-3.7-${{ hashFiles('setup.py') }} | ||||||
|  |           restore-keys: | | ||||||
|  |             esphome-pip-3.7- | ||||||
|  |       # Use per test platformio cache because tests have different platform versions | ||||||
|  |       - name: Cache ~/.platformio | ||||||
|  |         uses: actions/cache@v1 | ||||||
|  |         with: | ||||||
|  |           path: ~/.platformio | ||||||
|  |           key: test-home-platformio-${{ matrix.test }}-${{ hashFiles('esphome/core_config.py') }} | ||||||
|  |           restore-keys: | | ||||||
|  |             test-home-platformio-${{ matrix.test }}- | ||||||
|  |       - name: Set up environment | ||||||
|  |         run: script/setup | ||||||
|  |  | ||||||
|  |  | ||||||
|  |       - name: Register problem matchers | ||||||
|  |         run: | | ||||||
|  |           echo "::add-matcher::.github/workflows/matchers/gcc.json" | ||||||
|  |           echo "::add-matcher::.github/workflows/matchers/python.json" | ||||||
|  |       - run: esphome tests/${{ matrix.test }}.yaml compile | ||||||
|  |  | ||||||
|  |   pytest: | ||||||
|  |     runs-on: ubuntu-latest | ||||||
|  |     steps: | ||||||
|  |       - uses: actions/checkout@v2 | ||||||
|  |       - name: Set up Python | ||||||
|  |         uses: actions/setup-python@v2 | ||||||
|  |         with: | ||||||
|  |           python-version: '3.7' | ||||||
|  |       - name: Cache pip modules | ||||||
|  |         uses: actions/cache@v1 | ||||||
|  |         with: | ||||||
|  |           path: ~/.cache/pip | ||||||
|  |           key: esphome-pip-3.7-${{ hashFiles('setup.py') }} | ||||||
|  |           restore-keys: | | ||||||
|  |             esphome-pip-3.7- | ||||||
|  |       - name: Set up environment | ||||||
|  |         run: script/setup | ||||||
|  |       - name: Install Github Actions annotator | ||||||
|  |         run: pip install pytest-github-actions-annotate-failures | ||||||
|  |  | ||||||
|  |       - name: Register problem matchers | ||||||
|  |         run: | | ||||||
|  |           echo "::add-matcher::.github/workflows/matchers/python.json" | ||||||
|  |       - name: Run pytest | ||||||
|  |         run: | | ||||||
|  |           pytest \ | ||||||
|  |             -qq \ | ||||||
|  |             --durations=10 \ | ||||||
|  |             -o console_output_style=count \ | ||||||
|  |             tests | ||||||
							
								
								
									
										16
									
								
								.github/workflows/matchers/ci-custom.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								.github/workflows/matchers/ci-custom.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | |||||||
|  | { | ||||||
|  |     "problemMatcher": [ | ||||||
|  |         { | ||||||
|  |             "owner": "ci-custom", | ||||||
|  |             "pattern": [ | ||||||
|  |                 { | ||||||
|  |                     "regexp": "^ERROR (.*):(\\d+):(\\d+) - (.*)$", | ||||||
|  |                     "file": 1, | ||||||
|  |                     "line": 2, | ||||||
|  |                     "column": 3, | ||||||
|  |                     "message": 4 | ||||||
|  |                 } | ||||||
|  |             ] | ||||||
|  |         } | ||||||
|  |     ] | ||||||
|  | } | ||||||
							
								
								
									
										17
									
								
								.github/workflows/matchers/clang-tidy.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								.github/workflows/matchers/clang-tidy.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,17 @@ | |||||||
|  | { | ||||||
|  |     "problemMatcher": [ | ||||||
|  |         { | ||||||
|  |             "owner": "clang-tidy", | ||||||
|  |             "pattern": [ | ||||||
|  |                 { | ||||||
|  |                     "regexp": "^(.*):(\\d+):(\\d+):\\s+(error):\\s+(.*) \\[([a-z0-9,\\-]+)\\]\\s*$", | ||||||
|  |                     "file": 1, | ||||||
|  |                     "line": 2, | ||||||
|  |                     "column": 3, | ||||||
|  |                     "severity": 4, | ||||||
|  |                     "message": 5 | ||||||
|  |                 } | ||||||
|  |             ] | ||||||
|  |         } | ||||||
|  |     ] | ||||||
|  | } | ||||||
							
								
								
									
										18
									
								
								.github/workflows/matchers/gcc.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								.github/workflows/matchers/gcc.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | |||||||
|  | { | ||||||
|  |   "problemMatcher": [ | ||||||
|  |     { | ||||||
|  |       "owner": "gcc", | ||||||
|  |       "severity": "error", | ||||||
|  |       "pattern": [ | ||||||
|  |         { | ||||||
|  |           "regexp": "^(.*):(\\d+):(\\d+):\\s+(?:fatal\\s+)?(warning|error):\\s+(.*)$", | ||||||
|  |           "file": 1, | ||||||
|  |           "line": 2, | ||||||
|  |           "column": 3, | ||||||
|  |           "severity": 4, | ||||||
|  |           "message": 5 | ||||||
|  |         } | ||||||
|  |       ] | ||||||
|  |     } | ||||||
|  |   ] | ||||||
|  | } | ||||||
							
								
								
									
										28
									
								
								.github/workflows/matchers/lint-python.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								.github/workflows/matchers/lint-python.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,28 @@ | |||||||
|  | { | ||||||
|  |   "problemMatcher": [ | ||||||
|  |     { | ||||||
|  |       "owner": "flake8", | ||||||
|  |       "severity": "error", | ||||||
|  |       "pattern": [ | ||||||
|  |           { | ||||||
|  |           "regexp": "^(.*):(\\d+) - ([EFCDNW]\\d{3}.*)$", | ||||||
|  |           "file": 1, | ||||||
|  |           "line": 2, | ||||||
|  |           "message": 3 | ||||||
|  |           } | ||||||
|  |       ] | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "owner": "pylint", | ||||||
|  |       "severity": "error", | ||||||
|  |       "pattern": [ | ||||||
|  |         { | ||||||
|  |           "regexp": "^(.*):(\\d+) - (\\[[EFCRW]\\d{4}\\(.*\\),.*\\].*)$", | ||||||
|  |           "file": 1, | ||||||
|  |           "line": 2, | ||||||
|  |           "message": 3 | ||||||
|  |         } | ||||||
|  |       ] | ||||||
|  |     } | ||||||
|  |   ] | ||||||
|  | } | ||||||
							
								
								
									
										18
									
								
								.github/workflows/matchers/python.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								.github/workflows/matchers/python.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | |||||||
|  | { | ||||||
|  |     "problemMatcher": [ | ||||||
|  |         { | ||||||
|  |             "owner": "python", | ||||||
|  |             "pattern": [ | ||||||
|  |                 { | ||||||
|  |                     "regexp": "^\\s*File\\s\\\"(.*)\\\",\\sline\\s(\\d+),\\sin\\s(.*)$", | ||||||
|  |                     "file": 1, | ||||||
|  |                     "line": 2 | ||||||
|  |                 }, | ||||||
|  |                 { | ||||||
|  |                     "regexp": "^\\s*raise\\s(.*)\\(\\'(.*)\\'\\)$", | ||||||
|  |                     "message": 2 | ||||||
|  |                 } | ||||||
|  |             ] | ||||||
|  |         } | ||||||
|  |     ] | ||||||
|  | } | ||||||
							
								
								
									
										262
									
								
								.github/workflows/release-dev.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										262
									
								
								.github/workflows/release-dev.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,262 @@ | |||||||
|  | name: Publish dev releases to docker hub | ||||||
|  |  | ||||||
|  | on: | ||||||
|  |   push: | ||||||
|  |     branches: | ||||||
|  |     - dev | ||||||
|  |  | ||||||
|  | jobs: | ||||||
|  |   # THE LINT/TEST JOBS ARE COPIED FROM ci.yaml | ||||||
|  |  | ||||||
|  |   lint-clang-format: | ||||||
|  |     runs-on: ubuntu-latest | ||||||
|  |     # cpp lint job runs with esphome-lint docker image so that clang-format-* | ||||||
|  |     # doesn't have to be installed | ||||||
|  |     container: esphome/esphome-lint:latest | ||||||
|  |     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 | ||||||
|  |         run: pio init --ide atom | ||||||
|  |  | ||||||
|  |       - name: Run clang-format | ||||||
|  |         run: script/clang-format -i | ||||||
|  |       - name: Suggest changes | ||||||
|  |         run: script/ci-suggest-changes | ||||||
|  |  | ||||||
|  |   lint-clang-tidy: | ||||||
|  |     runs-on: ubuntu-latest | ||||||
|  |     # cpp lint job runs with esphome-lint docker image so that clang-format-* | ||||||
|  |     # doesn't have to be installed | ||||||
|  |     container: esphome/esphome-lint:latest | ||||||
|  |     # Split clang-tidy check into 4 jobs. Each one will check 1/4th of the .cpp files | ||||||
|  |     strategy: | ||||||
|  |       matrix: | ||||||
|  |         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 | ||||||
|  |         run: pio init --ide atom | ||||||
|  |  | ||||||
|  |  | ||||||
|  |       - name: Register problem matchers | ||||||
|  |         run: | | ||||||
|  |           echo "::add-matcher::.github/workflows/matchers/clang-tidy.json" | ||||||
|  |           echo "::add-matcher::.github/workflows/matchers/gcc.json" | ||||||
|  |       - name: Run clang-tidy | ||||||
|  |         run: script/clang-tidy --all-headers --fix --split-num 4 --split-at ${{ matrix.split }} | ||||||
|  |       - name: Suggest changes | ||||||
|  |         run: script/ci-suggest-changes | ||||||
|  |  | ||||||
|  |   lint-python: | ||||||
|  |     # Don't use the esphome-lint docker image because it may contain outdated requirements. | ||||||
|  |     # This way, all dependencies are cached via the cache action. | ||||||
|  |     runs-on: ubuntu-latest | ||||||
|  |     steps: | ||||||
|  |       - uses: actions/checkout@v2 | ||||||
|  |       - name: Set up Python | ||||||
|  |         uses: actions/setup-python@v2 | ||||||
|  |         with: | ||||||
|  |           python-version: '3.7' | ||||||
|  |       - name: Cache pip modules | ||||||
|  |         uses: actions/cache@v1 | ||||||
|  |         with: | ||||||
|  |           path: ~/.cache/pip | ||||||
|  |           key: esphome-pip-3.7-${{ hashFiles('setup.py') }} | ||||||
|  |           restore-keys: | | ||||||
|  |             esphome-pip-3.7- | ||||||
|  |       - name: Set up python environment | ||||||
|  |         run: script/setup | ||||||
|  |  | ||||||
|  |       - name: Register problem matchers | ||||||
|  |         run: | | ||||||
|  |           echo "::add-matcher::.github/workflows/matchers/ci-custom.json" | ||||||
|  |           echo "::add-matcher::.github/workflows/matchers/lint-python.json" | ||||||
|  |           echo "::add-matcher::.github/workflows/matchers/python.json" | ||||||
|  |       - name: Lint Custom | ||||||
|  |         run: script/ci-custom.py | ||||||
|  |       - name: Lint Python | ||||||
|  |         run: script/lint-python | ||||||
|  |       - name: Lint CODEOWNERS | ||||||
|  |         run: script/build_codeowners.py --check | ||||||
|  |  | ||||||
|  |   test: | ||||||
|  |     runs-on: ubuntu-latest | ||||||
|  |     strategy: | ||||||
|  |       matrix: | ||||||
|  |           test: | ||||||
|  |           - test1 | ||||||
|  |           - test2 | ||||||
|  |           - test3 | ||||||
|  |           - test4 | ||||||
|  |     steps: | ||||||
|  |       - uses: actions/checkout@v2 | ||||||
|  |       - name: Set up Python | ||||||
|  |         uses: actions/setup-python@v2 | ||||||
|  |         with: | ||||||
|  |           python-version: '3.7' | ||||||
|  |       - name: Cache pip modules | ||||||
|  |         uses: actions/cache@v1 | ||||||
|  |         with: | ||||||
|  |           path: ~/.cache/pip | ||||||
|  |           key: esphome-pip-3.7-${{ hashFiles('setup.py') }} | ||||||
|  |           restore-keys: | | ||||||
|  |             esphome-pip-3.7- | ||||||
|  |       # Use per test platformio cache because tests have different platform versions | ||||||
|  |       - name: Cache ~/.platformio | ||||||
|  |         uses: actions/cache@v1 | ||||||
|  |         with: | ||||||
|  |           path: ~/.platformio | ||||||
|  |           key: test-home-platformio-${{ matrix.test }}-${{ hashFiles('esphome/core_config.py') }} | ||||||
|  |           restore-keys: | | ||||||
|  |             test-home-platformio-${{ matrix.test }}- | ||||||
|  |       - name: Set up environment | ||||||
|  |         run: script/setup | ||||||
|  |  | ||||||
|  |  | ||||||
|  |       - name: Register problem matchers | ||||||
|  |         run: | | ||||||
|  |           echo "::add-matcher::.github/workflows/matchers/gcc.json" | ||||||
|  |           echo "::add-matcher::.github/workflows/matchers/python.json" | ||||||
|  |       - run: esphome tests/${{ matrix.test }}.yaml compile | ||||||
|  |  | ||||||
|  |   pytest: | ||||||
|  |     runs-on: ubuntu-latest | ||||||
|  |     steps: | ||||||
|  |       - uses: actions/checkout@v2 | ||||||
|  |       - name: Set up Python | ||||||
|  |         uses: actions/setup-python@v2 | ||||||
|  |         with: | ||||||
|  |           python-version: '3.7' | ||||||
|  |       - name: Cache pip modules | ||||||
|  |         uses: actions/cache@v1 | ||||||
|  |         with: | ||||||
|  |           path: ~/.cache/pip | ||||||
|  |           key: esphome-pip-3.7-${{ hashFiles('setup.py') }} | ||||||
|  |           restore-keys: | | ||||||
|  |             esphome-pip-3.7- | ||||||
|  |       - name: Set up environment | ||||||
|  |         run: script/setup | ||||||
|  |       - name: Install Github Actions annotator | ||||||
|  |         run: pip install pytest-github-actions-annotate-failures | ||||||
|  |  | ||||||
|  |       - name: Register problem matchers | ||||||
|  |         run: | | ||||||
|  |           echo "::add-matcher::.github/workflows/matchers/python.json" | ||||||
|  |       - name: Run pytest | ||||||
|  |         run: | | ||||||
|  |           pytest \ | ||||||
|  |             -qq \ | ||||||
|  |             --durations=10 \ | ||||||
|  |             -o console_output_style=count \ | ||||||
|  |             tests | ||||||
|  |  | ||||||
|  |   deploy-docker: | ||||||
|  |     name: Build and publish docker containers | ||||||
|  |     if: github.repository == 'esphome/esphome' | ||||||
|  |     runs-on: ubuntu-latest | ||||||
|  |     needs: [lint-clang-format, lint-clang-tidy, lint-python, test, pytest] | ||||||
|  |     strategy: | ||||||
|  |       matrix: | ||||||
|  |         arch: [amd64, armv7, aarch64] | ||||||
|  |         # Hassio dev image doesn't use esphome/esphome-hassio-$arch and uses base directly | ||||||
|  |         build_type: ["docker"] | ||||||
|  |     steps: | ||||||
|  |       - uses: actions/checkout@v2 | ||||||
|  |       - name: Set TAG | ||||||
|  |         run: | | ||||||
|  |           TAG="${GITHUB_SHA:0:7}" | ||||||
|  |           echo "::set-env name=TAG::${TAG}" | ||||||
|  |       - name: Set up env variables | ||||||
|  |         run: | | ||||||
|  |           base_version="2.6.0" | ||||||
|  |  | ||||||
|  |           if [[ "${{ matrix.build_type }}" == "hassio" ]]; then | ||||||
|  |             build_from="esphome/esphome-hassio-base-${{ matrix.arch }}:${base_version}" | ||||||
|  |             build_to="esphome/esphome-hassio-${{ matrix.arch }}" | ||||||
|  |             dockerfile="docker/Dockerfile.hassio" | ||||||
|  |           else | ||||||
|  |             build_from="esphome/esphome-base-${{ matrix.arch }}:${base_version}" | ||||||
|  |             build_to="esphome/esphome-${{ matrix.arch }}" | ||||||
|  |             dockerfile="docker/Dockerfile" | ||||||
|  |           fi | ||||||
|  |  | ||||||
|  |           echo "::set-env name=BUILD_FROM::${build_from}" | ||||||
|  |           echo "::set-env name=BUILD_TO::${build_to}" | ||||||
|  |           echo "::set-env name=DOCKERFILE::${dockerfile}" | ||||||
|  |       - name: Pull for cache | ||||||
|  |         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 build \ | ||||||
|  |             --build-arg "BUILD_FROM=${BUILD_FROM}" \ | ||||||
|  |             --build-arg "BUILD_VERSION=${TAG}" \ | ||||||
|  |             --tag "${BUILD_TO}:${TAG}" \ | ||||||
|  |             --tag "${BUILD_TO}:dev" \ | ||||||
|  |             --cache-from "${BUILD_TO}:dev" \ | ||||||
|  |             --file "${DOCKERFILE}" \ | ||||||
|  |             . | ||||||
|  |       - name: Log in to docker hub | ||||||
|  |         env: | ||||||
|  |           DOCKER_USER: ${{ secrets.DOCKER_USER }} | ||||||
|  |           DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} | ||||||
|  |         run: docker login -u "${DOCKER_USER}" -p "${DOCKER_PASSWORD}" | ||||||
|  |       - run: | | ||||||
|  |           docker push "${BUILD_TO}:${TAG}" | ||||||
|  |           docker push "${BUILD_TO}:dev" | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   deploy-docker-manifest: | ||||||
|  |     if: github.repository == 'esphome/esphome' | ||||||
|  |     runs-on: ubuntu-latest | ||||||
|  |     needs: [deploy-docker] | ||||||
|  |     steps: | ||||||
|  |     - name: Enable experimental manifest support | ||||||
|  |       run: | | ||||||
|  |         mkdir -p ~/.docker | ||||||
|  |         echo "{\"experimental\": \"enabled\"}" > ~/.docker/config.json | ||||||
|  |     - name: Set TAG | ||||||
|  |       run: | | ||||||
|  |         TAG="${GITHUB_SHA:0:7}" | ||||||
|  |         echo "::set-env name=TAG::${TAG}" | ||||||
|  |     - name: Log in to docker hub | ||||||
|  |       env: | ||||||
|  |         DOCKER_USER: ${{ secrets.DOCKER_USER }} | ||||||
|  |         DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} | ||||||
|  |       run: docker login -u "${DOCKER_USER}" -p "${DOCKER_PASSWORD}" | ||||||
|  |     - name: "Create the manifest" | ||||||
|  |       run: | | ||||||
|  |         docker manifest create esphome/esphome:${TAG} \ | ||||||
|  |           esphome/esphome-aarch64:${TAG} \ | ||||||
|  |           esphome/esphome-amd64:${TAG} \ | ||||||
|  |           esphome/esphome-armv7:${TAG} | ||||||
|  |         docker manifest push esphome/esphome:${TAG} | ||||||
|  |  | ||||||
|  |         docker manifest create esphome/esphome:dev \ | ||||||
|  |           esphome/esphome-aarch64:${TAG} \ | ||||||
|  |           esphome/esphome-amd64:${TAG} \ | ||||||
|  |           esphome/esphome-armv7:${TAG} | ||||||
|  |         docker manifest push esphome/esphome:dev | ||||||
							
								
								
									
										325
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										325
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,325 @@ | |||||||
|  | name: Publish Release | ||||||
|  |  | ||||||
|  | on: | ||||||
|  |   release: | ||||||
|  |     types: [published] | ||||||
|  |  | ||||||
|  | jobs: | ||||||
|  |   # THE LINT/TEST JOBS ARE COPIED FROM ci.yaml | ||||||
|  |  | ||||||
|  |   lint-clang-format: | ||||||
|  |     runs-on: ubuntu-latest | ||||||
|  |     # cpp lint job runs with esphome-lint docker image so that clang-format-* | ||||||
|  |     # doesn't have to be installed | ||||||
|  |     container: esphome/esphome-lint:latest | ||||||
|  |     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 | ||||||
|  |         run: pio init --ide atom | ||||||
|  |  | ||||||
|  |       - name: Run clang-format | ||||||
|  |         run: script/clang-format -i | ||||||
|  |       - name: Suggest changes | ||||||
|  |         run: script/ci-suggest-changes | ||||||
|  |  | ||||||
|  |   lint-clang-tidy: | ||||||
|  |     runs-on: ubuntu-latest | ||||||
|  |     # cpp lint job runs with esphome-lint docker image so that clang-format-* | ||||||
|  |     # doesn't have to be installed | ||||||
|  |     container: esphome/esphome-lint:latest | ||||||
|  |     # Split clang-tidy check into 4 jobs. Each one will check 1/4th of the .cpp files | ||||||
|  |     strategy: | ||||||
|  |       matrix: | ||||||
|  |         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 | ||||||
|  |         run: pio init --ide atom | ||||||
|  |  | ||||||
|  |  | ||||||
|  |       - name: Register problem matchers | ||||||
|  |         run: | | ||||||
|  |           echo "::add-matcher::.github/workflows/matchers/clang-tidy.json" | ||||||
|  |           echo "::add-matcher::.github/workflows/matchers/gcc.json" | ||||||
|  |       - name: Run clang-tidy | ||||||
|  |         run: script/clang-tidy --all-headers --fix --split-num 4 --split-at ${{ matrix.split }} | ||||||
|  |       - name: Suggest changes | ||||||
|  |         run: script/ci-suggest-changes | ||||||
|  |  | ||||||
|  |   lint-python: | ||||||
|  |     # Don't use the esphome-lint docker image because it may contain outdated requirements. | ||||||
|  |     # This way, all dependencies are cached via the cache action. | ||||||
|  |     runs-on: ubuntu-latest | ||||||
|  |     steps: | ||||||
|  |       - uses: actions/checkout@v2 | ||||||
|  |       - name: Set up Python | ||||||
|  |         uses: actions/setup-python@v2 | ||||||
|  |         with: | ||||||
|  |           python-version: '3.7' | ||||||
|  |       - name: Cache pip modules | ||||||
|  |         uses: actions/cache@v1 | ||||||
|  |         with: | ||||||
|  |           path: ~/.cache/pip | ||||||
|  |           key: esphome-pip-3.7-${{ hashFiles('setup.py') }} | ||||||
|  |           restore-keys: | | ||||||
|  |             esphome-pip-3.7- | ||||||
|  |       - name: Set up python environment | ||||||
|  |         run: script/setup | ||||||
|  |  | ||||||
|  |       - name: Register problem matchers | ||||||
|  |         run: | | ||||||
|  |           echo "::add-matcher::.github/workflows/matchers/ci-custom.json" | ||||||
|  |           echo "::add-matcher::.github/workflows/matchers/lint-python.json" | ||||||
|  |           echo "::add-matcher::.github/workflows/matchers/python.json" | ||||||
|  |       - name: Lint Custom | ||||||
|  |         run: script/ci-custom.py | ||||||
|  |       - name: Lint Python | ||||||
|  |         run: script/lint-python | ||||||
|  |       - name: Lint CODEOWNERS | ||||||
|  |         run: script/build_codeowners.py --check | ||||||
|  |  | ||||||
|  |   test: | ||||||
|  |     runs-on: ubuntu-latest | ||||||
|  |     strategy: | ||||||
|  |       matrix: | ||||||
|  |           test: | ||||||
|  |           - test1 | ||||||
|  |           - test2 | ||||||
|  |           - test3 | ||||||
|  |           - test4 | ||||||
|  |     steps: | ||||||
|  |       - uses: actions/checkout@v2 | ||||||
|  |       - name: Set up Python | ||||||
|  |         uses: actions/setup-python@v2 | ||||||
|  |         with: | ||||||
|  |           python-version: '3.7' | ||||||
|  |       - name: Cache pip modules | ||||||
|  |         uses: actions/cache@v1 | ||||||
|  |         with: | ||||||
|  |           path: ~/.cache/pip | ||||||
|  |           key: esphome-pip-3.7-${{ hashFiles('setup.py') }} | ||||||
|  |           restore-keys: | | ||||||
|  |             esphome-pip-3.7- | ||||||
|  |       # Use per test platformio cache because tests have different platform versions | ||||||
|  |       - name: Cache ~/.platformio | ||||||
|  |         uses: actions/cache@v1 | ||||||
|  |         with: | ||||||
|  |           path: ~/.platformio | ||||||
|  |           key: test-home-platformio-${{ matrix.test }}-${{ hashFiles('esphome/core_config.py') }} | ||||||
|  |           restore-keys: | | ||||||
|  |             test-home-platformio-${{ matrix.test }}- | ||||||
|  |       - name: Set up environment | ||||||
|  |         run: script/setup | ||||||
|  |  | ||||||
|  |       - name: Register problem matchers | ||||||
|  |         run: | | ||||||
|  |           echo "::add-matcher::.github/workflows/matchers/gcc.json" | ||||||
|  |           echo "::add-matcher::.github/workflows/matchers/python.json" | ||||||
|  |       - run: esphome tests/${{ matrix.test }}.yaml compile | ||||||
|  |  | ||||||
|  |   pytest: | ||||||
|  |     runs-on: ubuntu-latest | ||||||
|  |     steps: | ||||||
|  |       - uses: actions/checkout@v2 | ||||||
|  |       - name: Set up Python | ||||||
|  |         uses: actions/setup-python@v2 | ||||||
|  |         with: | ||||||
|  |           python-version: '3.7' | ||||||
|  |       - name: Cache pip modules | ||||||
|  |         uses: actions/cache@v1 | ||||||
|  |         with: | ||||||
|  |           path: ~/.cache/pip | ||||||
|  |           key: esphome-pip-3.7-${{ hashFiles('setup.py') }} | ||||||
|  |           restore-keys: | | ||||||
|  |             esphome-pip-3.7- | ||||||
|  |       - name: Set up environment | ||||||
|  |         run: script/setup | ||||||
|  |       - name: Install Github Actions annotator | ||||||
|  |         run: pip install pytest-github-actions-annotate-failures | ||||||
|  |  | ||||||
|  |       - name: Register problem matchers | ||||||
|  |         run: | | ||||||
|  |           echo "::add-matcher::.github/workflows/matchers/python.json" | ||||||
|  |       - name: Run pytest | ||||||
|  |         run: | | ||||||
|  |           pytest \ | ||||||
|  |             -qq \ | ||||||
|  |             --durations=10 \ | ||||||
|  |             -o console_output_style=count \ | ||||||
|  |             tests | ||||||
|  |  | ||||||
|  |   deploy-pypi: | ||||||
|  |     name: Build and publish to PyPi | ||||||
|  |     if: github.repository == 'esphome/esphome' | ||||||
|  |     needs: [lint-clang-format, lint-clang-tidy, lint-python, test, pytest] | ||||||
|  |     runs-on: ubuntu-latest | ||||||
|  |     steps: | ||||||
|  |       - uses: actions/checkout@v2 | ||||||
|  |       - name: Set up Python | ||||||
|  |         uses: actions/setup-python@v1 | ||||||
|  |         with: | ||||||
|  |           python-version: '3.x' | ||||||
|  |       - name: Set up python environment | ||||||
|  |         run: | | ||||||
|  |           script/setup | ||||||
|  |           pip install setuptools wheel twine | ||||||
|  |       - name: Build | ||||||
|  |         run: python setup.py sdist bdist_wheel | ||||||
|  |       - name: Upload | ||||||
|  |         env: | ||||||
|  |           TWINE_USERNAME: __token__ | ||||||
|  |           TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }} | ||||||
|  |         run: twine upload dist/* | ||||||
|  |  | ||||||
|  |   deploy-docker: | ||||||
|  |     name: Build and publish docker containers | ||||||
|  |     if: github.repository == 'esphome/esphome' | ||||||
|  |     runs-on: ubuntu-latest | ||||||
|  |     needs: [lint-clang-format, lint-clang-tidy, lint-python, test, pytest] | ||||||
|  |     strategy: | ||||||
|  |       matrix: | ||||||
|  |         arch: [amd64, armv7, aarch64] | ||||||
|  |         build_type: ["hassio", "docker"] | ||||||
|  |     steps: | ||||||
|  |       - uses: actions/checkout@v2 | ||||||
|  |       - name: Set TAG | ||||||
|  |         run: | | ||||||
|  |           TAG="${GITHUB_REF#refs/tags/v}" | ||||||
|  |           echo "::set-env name=TAG::${TAG}" | ||||||
|  |       - name: Set up env variables | ||||||
|  |         run: | | ||||||
|  |           base_version="2.6.0" | ||||||
|  |  | ||||||
|  |           if [[ "${{ matrix.build_type }}" == "hassio" ]]; then | ||||||
|  |             build_from="esphome/esphome-hassio-base-${{ matrix.arch }}:${base_version}" | ||||||
|  |             build_to="esphome/esphome-hassio-${{ matrix.arch }}" | ||||||
|  |             dockerfile="docker/Dockerfile.hassio" | ||||||
|  |           else | ||||||
|  |             build_from="esphome/esphome-base-${{ matrix.arch }}:${base_version}" | ||||||
|  |             build_to="esphome/esphome-${{ matrix.arch }}" | ||||||
|  |             dockerfile="docker/Dockerfile" | ||||||
|  |           fi | ||||||
|  |  | ||||||
|  |           if [[ "${{ github.event.release.prerelease }}" == "true" ]]; then | ||||||
|  |             cache_tag="beta" | ||||||
|  |           else | ||||||
|  |             cache_tag="latest" | ||||||
|  |           fi | ||||||
|  |  | ||||||
|  |           # Set env variables so these values don't need to be calculated again | ||||||
|  |           echo "::set-env name=BUILD_FROM::${build_from}" | ||||||
|  |           echo "::set-env name=BUILD_TO::${build_to}" | ||||||
|  |           echo "::set-env name=DOCKERFILE::${dockerfile}" | ||||||
|  |           echo "::set-env name=CACHE_TAG::${cache_tag}" | ||||||
|  |       - name: Pull for cache | ||||||
|  |         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 build \ | ||||||
|  |             --build-arg "BUILD_FROM=${BUILD_FROM}" \ | ||||||
|  |             --build-arg "BUILD_VERSION=${TAG}" \ | ||||||
|  |             --tag "${BUILD_TO}:${TAG}" \ | ||||||
|  |             --cache-from "${BUILD_TO}:${CACHE_TAG}" \ | ||||||
|  |             --file "${DOCKERFILE}" \ | ||||||
|  |             . | ||||||
|  |       - name: Log in to docker hub | ||||||
|  |         env: | ||||||
|  |           DOCKER_USER: ${{ secrets.DOCKER_USER }} | ||||||
|  |           DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} | ||||||
|  |         run: docker login -u "${DOCKER_USER}" -p "${DOCKER_PASSWORD}" | ||||||
|  |       - run: docker push "${BUILD_TO}:${TAG}" | ||||||
|  |  | ||||||
|  |       # Always publish to beta tag (also full releases) | ||||||
|  |       - name: Publish docker beta tag | ||||||
|  |         run: | | ||||||
|  |           docker tag "${BUILD_TO}:${TAG}" "${BUILD_TO}:beta" | ||||||
|  |           docker push "${BUILD_TO}:beta" | ||||||
|  |  | ||||||
|  |       - if: ${{ !github.event.release.prerelease }} | ||||||
|  |         name: Publish docker latest tag | ||||||
|  |         run: | | ||||||
|  |           docker tag "${BUILD_TO}:${TAG}" "${BUILD_TO}:latest" | ||||||
|  |           docker push "${BUILD_TO}:latest" | ||||||
|  |  | ||||||
|  |   deploy-docker-manifest: | ||||||
|  |     if: github.repository == 'esphome/esphome' | ||||||
|  |     runs-on: ubuntu-latest | ||||||
|  |     needs: [deploy-docker] | ||||||
|  |     steps: | ||||||
|  |     - name: Enable experimental manifest support | ||||||
|  |       run: | | ||||||
|  |         mkdir -p ~/.docker | ||||||
|  |         echo "{\"experimental\": \"enabled\"}" > ~/.docker/config.json | ||||||
|  |     - name: Set TAG | ||||||
|  |       run: | | ||||||
|  |         TAG="${GITHUB_REF#refs/tags/v}" | ||||||
|  |         echo "::set-env name=TAG::${TAG}" | ||||||
|  |     - name: Log in to docker hub | ||||||
|  |       env: | ||||||
|  |         DOCKER_USER: ${{ secrets.DOCKER_USER }} | ||||||
|  |         DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} | ||||||
|  |       run: docker login -u "${DOCKER_USER}" -p "${DOCKER_PASSWORD}" | ||||||
|  |     - name: "Create the manifest" | ||||||
|  |       run: | | ||||||
|  |         docker manifest create esphome/esphome:${TAG} \ | ||||||
|  |           esphome/esphome-aarch64:${TAG} \ | ||||||
|  |           esphome/esphome-amd64:${TAG} \ | ||||||
|  |           esphome/esphome-armv7:${TAG} | ||||||
|  |         docker manifest push esphome/esphome:${TAG} | ||||||
|  |  | ||||||
|  |     - name: Publish docker beta tag | ||||||
|  |       run: | | ||||||
|  |         docker manifest create esphome/esphome:beta \ | ||||||
|  |           esphome/esphome-aarch64:${TAG} \ | ||||||
|  |           esphome/esphome-amd64:${TAG} \ | ||||||
|  |           esphome/esphome-armv7:${TAG} | ||||||
|  |         docker manifest push esphome/esphome:beta | ||||||
|  |  | ||||||
|  |     - name: Publish docker latest tag | ||||||
|  |       if: ${{ !github.event.release.prerelease }} | ||||||
|  |       run: | | ||||||
|  |         docker manifest create esphome/esphome:latest \ | ||||||
|  |           esphome/esphome-aarch64:${TAG} \ | ||||||
|  |           esphome/esphome-amd64:${TAG} \ | ||||||
|  |           esphome/esphome-armv7:${TAG} | ||||||
|  |         docker manifest push esphome/esphome:latest | ||||||
|  |  | ||||||
|  |   deploy-hassio-repo: | ||||||
|  |     if: github.repository == 'esphome/esphome' | ||||||
|  |     runs-on: ubuntu-latest | ||||||
|  |     needs: [deploy-docker] | ||||||
|  |     steps: | ||||||
|  |       - env: | ||||||
|  |           TOKEN: ${{ secrets.DEPLOY_HASSIO_TOKEN }} | ||||||
|  |         run: | | ||||||
|  |           TAG="${GITHUB_REF#refs/tags/v}" | ||||||
|  |           curl \ | ||||||
|  |             -u ":$TOKEN" \ | ||||||
|  |             -X POST \ | ||||||
|  |             -H "Accept: application/vnd.github.v3+json" \ | ||||||
|  |             https://api.github.com/repos/esphome/hassio/actions/workflows/bump-version.yml/dispatches \ | ||||||
|  |             -d "{\"ref\":\"master\",\"inputs\":{\"version\":\"$TAG\"}}" | ||||||
							
								
								
									
										5
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										5
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -10,6 +10,9 @@ __pycache__/ | |||||||
| *.sublime-project | *.sublime-project | ||||||
| *.sublime-workspace | *.sublime-workspace | ||||||
|  |  | ||||||
|  | # Intellij Idea | ||||||
|  | .idea | ||||||
|  |  | ||||||
| # Hide some OS X stuff | # Hide some OS X stuff | ||||||
| .DS_Store | .DS_Store | ||||||
| .AppleDouble | .AppleDouble | ||||||
| @@ -48,8 +51,10 @@ htmlcov/ | |||||||
| .coverage | .coverage | ||||||
| .coverage.* | .coverage.* | ||||||
| .cache | .cache | ||||||
|  | .esphome | ||||||
| nosetests.xml | nosetests.xml | ||||||
| coverage.xml | coverage.xml | ||||||
|  | cov.xml | ||||||
| *.cover | *.cover | ||||||
| .hypothesis/ | .hypothesis/ | ||||||
| .pytest_cache/ | .pytest_cache/ | ||||||
|   | |||||||
							
								
								
									
										342
									
								
								.gitlab-ci.yml
									
									
									
									
									
								
							
							
						
						
									
										342
									
								
								.gitlab-ci.yml
									
									
									
									
									
								
							| @@ -1,342 +0,0 @@ | |||||||
| --- |  | ||||||
| # Based on https://gitlab.com/hassio-addons/addon-node-red/blob/master/.gitlab-ci.yml |  | ||||||
| variables: |  | ||||||
|   DOCKER_DRIVER: overlay2 |  | ||||||
|   DOCKER_HOST: tcp://docker:2375/ |  | ||||||
|   BASE_VERSION: '2.1.1' |  | ||||||
|   TZ: UTC |  | ||||||
|  |  | ||||||
| stages: |  | ||||||
|   - lint |  | ||||||
|   - test |  | ||||||
|   - deploy |  | ||||||
|  |  | ||||||
| .lint: &lint |  | ||||||
|   image: esphome/esphome-lint:latest |  | ||||||
|   stage: lint |  | ||||||
|   before_script: |  | ||||||
|     - script/setup |  | ||||||
|   tags: |  | ||||||
|     - docker |  | ||||||
|  |  | ||||||
| .test: &test |  | ||||||
|   image: esphome/esphome-lint:latest |  | ||||||
|   stage: test |  | ||||||
|   before_script: |  | ||||||
|     - script/setup |  | ||||||
|   tags: |  | ||||||
|     - docker |  | ||||||
|  |  | ||||||
| .docker-base: &docker-base |  | ||||||
|   image: esphome/esphome-base-builder |  | ||||||
|   before_script: |  | ||||||
|     - docker info |  | ||||||
|     - docker login -u "$DOCKER_USER" -p "$DOCKER_PASSWORD" |  | ||||||
|   script: |  | ||||||
|     - docker run --rm --privileged multiarch/qemu-user-static:5.0.0-2 --reset -p yes |  | ||||||
|     - TAG="${CI_COMMIT_TAG#v}" |  | ||||||
|     - TAG="${TAG:-${CI_COMMIT_SHA:0:7}}" |  | ||||||
|     - echo "Tag ${TAG}" |  | ||||||
|  |  | ||||||
|     - | |  | ||||||
|       if [[ "${IS_HASSIO}" == "YES" ]]; then |  | ||||||
|         BUILD_FROM=esphome/esphome-hassio-base-${BUILD_ARCH}:${BASE_VERSION} |  | ||||||
|         BUILD_TO=esphome/esphome-hassio-${BUILD_ARCH} |  | ||||||
|         DOCKERFILE=docker/Dockerfile.hassio |  | ||||||
|       else |  | ||||||
|         BUILD_FROM=esphome/esphome-base-${BUILD_ARCH}:${BASE_VERSION} |  | ||||||
|         if [[ "${BUILD_ARCH}" == "amd64" ]]; then |  | ||||||
|           BUILD_TO=esphome/esphome |  | ||||||
|         else |  | ||||||
|           BUILD_TO=esphome/esphome-${BUILD_ARCH} |  | ||||||
|         fi |  | ||||||
|         DOCKERFILE=docker/Dockerfile |  | ||||||
|       fi |  | ||||||
|  |  | ||||||
|     - | |  | ||||||
|       docker build \ |  | ||||||
|         --build-arg "BUILD_FROM=${BUILD_FROM}" \ |  | ||||||
|         --build-arg "BUILD_VERSION=${TAG}" \ |  | ||||||
|         --tag "${BUILD_TO}:${TAG}" \ |  | ||||||
|         --file "${DOCKERFILE}" \ |  | ||||||
|         . |  | ||||||
|     - | |  | ||||||
|       if [[ "${RELEASE}" = "YES" ]]; then |  | ||||||
|         echo "Pushing to ${BUILD_TO}:${TAG}" |  | ||||||
|         docker push "${BUILD_TO}:${TAG}" |  | ||||||
|       fi |  | ||||||
|     - | |  | ||||||
|       if [[ "${LATEST}" = "YES" ]]; then |  | ||||||
|         echo "Pushing to :latest" |  | ||||||
|         docker tag ${BUILD_TO}:${TAG} ${BUILD_TO}:latest |  | ||||||
|         docker push ${BUILD_TO}:latest |  | ||||||
|       fi |  | ||||||
|     - | |  | ||||||
|       if [[ "${BETA}" = "YES" ]]; then |  | ||||||
|         echo "Pushing to :beta" |  | ||||||
|         docker tag \ |  | ||||||
|           ${BUILD_TO}:${TAG} \ |  | ||||||
|           ${BUILD_TO}:beta |  | ||||||
|         docker push ${BUILD_TO}:beta |  | ||||||
|       fi |  | ||||||
|     - | |  | ||||||
|       if [[ "${DEV}" = "YES" ]]; then |  | ||||||
|         echo "Pushing to :dev" |  | ||||||
|         docker tag \ |  | ||||||
|           ${BUILD_TO}:${TAG} \ |  | ||||||
|           ${BUILD_TO}:dev |  | ||||||
|         docker push ${BUILD_TO}:dev |  | ||||||
|       fi |  | ||||||
|   services: |  | ||||||
|     - docker:dind |  | ||||||
|   tags: |  | ||||||
|     - docker |  | ||||||
|   stage: deploy |  | ||||||
|  |  | ||||||
| lint-custom: |  | ||||||
|   <<: *lint |  | ||||||
|   script: |  | ||||||
|     - script/ci-custom.py |  | ||||||
|  |  | ||||||
| lint-python: |  | ||||||
|   <<: *lint |  | ||||||
|   script: |  | ||||||
|     - script/lint-python |  | ||||||
|  |  | ||||||
| lint-tidy: |  | ||||||
|   <<: *lint |  | ||||||
|   script: |  | ||||||
|     - pio init --ide atom |  | ||||||
|     - script/clang-tidy --all-headers --fix |  | ||||||
|     - script/ci-suggest-changes |  | ||||||
|  |  | ||||||
| lint-format: |  | ||||||
|   <<: *lint |  | ||||||
|   script: |  | ||||||
|     - script/clang-format -i |  | ||||||
|     - script/ci-suggest-changes |  | ||||||
|  |  | ||||||
| test1: |  | ||||||
|   <<: *test |  | ||||||
|   script: |  | ||||||
|     - esphome tests/test1.yaml compile |  | ||||||
|  |  | ||||||
| test2: |  | ||||||
|   <<: *test |  | ||||||
|   script: |  | ||||||
|     - esphome tests/test2.yaml compile |  | ||||||
|  |  | ||||||
| test3: |  | ||||||
|   <<: *test |  | ||||||
|   script: |  | ||||||
|     - esphome tests/test3.yaml compile |  | ||||||
|  |  | ||||||
| .deploy-pypi: &deploy-pypi |  | ||||||
|   <<: *lint |  | ||||||
|   stage: deploy |  | ||||||
|   script: |  | ||||||
|     - pip install twine wheel |  | ||||||
|     - python setup.py sdist bdist_wheel |  | ||||||
|     - twine upload dist/* |  | ||||||
|  |  | ||||||
| deploy-release:pypi: |  | ||||||
|   <<: *deploy-pypi |  | ||||||
|   only: |  | ||||||
|     - /^v\d+\.\d+\.\d+$/ |  | ||||||
|   except: |  | ||||||
|     - /^(?!master).+@/ |  | ||||||
|  |  | ||||||
| deploy-beta:pypi: |  | ||||||
|   <<: *deploy-pypi |  | ||||||
|   only: |  | ||||||
|     - /^v\d+\.\d+\.\d+b\d+$/ |  | ||||||
|   except: |  | ||||||
|     - /^(?!rc).+@/ |  | ||||||
|  |  | ||||||
| .latest: &latest |  | ||||||
|   <<: *docker-base |  | ||||||
|   only: |  | ||||||
|     - /^v([0-9\.]+)$/ |  | ||||||
|   except: |  | ||||||
|     - branches |  | ||||||
|  |  | ||||||
| .beta: &beta |  | ||||||
|   <<: *docker-base |  | ||||||
|   only: |  | ||||||
|     - /^v([0-9\.]+b\d+)$/ |  | ||||||
|   except: |  | ||||||
|     - branches |  | ||||||
|  |  | ||||||
| .dev: &dev |  | ||||||
|   <<: *docker-base |  | ||||||
|   only: |  | ||||||
|     - dev |  | ||||||
|  |  | ||||||
| aarch64-beta-docker: |  | ||||||
|   <<: *beta |  | ||||||
|   variables: |  | ||||||
|     BETA: "YES" |  | ||||||
|     BUILD_ARCH: aarch64 |  | ||||||
|     IS_HASSIO: "NO" |  | ||||||
|     RELEASE: "YES" |  | ||||||
| aarch64-beta-hassio: |  | ||||||
|   <<: *beta |  | ||||||
|   variables: |  | ||||||
|     BETA: "YES" |  | ||||||
|     BUILD_ARCH: aarch64 |  | ||||||
|     IS_HASSIO: "YES" |  | ||||||
|     RELEASE: "YES" |  | ||||||
| aarch64-dev-docker: |  | ||||||
|   <<: *dev |  | ||||||
|   variables: |  | ||||||
|     BUILD_ARCH: aarch64 |  | ||||||
|     DEV: "YES" |  | ||||||
|     IS_HASSIO: "NO" |  | ||||||
| aarch64-dev-hassio: |  | ||||||
|   <<: *dev |  | ||||||
|   variables: |  | ||||||
|     BUILD_ARCH: aarch64 |  | ||||||
|     DEV: "YES" |  | ||||||
|     IS_HASSIO: "YES" |  | ||||||
| aarch64-latest-docker: |  | ||||||
|   <<: *latest |  | ||||||
|   variables: |  | ||||||
|     BETA: "YES" |  | ||||||
|     BUILD_ARCH: aarch64 |  | ||||||
|     IS_HASSIO: "NO" |  | ||||||
|     LATEST: "YES" |  | ||||||
|     RELEASE: "YES" |  | ||||||
| aarch64-latest-hassio: |  | ||||||
|   <<: *latest |  | ||||||
|   variables: |  | ||||||
|     BETA: "YES" |  | ||||||
|     BUILD_ARCH: aarch64 |  | ||||||
|     IS_HASSIO: "YES" |  | ||||||
|     LATEST: "YES" |  | ||||||
|     RELEASE: "YES" |  | ||||||
| amd64-beta-docker: |  | ||||||
|   <<: *beta |  | ||||||
|   variables: |  | ||||||
|     BETA: "YES" |  | ||||||
|     BUILD_ARCH: amd64 |  | ||||||
|     IS_HASSIO: "NO" |  | ||||||
|     RELEASE: "YES" |  | ||||||
| amd64-beta-hassio: |  | ||||||
|   <<: *beta |  | ||||||
|   variables: |  | ||||||
|     BETA: "YES" |  | ||||||
|     BUILD_ARCH: amd64 |  | ||||||
|     IS_HASSIO: "YES" |  | ||||||
|     RELEASE: "YES" |  | ||||||
| amd64-dev-docker: |  | ||||||
|   <<: *dev |  | ||||||
|   variables: |  | ||||||
|     BUILD_ARCH: amd64 |  | ||||||
|     DEV: "YES" |  | ||||||
|     IS_HASSIO: "NO" |  | ||||||
| amd64-dev-hassio: |  | ||||||
|   <<: *dev |  | ||||||
|   variables: |  | ||||||
|     BUILD_ARCH: amd64 |  | ||||||
|     DEV: "YES" |  | ||||||
|     IS_HASSIO: "YES" |  | ||||||
| amd64-latest-docker: |  | ||||||
|   <<: *latest |  | ||||||
|   variables: |  | ||||||
|     BETA: "YES" |  | ||||||
|     BUILD_ARCH: amd64 |  | ||||||
|     IS_HASSIO: "NO" |  | ||||||
|     LATEST: "YES" |  | ||||||
|     RELEASE: "YES" |  | ||||||
| amd64-latest-hassio: |  | ||||||
|   <<: *latest |  | ||||||
|   variables: |  | ||||||
|     BETA: "YES" |  | ||||||
|     BUILD_ARCH: amd64 |  | ||||||
|     IS_HASSIO: "YES" |  | ||||||
|     LATEST: "YES" |  | ||||||
|     RELEASE: "YES" |  | ||||||
| armv7-beta-docker: |  | ||||||
|   <<: *beta |  | ||||||
|   variables: |  | ||||||
|     BETA: "YES" |  | ||||||
|     BUILD_ARCH: armv7 |  | ||||||
|     IS_HASSIO: "NO" |  | ||||||
|     RELEASE: "YES" |  | ||||||
| armv7-beta-hassio: |  | ||||||
|   <<: *beta |  | ||||||
|   variables: |  | ||||||
|     BETA: "YES" |  | ||||||
|     BUILD_ARCH: armv7 |  | ||||||
|     IS_HASSIO: "YES" |  | ||||||
|     RELEASE: "YES" |  | ||||||
| armv7-dev-docker: |  | ||||||
|   <<: *dev |  | ||||||
|   variables: |  | ||||||
|     BUILD_ARCH: armv7 |  | ||||||
|     DEV: "YES" |  | ||||||
|     IS_HASSIO: "NO" |  | ||||||
| armv7-dev-hassio: |  | ||||||
|   <<: *dev |  | ||||||
|   variables: |  | ||||||
|     BUILD_ARCH: armv7 |  | ||||||
|     DEV: "YES" |  | ||||||
|     IS_HASSIO: "YES" |  | ||||||
| armv7-latest-docker: |  | ||||||
|   <<: *latest |  | ||||||
|   variables: |  | ||||||
|     BETA: "YES" |  | ||||||
|     BUILD_ARCH: armv7 |  | ||||||
|     IS_HASSIO: "NO" |  | ||||||
|     LATEST: "YES" |  | ||||||
|     RELEASE: "YES" |  | ||||||
| armv7-latest-hassio: |  | ||||||
|   <<: *latest |  | ||||||
|   variables: |  | ||||||
|     BETA: "YES" |  | ||||||
|     BUILD_ARCH: armv7 |  | ||||||
|     IS_HASSIO: "YES" |  | ||||||
|     LATEST: "YES" |  | ||||||
|     RELEASE: "YES" |  | ||||||
| i386-beta-docker: |  | ||||||
|   <<: *beta |  | ||||||
|   variables: |  | ||||||
|     BETA: "YES" |  | ||||||
|     BUILD_ARCH: i386 |  | ||||||
|     IS_HASSIO: "NO" |  | ||||||
|     RELEASE: "YES" |  | ||||||
| i386-beta-hassio: |  | ||||||
|   <<: *beta |  | ||||||
|   variables: |  | ||||||
|     BETA: "YES" |  | ||||||
|     BUILD_ARCH: i386 |  | ||||||
|     IS_HASSIO: "YES" |  | ||||||
|     RELEASE: "YES" |  | ||||||
| i386-dev-docker: |  | ||||||
|   <<: *dev |  | ||||||
|   variables: |  | ||||||
|     BUILD_ARCH: i386 |  | ||||||
|     DEV: "YES" |  | ||||||
|     IS_HASSIO: "NO" |  | ||||||
| i386-dev-hassio: |  | ||||||
|   <<: *dev |  | ||||||
|   variables: |  | ||||||
|     BUILD_ARCH: i386 |  | ||||||
|     DEV: "YES" |  | ||||||
|     IS_HASSIO: "YES" |  | ||||||
| i386-latest-docker: |  | ||||||
|   <<: *latest |  | ||||||
|   variables: |  | ||||||
|     BETA: "YES" |  | ||||||
|     BUILD_ARCH: i386 |  | ||||||
|     IS_HASSIO: "NO" |  | ||||||
|     LATEST: "YES" |  | ||||||
|     RELEASE: "YES" |  | ||||||
| i386-latest-hassio: |  | ||||||
|   <<: *latest |  | ||||||
|   variables: |  | ||||||
|     BETA: "YES" |  | ||||||
|     BUILD_ARCH: i386 |  | ||||||
|     IS_HASSIO: "YES" |  | ||||||
|     LATEST: "YES" |  | ||||||
|     RELEASE: "YES" |  | ||||||
| @@ -2,5 +2,5 @@ ports: | |||||||
| - port: 6052 | - port: 6052 | ||||||
|   onOpen: open-preview |   onOpen: open-preview | ||||||
| tasks: | tasks: | ||||||
| - before: script/setup | - before: pyenv local $(pyenv version | grep '^3\.' | cut -d ' ' -f 1) && script/setup | ||||||
|   command: python -m esphome config dashboard |   command: python -m esphome config dashboard | ||||||
|   | |||||||
							
								
								
									
										11
									
								
								.pre-commit-config.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								.pre-commit-config.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | |||||||
|  | # 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 | ||||||
|  |     hooks: | ||||||
|  |     -   id: trailing-whitespace | ||||||
|  |     -   id: end-of-file-fixer | ||||||
|  |     -   id: check-yaml | ||||||
|  |     -   id: check-added-large-files | ||||||
|  |     -   id: flake8 | ||||||
							
								
								
									
										49
									
								
								.travis.yml
									
									
									
									
									
								
							
							
						
						
									
										49
									
								
								.travis.yml
									
									
									
									
									
								
							| @@ -1,49 +0,0 @@ | |||||||
| sudo: false |  | ||||||
| language: python |  | ||||||
| python: '3.6' |  | ||||||
| install: script/setup |  | ||||||
| cache: |  | ||||||
|   directories: |  | ||||||
|     - "~/.platformio" |  | ||||||
|  |  | ||||||
| matrix: |  | ||||||
|   fast_finish: true |  | ||||||
|   include: |  | ||||||
|     - python: "3.7" |  | ||||||
|       env: TARGET=Lint3.7 |  | ||||||
|       script: |  | ||||||
|         - script/ci-custom.py |  | ||||||
|         - flake8 esphome |  | ||||||
|         - pylint esphome |  | ||||||
|     - python: "3.6" |  | ||||||
|       env: TARGET=Test3.6 |  | ||||||
|       script: |  | ||||||
|         - esphome tests/test1.yaml compile |  | ||||||
|         - esphome tests/test2.yaml compile |  | ||||||
|         - esphome tests/test3.yaml compile |  | ||||||
|     - python: "2.7" |  | ||||||
|       env: TARGET=Test2.7 |  | ||||||
|       script: |  | ||||||
|         - esphome tests/test1.yaml compile |  | ||||||
|         - esphome tests/test2.yaml compile |  | ||||||
|         - esphome tests/test3.yaml compile |  | ||||||
|     - env: TARGET=Cpp-Lint |  | ||||||
|       dist: trusty |  | ||||||
|       sudo: required |  | ||||||
|       addons: |  | ||||||
|         apt: |  | ||||||
|           sources: |  | ||||||
|             - ubuntu-toolchain-r-test |  | ||||||
|             - llvm-toolchain-trusty-7 |  | ||||||
|           packages: |  | ||||||
|             - clang-tidy-7 |  | ||||||
|             - clang-format-7 |  | ||||||
|       before_script: |  | ||||||
|         - pio init --ide atom |  | ||||||
|         - clang-tidy-7 -version |  | ||||||
|         - clang-format-7 -version |  | ||||||
|         - clang-apply-replacements-7 -version |  | ||||||
|       script: |  | ||||||
|         - script/clang-tidy --all-headers -j 2 --fix |  | ||||||
|         - script/clang-format -i -j 2 |  | ||||||
|         - script/ci-suggest-changes |  | ||||||
							
								
								
									
										69
									
								
								CODEOWNERS
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								CODEOWNERS
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,69 @@ | |||||||
|  | # This file is generated by script/build_codeowners.py | ||||||
|  | # People marked here will be automatically requested for a review | ||||||
|  | # when the code that they own is touched. | ||||||
|  | # | ||||||
|  | # Every time an issue is created with a label corresponding to an integration, | ||||||
|  | # the integration's code owner is automatically notified. | ||||||
|  |  | ||||||
|  | # Core Code | ||||||
|  | setup.py @esphome/core | ||||||
|  | esphome/*.py @esphome/core | ||||||
|  | esphome/core/* @esphome/core | ||||||
|  |  | ||||||
|  | # Integrations | ||||||
|  | esphome/components/ac_dimmer/* @glmnet | ||||||
|  | esphome/components/adc/* @esphome/core | ||||||
|  | esphome/components/api/* @OttoWinter | ||||||
|  | esphome/components/async_tcp/* @OttoWinter | ||||||
|  | esphome/components/bang_bang/* @OttoWinter | ||||||
|  | esphome/components/binary_sensor/* @esphome/core | ||||||
|  | esphome/components/captive_portal/* @OttoWinter | ||||||
|  | esphome/components/climate/* @esphome/core | ||||||
|  | esphome/components/climate_ir/* @glmnet | ||||||
|  | esphome/components/coolix/* @glmnet | ||||||
|  | esphome/components/cover/* @esphome/core | ||||||
|  | esphome/components/ct_clamp/* @jesserockz | ||||||
|  | esphome/components/debug/* @OttoWinter | ||||||
|  | esphome/components/dfplayer/* @glmnet | ||||||
|  | esphome/components/dht/* @OttoWinter | ||||||
|  | esphome/components/exposure_notifications/* @OttoWinter | ||||||
|  | esphome/components/fastled_base/* @OttoWinter | ||||||
|  | esphome/components/globals/* @esphome/core | ||||||
|  | esphome/components/gpio/* @esphome/core | ||||||
|  | esphome/components/homeassistant/* @OttoWinter | ||||||
|  | esphome/components/i2c/* @esphome/core | ||||||
|  | esphome/components/integration/* @OttoWinter | ||||||
|  | esphome/components/interval/* @esphome/core | ||||||
|  | esphome/components/json/* @OttoWinter | ||||||
|  | esphome/components/ledc/* @OttoWinter | ||||||
|  | esphome/components/light/* @esphome/core | ||||||
|  | esphome/components/logger/* @esphome/core | ||||||
|  | esphome/components/network/* @esphome/core | ||||||
|  | esphome/components/ota/* @esphome/core | ||||||
|  | esphome/components/output/* @esphome/core | ||||||
|  | esphome/components/pid/* @OttoWinter | ||||||
|  | esphome/components/pn532/* @OttoWinter | ||||||
|  | esphome/components/power_supply/* @esphome/core | ||||||
|  | esphome/components/restart/* @esphome/core | ||||||
|  | esphome/components/rf_bridge/* @jesserockz | ||||||
|  | esphome/components/rtttl/* @glmnet | ||||||
|  | esphome/components/script/* @esphome/core | ||||||
|  | esphome/components/sensor/* @esphome/core | ||||||
|  | esphome/components/shutdown/* @esphome/core | ||||||
|  | esphome/components/sim800l/* @glmnet | ||||||
|  | esphome/components/spi/* @esphome/core | ||||||
|  | esphome/components/substitutions/* @esphome/core | ||||||
|  | esphome/components/sun/* @OttoWinter | ||||||
|  | esphome/components/switch/* @esphome/core | ||||||
|  | esphome/components/tcl112/* @glmnet | ||||||
|  | esphome/components/time/* @OttoWinter | ||||||
|  | esphome/components/tm1637/* @glmnet | ||||||
|  | esphome/components/tuya/binary_sensor/* @jesserockz | ||||||
|  | esphome/components/tuya/climate/* @jesserockz | ||||||
|  | esphome/components/tuya/sensor/* @jesserockz | ||||||
|  | esphome/components/tuya/switch/* @jesserockz | ||||||
|  | esphome/components/uart/* @esphome/core | ||||||
|  | esphome/components/ultrasonic/* @OttoWinter | ||||||
|  | esphome/components/version/* @esphome/core | ||||||
|  | esphome/components/web_server_base/* @OttoWinter | ||||||
|  | esphome/components/whirlpool/* @glmnet | ||||||
| @@ -1,5 +1,6 @@ | |||||||
| include LICENSE | include LICENSE | ||||||
| include README.md | include README.md | ||||||
|  | include requirements.txt | ||||||
| include esphome/dashboard/templates/*.html | include esphome/dashboard/templates/*.html | ||||||
| recursive-include esphome/dashboard/static *.ico *.js *.css *.woff* LICENSE | recursive-include esphome/dashboard/static *.ico *.js *.css *.woff* LICENSE | ||||||
| recursive-include esphome *.cpp *.h *.tcc | recursive-include esphome *.cpp *.h *.tcc | ||||||
|   | |||||||
| @@ -1,12 +1,21 @@ | |||||||
| ARG BUILD_FROM=esphome/esphome-base-amd64:2.1.1 | ARG BUILD_FROM=esphome/esphome-base-amd64:2.6.0 | ||||||
| FROM ${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 | ||||||
|  |  | ||||||
|  | # Then copy esphome and install | ||||||
| COPY . . | COPY . . | ||||||
| RUN pip3 install --no-cache-dir -e . | RUN pip3 install --no-cache-dir -e . | ||||||
|  |  | ||||||
| ENV USERNAME="" | # Settings for dashboard | ||||||
| ENV PASSWORD="" | ENV USERNAME="" PASSWORD="" | ||||||
|  |  | ||||||
|  | # The directory the user should mount their configuration files to | ||||||
| WORKDIR /config | WORKDIR /config | ||||||
|  | # Set entrypoint to esphome so that the user doesn't have to type 'esphome' | ||||||
|  | # in every docker command twice | ||||||
| ENTRYPOINT ["esphome"] | ENTRYPOINT ["esphome"] | ||||||
|  | # When no arguments given, start the dashboard in the workdir | ||||||
| CMD ["/config", "dashboard"] | CMD ["/config", "dashboard"] | ||||||
|   | |||||||
							
								
								
									
										13
									
								
								docker/Dockerfile.dev
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								docker/Dockerfile.dev
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | |||||||
|  | FROM esphome/esphome-base-amd64:2.6.0 | ||||||
|  |  | ||||||
|  | COPY . . | ||||||
|  |  | ||||||
|  | RUN apt-get update \ | ||||||
|  |     && apt-get install -y --no-install-recommends \ | ||||||
|  |         python3-wheel \ | ||||||
|  |         net-tools \ | ||||||
|  |     && apt-get clean \ | ||||||
|  |     && rm -rf /var/lib/apt/lists/* | ||||||
|  |  | ||||||
|  | WORKDIR /workspaces | ||||||
|  | ENV SHELL /bin/bash | ||||||
| @@ -1,11 +1,15 @@ | |||||||
| ARG BUILD_FROM | ARG BUILD_FROM | ||||||
| FROM ${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 root filesystem | # Copy root filesystem | ||||||
| COPY docker/rootfs/ / | COPY docker/rootfs/ / | ||||||
| COPY setup.py setup.cfg MANIFEST.in /opt/esphome/ |  | ||||||
| COPY esphome /opt/esphome/esphome |  | ||||||
|  |  | ||||||
|  | # Then copy esphome and install | ||||||
|  | COPY . /opt/esphome/ | ||||||
| RUN pip3 install --no-cache-dir -e /opt/esphome | RUN pip3 install --no-cache-dir -e /opt/esphome | ||||||
|  |  | ||||||
| # Build arguments | # Build arguments | ||||||
|   | |||||||
| @@ -1,20 +1,7 @@ | |||||||
| FROM esphome/esphome-base-amd64:2.1.1 | FROM esphome/esphome-lint-base:2.6.0 | ||||||
|  |  | ||||||
| RUN \ | COPY requirements.txt requirements_test.txt / | ||||||
|     apt-get update \ | RUN pip3 install --no-cache-dir -r /requirements.txt -r /requirements_test.txt | ||||||
|     && apt-get install -y --no-install-recommends \ |  | ||||||
|         clang-format-7 \ |  | ||||||
|         clang-tidy-7 \ |  | ||||||
|         patch \ |  | ||||||
|     && rm -rf \ |  | ||||||
|         /tmp/* \ |  | ||||||
|         /var/{cache,log}/* \ |  | ||||||
|         /var/lib/apt/lists/* |  | ||||||
|  |  | ||||||
| COPY requirements_test.txt /requirements_test.txt |  | ||||||
| RUN pip3 install --no-cache-dir wheel && pip3 install --no-cache-dir -r /requirements_test.txt |  | ||||||
|  |  | ||||||
| RUN ln -s /usr/bin/pip3 /usr/bin/pip && ln -f -s /usr/bin/python3 /usr/bin/python |  | ||||||
|  |  | ||||||
| VOLUME ["/esphome"] | VOLUME ["/esphome"] | ||||||
| WORKDIR /esphome | WORKDIR /esphome | ||||||
|   | |||||||
							
								
								
									
										10
									
								
								docker/rootfs/etc/cont-init.d/30-esphome.sh
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							
							
						
						
									
										10
									
								
								docker/rootfs/etc/cont-init.d/30-esphome.sh
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							| @@ -8,7 +8,15 @@ declare esphome_version | |||||||
|  |  | ||||||
| if bashio::config.has_value 'esphome_version'; then | if bashio::config.has_value 'esphome_version'; then | ||||||
|     esphome_version=$(bashio::config 'esphome_version') |     esphome_version=$(bashio::config 'esphome_version') | ||||||
|     full_url="https://github.com/esphome/esphome/archive/${esphome_version}.zip" |     if [[ $esphome_version == *":"* ]]; then | ||||||
|  |       IFS=':' read -r -a array <<< "$esphome_version" | ||||||
|  |       username=${array[0]} | ||||||
|  |       ref=${array[1]} | ||||||
|  |     else | ||||||
|  |       username="esphome" | ||||||
|  |       ref=$esphome_version | ||||||
|  |     fi | ||||||
|  |     full_url="https://github.com/${username}/esphome/archive/${ref}.zip" | ||||||
|     bashio::log.info "Installing esphome version '${esphome_version}' (${full_url})..." |     bashio::log.info "Installing esphome version '${esphome_version}' (${full_url})..." | ||||||
|     pip3 install -U --no-cache-dir "${full_url}" \ |     pip3 install -U --no-cache-dir "${full_url}" \ | ||||||
|       || bashio::exit.nok "Failed installing esphome pinned version." |       || bashio::exit.nok "Failed installing esphome pinned version." | ||||||
|   | |||||||
							
								
								
									
										0
									
								
								docker/rootfs/etc/cont-init.d/40-migrate.sh
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							
							
						
						
									
										0
									
								
								docker/rootfs/etc/cont-init.d/40-migrate.sh
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							
							
								
								
									
										0
									
								
								docker/rootfs/etc/nginx/nginx.conf
									
									
									
									
									
										
										
										Executable file → Normal file
									
								
							
							
						
						
									
										0
									
								
								docker/rootfs/etc/nginx/nginx.conf
									
									
									
									
									
										
										
										Executable file → Normal file
									
								
							| @@ -1,5 +1,3 @@ | |||||||
| from __future__ import print_function |  | ||||||
|  |  | ||||||
| import argparse | import argparse | ||||||
| import functools | import functools | ||||||
| import logging | import logging | ||||||
| @@ -14,40 +12,27 @@ from esphome.const import CONF_BAUD_RATE, CONF_BROKER, CONF_LOGGER, CONF_OTA, \ | |||||||
|     CONF_PASSWORD, CONF_PORT, CONF_ESPHOME, CONF_PLATFORMIO_OPTIONS |     CONF_PASSWORD, CONF_PORT, CONF_ESPHOME, CONF_PLATFORMIO_OPTIONS | ||||||
| from esphome.core import CORE, EsphomeError, coroutine, coroutine_with_priority | from esphome.core import CORE, EsphomeError, coroutine, coroutine_with_priority | ||||||
| from esphome.helpers import color, indent | from esphome.helpers import color, indent | ||||||
| from esphome.py_compat import IS_PY2, safe_input, IS_PY3 | from esphome.util import run_external_command, run_external_process, safe_print, list_yaml_files, \ | ||||||
| from esphome.util import run_external_command, run_external_process, safe_print, list_yaml_files |     get_serial_ports | ||||||
|  |  | ||||||
| _LOGGER = logging.getLogger(__name__) | _LOGGER = logging.getLogger(__name__) | ||||||
|  |  | ||||||
|  |  | ||||||
| def get_serial_ports(): |  | ||||||
|     # from https://github.com/pyserial/pyserial/blob/master/serial/tools/list_ports.py |  | ||||||
|     from serial.tools.list_ports import comports |  | ||||||
|     result = [] |  | ||||||
|     for port, desc, info in comports(include_links=True): |  | ||||||
|         if not port: |  | ||||||
|             continue |  | ||||||
|         if "VID:PID" in info: |  | ||||||
|             result.append((port, desc)) |  | ||||||
|     result.sort(key=lambda x: x[0]) |  | ||||||
|     return result |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def choose_prompt(options): | def choose_prompt(options): | ||||||
|     if not options: |     if not options: | ||||||
|         raise EsphomeError("Found no valid options for upload/logging, please make sure relevant " |         raise EsphomeError("Found no valid options for upload/logging, please make sure relevant " | ||||||
|                            "sections (ota, mqtt, ...) are in your configuration and/or the device " |                            "sections (ota, api, mqtt, ...) are in your configuration and/or the " | ||||||
|                            "is plugged in.") |                            "device is plugged in.") | ||||||
|  |  | ||||||
|     if len(options) == 1: |     if len(options) == 1: | ||||||
|         return options[0][1] |         return options[0][1] | ||||||
|  |  | ||||||
|     safe_print(u"Found multiple options, please choose one:") |     safe_print("Found multiple options, please choose one:") | ||||||
|     for i, (desc, _) in enumerate(options): |     for i, (desc, _) in enumerate(options): | ||||||
|         safe_print(u"  [{}] {}".format(i + 1, desc)) |         safe_print(f"  [{i+1}] {desc}") | ||||||
|  |  | ||||||
|     while True: |     while True: | ||||||
|         opt = safe_input('(number): ') |         opt = input('(number): ') | ||||||
|         if opt in options: |         if opt in options: | ||||||
|             opt = options.index(opt) |             opt = options.index(opt) | ||||||
|             break |             break | ||||||
| @@ -57,20 +42,20 @@ def choose_prompt(options): | |||||||
|                 raise ValueError |                 raise ValueError | ||||||
|             break |             break | ||||||
|         except ValueError: |         except ValueError: | ||||||
|             safe_print(color('red', u"Invalid option: '{}'".format(opt))) |             safe_print(color('red', f"Invalid option: '{opt}'")) | ||||||
|     return options[opt - 1][1] |     return options[opt - 1][1] | ||||||
|  |  | ||||||
|  |  | ||||||
| def choose_upload_log_host(default, check_default, show_ota, show_mqtt, show_api): | def choose_upload_log_host(default, check_default, show_ota, show_mqtt, show_api): | ||||||
|     options = [] |     options = [] | ||||||
|     for res, desc in get_serial_ports(): |     for port in get_serial_ports(): | ||||||
|         options.append((u"{} ({})".format(res, desc), res)) |         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((u"Over The Air ({})".format(CORE.address), CORE.address)) |         options.append((f"Over The Air ({CORE.address})", CORE.address)) | ||||||
|         if default == 'OTA': |         if default == 'OTA': | ||||||
|             return CORE.address |             return CORE.address | ||||||
|     if show_mqtt and 'mqtt' in CORE.config: |     if show_mqtt and 'mqtt' in CORE.config: | ||||||
|         options.append((u"MQTT ({})".format(CORE.config['mqtt'][CONF_BROKER]), 'MQTT')) |         options.append(("MQTT ({})".format(CORE.config['mqtt'][CONF_BROKER]), 'MQTT')) | ||||||
|         if default == 'OTA': |         if default == 'OTA': | ||||||
|             return 'MQTT' |             return 'MQTT' | ||||||
|     if default is not None: |     if default is not None: | ||||||
| @@ -108,11 +93,7 @@ def run_miniterm(config, port): | |||||||
|             except serial.SerialException: |             except serial.SerialException: | ||||||
|                 _LOGGER.error("Serial port closed!") |                 _LOGGER.error("Serial port closed!") | ||||||
|                 return |                 return | ||||||
|             if IS_PY2: |             line = raw.replace(b'\r', b'').replace(b'\n', b'').decode('utf8', 'backslashreplace') | ||||||
|                 line = raw.replace('\r', '').replace('\n', '') |  | ||||||
|             else: |  | ||||||
|                 line = raw.replace(b'\r', b'').replace(b'\n', b'').decode('utf8', |  | ||||||
|                                                                           'backslashreplace') |  | ||||||
|             time = datetime.now().time().strftime('[%H:%M:%S]') |             time = datetime.now().time().strftime('[%H:%M:%S]') | ||||||
|             message = time + line |             message = time + line | ||||||
|             safe_print(message) |             safe_print(message) | ||||||
| @@ -127,11 +108,9 @@ def wrap_to_code(name, comp): | |||||||
|     @functools.wraps(comp.to_code) |     @functools.wraps(comp.to_code) | ||||||
|     @coroutine_with_priority(coro.priority) |     @coroutine_with_priority(coro.priority) | ||||||
|     def wrapped(conf): |     def wrapped(conf): | ||||||
|         cg.add(cg.LineComment(u"{}:".format(name))) |         cg.add(cg.LineComment(f"{name}:")) | ||||||
|         if comp.config_schema is not None: |         if comp.config_schema is not None: | ||||||
|             conf_str = yaml_util.dump(conf) |             conf_str = yaml_util.dump(conf) | ||||||
|             if IS_PY2: |  | ||||||
|                 conf_str = conf_str.decode('utf-8') |  | ||||||
|             conf_str = conf_str.replace('//', '') |             conf_str = conf_str.replace('//', '') | ||||||
|             cg.add(cg.LineComment(indent(conf_str))) |             cg.add(cg.LineComment(indent(conf_str))) | ||||||
|         yield coro(conf) |         yield coro(conf) | ||||||
| @@ -140,6 +119,11 @@ def wrap_to_code(name, comp): | |||||||
|  |  | ||||||
|  |  | ||||||
| def write_cpp(config): | def write_cpp(config): | ||||||
|  |     generate_cpp_contents(config) | ||||||
|  |     return write_cpp_file() | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def generate_cpp_contents(config): | ||||||
|     _LOGGER.info("Generating C++ source...") |     _LOGGER.info("Generating C++ source...") | ||||||
|  |  | ||||||
|     for name, component, conf in iter_components(CORE.config): |     for name, component, conf in iter_components(CORE.config): | ||||||
| @@ -149,6 +133,8 @@ def write_cpp(config): | |||||||
|  |  | ||||||
|     CORE.flush_tasks() |     CORE.flush_tasks() | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def write_cpp_file(): | ||||||
|     writer.write_platformio_project() |     writer.write_platformio_project() | ||||||
|  |  | ||||||
|     code_s = indent(CORE.cpp_main_section) |     code_s = indent(CORE.cpp_main_section) | ||||||
| @@ -199,6 +185,10 @@ def upload_program(config, args, host): | |||||||
|  |  | ||||||
|     from esphome import espota2 |     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") | ||||||
|  |  | ||||||
|     ota_conf = config[CONF_OTA] |     ota_conf = config[CONF_OTA] | ||||||
|     remote_port = ota_conf[CONF_PORT] |     remote_port = ota_conf[CONF_PORT] | ||||||
|     password = ota_conf[CONF_PASSWORD] |     password = ota_conf[CONF_PASSWORD] | ||||||
| @@ -239,7 +229,7 @@ def setup_log(debug=False, quiet=False): | |||||||
|         log_level = logging.INFO |         log_level = logging.INFO | ||||||
|     logging.basicConfig(level=log_level) |     logging.basicConfig(level=log_level) | ||||||
|     fmt = "%(levelname)s %(message)s" |     fmt = "%(levelname)s %(message)s" | ||||||
|     colorfmt = "%(log_color)s{}%(reset)s".format(fmt) |     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) | ||||||
| @@ -288,12 +278,12 @@ def command_compile(args, config): | |||||||
|     if exit_code != 0: |     if exit_code != 0: | ||||||
|         return exit_code |         return exit_code | ||||||
|     if args.only_generate: |     if args.only_generate: | ||||||
|         _LOGGER.info(u"Successfully generated source code.") |         _LOGGER.info("Successfully generated source code.") | ||||||
|         return 0 |         return 0 | ||||||
|     exit_code = compile_program(args, config) |     exit_code = compile_program(args, config) | ||||||
|     if exit_code != 0: |     if exit_code != 0: | ||||||
|         return exit_code |         return exit_code | ||||||
|     _LOGGER.info(u"Successfully compiled program.") |     _LOGGER.info("Successfully compiled program.") | ||||||
|     return 0 |     return 0 | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -303,7 +293,7 @@ def command_upload(args, config): | |||||||
|     exit_code = upload_program(config, args, port) |     exit_code = upload_program(config, args, port) | ||||||
|     if exit_code != 0: |     if exit_code != 0: | ||||||
|         return exit_code |         return exit_code | ||||||
|     _LOGGER.info(u"Successfully uploaded program.") |     _LOGGER.info("Successfully uploaded program.") | ||||||
|     return 0 |     return 0 | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -320,13 +310,13 @@ def command_run(args, config): | |||||||
|     exit_code = compile_program(args, config) |     exit_code = compile_program(args, config) | ||||||
|     if exit_code != 0: |     if exit_code != 0: | ||||||
|         return exit_code |         return exit_code | ||||||
|     _LOGGER.info(u"Successfully compiled program.") |     _LOGGER.info("Successfully compiled program.") | ||||||
|     port = choose_upload_log_host(default=args.upload_port, check_default=None, |     port = choose_upload_log_host(default=args.upload_port, check_default=None, | ||||||
|                                   show_ota=True, show_mqtt=False, show_api=True) |                                   show_ota=True, show_mqtt=False, show_api=True) | ||||||
|     exit_code = upload_program(config, args, port) |     exit_code = upload_program(config, args, port) | ||||||
|     if exit_code != 0: |     if exit_code != 0: | ||||||
|         return exit_code |         return exit_code | ||||||
|     _LOGGER.info(u"Successfully uploaded program.") |     _LOGGER.info("Successfully uploaded program.") | ||||||
|     if args.no_logs: |     if args.no_logs: | ||||||
|         return 0 |         return 0 | ||||||
|     port = choose_upload_log_host(default=args.upload_port, check_default=port, |     port = choose_upload_log_host(default=args.upload_port, check_default=port, | ||||||
| @@ -345,7 +335,7 @@ def command_mqtt_fingerprint(args, config): | |||||||
|  |  | ||||||
|  |  | ||||||
| def command_version(args): | def command_version(args): | ||||||
|     safe_print(u"Version: {}".format(const.__version__)) |     safe_print(f"Version: {const.__version__}") | ||||||
|     return 0 |     return 0 | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -373,10 +363,10 @@ def command_update_all(args): | |||||||
|     twidth = 60 |     twidth = 60 | ||||||
|  |  | ||||||
|     def print_bar(middle_text): |     def print_bar(middle_text): | ||||||
|         middle_text = " {} ".format(middle_text) |         middle_text = f" {middle_text} " | ||||||
|         width = len(click.unstyle(middle_text)) |         width = len(click.unstyle(middle_text)) | ||||||
|         half_line = "=" * ((twidth - width) // 2) |         half_line = "=" * ((twidth - width) // 2) | ||||||
|         click.echo("%s%s%s" % (half_line, middle_text, half_line)) |         click.echo(f"{half_line}{middle_text}{half_line}") | ||||||
|  |  | ||||||
|     for f in files: |     for f in files: | ||||||
|         print("Updating {}".format(color('cyan', f))) |         print("Updating {}".format(color('cyan', f))) | ||||||
| @@ -427,12 +417,14 @@ POST_CONFIG_ACTIONS = { | |||||||
|  |  | ||||||
|  |  | ||||||
| def parse_args(argv): | def parse_args(argv): | ||||||
|     parser = argparse.ArgumentParser(description='ESPHome v{}'.format(const.__version__)) |     parser = argparse.ArgumentParser(description=f'ESPHome v{const.__version__}') | ||||||
|     parser.add_argument('-v', '--verbose', help="Enable verbose esphome logs.", |     parser.add_argument('-v', '--verbose', help="Enable verbose esphome logs.", | ||||||
|                         action='store_true') |                         action='store_true') | ||||||
|     parser.add_argument('-q', '--quiet', help="Disable all esphome logs.", |     parser.add_argument('-q', '--quiet', help="Disable all esphome logs.", | ||||||
|                         action='store_true') |                         action='store_true') | ||||||
|     parser.add_argument('--dashboard', help=argparse.SUPPRESS, 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.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') | ||||||
| @@ -521,14 +513,10 @@ def run_esphome(argv): | |||||||
|         _LOGGER.error("Missing configuration parameter, see esphome --help.") |         _LOGGER.error("Missing configuration parameter, see esphome --help.") | ||||||
|         return 1 |         return 1 | ||||||
|  |  | ||||||
|     if IS_PY2: |     if sys.version_info < (3, 6, 0): | ||||||
|         _LOGGER.warning("You're using ESPHome with python 2. Support for python 2 is deprecated " |         _LOGGER.error("You're running ESPHome with Python <3.6. ESPHome is no longer compatible " | ||||||
|                         "and will be removed in 1.15.0. Please reinstall ESPHome with python 3.6 " |                       "with this Python version. Please reinstall ESPHome with Python 3.6+") | ||||||
|                         "or higher.") |         return 1 | ||||||
|     elif IS_PY3 and sys.version_info < (3, 6, 0): |  | ||||||
|         _LOGGER.warning("You're using ESPHome with python 3.5. Support for python 3.5 is " |  | ||||||
|                         "deprecated and will be removed in 1.15.0. Please reinstall ESPHome with " |  | ||||||
|                         "python 3.6 or higher.") |  | ||||||
|  |  | ||||||
|     if args.command in PRE_CONFIG_ACTIONS: |     if args.command in PRE_CONFIG_ACTIONS: | ||||||
|         try: |         try: | ||||||
| @@ -541,13 +529,13 @@ def run_esphome(argv): | |||||||
|         CORE.config_path = conf_path |         CORE.config_path = conf_path | ||||||
|         CORE.dashboard = args.dashboard |         CORE.dashboard = args.dashboard | ||||||
|  |  | ||||||
|         config = read_config() |         config = read_config(dict(args.substitution) if args.substitution else {}) | ||||||
|         if config is None: |         if config is None: | ||||||
|             return 1 |             return 1 | ||||||
|         CORE.config = config |         CORE.config = config | ||||||
|  |  | ||||||
|         if args.command not in POST_CONFIG_ACTIONS: |         if args.command not in POST_CONFIG_ACTIONS: | ||||||
|             safe_print(u"Unknown command {}".format(args.command)) |             safe_print(f"Unknown command {args.command}") | ||||||
|  |  | ||||||
|         try: |         try: | ||||||
|             rc = POST_CONFIG_ACTIONS[args.command](args, config) |             rc = POST_CONFIG_ACTIONS[args.command](args, config) | ||||||
|   | |||||||
| @@ -14,7 +14,6 @@ import esphome.api.api_pb2 as pb | |||||||
| from esphome.const import CONF_PASSWORD, CONF_PORT | from esphome.const import CONF_PASSWORD, CONF_PORT | ||||||
| from esphome.core import EsphomeError | from esphome.core import EsphomeError | ||||||
| from esphome.helpers import resolve_ip_address, indent, color | from esphome.helpers import resolve_ip_address, indent, color | ||||||
| from esphome.py_compat import text_type, IS_PY2, byte_to_bytes, char_to_byte |  | ||||||
| from esphome.util import safe_print | from esphome.util import safe_print | ||||||
|  |  | ||||||
| _LOGGER = logging.getLogger(__name__) | _LOGGER = logging.getLogger(__name__) | ||||||
| @@ -67,16 +66,16 @@ MESSAGE_TYPE_TO_PROTO = { | |||||||
|  |  | ||||||
| def _varuint_to_bytes(value): | def _varuint_to_bytes(value): | ||||||
|     if value <= 0x7F: |     if value <= 0x7F: | ||||||
|         return byte_to_bytes(value) |         return bytes([value]) | ||||||
|  |  | ||||||
|     ret = bytes() |     ret = bytes() | ||||||
|     while value: |     while value: | ||||||
|         temp = value & 0x7F |         temp = value & 0x7F | ||||||
|         value >>= 7 |         value >>= 7 | ||||||
|         if value: |         if value: | ||||||
|             ret += byte_to_bytes(temp | 0x80) |             ret += bytes([temp | 0x80]) | ||||||
|         else: |         else: | ||||||
|             ret += byte_to_bytes(temp) |             ret += bytes([temp]) | ||||||
|  |  | ||||||
|     return ret |     return ret | ||||||
|  |  | ||||||
| @@ -84,8 +83,7 @@ def _varuint_to_bytes(value): | |||||||
| def _bytes_to_varuint(value): | def _bytes_to_varuint(value): | ||||||
|     result = 0 |     result = 0 | ||||||
|     bitpos = 0 |     bitpos = 0 | ||||||
|     for c in value: |     for val in value: | ||||||
|         val = char_to_byte(c) |  | ||||||
|         result |= (val & 0x7F) << bitpos |         result |= (val & 0x7F) << bitpos | ||||||
|         bitpos += 7 |         bitpos += 7 | ||||||
|         if (val & 0x80) == 0: |         if (val & 0x80) == 0: | ||||||
| @@ -191,8 +189,8 @@ class APIClient(threading.Thread): | |||||||
|         self._socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) |         self._socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) | ||||||
|         try: |         try: | ||||||
|             self._socket.connect((ip, self._port)) |             self._socket.connect((ip, self._port)) | ||||||
|         except socket.error as err: |         except OSError as err: | ||||||
|             err = APIConnectionError("Error connecting to {}: {}".format(ip, err)) |             err = APIConnectionError(f"Error connecting to {ip}: {err}") | ||||||
|             self._fatal_error(err) |             self._fatal_error(err) | ||||||
|             raise err |             raise err | ||||||
|         self._socket.settimeout(0.1) |         self._socket.settimeout(0.1) | ||||||
| @@ -200,7 +198,7 @@ class APIClient(threading.Thread): | |||||||
|         self._socket_open_event.set() |         self._socket_open_event.set() | ||||||
|  |  | ||||||
|         hello = pb.HelloRequest() |         hello = pb.HelloRequest() | ||||||
|         hello.client_info = 'ESPHome v{}'.format(const.__version__) |         hello.client_info = f'ESPHome v{const.__version__}' | ||||||
|         try: |         try: | ||||||
|             resp = self._send_message_await_response(hello, pb.HelloResponse) |             resp = self._send_message_await_response(hello, pb.HelloResponse) | ||||||
|         except APIConnectionError as err: |         except APIConnectionError as err: | ||||||
| @@ -251,8 +249,8 @@ class APIClient(threading.Thread): | |||||||
|         with self._socket_write_lock: |         with self._socket_write_lock: | ||||||
|             try: |             try: | ||||||
|                 self._socket.sendall(data) |                 self._socket.sendall(data) | ||||||
|             except socket.error as err: |             except OSError as err: | ||||||
|                 err = APIConnectionError("Error while writing data: {}".format(err)) |                 err = APIConnectionError(f"Error while writing data: {err}") | ||||||
|                 self._fatal_error(err) |                 self._fatal_error(err) | ||||||
|                 raise err |                 raise err | ||||||
|  |  | ||||||
| @@ -265,11 +263,8 @@ class APIClient(threading.Thread): | |||||||
|             raise ValueError |             raise ValueError | ||||||
|  |  | ||||||
|         encoded = msg.SerializeToString() |         encoded = msg.SerializeToString() | ||||||
|         _LOGGER.debug("Sending %s:\n%s", type(msg), indent(text_type(msg))) |         _LOGGER.debug("Sending %s:\n%s", type(msg), indent(str(msg))) | ||||||
|         if IS_PY2: |         req = bytes([0]) | ||||||
|             req = chr(0x00) |  | ||||||
|         else: |  | ||||||
|             req = bytes([0]) |  | ||||||
|         req += _varuint_to_bytes(len(encoded)) |         req += _varuint_to_bytes(len(encoded)) | ||||||
|         req += _varuint_to_bytes(message_type) |         req += _varuint_to_bytes(message_type) | ||||||
|         req += encoded |         req += encoded | ||||||
| @@ -355,14 +350,14 @@ class APIClient(threading.Thread): | |||||||
|                 raise APIConnectionError("Socket was closed") |                 raise APIConnectionError("Socket was closed") | ||||||
|             except socket.timeout: |             except socket.timeout: | ||||||
|                 continue |                 continue | ||||||
|             except socket.error as err: |             except OSError as err: | ||||||
|                 raise APIConnectionError("Error while receiving data: {}".format(err)) |                 raise APIConnectionError(f"Error while receiving data: {err}") | ||||||
|             ret += val |             ret += val | ||||||
|         return ret |         return ret | ||||||
|  |  | ||||||
|     def _recv_varint(self): |     def _recv_varint(self): | ||||||
|         raw = bytes() |         raw = bytes() | ||||||
|         while not raw or char_to_byte(raw[-1]) & 0x80: |         while not raw or raw[-1] & 0x80: | ||||||
|             raw += self._recv(1) |             raw += self._recv(1) | ||||||
|         return _bytes_to_varuint(raw) |         return _bytes_to_varuint(raw) | ||||||
|  |  | ||||||
| @@ -371,7 +366,7 @@ class APIClient(threading.Thread): | |||||||
|             return |             return | ||||||
|  |  | ||||||
|         # Preamble |         # Preamble | ||||||
|         if char_to_byte(self._recv(1)[0]) != 0x00: |         if self._recv(1)[0] != 0x00: | ||||||
|             raise APIConnectionError("Invalid preamble") |             raise APIConnectionError("Invalid preamble") | ||||||
|  |  | ||||||
|         length = self._recv_varint() |         length = self._recv_varint() | ||||||
| @@ -436,7 +431,7 @@ def run_logs(config, address): | |||||||
|             return |             return | ||||||
|  |  | ||||||
|         if err: |         if err: | ||||||
|             _LOGGER.warning(u"Disconnected from API: %s", err) |             _LOGGER.warning("Disconnected from API: %s", err) | ||||||
|  |  | ||||||
|         while retry_timer: |         while retry_timer: | ||||||
|             retry_timer.pop(0).cancel() |             retry_timer.pop(0).cancel() | ||||||
| @@ -454,18 +449,18 @@ def run_logs(config, address): | |||||||
|  |  | ||||||
|         wait_time = int(min(1.5**min(tries, 100), 30)) |         wait_time = int(min(1.5**min(tries, 100), 30)) | ||||||
|         if not has_connects: |         if not has_connects: | ||||||
|             _LOGGER.warning(u"Initial connection failed. The ESP might not be connected " |             _LOGGER.warning("Initial connection failed. The ESP might not be connected " | ||||||
|                             u"to WiFi yet (%s). Re-Trying in %s seconds", |                             "to WiFi yet (%s). Re-Trying in %s seconds", | ||||||
|                             error, wait_time) |                             error, wait_time) | ||||||
|         else: |         else: | ||||||
|             _LOGGER.warning(u"Couldn't connect to API (%s). Trying to reconnect in %s seconds", |             _LOGGER.warning("Couldn't connect to API (%s). Trying to reconnect in %s seconds", | ||||||
|                             error, wait_time) |                             error, wait_time) | ||||||
|         timer = threading.Timer(wait_time, functools.partial(try_connect, None, tries + 1)) |         timer = threading.Timer(wait_time, functools.partial(try_connect, None, tries + 1)) | ||||||
|         timer.start() |         timer.start() | ||||||
|         retry_timer.append(timer) |         retry_timer.append(timer) | ||||||
|  |  | ||||||
|     def on_log(msg): |     def on_log(msg): | ||||||
|         time_ = datetime.now().time().strftime(u'[%H:%M:%S]') |         time_ = datetime.now().time().strftime('[%H:%M:%S]') | ||||||
|         text = msg.message |         text = msg.message | ||||||
|         if msg.send_failed: |         if msg.send_failed: | ||||||
|             text = color('white', '(Message skipped because it was too big to fit in ' |             text = color('white', '(Message skipped because it was too big to fit in ' | ||||||
|   | |||||||
| @@ -7,13 +7,17 @@ from esphome.util import Registry | |||||||
|  |  | ||||||
|  |  | ||||||
| def maybe_simple_id(*validators): | def maybe_simple_id(*validators): | ||||||
|  |     return maybe_conf(CONF_ID, *validators) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def maybe_conf(conf, *validators): | ||||||
|     validator = cv.All(*validators) |     validator = cv.All(*validators) | ||||||
|  |  | ||||||
|     def validate(value): |     def validate(value): | ||||||
|         if isinstance(value, dict): |         if isinstance(value, dict): | ||||||
|             return validator(value) |             return validator(value) | ||||||
|         with cv.remove_prepend_path([CONF_ID]): |         with cv.remove_prepend_path([conf]): | ||||||
|             return validator({CONF_ID: value}) |             return validator({conf: value}) | ||||||
|  |  | ||||||
|     return validate |     return validate | ||||||
|  |  | ||||||
| @@ -79,9 +83,9 @@ def validate_automation(extra_schema=None, extra_validators=None, single=False): | |||||||
|                 try: |                 try: | ||||||
|                     return cv.Schema([schema])(value) |                     return cv.Schema([schema])(value) | ||||||
|                 except cv.Invalid as err2: |                 except cv.Invalid as err2: | ||||||
|                     if u'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: | ||||||
|                         raise err |                         raise err | ||||||
|                     if u'Unable to find action' in str(err): |                     if 'Unable to find action' in str(err): | ||||||
|                         raise err2 |                         raise err2 | ||||||
|                     raise cv.MultipleInvalid([err, err2]) |                     raise cv.MultipleInvalid([err, err2]) | ||||||
|         elif isinstance(value, dict): |         elif isinstance(value, dict): | ||||||
|   | |||||||
| @@ -19,7 +19,7 @@ from esphome.cpp_helpers import (  # noqa | |||||||
|     gpio_pin_expression, register_component, build_registry_entry, |     gpio_pin_expression, register_component, build_registry_entry, | ||||||
|     build_registry_list, extract_registry_entry_config, register_parented) |     build_registry_list, extract_registry_entry_config, register_parented) | ||||||
| from esphome.cpp_types import (  # noqa | from esphome.cpp_types import (  # noqa | ||||||
|     global_ns, void, nullptr, float_, double, bool_, std_ns, std_string, |     global_ns, void, nullptr, float_, double, bool_, int_, std_ns, std_string, | ||||||
|     std_vector, uint8, uint16, uint32, int32, const_char_ptr, NAN, |     std_vector, uint8, uint16, uint32, int32, const_char_ptr, NAN, | ||||||
|     esphome_ns, App, Nameable, Component, ComponentPtr, |     esphome_ns, App, Nameable, Component, ComponentPtr, | ||||||
|     PollingComponent, Application, optional, arduino_json_ns, JsonObject, |     PollingComponent, Application, optional, arduino_json_ns, JsonObject, | ||||||
|   | |||||||
							
								
								
									
										0
									
								
								esphome/components/ac_dimmer/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								esphome/components/ac_dimmer/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										217
									
								
								esphome/components/ac_dimmer/ac_dimmer.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										217
									
								
								esphome/components/ac_dimmer/ac_dimmer.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,217 @@ | |||||||
|  | #include "ac_dimmer.h" | ||||||
|  | #include "esphome/core/helpers.h" | ||||||
|  | #include "esphome/core/log.h" | ||||||
|  |  | ||||||
|  | #ifdef ARDUINO_ARCH_ESP8266 | ||||||
|  | #include <core_esp8266_waveform.h> | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace ac_dimmer { | ||||||
|  |  | ||||||
|  | static const char *TAG = "ac_dimmer"; | ||||||
|  |  | ||||||
|  | // Global array to store dimmer objects | ||||||
|  | static AcDimmerDataStore *all_dimmers[32]; | ||||||
|  |  | ||||||
|  | /// Time in microseconds the gate should be held high | ||||||
|  | /// 10µs should be long enough for most triacs | ||||||
|  | /// For reference: BT136 datasheet says 2µs nominal (page 7) | ||||||
|  | static uint32_t GATE_ENABLE_TIME = 10; | ||||||
|  |  | ||||||
|  | /// Function called from timer interrupt | ||||||
|  | /// Input is current time in microseconds (micros()) | ||||||
|  | /// Returns when next "event" is expected in µs, or 0 if no such event known. | ||||||
|  | uint32_t ICACHE_RAM_ATTR HOT AcDimmerDataStore::timer_intr(uint32_t now) { | ||||||
|  |   // If no ZC signal received yet. | ||||||
|  |   if (this->crossed_zero_at == 0) | ||||||
|  |     return 0; | ||||||
|  |  | ||||||
|  |   uint32_t time_since_zc = now - this->crossed_zero_at; | ||||||
|  |   if (this->value == 65535 || this->value == 0) { | ||||||
|  |     return 0; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (this->enable_time_us != 0 && time_since_zc >= this->enable_time_us) { | ||||||
|  |     this->enable_time_us = 0; | ||||||
|  |     this->gate_pin->digital_write(true); | ||||||
|  |     // Prevent too short pulses | ||||||
|  |     this->disable_time_us = max(this->disable_time_us, time_since_zc + GATE_ENABLE_TIME); | ||||||
|  |   } | ||||||
|  |   if (this->disable_time_us != 0 && time_since_zc >= this->disable_time_us) { | ||||||
|  |     this->disable_time_us = 0; | ||||||
|  |     this->gate_pin->digital_write(false); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (time_since_zc < this->enable_time_us) | ||||||
|  |     // Next event is enable, return time until that event | ||||||
|  |     return this->enable_time_us - time_since_zc; | ||||||
|  |   else if (time_since_zc < disable_time_us) { | ||||||
|  |     // Next event is disable, return time until that event | ||||||
|  |     return this->disable_time_us - time_since_zc; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (time_since_zc >= this->cycle_time_us) { | ||||||
|  |     // Already past last cycle time, schedule next call shortly | ||||||
|  |     return 100; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return this->cycle_time_us - time_since_zc; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// Run timer interrupt code and return in how many µs the next event is expected | ||||||
|  | uint32_t ICACHE_RAM_ATTR HOT timer_interrupt() { | ||||||
|  |   // run at least with 1kHz | ||||||
|  |   uint32_t min_dt_us = 1000; | ||||||
|  |   uint32_t now = micros(); | ||||||
|  |   for (auto *dimmer : all_dimmers) { | ||||||
|  |     if (dimmer == nullptr) | ||||||
|  |       // no more dimmers | ||||||
|  |       break; | ||||||
|  |     uint32_t res = dimmer->timer_intr(now); | ||||||
|  |     if (res != 0 && res < min_dt_us) | ||||||
|  |       min_dt_us = res; | ||||||
|  |   } | ||||||
|  |   // return time until next timer1 interrupt in µs | ||||||
|  |   return min_dt_us; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// GPIO interrupt routine, called when ZC pin triggers | ||||||
|  | void ICACHE_RAM_ATTR HOT AcDimmerDataStore::gpio_intr() { | ||||||
|  |   uint32_t prev_crossed = this->crossed_zero_at; | ||||||
|  |  | ||||||
|  |   // 50Hz mains frequency should give a half cycle of 10ms a 60Hz will give 8.33ms | ||||||
|  |   // in any case the cycle last at least 5ms | ||||||
|  |   this->crossed_zero_at = micros(); | ||||||
|  |   uint32_t cycle_time = this->crossed_zero_at - prev_crossed; | ||||||
|  |   if (cycle_time > 5000) { | ||||||
|  |     this->cycle_time_us = cycle_time; | ||||||
|  |   } else { | ||||||
|  |     // Otherwise this is noise and this is 2nd (or 3rd...) fall in the same pulse | ||||||
|  |     // Consider this is the right fall edge and accumulate the cycle time instead | ||||||
|  |     this->cycle_time_us += cycle_time; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (this->value == 65535) { | ||||||
|  |     // fully on, enable output immediately | ||||||
|  |     this->gate_pin->digital_write(true); | ||||||
|  |   } else if (this->init_cycle) { | ||||||
|  |     // send a full cycle | ||||||
|  |     this->init_cycle = false; | ||||||
|  |     this->enable_time_us = 0; | ||||||
|  |     this->disable_time_us = cycle_time_us; | ||||||
|  |   } else if (this->value == 0) { | ||||||
|  |     // fully off, disable output immediately | ||||||
|  |     this->gate_pin->digital_write(false); | ||||||
|  |   } else { | ||||||
|  |     if (this->method == DIM_METHOD_TRAILING) { | ||||||
|  |       this->enable_time_us = 1;  // cannot be 0 | ||||||
|  |       this->disable_time_us = max((uint32_t) 10, this->value * this->cycle_time_us / 65535); | ||||||
|  |     } else { | ||||||
|  |       // calculate time until enable in µs: (1.0-value)*cycle_time, but with integer arithmetic | ||||||
|  |       // also take into account min_power | ||||||
|  |       auto min_us = this->cycle_time_us * this->min_power / 1000; | ||||||
|  |       this->enable_time_us = max((uint32_t) 1, ((65535 - this->value) * (this->cycle_time_us - min_us)) / 65535); | ||||||
|  |       if (this->method == DIM_METHOD_LEADING_PULSE) { | ||||||
|  |         // Minimum pulse time should be enough for the triac to trigger when it is close to the ZC zone | ||||||
|  |         // this is for brightness near 99% | ||||||
|  |         this->disable_time_us = max(this->enable_time_us + GATE_ENABLE_TIME, (uint32_t) cycle_time_us / 10); | ||||||
|  |       } else { | ||||||
|  |         this->gate_pin->digital_write(false); | ||||||
|  |         this->disable_time_us = this->cycle_time_us; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void ICACHE_RAM_ATTR HOT AcDimmerDataStore::s_gpio_intr(AcDimmerDataStore *store) { | ||||||
|  |   // Attaching pin interrupts on the same pin will override the previous interupt | ||||||
|  |   // However, the user expects that multiple dimmers sharing the same ZC pin will work. | ||||||
|  |   // We solve this in a bit of a hacky way: On each pin interrupt, we check all dimmers | ||||||
|  |   // if any of them are using the same ZC pin, and also trigger the interrupt for *them*. | ||||||
|  |   for (auto *dimmer : all_dimmers) { | ||||||
|  |     if (dimmer == nullptr) | ||||||
|  |       break; | ||||||
|  |     if (dimmer->zero_cross_pin_number == store->zero_cross_pin_number) { | ||||||
|  |       dimmer->gpio_intr(); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #ifdef ARDUINO_ARCH_ESP32 | ||||||
|  | // ESP32 implementation, uses basically the same code but needs to wrap | ||||||
|  | // timer_interrupt() function to auto-reschedule | ||||||
|  | static hw_timer_t *dimmer_timer = nullptr; | ||||||
|  | void ICACHE_RAM_ATTR HOT AcDimmerDataStore::s_timer_intr() { timer_interrupt(); } | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  | void AcDimmer::setup() { | ||||||
|  |   // extend all_dimmers array with our dimmer | ||||||
|  |  | ||||||
|  |   // Need to be sure the zero cross pin is setup only once, ESP8266 fails and ESP32 seems to fail silently | ||||||
|  |   auto setup_zero_cross_pin = true; | ||||||
|  |  | ||||||
|  |   for (auto &all_dimmer : all_dimmers) { | ||||||
|  |     if (all_dimmer == nullptr) { | ||||||
|  |       all_dimmer = &this->store_; | ||||||
|  |       break; | ||||||
|  |     } | ||||||
|  |     if (all_dimmer->zero_cross_pin_number == this->zero_cross_pin_->get_pin()) { | ||||||
|  |       setup_zero_cross_pin = false; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   this->gate_pin_->setup(); | ||||||
|  |   this->store_.gate_pin = this->gate_pin_->to_isr(); | ||||||
|  |   this->store_.zero_cross_pin_number = this->zero_cross_pin_->get_pin(); | ||||||
|  |   this->store_.min_power = static_cast<uint16_t>(this->min_power_ * 1000); | ||||||
|  |   this->min_power_ = 0; | ||||||
|  |   this->store_.method = this->method_; | ||||||
|  |  | ||||||
|  |   if (setup_zero_cross_pin) { | ||||||
|  |     this->zero_cross_pin_->setup(); | ||||||
|  |     this->store_.zero_cross_pin = this->zero_cross_pin_->to_isr(); | ||||||
|  |     this->zero_cross_pin_->attach_interrupt(&AcDimmerDataStore::s_gpio_intr, &this->store_, FALLING); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  | #ifdef ARDUINO_ARCH_ESP8266 | ||||||
|  |   // Uses ESP8266 waveform (soft PWM) class | ||||||
|  |   // PWM and AcDimmer can even run at the same time this way | ||||||
|  |   setTimer1Callback(&timer_interrupt); | ||||||
|  | #endif | ||||||
|  | #ifdef ARDUINO_ARCH_ESP32 | ||||||
|  |   // 80 Divider -> 1 count=1µs | ||||||
|  |   dimmer_timer = timerBegin(0, 80, true); | ||||||
|  |   timerAttachInterrupt(dimmer_timer, &AcDimmerDataStore::s_timer_intr, true); | ||||||
|  |   // For ESP32, we can't use dynamic interval calculation because the timerX functions | ||||||
|  |   // are not callable from ISR (placed in flash storage). | ||||||
|  |   // Here we just use an interrupt firing every 50 µs. | ||||||
|  |   timerAlarmWrite(dimmer_timer, 50, true); | ||||||
|  |   timerAlarmEnable(dimmer_timer); | ||||||
|  | #endif | ||||||
|  | } | ||||||
|  | void AcDimmer::write_state(float state) { | ||||||
|  |   auto new_value = static_cast<uint16_t>(roundf(state * 65535)); | ||||||
|  |   if (new_value != 0 && this->store_.value == 0) | ||||||
|  |     this->store_.init_cycle = this->init_with_half_cycle_; | ||||||
|  |   this->store_.value = new_value; | ||||||
|  | } | ||||||
|  | void AcDimmer::dump_config() { | ||||||
|  |   ESP_LOGCONFIG(TAG, "AcDimmer:"); | ||||||
|  |   LOG_PIN("  Output Pin: ", this->gate_pin_); | ||||||
|  |   LOG_PIN("  Zero-Cross Pin: ", this->zero_cross_pin_); | ||||||
|  |   ESP_LOGCONFIG(TAG, "   Min Power: %.1f%%", this->store_.min_power / 10.0f); | ||||||
|  |   ESP_LOGCONFIG(TAG, "   Init with half cycle: %s", YESNO(this->init_with_half_cycle_)); | ||||||
|  |   if (method_ == DIM_METHOD_LEADING_PULSE) | ||||||
|  |     ESP_LOGCONFIG(TAG, "   Method: leading pulse"); | ||||||
|  |   else if (method_ == DIM_METHOD_LEADING) | ||||||
|  |     ESP_LOGCONFIG(TAG, "   Method: leading"); | ||||||
|  |   else | ||||||
|  |     ESP_LOGCONFIG(TAG, "   Method: trailing"); | ||||||
|  |  | ||||||
|  |   LOG_FLOAT_OUTPUT(this); | ||||||
|  |   ESP_LOGV(TAG, "  Estimated Frequency: %.3fHz", 1e6f / this->store_.cycle_time_us / 2); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | }  // namespace ac_dimmer | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										66
									
								
								esphome/components/ac_dimmer/ac_dimmer.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								esphome/components/ac_dimmer/ac_dimmer.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,66 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include "esphome/core/component.h" | ||||||
|  | #include "esphome/core/esphal.h" | ||||||
|  | #include "esphome/components/output/float_output.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace ac_dimmer { | ||||||
|  |  | ||||||
|  | enum DimMethod { DIM_METHOD_LEADING_PULSE = 0, DIM_METHOD_LEADING, DIM_METHOD_TRAILING }; | ||||||
|  |  | ||||||
|  | struct AcDimmerDataStore { | ||||||
|  |   /// Zero-cross pin | ||||||
|  |   ISRInternalGPIOPin *zero_cross_pin; | ||||||
|  |   /// Zero-cross pin number - used to share ZC pin across multiple dimmers | ||||||
|  |   uint8_t zero_cross_pin_number; | ||||||
|  |   /// Output pin to write to | ||||||
|  |   ISRInternalGPIOPin *gate_pin; | ||||||
|  |   /// Value of the dimmer - 0 to 65535. | ||||||
|  |   uint16_t value; | ||||||
|  |   /// Minimum power for activation | ||||||
|  |   uint16_t min_power; | ||||||
|  |   /// Time between the last two ZC pulses | ||||||
|  |   uint32_t cycle_time_us; | ||||||
|  |   /// Time (in micros()) of last ZC signal | ||||||
|  |   uint32_t crossed_zero_at; | ||||||
|  |   /// Time since last ZC pulse to enable gate pin. 0 means not set. | ||||||
|  |   uint32_t enable_time_us; | ||||||
|  |   /// Time since last ZC pulse to disable gate pin. 0 means no disable. | ||||||
|  |   uint32_t disable_time_us; | ||||||
|  |   /// Set to send the first half ac cycle complete | ||||||
|  |   bool init_cycle; | ||||||
|  |   /// Dimmer method | ||||||
|  |   DimMethod method; | ||||||
|  |  | ||||||
|  |   uint32_t timer_intr(uint32_t now); | ||||||
|  |  | ||||||
|  |   void gpio_intr(); | ||||||
|  |   static void s_gpio_intr(AcDimmerDataStore *store); | ||||||
|  | #ifdef ARDUINO_ARCH_ESP32 | ||||||
|  |   static void s_timer_intr(); | ||||||
|  | #endif | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | class AcDimmer : public output::FloatOutput, public Component { | ||||||
|  |  public: | ||||||
|  |   void setup() override; | ||||||
|  |  | ||||||
|  |   void dump_config() override; | ||||||
|  |   void set_gate_pin(GPIOPin *gate_pin) { gate_pin_ = gate_pin; } | ||||||
|  |   void set_zero_cross_pin(GPIOPin *zero_cross_pin) { zero_cross_pin_ = zero_cross_pin; } | ||||||
|  |   void set_init_with_half_cycle(bool init_with_half_cycle) { init_with_half_cycle_ = init_with_half_cycle; } | ||||||
|  |   void set_method(DimMethod method) { method_ = method; } | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   void write_state(float state) override; | ||||||
|  |  | ||||||
|  |   GPIOPin *gate_pin_; | ||||||
|  |   GPIOPin *zero_cross_pin_; | ||||||
|  |   AcDimmerDataStore store_; | ||||||
|  |   bool init_with_half_cycle_; | ||||||
|  |   DimMethod method_; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | }  // namespace ac_dimmer | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										45
									
								
								esphome/components/ac_dimmer/output.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								esphome/components/ac_dimmer/output.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,45 @@ | |||||||
|  | import esphome.codegen as cg | ||||||
|  | import esphome.config_validation as cv | ||||||
|  | from esphome import pins | ||||||
|  | from esphome.components import output | ||||||
|  | from esphome.const import CONF_ID, CONF_MIN_POWER, CONF_METHOD | ||||||
|  |  | ||||||
|  | CODEOWNERS = ['@glmnet'] | ||||||
|  |  | ||||||
|  | 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') | ||||||
|  | DIM_METHODS = { | ||||||
|  |     'LEADING_PULSE': DimMethod.DIM_METHOD_LEADING_PULSE, | ||||||
|  |     'LEADING': DimMethod.DIM_METHOD_LEADING, | ||||||
|  |     'TRAILING': DimMethod.DIM_METHOD_TRAILING, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | CONF_GATE_PIN = 'gate_pin' | ||||||
|  | CONF_ZERO_CROSS_PIN = 'zero_cross_pin' | ||||||
|  | CONF_INIT_WITH_HALF_CYCLE = 'init_with_half_cycle' | ||||||
|  | CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend({ | ||||||
|  |     cv.Required(CONF_ID): cv.declare_id(AcDimmer), | ||||||
|  |     cv.Required(CONF_GATE_PIN): pins.internal_gpio_output_pin_schema, | ||||||
|  |     cv.Required(CONF_ZERO_CROSS_PIN): pins.internal_gpio_input_pin_schema, | ||||||
|  |     cv.Optional(CONF_INIT_WITH_HALF_CYCLE, default=True): cv.boolean, | ||||||
|  |     cv.Optional(CONF_METHOD, default='leading pulse'): cv.enum(DIM_METHODS, upper=True, space='_'), | ||||||
|  | }).extend(cv.COMPONENT_SCHEMA) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def to_code(config): | ||||||
|  |     var = cg.new_Pvariable(config[CONF_ID]) | ||||||
|  |     yield cg.register_component(var, config) | ||||||
|  |  | ||||||
|  |     # override default min power to 10% | ||||||
|  |     if CONF_MIN_POWER not in config: | ||||||
|  |         config[CONF_MIN_POWER] = 0.1 | ||||||
|  |     yield output.register_output(var, config) | ||||||
|  |  | ||||||
|  |     pin = yield cg.gpio_pin_expression(config[CONF_GATE_PIN]) | ||||||
|  |     cg.add(var.set_gate_pin(pin)) | ||||||
|  |     pin = yield cg.gpio_pin_expression(config[CONF_ZERO_CROSS_PIN]) | ||||||
|  |     cg.add(var.set_zero_cross_pin(pin)) | ||||||
|  |     cg.add(var.set_init_with_half_cycle(config[CONF_INIT_WITH_HALF_CYCLE])) | ||||||
|  |     cg.add(var.set_method(config[CONF_METHOD])) | ||||||
							
								
								
									
										24
									
								
								esphome/components/adalight/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								esphome/components/adalight/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,24 @@ | |||||||
|  | import esphome.codegen as cg | ||||||
|  | import esphome.config_validation as cv | ||||||
|  | from esphome.components import uart | ||||||
|  | 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'] | ||||||
|  |  | ||||||
|  | adalight_ns = cg.esphome_ns.namespace('adalight') | ||||||
|  | AdalightLightEffect = adalight_ns.class_( | ||||||
|  |     'AdalightLightEffect', uart.UARTDevice, AddressableLightEffect) | ||||||
|  |  | ||||||
|  | CONFIG_SCHEMA = cv.Schema({}) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @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) | ||||||
|  |  | ||||||
|  |     yield effect | ||||||
							
								
								
									
										140
									
								
								esphome/components/adalight/adalight_light_effect.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										140
									
								
								esphome/components/adalight/adalight_light_effect.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,140 @@ | |||||||
|  | #include "adalight_light_effect.h" | ||||||
|  | #include "esphome/core/log.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace adalight { | ||||||
|  |  | ||||||
|  | static const char *TAG = "adalight_light_effect"; | ||||||
|  |  | ||||||
|  | static const uint32_t ADALIGHT_ACK_INTERVAL = 1000; | ||||||
|  | static const uint32_t ADALIGHT_RECEIVE_TIMEOUT = 1000; | ||||||
|  |  | ||||||
|  | AdalightLightEffect::AdalightLightEffect(const std::string &name) : AddressableLightEffect(name) {} | ||||||
|  |  | ||||||
|  | void AdalightLightEffect::start() { | ||||||
|  |   AddressableLightEffect::start(); | ||||||
|  |  | ||||||
|  |   last_ack_ = 0; | ||||||
|  |   last_byte_ = 0; | ||||||
|  |   last_reset_ = 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void AdalightLightEffect::stop() { | ||||||
|  |   frame_.resize(0); | ||||||
|  |  | ||||||
|  |   AddressableLightEffect::stop(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | int AdalightLightEffect::get_frame_size_(int led_count) const { | ||||||
|  |   // 3 bytes: Ada | ||||||
|  |   // 2 bytes: LED count | ||||||
|  |   // 1 byte: checksum | ||||||
|  |   // 3 bytes per LED | ||||||
|  |   return 3 + 2 + 1 + led_count * 3; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void AdalightLightEffect::reset_frame_(light::AddressableLight &it) { | ||||||
|  |   int buffer_capacity = get_frame_size_(it.size()); | ||||||
|  |  | ||||||
|  |   frame_.clear(); | ||||||
|  |   frame_.reserve(buffer_capacity); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void AdalightLightEffect::blank_all_leds_(light::AddressableLight &it) { | ||||||
|  |   for (int led = it.size(); led-- > 0;) { | ||||||
|  |     it[led].set(light::ESPColor::BLACK); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void AdalightLightEffect::apply(light::AddressableLight &it, const light::ESPColor ¤t_color) { | ||||||
|  |   const uint32_t now = millis(); | ||||||
|  |  | ||||||
|  |   if (now - this->last_ack_ >= ADALIGHT_ACK_INTERVAL) { | ||||||
|  |     ESP_LOGV(TAG, "Sending ACK"); | ||||||
|  |     this->write_str("Ada\n"); | ||||||
|  |     this->last_ack_ = now; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (!this->last_reset_) { | ||||||
|  |     ESP_LOGW(TAG, "Frame: Reset."); | ||||||
|  |     reset_frame_(it); | ||||||
|  |     blank_all_leds_(it); | ||||||
|  |     this->last_reset_ = now; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (!this->frame_.empty() && now - this->last_byte_ >= ADALIGHT_RECEIVE_TIMEOUT) { | ||||||
|  |     ESP_LOGW(TAG, "Frame: Receive timeout (size=%zu).", this->frame_.size()); | ||||||
|  |     reset_frame_(it); | ||||||
|  |     blank_all_leds_(it); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (this->available() > 0) { | ||||||
|  |     ESP_LOGV(TAG, "Frame: Available (size=%d).", this->available()); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   while (this->available() != 0) { | ||||||
|  |     uint8_t data; | ||||||
|  |     if (!this->read_byte(&data)) | ||||||
|  |       break; | ||||||
|  |     this->frame_.push_back(data); | ||||||
|  |     this->last_byte_ = now; | ||||||
|  |  | ||||||
|  |     switch (this->parse_frame_(it)) { | ||||||
|  |       case INVALID: | ||||||
|  |         ESP_LOGD(TAG, "Frame: Invalid (size=%zu, first=%d).", this->frame_.size(), this->frame_[0]); | ||||||
|  |         reset_frame_(it); | ||||||
|  |         break; | ||||||
|  |  | ||||||
|  |       case PARTIAL: | ||||||
|  |         break; | ||||||
|  |  | ||||||
|  |       case CONSUMED: | ||||||
|  |         ESP_LOGV(TAG, "Frame: Consumed (size=%zu).", this->frame_.size()); | ||||||
|  |         reset_frame_(it); | ||||||
|  |         break; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | AdalightLightEffect::Frame AdalightLightEffect::parse_frame_(light::AddressableLight &it) { | ||||||
|  |   if (frame_.empty()) | ||||||
|  |     return INVALID; | ||||||
|  |  | ||||||
|  |   // Check header: `Ada` | ||||||
|  |   if (frame_[0] != 'A') | ||||||
|  |     return INVALID; | ||||||
|  |   if (frame_.size() > 1 && frame_[1] != 'd') | ||||||
|  |     return INVALID; | ||||||
|  |   if (frame_.size() > 2 && frame_[2] != 'a') | ||||||
|  |     return INVALID; | ||||||
|  |  | ||||||
|  |   // 3 bytes: Count Hi, Count Lo, Checksum | ||||||
|  |   if (frame_.size() < 6) | ||||||
|  |     return PARTIAL; | ||||||
|  |  | ||||||
|  |   // Check checksum | ||||||
|  |   uint16_t checksum = frame_[3] ^ frame_[4] ^ 0x55; | ||||||
|  |   if (checksum != frame_[5]) | ||||||
|  |     return INVALID; | ||||||
|  |  | ||||||
|  |   // Check if we received the full frame | ||||||
|  |   uint16_t led_count = (frame_[3] << 8) + frame_[4] + 1; | ||||||
|  |   auto buffer_size = get_frame_size_(led_count); | ||||||
|  |   if (frame_.size() < buffer_size) | ||||||
|  |     return PARTIAL; | ||||||
|  |  | ||||||
|  |   // Apply lights | ||||||
|  |   auto accepted_led_count = std::min<int>(led_count, it.size()); | ||||||
|  |   uint8_t *led_data = &frame_[6]; | ||||||
|  |  | ||||||
|  |   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)); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return CONSUMED; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | }  // namespace adalight | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										41
									
								
								esphome/components/adalight/adalight_light_effect.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								esphome/components/adalight/adalight_light_effect.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,41 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include "esphome/core/component.h" | ||||||
|  | #include "esphome/components/light/addressable_light_effect.h" | ||||||
|  | #include "esphome/components/uart/uart.h" | ||||||
|  |  | ||||||
|  | #include <vector> | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace adalight { | ||||||
|  |  | ||||||
|  | class AdalightLightEffect : public light::AddressableLightEffect, public uart::UARTDevice { | ||||||
|  |  public: | ||||||
|  |   AdalightLightEffect(const std::string &name); | ||||||
|  |  | ||||||
|  |  public: | ||||||
|  |   void start() override; | ||||||
|  |   void stop() override; | ||||||
|  |   void apply(light::AddressableLight &it, const light::ESPColor ¤t_color) override; | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   enum Frame { | ||||||
|  |     INVALID, | ||||||
|  |     PARTIAL, | ||||||
|  |     CONSUMED, | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   int get_frame_size_(int led_count) const; | ||||||
|  |   void reset_frame_(light::AddressableLight &it); | ||||||
|  |   void blank_all_leds_(light::AddressableLight &it); | ||||||
|  |   Frame parse_frame_(light::AddressableLight &it); | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   uint32_t last_ack_{0}; | ||||||
|  |   uint32_t last_byte_{0}; | ||||||
|  |   uint32_t last_reset_{0}; | ||||||
|  |   std::vector<uint8_t> frame_; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | }  // namespace adalight | ||||||
|  | }  // namespace esphome | ||||||
| @@ -0,0 +1 @@ | |||||||
|  | CODEOWNERS = ['@esphome/core'] | ||||||
|   | |||||||
| @@ -58,7 +58,7 @@ void ADCSensor::update() { | |||||||
| } | } | ||||||
| float ADCSensor::sample() { | float ADCSensor::sample() { | ||||||
| #ifdef ARDUINO_ARCH_ESP32 | #ifdef ARDUINO_ARCH_ESP32 | ||||||
|   float value_v = analogRead(this->pin_) / 4095.0f; |   float value_v = analogRead(this->pin_) / 4095.0f;  // NOLINT | ||||||
|   switch (this->attenuation_) { |   switch (this->attenuation_) { | ||||||
|     case ADC_0db: |     case ADC_0db: | ||||||
|       value_v *= 1.1; |       value_v *= 1.1; | ||||||
| @@ -80,7 +80,7 @@ float ADCSensor::sample() { | |||||||
| #ifdef USE_ADC_SENSOR_VCC | #ifdef USE_ADC_SENSOR_VCC | ||||||
|   return ESP.getVcc() / 1024.0f; |   return ESP.getVcc() / 1024.0f; | ||||||
| #else | #else | ||||||
|   return analogRead(this->pin_) / 1024.0f; |   return analogRead(this->pin_) / 1024.0f;  // NOLINT | ||||||
| #endif | #endif | ||||||
| #endif | #endif | ||||||
| } | } | ||||||
|   | |||||||
| @@ -18,7 +18,7 @@ void ADE7953::dump_config() { | |||||||
| } | } | ||||||
|  |  | ||||||
| #define ADE_PUBLISH_(name, factor) \ | #define ADE_PUBLISH_(name, factor) \ | ||||||
|   if (name) { \ |   if (name && this->name##_sensor_) { \ | ||||||
|     float value = *name / factor; \ |     float value = *name / factor; \ | ||||||
|     this->name##_sensor_->publish_state(value); \ |     this->name##_sensor_->publish_state(value); \ | ||||||
|   } |   } | ||||||
|   | |||||||
| @@ -36,4 +36,4 @@ def to_code(config): | |||||||
|             continue |             continue | ||||||
|         conf = config[key] |         conf = config[key] | ||||||
|         sens = yield sensor.new_sensor(conf) |         sens = yield sensor.new_sensor(conf) | ||||||
|         cg.add(getattr(var, 'set_{}_sensor'.format(key))(sens)) |         cg.add(getattr(var, f'set_{key}_sensor')(sens)) | ||||||
|   | |||||||
| @@ -2,7 +2,6 @@ import esphome.codegen as cg | |||||||
| import esphome.config_validation as cv | import esphome.config_validation as cv | ||||||
| from esphome.components import sensor, voltage_sampler | from esphome.components import sensor, voltage_sampler | ||||||
| from esphome.const import CONF_GAIN, CONF_MULTIPLEXER, ICON_FLASH, UNIT_VOLT, CONF_ID | from esphome.const import CONF_GAIN, CONF_MULTIPLEXER, ICON_FLASH, UNIT_VOLT, CONF_ID | ||||||
| from esphome.py_compat import string_types |  | ||||||
| from . import ads1115_ns, ADS1115Component | from . import ads1115_ns, ADS1115Component | ||||||
|  |  | ||||||
| DEPENDENCIES = ['ads1115'] | DEPENDENCIES = ['ads1115'] | ||||||
| @@ -32,9 +31,9 @@ GAIN = { | |||||||
|  |  | ||||||
| def validate_gain(value): | def validate_gain(value): | ||||||
|     if isinstance(value, float): |     if isinstance(value, float): | ||||||
|         value = u'{:0.03f}'.format(value) |         value = f'{value:0.03f}' | ||||||
|     elif not isinstance(value, string_types): |     elif not isinstance(value, str): | ||||||
|         raise cv.Invalid('invalid gain "{}"'.format(value)) |         raise cv.Invalid(f'invalid gain "{value}"') | ||||||
|  |  | ||||||
|     return cv.enum(GAIN)(value) |     return cv.enum(GAIN)(value) | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										0
									
								
								esphome/components/aht10/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								esphome/components/aht10/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										127
									
								
								esphome/components/aht10/aht10.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										127
									
								
								esphome/components/aht10/aht10.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,127 @@ | |||||||
|  | // Implementation based on: | ||||||
|  | //  - AHT10: https://github.com/Thinary/AHT10 | ||||||
|  | //  - Official Datasheet (cn): | ||||||
|  | //  http://www.aosong.com/userfiles/files/media/aht10%E8%A7%84%E6%A0%BC%E4%B9%A6v1_1%EF%BC%8820191015%EF%BC%89.pdf | ||||||
|  | //  - Unofficial Translated Datasheet (en): | ||||||
|  | //  https://wiki.liutyi.info/download/attachments/30507639/Aosong_AHT10_en_draft_0c.pdf | ||||||
|  | // | ||||||
|  | // When configured for humidity, the log 'Components should block for at most 20-30ms in loop().' will be generated in | ||||||
|  | // verbose mode. This is due to technical specs of the sensor and can not be avoided. | ||||||
|  | // | ||||||
|  | // According to the datasheet, the component is supposed to respond in more than 75ms. In fact, it can answer almost | ||||||
|  | // immediately for temperature. But for humidity, it takes >90ms to get a valid data. From experience, we have best | ||||||
|  | // results making successive requests; the current implementation make 3 attemps with a delay of 30ms each time. | ||||||
|  |  | ||||||
|  | #include "aht10.h" | ||||||
|  | #include "esphome/core/log.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace aht10 { | ||||||
|  |  | ||||||
|  | static const char *TAG = "aht10"; | ||||||
|  | static const uint8_t AHT10_CALIBRATE_CMD[] = {0xE1}; | ||||||
|  | static const uint8_t AHT10_MEASURE_CMD[] = {0xAC, 0x33, 0x00}; | ||||||
|  | static const uint8_t AHT10_DEFAULT_DELAY = 5;    // ms, for calibration and temperature measurement | ||||||
|  | static const uint8_t AHT10_HUMIDITY_DELAY = 30;  // ms | ||||||
|  | static const uint8_t AHT10_ATTEMPS = 3;          // safety margin, normally 3 attemps are enough: 3*30=90ms | ||||||
|  |  | ||||||
|  | void AHT10Component::setup() { | ||||||
|  |   ESP_LOGCONFIG(TAG, "Setting up AHT10..."); | ||||||
|  |  | ||||||
|  |   if (!this->write_bytes(0, AHT10_CALIBRATE_CMD, sizeof(AHT10_CALIBRATE_CMD))) { | ||||||
|  |     ESP_LOGE(TAG, "Communication with AHT10 failed!"); | ||||||
|  |     this->mark_failed(); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   uint8_t data; | ||||||
|  |   if (!this->read_byte(0, &data, AHT10_DEFAULT_DELAY)) { | ||||||
|  |     ESP_LOGD(TAG, "Communication with AHT10 failed!"); | ||||||
|  |     this->mark_failed(); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   if ((data & 0x68) != 0x08) {  // Bit[6:5] = 0b00, NORMAL mode and Bit[3] = 0b1, CALIBRATED | ||||||
|  |     ESP_LOGE(TAG, "AHT10 calibration failed!"); | ||||||
|  |     this->mark_failed(); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   ESP_LOGV(TAG, "AHT10 calibrated"); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void AHT10Component::update() { | ||||||
|  |   if (!this->write_bytes(0, AHT10_MEASURE_CMD, sizeof(AHT10_MEASURE_CMD))) { | ||||||
|  |     ESP_LOGE(TAG, "Communication with AHT10 failed!"); | ||||||
|  |     this->status_set_warning(); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   uint8_t data[6]; | ||||||
|  |   uint8_t delay = AHT10_DEFAULT_DELAY; | ||||||
|  |   if (this->humidity_sensor_ != nullptr) | ||||||
|  |     delay = AHT10_HUMIDITY_DELAY; | ||||||
|  |   for (int i = 0; i < AHT10_ATTEMPS; ++i) { | ||||||
|  |     ESP_LOGVV(TAG, "Attemps %u at %6ld", i, millis()); | ||||||
|  |     if (!this->read_bytes(0, data, 6, delay)) { | ||||||
|  |       ESP_LOGD(TAG, "Communication with AHT10 failed, waiting..."); | ||||||
|  |     } else if ((data[0] & 0x80) == 0x80) {  // Bit[7] = 0b1, device is busy | ||||||
|  |       ESP_LOGD(TAG, "AHT10 is busy, waiting..."); | ||||||
|  |     } else if (data[1] == 0x0 && data[2] == 0x0 && (data[3] >> 4) == 0x0) { | ||||||
|  |       // Unrealistic humidity (0x0) | ||||||
|  |       if (this->humidity_sensor_ == nullptr) { | ||||||
|  |         ESP_LOGVV(TAG, "ATH10 Unrealistic humidity (0x0), but humidity is not required"); | ||||||
|  |         break; | ||||||
|  |       } else { | ||||||
|  |         ESP_LOGD(TAG, "ATH10 Unrealistic humidity (0x0), retrying..."); | ||||||
|  |         if (!this->write_bytes(0, AHT10_MEASURE_CMD, sizeof(AHT10_MEASURE_CMD))) { | ||||||
|  |           ESP_LOGE(TAG, "Communication with AHT10 failed!"); | ||||||
|  |           this->status_set_warning(); | ||||||
|  |           return; | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } else { | ||||||
|  |       // data is valid, we can break the loop | ||||||
|  |       ESP_LOGVV(TAG, "Answer at %6ld", millis()); | ||||||
|  |       break; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   if ((data[0] & 0x80) == 0x80) { | ||||||
|  |     ESP_LOGE(TAG, "Measurements reading timed-out!"); | ||||||
|  |     this->status_set_warning(); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   uint32_t raw_temperature = ((data[3] & 0x0F) << 16) | (data[4] << 8) | data[5]; | ||||||
|  |   uint32_t raw_humidity = ((data[1] << 16) | (data[2] << 8) | data[3]) >> 4; | ||||||
|  |  | ||||||
|  |   float temperature = ((200.0 * (float) raw_temperature) / 1048576.0) - 50.0; | ||||||
|  |   float humidity; | ||||||
|  |   if (raw_humidity == 0) {  // unrealistic value | ||||||
|  |     humidity = NAN; | ||||||
|  |   } else { | ||||||
|  |     humidity = (float) raw_humidity * 100.0 / 1048576.0; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (this->temperature_sensor_ != nullptr) { | ||||||
|  |     this->temperature_sensor_->publish_state(temperature); | ||||||
|  |   } | ||||||
|  |   if (this->humidity_sensor_ != nullptr) { | ||||||
|  |     if (isnan(humidity)) | ||||||
|  |       ESP_LOGW(TAG, "Invalid humidity! Sensor reported 0%% Hum"); | ||||||
|  |     this->humidity_sensor_->publish_state(humidity); | ||||||
|  |   } | ||||||
|  |   this->status_clear_warning(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | float AHT10Component::get_setup_priority() const { return setup_priority::DATA; } | ||||||
|  |  | ||||||
|  | void AHT10Component::dump_config() { | ||||||
|  |   ESP_LOGCONFIG(TAG, "AHT10:"); | ||||||
|  |   LOG_I2C_DEVICE(this); | ||||||
|  |   if (this->is_failed()) { | ||||||
|  |     ESP_LOGE(TAG, "Communication with AHT10 failed!"); | ||||||
|  |   } | ||||||
|  |   LOG_SENSOR("  ", "Temperature", this->temperature_sensor_); | ||||||
|  |   LOG_SENSOR("  ", "Humidity", this->humidity_sensor_); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | }  // namespace aht10 | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										26
									
								
								esphome/components/aht10/aht10.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								esphome/components/aht10/aht10.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include "esphome/core/component.h" | ||||||
|  | #include "esphome/components/sensor/sensor.h" | ||||||
|  | #include "esphome/components/i2c/i2c.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace aht10 { | ||||||
|  |  | ||||||
|  | class AHT10Component : public PollingComponent, public i2c::I2CDevice { | ||||||
|  |  public: | ||||||
|  |   void setup() override; | ||||||
|  |   void update() override; | ||||||
|  |   void dump_config() override; | ||||||
|  |   float get_setup_priority() const override; | ||||||
|  |  | ||||||
|  |   void set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_sensor; } | ||||||
|  |   void set_humidity_sensor(sensor::Sensor *humidity_sensor) { humidity_sensor_ = humidity_sensor; } | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   sensor::Sensor *temperature_sensor_; | ||||||
|  |   sensor::Sensor *humidity_sensor_; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | }  // namespace aht10 | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										30
									
								
								esphome/components/aht10/sensor.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								esphome/components/aht10/sensor.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,30 @@ | |||||||
|  | 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 | ||||||
|  |  | ||||||
|  | DEPENDENCIES = ['i2c'] | ||||||
|  |  | ||||||
|  | aht10_ns = cg.esphome_ns.namespace('aht10') | ||||||
|  | AHT10Component = aht10_ns.class_('AHT10Component', cg.PollingComponent, i2c.I2CDevice) | ||||||
|  |  | ||||||
|  | CONFIG_SCHEMA = cv.Schema({ | ||||||
|  |     cv.GenerateID(): cv.declare_id(AHT10Component), | ||||||
|  |     cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 2), | ||||||
|  |     cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(UNIT_PERCENT, ICON_WATER_PERCENT, 2), | ||||||
|  | }).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x38)) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def to_code(config): | ||||||
|  |     var = cg.new_Pvariable(config[CONF_ID]) | ||||||
|  |     yield cg.register_component(var, config) | ||||||
|  |     yield i2c.register_i2c_device(var, config) | ||||||
|  |  | ||||||
|  |     if CONF_TEMPERATURE in config: | ||||||
|  |         sens = yield sensor.new_sensor(config[CONF_TEMPERATURE]) | ||||||
|  |         cg.add(var.set_temperature_sensor(sens)) | ||||||
|  |  | ||||||
|  |     if CONF_HUMIDITY in config: | ||||||
|  |         sens = yield sensor.new_sensor(config[CONF_HUMIDITY]) | ||||||
|  |         cg.add(var.set_humidity_sensor(sens)) | ||||||
| @@ -8,6 +8,7 @@ from esphome.core import coroutine_with_priority | |||||||
|  |  | ||||||
| DEPENDENCIES = ['network'] | DEPENDENCIES = ['network'] | ||||||
| AUTO_LOAD = ['async_tcp'] | AUTO_LOAD = ['async_tcp'] | ||||||
|  | CODEOWNERS = ['@OttoWinter'] | ||||||
|  |  | ||||||
| api_ns = cg.esphome_ns.namespace('api') | api_ns = cg.esphome_ns.namespace('api') | ||||||
| APIServer = api_ns.class_('APIServer', cg.Component, cg.Controller) | APIServer = api_ns.class_('APIServer', cg.Component, cg.Controller) | ||||||
| @@ -102,7 +103,7 @@ def homeassistant_service_to_code(config, action_id, template_arg, args): | |||||||
|  |  | ||||||
| def validate_homeassistant_event(value): | def validate_homeassistant_event(value): | ||||||
|     value = cv.string(value) |     value = cv.string(value) | ||||||
|     if not value.startswith(u'esphome.'): |     if not value.startswith('esphome.'): | ||||||
|         raise cv.Invalid("ESPHome can only generate Home Assistant events that begin with " |         raise cv.Invalid("ESPHome can only generate Home Assistant events that begin with " | ||||||
|                          "esphome. For example 'esphome.xyz'") |                          "esphome. For example 'esphome.xyz'") | ||||||
|     return value |     return value | ||||||
|   | |||||||
| @@ -301,12 +301,17 @@ message ListEntitiesFanResponse { | |||||||
|  |  | ||||||
|   bool supports_oscillation = 5; |   bool supports_oscillation = 5; | ||||||
|   bool supports_speed = 6; |   bool supports_speed = 6; | ||||||
|  |   bool supports_direction = 7; | ||||||
| } | } | ||||||
| enum FanSpeed { | enum FanSpeed { | ||||||
|   FAN_SPEED_LOW = 0; |   FAN_SPEED_LOW = 0; | ||||||
|   FAN_SPEED_MEDIUM = 1; |   FAN_SPEED_MEDIUM = 1; | ||||||
|   FAN_SPEED_HIGH = 2; |   FAN_SPEED_HIGH = 2; | ||||||
| } | } | ||||||
|  | enum FanDirection { | ||||||
|  |   FAN_DIRECTION_FORWARD = 0; | ||||||
|  |   FAN_DIRECTION_REVERSE = 1; | ||||||
|  | } | ||||||
| message FanStateResponse { | message FanStateResponse { | ||||||
|   option (id) = 23; |   option (id) = 23; | ||||||
|   option (source) = SOURCE_SERVER; |   option (source) = SOURCE_SERVER; | ||||||
| @@ -317,6 +322,7 @@ message FanStateResponse { | |||||||
|   bool state = 2; |   bool state = 2; | ||||||
|   bool oscillating = 3; |   bool oscillating = 3; | ||||||
|   FanSpeed speed = 4; |   FanSpeed speed = 4; | ||||||
|  |   FanDirection direction = 5; | ||||||
| } | } | ||||||
| message FanCommandRequest { | message FanCommandRequest { | ||||||
|   option (id) = 31; |   option (id) = 31; | ||||||
| @@ -331,6 +337,8 @@ message FanCommandRequest { | |||||||
|   FanSpeed speed = 5; |   FanSpeed speed = 5; | ||||||
|   bool has_oscillating = 6; |   bool has_oscillating = 6; | ||||||
|   bool oscillating = 7; |   bool oscillating = 7; | ||||||
|  |   bool has_direction = 8; | ||||||
|  |   FanDirection direction = 9; | ||||||
| } | } | ||||||
|  |  | ||||||
| // ==================== LIGHT ==================== | // ==================== LIGHT ==================== | ||||||
| @@ -653,12 +661,34 @@ enum ClimateMode { | |||||||
|   CLIMATE_MODE_AUTO = 1; |   CLIMATE_MODE_AUTO = 1; | ||||||
|   CLIMATE_MODE_COOL = 2; |   CLIMATE_MODE_COOL = 2; | ||||||
|   CLIMATE_MODE_HEAT = 3; |   CLIMATE_MODE_HEAT = 3; | ||||||
|  |   CLIMATE_MODE_FAN_ONLY = 4; | ||||||
|  |   CLIMATE_MODE_DRY = 5; | ||||||
|  | } | ||||||
|  | enum ClimateFanMode { | ||||||
|  |   CLIMATE_FAN_ON = 0; | ||||||
|  |   CLIMATE_FAN_OFF = 1; | ||||||
|  |   CLIMATE_FAN_AUTO = 2; | ||||||
|  |   CLIMATE_FAN_LOW = 3; | ||||||
|  |   CLIMATE_FAN_MEDIUM = 4; | ||||||
|  |   CLIMATE_FAN_HIGH = 5; | ||||||
|  |   CLIMATE_FAN_MIDDLE = 6; | ||||||
|  |   CLIMATE_FAN_FOCUS = 7; | ||||||
|  |   CLIMATE_FAN_DIFFUSE = 8; | ||||||
|  | } | ||||||
|  | enum ClimateSwingMode { | ||||||
|  |   CLIMATE_SWING_OFF = 0; | ||||||
|  |   CLIMATE_SWING_BOTH = 1; | ||||||
|  |   CLIMATE_SWING_VERTICAL = 2; | ||||||
|  |   CLIMATE_SWINT_HORIZONTAL = 3; | ||||||
| } | } | ||||||
| enum ClimateAction { | enum ClimateAction { | ||||||
|   CLIMATE_ACTION_OFF = 0; |   CLIMATE_ACTION_OFF = 0; | ||||||
|   // values same as mode for readability |   // values same as mode for readability | ||||||
|   CLIMATE_ACTION_COOLING = 2; |   CLIMATE_ACTION_COOLING = 2; | ||||||
|   CLIMATE_ACTION_HEATING = 3; |   CLIMATE_ACTION_HEATING = 3; | ||||||
|  |   CLIMATE_ACTION_IDLE = 4; | ||||||
|  |   CLIMATE_ACTION_DRYING = 5; | ||||||
|  |   CLIMATE_ACTION_FAN = 6; | ||||||
| } | } | ||||||
| message ListEntitiesClimateResponse { | message ListEntitiesClimateResponse { | ||||||
|   option (id) = 46; |   option (id) = 46; | ||||||
| @@ -678,6 +708,8 @@ message ListEntitiesClimateResponse { | |||||||
|   float visual_temperature_step = 10; |   float visual_temperature_step = 10; | ||||||
|   bool supports_away = 11; |   bool supports_away = 11; | ||||||
|   bool supports_action = 12; |   bool supports_action = 12; | ||||||
|  |   repeated ClimateFanMode supported_fan_modes = 13; | ||||||
|  |   repeated ClimateSwingMode supported_swing_modes = 14; | ||||||
| } | } | ||||||
| message ClimateStateResponse { | message ClimateStateResponse { | ||||||
|   option (id) = 47; |   option (id) = 47; | ||||||
| @@ -693,6 +725,8 @@ message ClimateStateResponse { | |||||||
|   float target_temperature_high = 6; |   float target_temperature_high = 6; | ||||||
|   bool away = 7; |   bool away = 7; | ||||||
|   ClimateAction action = 8; |   ClimateAction action = 8; | ||||||
|  |   ClimateFanMode fan_mode = 9; | ||||||
|  |   ClimateSwingMode swing_mode = 10; | ||||||
| } | } | ||||||
| message ClimateCommandRequest { | message ClimateCommandRequest { | ||||||
|   option (id) = 48; |   option (id) = 48; | ||||||
| @@ -711,4 +745,8 @@ message ClimateCommandRequest { | |||||||
|   float target_temperature_high = 9; |   float target_temperature_high = 9; | ||||||
|   bool has_away = 10; |   bool has_away = 10; | ||||||
|   bool away = 11; |   bool away = 11; | ||||||
|  |   bool has_fan_mode = 12; | ||||||
|  |   ClimateFanMode fan_mode = 13; | ||||||
|  |   bool has_swing_mode = 14; | ||||||
|  |   ClimateSwingMode swing_mode = 15; | ||||||
| } | } | ||||||
|   | |||||||
| @@ -137,7 +137,6 @@ void APIConnection::loop() { | |||||||
|       // bool done = 3; |       // bool done = 3; | ||||||
|       bool done = this->image_reader_.available() == to_send; |       bool done = this->image_reader_.available() == to_send; | ||||||
|       buffer.encode_bool(3, done); |       buffer.encode_bool(3, done); | ||||||
|       this->set_nodelay(false); |  | ||||||
|       bool success = this->send_buffer(buffer, 44); |       bool success = this->send_buffer(buffer, 44); | ||||||
|  |  | ||||||
|       if (success) { |       if (success) { | ||||||
| @@ -249,6 +248,8 @@ bool APIConnection::send_fan_state(fan::FanState *fan) { | |||||||
|     resp.oscillating = fan->oscillating; |     resp.oscillating = fan->oscillating; | ||||||
|   if (traits.supports_speed()) |   if (traits.supports_speed()) | ||||||
|     resp.speed = static_cast<enums::FanSpeed>(fan->speed); |     resp.speed = static_cast<enums::FanSpeed>(fan->speed); | ||||||
|  |   if (traits.supports_direction()) | ||||||
|  |     resp.direction = static_cast<enums::FanDirection>(fan->direction); | ||||||
|   return this->send_fan_state_response(resp); |   return this->send_fan_state_response(resp); | ||||||
| } | } | ||||||
| bool APIConnection::send_fan_info(fan::FanState *fan) { | bool APIConnection::send_fan_info(fan::FanState *fan) { | ||||||
| @@ -260,6 +261,7 @@ bool APIConnection::send_fan_info(fan::FanState *fan) { | |||||||
|   msg.unique_id = get_default_unique_id("fan", fan); |   msg.unique_id = get_default_unique_id("fan", fan); | ||||||
|   msg.supports_oscillation = traits.supports_oscillation(); |   msg.supports_oscillation = traits.supports_oscillation(); | ||||||
|   msg.supports_speed = traits.supports_speed(); |   msg.supports_speed = traits.supports_speed(); | ||||||
|  |   msg.supports_direction = traits.supports_direction(); | ||||||
|   return this->send_list_entities_fan_response(msg); |   return this->send_list_entities_fan_response(msg); | ||||||
| } | } | ||||||
| void APIConnection::fan_command(const FanCommandRequest &msg) { | void APIConnection::fan_command(const FanCommandRequest &msg) { | ||||||
| @@ -274,6 +276,8 @@ void APIConnection::fan_command(const FanCommandRequest &msg) { | |||||||
|     call.set_oscillating(msg.oscillating); |     call.set_oscillating(msg.oscillating); | ||||||
|   if (msg.has_speed) |   if (msg.has_speed) | ||||||
|     call.set_speed(static_cast<fan::FanSpeed>(msg.speed)); |     call.set_speed(static_cast<fan::FanSpeed>(msg.speed)); | ||||||
|  |   if (msg.has_direction) | ||||||
|  |     call.set_direction(static_cast<fan::FanDirection>(msg.direction)); | ||||||
|   call.perform(); |   call.perform(); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| @@ -458,6 +462,10 @@ bool APIConnection::send_climate_state(climate::Climate *climate) { | |||||||
|   } |   } | ||||||
|   if (traits.get_supports_away()) |   if (traits.get_supports_away()) | ||||||
|     resp.away = climate->away; |     resp.away = climate->away; | ||||||
|  |   if (traits.get_supports_fan_modes()) | ||||||
|  |     resp.fan_mode = static_cast<enums::ClimateFanMode>(climate->fan_mode); | ||||||
|  |   if (traits.get_supports_swing_modes()) | ||||||
|  |     resp.swing_mode = static_cast<enums::ClimateSwingMode>(climate->swing_mode); | ||||||
|   return this->send_climate_state_response(resp); |   return this->send_climate_state_response(resp); | ||||||
| } | } | ||||||
| bool APIConnection::send_climate_info(climate::Climate *climate) { | bool APIConnection::send_climate_info(climate::Climate *climate) { | ||||||
| @@ -470,7 +478,7 @@ bool APIConnection::send_climate_info(climate::Climate *climate) { | |||||||
|   msg.supports_current_temperature = traits.get_supports_current_temperature(); |   msg.supports_current_temperature = traits.get_supports_current_temperature(); | ||||||
|   msg.supports_two_point_target_temperature = traits.get_supports_two_point_target_temperature(); |   msg.supports_two_point_target_temperature = traits.get_supports_two_point_target_temperature(); | ||||||
|   for (auto mode : {climate::CLIMATE_MODE_AUTO, climate::CLIMATE_MODE_OFF, climate::CLIMATE_MODE_COOL, |   for (auto mode : {climate::CLIMATE_MODE_AUTO, climate::CLIMATE_MODE_OFF, climate::CLIMATE_MODE_COOL, | ||||||
|                     climate::CLIMATE_MODE_HEAT}) { |                     climate::CLIMATE_MODE_HEAT, climate::CLIMATE_MODE_DRY, climate::CLIMATE_MODE_FAN_ONLY}) { | ||||||
|     if (traits.supports_mode(mode)) |     if (traits.supports_mode(mode)) | ||||||
|       msg.supported_modes.push_back(static_cast<enums::ClimateMode>(mode)); |       msg.supported_modes.push_back(static_cast<enums::ClimateMode>(mode)); | ||||||
|   } |   } | ||||||
| @@ -479,6 +487,17 @@ bool APIConnection::send_climate_info(climate::Climate *climate) { | |||||||
|   msg.visual_temperature_step = traits.get_visual_temperature_step(); |   msg.visual_temperature_step = traits.get_visual_temperature_step(); | ||||||
|   msg.supports_away = traits.get_supports_away(); |   msg.supports_away = traits.get_supports_away(); | ||||||
|   msg.supports_action = traits.get_supports_action(); |   msg.supports_action = traits.get_supports_action(); | ||||||
|  |   for (auto fan_mode : {climate::CLIMATE_FAN_ON, climate::CLIMATE_FAN_OFF, climate::CLIMATE_FAN_AUTO, | ||||||
|  |                         climate::CLIMATE_FAN_LOW, climate::CLIMATE_FAN_MEDIUM, climate::CLIMATE_FAN_HIGH, | ||||||
|  |                         climate::CLIMATE_FAN_MIDDLE, climate::CLIMATE_FAN_FOCUS, climate::CLIMATE_FAN_DIFFUSE}) { | ||||||
|  |     if (traits.supports_fan_mode(fan_mode)) | ||||||
|  |       msg.supported_fan_modes.push_back(static_cast<enums::ClimateFanMode>(fan_mode)); | ||||||
|  |   } | ||||||
|  |   for (auto swing_mode : {climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_BOTH, climate::CLIMATE_SWING_VERTICAL, | ||||||
|  |                           climate::CLIMATE_SWING_HORIZONTAL}) { | ||||||
|  |     if (traits.supports_swing_mode(swing_mode)) | ||||||
|  |       msg.supported_swing_modes.push_back(static_cast<enums::ClimateSwingMode>(swing_mode)); | ||||||
|  |   } | ||||||
|   return this->send_list_entities_climate_response(msg); |   return this->send_list_entities_climate_response(msg); | ||||||
| } | } | ||||||
| void APIConnection::climate_command(const ClimateCommandRequest &msg) { | void APIConnection::climate_command(const ClimateCommandRequest &msg) { | ||||||
| @@ -497,6 +516,10 @@ void APIConnection::climate_command(const ClimateCommandRequest &msg) { | |||||||
|     call.set_target_temperature_high(msg.target_temperature_high); |     call.set_target_temperature_high(msg.target_temperature_high); | ||||||
|   if (msg.has_away) |   if (msg.has_away) | ||||||
|     call.set_away(msg.away); |     call.set_away(msg.away); | ||||||
|  |   if (msg.has_fan_mode) | ||||||
|  |     call.set_fan_mode(static_cast<climate::ClimateFanMode>(msg.fan_mode)); | ||||||
|  |   if (msg.has_swing_mode) | ||||||
|  |     call.set_swing_mode(static_cast<climate::ClimateSwingMode>(msg.swing_mode)); | ||||||
|   call.perform(); |   call.perform(); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| @@ -539,8 +562,6 @@ bool APIConnection::send_log_message(int level, const char *tag, const char *lin | |||||||
|   if (this->log_subscription_ < level) |   if (this->log_subscription_ < level) | ||||||
|     return false; |     return false; | ||||||
|  |  | ||||||
|   this->set_nodelay(false); |  | ||||||
|  |  | ||||||
|   // Send raw so that we don't copy too much |   // Send raw so that we don't copy too much | ||||||
|   auto buffer = this->create_buffer(); |   auto buffer = this->create_buffer(); | ||||||
|   // LogLevel level = 1; |   // LogLevel level = 1; | ||||||
|   | |||||||
| @@ -138,12 +138,6 @@ class APIConnection : public APIServerConnection { | |||||||
|   void on_timeout_(uint32_t time); |   void on_timeout_(uint32_t time); | ||||||
|   void on_data_(uint8_t *buf, size_t len); |   void on_data_(uint8_t *buf, size_t len); | ||||||
|   void parse_recv_buffer_(); |   void parse_recv_buffer_(); | ||||||
|   void set_nodelay(bool nodelay) override { |  | ||||||
|     if (nodelay == this->current_nodelay_) |  | ||||||
|       return; |  | ||||||
|     this->client_->setNoDelay(nodelay); |  | ||||||
|     this->current_nodelay_ = nodelay; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   enum class ConnectionState { |   enum class ConnectionState { | ||||||
|     WAITING_FOR_HELLO, |     WAITING_FOR_HELLO, | ||||||
|   | |||||||
| @@ -1,3 +1,5 @@ | |||||||
|  | // This file was automatically generated with a tool. | ||||||
|  | // See scripts/api_protobuf/api_protobuf.py | ||||||
| #include "api_pb2.h" | #include "api_pb2.h" | ||||||
| #include "esphome/core/log.h" | #include "esphome/core/log.h" | ||||||
|  |  | ||||||
| @@ -50,6 +52,16 @@ template<> const char *proto_enum_to_string<enums::FanSpeed>(enums::FanSpeed val | |||||||
|       return "UNKNOWN"; |       return "UNKNOWN"; | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  | template<> const char *proto_enum_to_string<enums::FanDirection>(enums::FanDirection value) { | ||||||
|  |   switch (value) { | ||||||
|  |     case enums::FAN_DIRECTION_FORWARD: | ||||||
|  |       return "FAN_DIRECTION_FORWARD"; | ||||||
|  |     case enums::FAN_DIRECTION_REVERSE: | ||||||
|  |       return "FAN_DIRECTION_REVERSE"; | ||||||
|  |     default: | ||||||
|  |       return "UNKNOWN"; | ||||||
|  |   } | ||||||
|  | } | ||||||
| template<> const char *proto_enum_to_string<enums::LogLevel>(enums::LogLevel value) { | template<> const char *proto_enum_to_string<enums::LogLevel>(enums::LogLevel value) { | ||||||
|   switch (value) { |   switch (value) { | ||||||
|     case enums::LOG_LEVEL_NONE: |     case enums::LOG_LEVEL_NONE: | ||||||
| @@ -102,6 +114,48 @@ template<> const char *proto_enum_to_string<enums::ClimateMode>(enums::ClimateMo | |||||||
|       return "CLIMATE_MODE_COOL"; |       return "CLIMATE_MODE_COOL"; | ||||||
|     case enums::CLIMATE_MODE_HEAT: |     case enums::CLIMATE_MODE_HEAT: | ||||||
|       return "CLIMATE_MODE_HEAT"; |       return "CLIMATE_MODE_HEAT"; | ||||||
|  |     case enums::CLIMATE_MODE_FAN_ONLY: | ||||||
|  |       return "CLIMATE_MODE_FAN_ONLY"; | ||||||
|  |     case enums::CLIMATE_MODE_DRY: | ||||||
|  |       return "CLIMATE_MODE_DRY"; | ||||||
|  |     default: | ||||||
|  |       return "UNKNOWN"; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | template<> const char *proto_enum_to_string<enums::ClimateFanMode>(enums::ClimateFanMode value) { | ||||||
|  |   switch (value) { | ||||||
|  |     case enums::CLIMATE_FAN_ON: | ||||||
|  |       return "CLIMATE_FAN_ON"; | ||||||
|  |     case enums::CLIMATE_FAN_OFF: | ||||||
|  |       return "CLIMATE_FAN_OFF"; | ||||||
|  |     case enums::CLIMATE_FAN_AUTO: | ||||||
|  |       return "CLIMATE_FAN_AUTO"; | ||||||
|  |     case enums::CLIMATE_FAN_LOW: | ||||||
|  |       return "CLIMATE_FAN_LOW"; | ||||||
|  |     case enums::CLIMATE_FAN_MEDIUM: | ||||||
|  |       return "CLIMATE_FAN_MEDIUM"; | ||||||
|  |     case enums::CLIMATE_FAN_HIGH: | ||||||
|  |       return "CLIMATE_FAN_HIGH"; | ||||||
|  |     case enums::CLIMATE_FAN_MIDDLE: | ||||||
|  |       return "CLIMATE_FAN_MIDDLE"; | ||||||
|  |     case enums::CLIMATE_FAN_FOCUS: | ||||||
|  |       return "CLIMATE_FAN_FOCUS"; | ||||||
|  |     case enums::CLIMATE_FAN_DIFFUSE: | ||||||
|  |       return "CLIMATE_FAN_DIFFUSE"; | ||||||
|  |     default: | ||||||
|  |       return "UNKNOWN"; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | template<> const char *proto_enum_to_string<enums::ClimateSwingMode>(enums::ClimateSwingMode value) { | ||||||
|  |   switch (value) { | ||||||
|  |     case enums::CLIMATE_SWING_OFF: | ||||||
|  |       return "CLIMATE_SWING_OFF"; | ||||||
|  |     case enums::CLIMATE_SWING_BOTH: | ||||||
|  |       return "CLIMATE_SWING_BOTH"; | ||||||
|  |     case enums::CLIMATE_SWING_VERTICAL: | ||||||
|  |       return "CLIMATE_SWING_VERTICAL"; | ||||||
|  |     case enums::CLIMATE_SWINT_HORIZONTAL: | ||||||
|  |       return "CLIMATE_SWINT_HORIZONTAL"; | ||||||
|     default: |     default: | ||||||
|       return "UNKNOWN"; |       return "UNKNOWN"; | ||||||
|   } |   } | ||||||
| @@ -114,6 +168,12 @@ template<> const char *proto_enum_to_string<enums::ClimateAction>(enums::Climate | |||||||
|       return "CLIMATE_ACTION_COOLING"; |       return "CLIMATE_ACTION_COOLING"; | ||||||
|     case enums::CLIMATE_ACTION_HEATING: |     case enums::CLIMATE_ACTION_HEATING: | ||||||
|       return "CLIMATE_ACTION_HEATING"; |       return "CLIMATE_ACTION_HEATING"; | ||||||
|  |     case enums::CLIMATE_ACTION_IDLE: | ||||||
|  |       return "CLIMATE_ACTION_IDLE"; | ||||||
|  |     case enums::CLIMATE_ACTION_DRYING: | ||||||
|  |       return "CLIMATE_ACTION_DRYING"; | ||||||
|  |     case enums::CLIMATE_ACTION_FAN: | ||||||
|  |       return "CLIMATE_ACTION_FAN"; | ||||||
|     default: |     default: | ||||||
|       return "UNKNOWN"; |       return "UNKNOWN"; | ||||||
|   } |   } | ||||||
| @@ -710,6 +770,10 @@ bool ListEntitiesFanResponse::decode_varint(uint32_t field_id, ProtoVarInt value | |||||||
|       this->supports_speed = value.as_bool(); |       this->supports_speed = value.as_bool(); | ||||||
|       return true; |       return true; | ||||||
|     } |     } | ||||||
|  |     case 7: { | ||||||
|  |       this->supports_direction = value.as_bool(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|     default: |     default: | ||||||
|       return false; |       return false; | ||||||
|   } |   } | ||||||
| @@ -749,6 +813,7 @@ void ListEntitiesFanResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
|   buffer.encode_string(4, this->unique_id); |   buffer.encode_string(4, this->unique_id); | ||||||
|   buffer.encode_bool(5, this->supports_oscillation); |   buffer.encode_bool(5, this->supports_oscillation); | ||||||
|   buffer.encode_bool(6, this->supports_speed); |   buffer.encode_bool(6, this->supports_speed); | ||||||
|  |   buffer.encode_bool(7, this->supports_direction); | ||||||
| } | } | ||||||
| void ListEntitiesFanResponse::dump_to(std::string &out) const { | void ListEntitiesFanResponse::dump_to(std::string &out) const { | ||||||
|   char buffer[64]; |   char buffer[64]; | ||||||
| @@ -777,6 +842,10 @@ void ListEntitiesFanResponse::dump_to(std::string &out) const { | |||||||
|   out.append("  supports_speed: "); |   out.append("  supports_speed: "); | ||||||
|   out.append(YESNO(this->supports_speed)); |   out.append(YESNO(this->supports_speed)); | ||||||
|   out.append("\n"); |   out.append("\n"); | ||||||
|  |  | ||||||
|  |   out.append("  supports_direction: "); | ||||||
|  |   out.append(YESNO(this->supports_direction)); | ||||||
|  |   out.append("\n"); | ||||||
|   out.append("}"); |   out.append("}"); | ||||||
| } | } | ||||||
| bool FanStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { | bool FanStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { | ||||||
| @@ -793,6 +862,10 @@ bool FanStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { | |||||||
|       this->speed = value.as_enum<enums::FanSpeed>(); |       this->speed = value.as_enum<enums::FanSpeed>(); | ||||||
|       return true; |       return true; | ||||||
|     } |     } | ||||||
|  |     case 5: { | ||||||
|  |       this->direction = value.as_enum<enums::FanDirection>(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|     default: |     default: | ||||||
|       return false; |       return false; | ||||||
|   } |   } | ||||||
| @@ -812,6 +885,7 @@ void FanStateResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
|   buffer.encode_bool(2, this->state); |   buffer.encode_bool(2, this->state); | ||||||
|   buffer.encode_bool(3, this->oscillating); |   buffer.encode_bool(3, this->oscillating); | ||||||
|   buffer.encode_enum<enums::FanSpeed>(4, this->speed); |   buffer.encode_enum<enums::FanSpeed>(4, this->speed); | ||||||
|  |   buffer.encode_enum<enums::FanDirection>(5, this->direction); | ||||||
| } | } | ||||||
| void FanStateResponse::dump_to(std::string &out) const { | void FanStateResponse::dump_to(std::string &out) const { | ||||||
|   char buffer[64]; |   char buffer[64]; | ||||||
| @@ -832,6 +906,10 @@ void FanStateResponse::dump_to(std::string &out) const { | |||||||
|   out.append("  speed: "); |   out.append("  speed: "); | ||||||
|   out.append(proto_enum_to_string<enums::FanSpeed>(this->speed)); |   out.append(proto_enum_to_string<enums::FanSpeed>(this->speed)); | ||||||
|   out.append("\n"); |   out.append("\n"); | ||||||
|  |  | ||||||
|  |   out.append("  direction: "); | ||||||
|  |   out.append(proto_enum_to_string<enums::FanDirection>(this->direction)); | ||||||
|  |   out.append("\n"); | ||||||
|   out.append("}"); |   out.append("}"); | ||||||
| } | } | ||||||
| bool FanCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { | bool FanCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { | ||||||
| @@ -860,6 +938,14 @@ bool FanCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { | |||||||
|       this->oscillating = value.as_bool(); |       this->oscillating = value.as_bool(); | ||||||
|       return true; |       return true; | ||||||
|     } |     } | ||||||
|  |     case 8: { | ||||||
|  |       this->has_direction = value.as_bool(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|  |     case 9: { | ||||||
|  |       this->direction = value.as_enum<enums::FanDirection>(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|     default: |     default: | ||||||
|       return false; |       return false; | ||||||
|   } |   } | ||||||
| @@ -882,6 +968,8 @@ void FanCommandRequest::encode(ProtoWriteBuffer buffer) const { | |||||||
|   buffer.encode_enum<enums::FanSpeed>(5, this->speed); |   buffer.encode_enum<enums::FanSpeed>(5, this->speed); | ||||||
|   buffer.encode_bool(6, this->has_oscillating); |   buffer.encode_bool(6, this->has_oscillating); | ||||||
|   buffer.encode_bool(7, this->oscillating); |   buffer.encode_bool(7, this->oscillating); | ||||||
|  |   buffer.encode_bool(8, this->has_direction); | ||||||
|  |   buffer.encode_enum<enums::FanDirection>(9, this->direction); | ||||||
| } | } | ||||||
| void FanCommandRequest::dump_to(std::string &out) const { | void FanCommandRequest::dump_to(std::string &out) const { | ||||||
|   char buffer[64]; |   char buffer[64]; | ||||||
| @@ -914,6 +1002,14 @@ void FanCommandRequest::dump_to(std::string &out) const { | |||||||
|   out.append("  oscillating: "); |   out.append("  oscillating: "); | ||||||
|   out.append(YESNO(this->oscillating)); |   out.append(YESNO(this->oscillating)); | ||||||
|   out.append("\n"); |   out.append("\n"); | ||||||
|  |  | ||||||
|  |   out.append("  has_direction: "); | ||||||
|  |   out.append(YESNO(this->has_direction)); | ||||||
|  |   out.append("\n"); | ||||||
|  |  | ||||||
|  |   out.append("  direction: "); | ||||||
|  |   out.append(proto_enum_to_string<enums::FanDirection>(this->direction)); | ||||||
|  |   out.append("\n"); | ||||||
|   out.append("}"); |   out.append("}"); | ||||||
| } | } | ||||||
| bool ListEntitiesLightResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { | bool ListEntitiesLightResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { | ||||||
| @@ -2458,6 +2554,14 @@ bool ListEntitiesClimateResponse::decode_varint(uint32_t field_id, ProtoVarInt v | |||||||
|       this->supports_action = value.as_bool(); |       this->supports_action = value.as_bool(); | ||||||
|       return true; |       return true; | ||||||
|     } |     } | ||||||
|  |     case 13: { | ||||||
|  |       this->supported_fan_modes.push_back(value.as_enum<enums::ClimateFanMode>()); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|  |     case 14: { | ||||||
|  |       this->supported_swing_modes.push_back(value.as_enum<enums::ClimateSwingMode>()); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|     default: |     default: | ||||||
|       return false; |       return false; | ||||||
|   } |   } | ||||||
| @@ -2517,6 +2621,12 @@ void ListEntitiesClimateResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
|   buffer.encode_float(10, this->visual_temperature_step); |   buffer.encode_float(10, this->visual_temperature_step); | ||||||
|   buffer.encode_bool(11, this->supports_away); |   buffer.encode_bool(11, this->supports_away); | ||||||
|   buffer.encode_bool(12, this->supports_action); |   buffer.encode_bool(12, this->supports_action); | ||||||
|  |   for (auto &it : this->supported_fan_modes) { | ||||||
|  |     buffer.encode_enum<enums::ClimateFanMode>(13, it, true); | ||||||
|  |   } | ||||||
|  |   for (auto &it : this->supported_swing_modes) { | ||||||
|  |     buffer.encode_enum<enums::ClimateSwingMode>(14, it, true); | ||||||
|  |   } | ||||||
| } | } | ||||||
| void ListEntitiesClimateResponse::dump_to(std::string &out) const { | void ListEntitiesClimateResponse::dump_to(std::string &out) const { | ||||||
|   char buffer[64]; |   char buffer[64]; | ||||||
| @@ -2574,6 +2684,18 @@ void ListEntitiesClimateResponse::dump_to(std::string &out) const { | |||||||
|   out.append("  supports_action: "); |   out.append("  supports_action: "); | ||||||
|   out.append(YESNO(this->supports_action)); |   out.append(YESNO(this->supports_action)); | ||||||
|   out.append("\n"); |   out.append("\n"); | ||||||
|  |  | ||||||
|  |   for (const auto &it : this->supported_fan_modes) { | ||||||
|  |     out.append("  supported_fan_modes: "); | ||||||
|  |     out.append(proto_enum_to_string<enums::ClimateFanMode>(it)); | ||||||
|  |     out.append("\n"); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   for (const auto &it : this->supported_swing_modes) { | ||||||
|  |     out.append("  supported_swing_modes: "); | ||||||
|  |     out.append(proto_enum_to_string<enums::ClimateSwingMode>(it)); | ||||||
|  |     out.append("\n"); | ||||||
|  |   } | ||||||
|   out.append("}"); |   out.append("}"); | ||||||
| } | } | ||||||
| bool ClimateStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { | bool ClimateStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { | ||||||
| @@ -2590,6 +2712,14 @@ bool ClimateStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { | |||||||
|       this->action = value.as_enum<enums::ClimateAction>(); |       this->action = value.as_enum<enums::ClimateAction>(); | ||||||
|       return true; |       return true; | ||||||
|     } |     } | ||||||
|  |     case 9: { | ||||||
|  |       this->fan_mode = value.as_enum<enums::ClimateFanMode>(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|  |     case 10: { | ||||||
|  |       this->swing_mode = value.as_enum<enums::ClimateSwingMode>(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|     default: |     default: | ||||||
|       return false; |       return false; | ||||||
|   } |   } | ||||||
| @@ -2629,6 +2759,8 @@ void ClimateStateResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
|   buffer.encode_float(6, this->target_temperature_high); |   buffer.encode_float(6, this->target_temperature_high); | ||||||
|   buffer.encode_bool(7, this->away); |   buffer.encode_bool(7, this->away); | ||||||
|   buffer.encode_enum<enums::ClimateAction>(8, this->action); |   buffer.encode_enum<enums::ClimateAction>(8, this->action); | ||||||
|  |   buffer.encode_enum<enums::ClimateFanMode>(9, this->fan_mode); | ||||||
|  |   buffer.encode_enum<enums::ClimateSwingMode>(10, this->swing_mode); | ||||||
| } | } | ||||||
| void ClimateStateResponse::dump_to(std::string &out) const { | void ClimateStateResponse::dump_to(std::string &out) const { | ||||||
|   char buffer[64]; |   char buffer[64]; | ||||||
| @@ -2669,6 +2801,14 @@ void ClimateStateResponse::dump_to(std::string &out) const { | |||||||
|   out.append("  action: "); |   out.append("  action: "); | ||||||
|   out.append(proto_enum_to_string<enums::ClimateAction>(this->action)); |   out.append(proto_enum_to_string<enums::ClimateAction>(this->action)); | ||||||
|   out.append("\n"); |   out.append("\n"); | ||||||
|  |  | ||||||
|  |   out.append("  fan_mode: "); | ||||||
|  |   out.append(proto_enum_to_string<enums::ClimateFanMode>(this->fan_mode)); | ||||||
|  |   out.append("\n"); | ||||||
|  |  | ||||||
|  |   out.append("  swing_mode: "); | ||||||
|  |   out.append(proto_enum_to_string<enums::ClimateSwingMode>(this->swing_mode)); | ||||||
|  |   out.append("\n"); | ||||||
|   out.append("}"); |   out.append("}"); | ||||||
| } | } | ||||||
| bool ClimateCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { | bool ClimateCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { | ||||||
| @@ -2701,6 +2841,22 @@ bool ClimateCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) | |||||||
|       this->away = value.as_bool(); |       this->away = value.as_bool(); | ||||||
|       return true; |       return true; | ||||||
|     } |     } | ||||||
|  |     case 12: { | ||||||
|  |       this->has_fan_mode = value.as_bool(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|  |     case 13: { | ||||||
|  |       this->fan_mode = value.as_enum<enums::ClimateFanMode>(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|  |     case 14: { | ||||||
|  |       this->has_swing_mode = value.as_bool(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|  |     case 15: { | ||||||
|  |       this->swing_mode = value.as_enum<enums::ClimateSwingMode>(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|     default: |     default: | ||||||
|       return false; |       return false; | ||||||
|   } |   } | ||||||
| @@ -2739,6 +2895,10 @@ void ClimateCommandRequest::encode(ProtoWriteBuffer buffer) const { | |||||||
|   buffer.encode_float(9, this->target_temperature_high); |   buffer.encode_float(9, this->target_temperature_high); | ||||||
|   buffer.encode_bool(10, this->has_away); |   buffer.encode_bool(10, this->has_away); | ||||||
|   buffer.encode_bool(11, this->away); |   buffer.encode_bool(11, this->away); | ||||||
|  |   buffer.encode_bool(12, this->has_fan_mode); | ||||||
|  |   buffer.encode_enum<enums::ClimateFanMode>(13, this->fan_mode); | ||||||
|  |   buffer.encode_bool(14, this->has_swing_mode); | ||||||
|  |   buffer.encode_enum<enums::ClimateSwingMode>(15, this->swing_mode); | ||||||
| } | } | ||||||
| void ClimateCommandRequest::dump_to(std::string &out) const { | void ClimateCommandRequest::dump_to(std::string &out) const { | ||||||
|   char buffer[64]; |   char buffer[64]; | ||||||
| @@ -2790,6 +2950,22 @@ void ClimateCommandRequest::dump_to(std::string &out) const { | |||||||
|   out.append("  away: "); |   out.append("  away: "); | ||||||
|   out.append(YESNO(this->away)); |   out.append(YESNO(this->away)); | ||||||
|   out.append("\n"); |   out.append("\n"); | ||||||
|  |  | ||||||
|  |   out.append("  has_fan_mode: "); | ||||||
|  |   out.append(YESNO(this->has_fan_mode)); | ||||||
|  |   out.append("\n"); | ||||||
|  |  | ||||||
|  |   out.append("  fan_mode: "); | ||||||
|  |   out.append(proto_enum_to_string<enums::ClimateFanMode>(this->fan_mode)); | ||||||
|  |   out.append("\n"); | ||||||
|  |  | ||||||
|  |   out.append("  has_swing_mode: "); | ||||||
|  |   out.append(YESNO(this->has_swing_mode)); | ||||||
|  |   out.append("\n"); | ||||||
|  |  | ||||||
|  |   out.append("  swing_mode: "); | ||||||
|  |   out.append(proto_enum_to_string<enums::ClimateSwingMode>(this->swing_mode)); | ||||||
|  |   out.append("\n"); | ||||||
|   out.append("}"); |   out.append("}"); | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,3 +1,5 @@ | |||||||
|  | // This file was automatically generated with a tool. | ||||||
|  | // See scripts/api_protobuf/api_protobuf.py | ||||||
| #pragma once | #pragma once | ||||||
|  |  | ||||||
| #include "proto.h" | #include "proto.h" | ||||||
| @@ -26,6 +28,10 @@ enum FanSpeed : uint32_t { | |||||||
|   FAN_SPEED_MEDIUM = 1, |   FAN_SPEED_MEDIUM = 1, | ||||||
|   FAN_SPEED_HIGH = 2, |   FAN_SPEED_HIGH = 2, | ||||||
| }; | }; | ||||||
|  | enum FanDirection : uint32_t { | ||||||
|  |   FAN_DIRECTION_FORWARD = 0, | ||||||
|  |   FAN_DIRECTION_REVERSE = 1, | ||||||
|  | }; | ||||||
| enum LogLevel : uint32_t { | enum LogLevel : uint32_t { | ||||||
|   LOG_LEVEL_NONE = 0, |   LOG_LEVEL_NONE = 0, | ||||||
|   LOG_LEVEL_ERROR = 1, |   LOG_LEVEL_ERROR = 1, | ||||||
| @@ -50,11 +56,33 @@ enum ClimateMode : uint32_t { | |||||||
|   CLIMATE_MODE_AUTO = 1, |   CLIMATE_MODE_AUTO = 1, | ||||||
|   CLIMATE_MODE_COOL = 2, |   CLIMATE_MODE_COOL = 2, | ||||||
|   CLIMATE_MODE_HEAT = 3, |   CLIMATE_MODE_HEAT = 3, | ||||||
|  |   CLIMATE_MODE_FAN_ONLY = 4, | ||||||
|  |   CLIMATE_MODE_DRY = 5, | ||||||
|  | }; | ||||||
|  | enum ClimateFanMode : uint32_t { | ||||||
|  |   CLIMATE_FAN_ON = 0, | ||||||
|  |   CLIMATE_FAN_OFF = 1, | ||||||
|  |   CLIMATE_FAN_AUTO = 2, | ||||||
|  |   CLIMATE_FAN_LOW = 3, | ||||||
|  |   CLIMATE_FAN_MEDIUM = 4, | ||||||
|  |   CLIMATE_FAN_HIGH = 5, | ||||||
|  |   CLIMATE_FAN_MIDDLE = 6, | ||||||
|  |   CLIMATE_FAN_FOCUS = 7, | ||||||
|  |   CLIMATE_FAN_DIFFUSE = 8, | ||||||
|  | }; | ||||||
|  | enum ClimateSwingMode : uint32_t { | ||||||
|  |   CLIMATE_SWING_OFF = 0, | ||||||
|  |   CLIMATE_SWING_BOTH = 1, | ||||||
|  |   CLIMATE_SWING_VERTICAL = 2, | ||||||
|  |   CLIMATE_SWINT_HORIZONTAL = 3, | ||||||
| }; | }; | ||||||
| enum ClimateAction : uint32_t { | enum ClimateAction : uint32_t { | ||||||
|   CLIMATE_ACTION_OFF = 0, |   CLIMATE_ACTION_OFF = 0, | ||||||
|   CLIMATE_ACTION_COOLING = 2, |   CLIMATE_ACTION_COOLING = 2, | ||||||
|   CLIMATE_ACTION_HEATING = 3, |   CLIMATE_ACTION_HEATING = 3, | ||||||
|  |   CLIMATE_ACTION_IDLE = 4, | ||||||
|  |   CLIMATE_ACTION_DRYING = 5, | ||||||
|  |   CLIMATE_ACTION_FAN = 6, | ||||||
| }; | }; | ||||||
|  |  | ||||||
| }  // namespace enums | }  // namespace enums | ||||||
| @@ -255,6 +283,7 @@ class ListEntitiesFanResponse : public ProtoMessage { | |||||||
|   std::string unique_id{};           // NOLINT |   std::string unique_id{};           // NOLINT | ||||||
|   bool supports_oscillation{false};  // NOLINT |   bool supports_oscillation{false};  // NOLINT | ||||||
|   bool supports_speed{false};        // NOLINT |   bool supports_speed{false};        // NOLINT | ||||||
|  |   bool supports_direction{false};    // NOLINT | ||||||
|   void encode(ProtoWriteBuffer buffer) const override; |   void encode(ProtoWriteBuffer buffer) const override; | ||||||
|   void dump_to(std::string &out) const override; |   void dump_to(std::string &out) const override; | ||||||
|  |  | ||||||
| @@ -265,10 +294,11 @@ class ListEntitiesFanResponse : public ProtoMessage { | |||||||
| }; | }; | ||||||
| class FanStateResponse : public ProtoMessage { | class FanStateResponse : public ProtoMessage { | ||||||
|  public: |  public: | ||||||
|   uint32_t key{0};          // NOLINT |   uint32_t key{0};                  // NOLINT | ||||||
|   bool state{false};        // NOLINT |   bool state{false};                // NOLINT | ||||||
|   bool oscillating{false};  // NOLINT |   bool oscillating{false};          // NOLINT | ||||||
|   enums::FanSpeed speed{};  // NOLINT |   enums::FanSpeed speed{};          // NOLINT | ||||||
|  |   enums::FanDirection direction{};  // NOLINT | ||||||
|   void encode(ProtoWriteBuffer buffer) const override; |   void encode(ProtoWriteBuffer buffer) const override; | ||||||
|   void dump_to(std::string &out) const override; |   void dump_to(std::string &out) const override; | ||||||
|  |  | ||||||
| @@ -278,13 +308,15 @@ class FanStateResponse : public ProtoMessage { | |||||||
| }; | }; | ||||||
| class FanCommandRequest : public ProtoMessage { | class FanCommandRequest : public ProtoMessage { | ||||||
|  public: |  public: | ||||||
|   uint32_t key{0};              // NOLINT |   uint32_t key{0};                  // NOLINT | ||||||
|   bool has_state{false};        // NOLINT |   bool has_state{false};            // NOLINT | ||||||
|   bool state{false};            // NOLINT |   bool state{false};                // NOLINT | ||||||
|   bool has_speed{false};        // NOLINT |   bool has_speed{false};            // NOLINT | ||||||
|   enums::FanSpeed speed{};      // NOLINT |   enums::FanSpeed speed{};          // NOLINT | ||||||
|   bool has_oscillating{false};  // NOLINT |   bool has_oscillating{false};      // NOLINT | ||||||
|   bool oscillating{false};      // NOLINT |   bool oscillating{false};          // NOLINT | ||||||
|  |   bool has_direction{false};        // NOLINT | ||||||
|  |   enums::FanDirection direction{};  // NOLINT | ||||||
|   void encode(ProtoWriteBuffer buffer) const override; |   void encode(ProtoWriteBuffer buffer) const override; | ||||||
|   void dump_to(std::string &out) const override; |   void dump_to(std::string &out) const override; | ||||||
|  |  | ||||||
| @@ -643,18 +675,20 @@ class CameraImageRequest : public ProtoMessage { | |||||||
| }; | }; | ||||||
| class ListEntitiesClimateResponse : public ProtoMessage { | class ListEntitiesClimateResponse : public ProtoMessage { | ||||||
|  public: |  public: | ||||||
|   std::string object_id{};                            // NOLINT |   std::string object_id{};                                       // NOLINT | ||||||
|   uint32_t key{0};                                    // NOLINT |   uint32_t key{0};                                               // NOLINT | ||||||
|   std::string name{};                                 // NOLINT |   std::string name{};                                            // NOLINT | ||||||
|   std::string unique_id{};                            // NOLINT |   std::string unique_id{};                                       // NOLINT | ||||||
|   bool supports_current_temperature{false};           // NOLINT |   bool supports_current_temperature{false};                      // NOLINT | ||||||
|   bool supports_two_point_target_temperature{false};  // NOLINT |   bool supports_two_point_target_temperature{false};             // NOLINT | ||||||
|   std::vector<enums::ClimateMode> supported_modes{};  // NOLINT |   std::vector<enums::ClimateMode> supported_modes{};             // NOLINT | ||||||
|   float visual_min_temperature{0.0f};                 // NOLINT |   float visual_min_temperature{0.0f};                            // NOLINT | ||||||
|   float visual_max_temperature{0.0f};                 // NOLINT |   float visual_max_temperature{0.0f};                            // NOLINT | ||||||
|   float visual_temperature_step{0.0f};                // NOLINT |   float visual_temperature_step{0.0f};                           // NOLINT | ||||||
|   bool supports_away{false};                          // NOLINT |   bool supports_away{false};                                     // NOLINT | ||||||
|   bool supports_action{false};                        // NOLINT |   bool supports_action{false};                                   // NOLINT | ||||||
|  |   std::vector<enums::ClimateFanMode> supported_fan_modes{};      // NOLINT | ||||||
|  |   std::vector<enums::ClimateSwingMode> supported_swing_modes{};  // NOLINT | ||||||
|   void encode(ProtoWriteBuffer buffer) const override; |   void encode(ProtoWriteBuffer buffer) const override; | ||||||
|   void dump_to(std::string &out) const override; |   void dump_to(std::string &out) const override; | ||||||
|  |  | ||||||
| @@ -665,14 +699,16 @@ class ListEntitiesClimateResponse : public ProtoMessage { | |||||||
| }; | }; | ||||||
| class ClimateStateResponse : public ProtoMessage { | class ClimateStateResponse : public ProtoMessage { | ||||||
|  public: |  public: | ||||||
|   uint32_t key{0};                      // NOLINT |   uint32_t key{0};                       // NOLINT | ||||||
|   enums::ClimateMode mode{};            // NOLINT |   enums::ClimateMode mode{};             // NOLINT | ||||||
|   float current_temperature{0.0f};      // NOLINT |   float current_temperature{0.0f};       // NOLINT | ||||||
|   float target_temperature{0.0f};       // NOLINT |   float target_temperature{0.0f};        // NOLINT | ||||||
|   float target_temperature_low{0.0f};   // NOLINT |   float target_temperature_low{0.0f};    // NOLINT | ||||||
|   float target_temperature_high{0.0f};  // NOLINT |   float target_temperature_high{0.0f};   // NOLINT | ||||||
|   bool away{false};                     // NOLINT |   bool away{false};                      // NOLINT | ||||||
|   enums::ClimateAction action{};        // NOLINT |   enums::ClimateAction action{};         // NOLINT | ||||||
|  |   enums::ClimateFanMode fan_mode{};      // NOLINT | ||||||
|  |   enums::ClimateSwingMode swing_mode{};  // NOLINT | ||||||
|   void encode(ProtoWriteBuffer buffer) const override; |   void encode(ProtoWriteBuffer buffer) const override; | ||||||
|   void dump_to(std::string &out) const override; |   void dump_to(std::string &out) const override; | ||||||
|  |  | ||||||
| @@ -693,6 +729,10 @@ class ClimateCommandRequest : public ProtoMessage { | |||||||
|   float target_temperature_high{0.0f};      // NOLINT |   float target_temperature_high{0.0f};      // NOLINT | ||||||
|   bool has_away{false};                     // NOLINT |   bool has_away{false};                     // NOLINT | ||||||
|   bool away{false};                         // NOLINT |   bool away{false};                         // NOLINT | ||||||
|  |   bool has_fan_mode{false};                 // NOLINT | ||||||
|  |   enums::ClimateFanMode fan_mode{};         // NOLINT | ||||||
|  |   bool has_swing_mode{false};               // NOLINT | ||||||
|  |   enums::ClimateSwingMode swing_mode{};     // NOLINT | ||||||
|   void encode(ProtoWriteBuffer buffer) const override; |   void encode(ProtoWriteBuffer buffer) const override; | ||||||
|   void dump_to(std::string &out) const override; |   void dump_to(std::string &out) const override; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,3 +1,5 @@ | |||||||
|  | // This file was automatically generated with a tool. | ||||||
|  | // See scripts/api_protobuf/api_protobuf.py | ||||||
| #include "api_pb2_service.h" | #include "api_pb2_service.h" | ||||||
| #include "esphome/core/log.h" | #include "esphome/core/log.h" | ||||||
|  |  | ||||||
| @@ -8,69 +10,57 @@ static const char *TAG = "api.service"; | |||||||
|  |  | ||||||
| bool APIServerConnectionBase::send_hello_response(const HelloResponse &msg) { | bool APIServerConnectionBase::send_hello_response(const HelloResponse &msg) { | ||||||
|   ESP_LOGVV(TAG, "send_hello_response: %s", msg.dump().c_str()); |   ESP_LOGVV(TAG, "send_hello_response: %s", msg.dump().c_str()); | ||||||
|   this->set_nodelay(true); |  | ||||||
|   return this->send_message_<HelloResponse>(msg, 2); |   return this->send_message_<HelloResponse>(msg, 2); | ||||||
| } | } | ||||||
| bool APIServerConnectionBase::send_connect_response(const ConnectResponse &msg) { | bool APIServerConnectionBase::send_connect_response(const ConnectResponse &msg) { | ||||||
|   ESP_LOGVV(TAG, "send_connect_response: %s", msg.dump().c_str()); |   ESP_LOGVV(TAG, "send_connect_response: %s", msg.dump().c_str()); | ||||||
|   this->set_nodelay(true); |  | ||||||
|   return this->send_message_<ConnectResponse>(msg, 4); |   return this->send_message_<ConnectResponse>(msg, 4); | ||||||
| } | } | ||||||
| bool APIServerConnectionBase::send_disconnect_request(const DisconnectRequest &msg) { | bool APIServerConnectionBase::send_disconnect_request(const DisconnectRequest &msg) { | ||||||
|   ESP_LOGVV(TAG, "send_disconnect_request: %s", msg.dump().c_str()); |   ESP_LOGVV(TAG, "send_disconnect_request: %s", msg.dump().c_str()); | ||||||
|   this->set_nodelay(true); |  | ||||||
|   return this->send_message_<DisconnectRequest>(msg, 5); |   return this->send_message_<DisconnectRequest>(msg, 5); | ||||||
| } | } | ||||||
| bool APIServerConnectionBase::send_disconnect_response(const DisconnectResponse &msg) { | bool APIServerConnectionBase::send_disconnect_response(const DisconnectResponse &msg) { | ||||||
|   ESP_LOGVV(TAG, "send_disconnect_response: %s", msg.dump().c_str()); |   ESP_LOGVV(TAG, "send_disconnect_response: %s", msg.dump().c_str()); | ||||||
|   this->set_nodelay(true); |  | ||||||
|   return this->send_message_<DisconnectResponse>(msg, 6); |   return this->send_message_<DisconnectResponse>(msg, 6); | ||||||
| } | } | ||||||
| bool APIServerConnectionBase::send_ping_request(const PingRequest &msg) { | bool APIServerConnectionBase::send_ping_request(const PingRequest &msg) { | ||||||
|   ESP_LOGVV(TAG, "send_ping_request: %s", msg.dump().c_str()); |   ESP_LOGVV(TAG, "send_ping_request: %s", msg.dump().c_str()); | ||||||
|   this->set_nodelay(false); |  | ||||||
|   return this->send_message_<PingRequest>(msg, 7); |   return this->send_message_<PingRequest>(msg, 7); | ||||||
| } | } | ||||||
| bool APIServerConnectionBase::send_ping_response(const PingResponse &msg) { | bool APIServerConnectionBase::send_ping_response(const PingResponse &msg) { | ||||||
|   ESP_LOGVV(TAG, "send_ping_response: %s", msg.dump().c_str()); |   ESP_LOGVV(TAG, "send_ping_response: %s", msg.dump().c_str()); | ||||||
|   this->set_nodelay(false); |  | ||||||
|   return this->send_message_<PingResponse>(msg, 8); |   return this->send_message_<PingResponse>(msg, 8); | ||||||
| } | } | ||||||
| bool APIServerConnectionBase::send_device_info_response(const DeviceInfoResponse &msg) { | bool APIServerConnectionBase::send_device_info_response(const DeviceInfoResponse &msg) { | ||||||
|   ESP_LOGVV(TAG, "send_device_info_response: %s", msg.dump().c_str()); |   ESP_LOGVV(TAG, "send_device_info_response: %s", msg.dump().c_str()); | ||||||
|   this->set_nodelay(false); |  | ||||||
|   return this->send_message_<DeviceInfoResponse>(msg, 10); |   return this->send_message_<DeviceInfoResponse>(msg, 10); | ||||||
| } | } | ||||||
| bool APIServerConnectionBase::send_list_entities_done_response(const ListEntitiesDoneResponse &msg) { | bool APIServerConnectionBase::send_list_entities_done_response(const ListEntitiesDoneResponse &msg) { | ||||||
|   ESP_LOGVV(TAG, "send_list_entities_done_response: %s", msg.dump().c_str()); |   ESP_LOGVV(TAG, "send_list_entities_done_response: %s", msg.dump().c_str()); | ||||||
|   this->set_nodelay(true); |  | ||||||
|   return this->send_message_<ListEntitiesDoneResponse>(msg, 19); |   return this->send_message_<ListEntitiesDoneResponse>(msg, 19); | ||||||
| } | } | ||||||
| #ifdef USE_BINARY_SENSOR | #ifdef USE_BINARY_SENSOR | ||||||
| bool APIServerConnectionBase::send_list_entities_binary_sensor_response(const ListEntitiesBinarySensorResponse &msg) { | bool APIServerConnectionBase::send_list_entities_binary_sensor_response(const ListEntitiesBinarySensorResponse &msg) { | ||||||
|   ESP_LOGVV(TAG, "send_list_entities_binary_sensor_response: %s", msg.dump().c_str()); |   ESP_LOGVV(TAG, "send_list_entities_binary_sensor_response: %s", msg.dump().c_str()); | ||||||
|   this->set_nodelay(false); |  | ||||||
|   return this->send_message_<ListEntitiesBinarySensorResponse>(msg, 12); |   return this->send_message_<ListEntitiesBinarySensorResponse>(msg, 12); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| #ifdef USE_BINARY_SENSOR | #ifdef USE_BINARY_SENSOR | ||||||
| bool APIServerConnectionBase::send_binary_sensor_state_response(const BinarySensorStateResponse &msg) { | bool APIServerConnectionBase::send_binary_sensor_state_response(const BinarySensorStateResponse &msg) { | ||||||
|   ESP_LOGVV(TAG, "send_binary_sensor_state_response: %s", msg.dump().c_str()); |   ESP_LOGVV(TAG, "send_binary_sensor_state_response: %s", msg.dump().c_str()); | ||||||
|   this->set_nodelay(true); |  | ||||||
|   return this->send_message_<BinarySensorStateResponse>(msg, 21); |   return this->send_message_<BinarySensorStateResponse>(msg, 21); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| #ifdef USE_COVER | #ifdef USE_COVER | ||||||
| bool APIServerConnectionBase::send_list_entities_cover_response(const ListEntitiesCoverResponse &msg) { | bool APIServerConnectionBase::send_list_entities_cover_response(const ListEntitiesCoverResponse &msg) { | ||||||
|   ESP_LOGVV(TAG, "send_list_entities_cover_response: %s", msg.dump().c_str()); |   ESP_LOGVV(TAG, "send_list_entities_cover_response: %s", msg.dump().c_str()); | ||||||
|   this->set_nodelay(false); |  | ||||||
|   return this->send_message_<ListEntitiesCoverResponse>(msg, 13); |   return this->send_message_<ListEntitiesCoverResponse>(msg, 13); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| #ifdef USE_COVER | #ifdef USE_COVER | ||||||
| bool APIServerConnectionBase::send_cover_state_response(const CoverStateResponse &msg) { | bool APIServerConnectionBase::send_cover_state_response(const CoverStateResponse &msg) { | ||||||
|   ESP_LOGVV(TAG, "send_cover_state_response: %s", msg.dump().c_str()); |   ESP_LOGVV(TAG, "send_cover_state_response: %s", msg.dump().c_str()); | ||||||
|   this->set_nodelay(true); |  | ||||||
|   return this->send_message_<CoverStateResponse>(msg, 22); |   return this->send_message_<CoverStateResponse>(msg, 22); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| @@ -79,14 +69,12 @@ bool APIServerConnectionBase::send_cover_state_response(const CoverStateResponse | |||||||
| #ifdef USE_FAN | #ifdef USE_FAN | ||||||
| bool APIServerConnectionBase::send_list_entities_fan_response(const ListEntitiesFanResponse &msg) { | bool APIServerConnectionBase::send_list_entities_fan_response(const ListEntitiesFanResponse &msg) { | ||||||
|   ESP_LOGVV(TAG, "send_list_entities_fan_response: %s", msg.dump().c_str()); |   ESP_LOGVV(TAG, "send_list_entities_fan_response: %s", msg.dump().c_str()); | ||||||
|   this->set_nodelay(false); |  | ||||||
|   return this->send_message_<ListEntitiesFanResponse>(msg, 14); |   return this->send_message_<ListEntitiesFanResponse>(msg, 14); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| #ifdef USE_FAN | #ifdef USE_FAN | ||||||
| bool APIServerConnectionBase::send_fan_state_response(const FanStateResponse &msg) { | bool APIServerConnectionBase::send_fan_state_response(const FanStateResponse &msg) { | ||||||
|   ESP_LOGVV(TAG, "send_fan_state_response: %s", msg.dump().c_str()); |   ESP_LOGVV(TAG, "send_fan_state_response: %s", msg.dump().c_str()); | ||||||
|   this->set_nodelay(true); |  | ||||||
|   return this->send_message_<FanStateResponse>(msg, 23); |   return this->send_message_<FanStateResponse>(msg, 23); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| @@ -95,14 +83,12 @@ bool APIServerConnectionBase::send_fan_state_response(const FanStateResponse &ms | |||||||
| #ifdef USE_LIGHT | #ifdef USE_LIGHT | ||||||
| bool APIServerConnectionBase::send_list_entities_light_response(const ListEntitiesLightResponse &msg) { | bool APIServerConnectionBase::send_list_entities_light_response(const ListEntitiesLightResponse &msg) { | ||||||
|   ESP_LOGVV(TAG, "send_list_entities_light_response: %s", msg.dump().c_str()); |   ESP_LOGVV(TAG, "send_list_entities_light_response: %s", msg.dump().c_str()); | ||||||
|   this->set_nodelay(false); |  | ||||||
|   return this->send_message_<ListEntitiesLightResponse>(msg, 15); |   return this->send_message_<ListEntitiesLightResponse>(msg, 15); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| #ifdef USE_LIGHT | #ifdef USE_LIGHT | ||||||
| bool APIServerConnectionBase::send_light_state_response(const LightStateResponse &msg) { | bool APIServerConnectionBase::send_light_state_response(const LightStateResponse &msg) { | ||||||
|   ESP_LOGVV(TAG, "send_light_state_response: %s", msg.dump().c_str()); |   ESP_LOGVV(TAG, "send_light_state_response: %s", msg.dump().c_str()); | ||||||
|   this->set_nodelay(true); |  | ||||||
|   return this->send_message_<LightStateResponse>(msg, 24); |   return this->send_message_<LightStateResponse>(msg, 24); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| @@ -111,28 +97,24 @@ bool APIServerConnectionBase::send_light_state_response(const LightStateResponse | |||||||
| #ifdef USE_SENSOR | #ifdef USE_SENSOR | ||||||
| bool APIServerConnectionBase::send_list_entities_sensor_response(const ListEntitiesSensorResponse &msg) { | bool APIServerConnectionBase::send_list_entities_sensor_response(const ListEntitiesSensorResponse &msg) { | ||||||
|   ESP_LOGVV(TAG, "send_list_entities_sensor_response: %s", msg.dump().c_str()); |   ESP_LOGVV(TAG, "send_list_entities_sensor_response: %s", msg.dump().c_str()); | ||||||
|   this->set_nodelay(false); |  | ||||||
|   return this->send_message_<ListEntitiesSensorResponse>(msg, 16); |   return this->send_message_<ListEntitiesSensorResponse>(msg, 16); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| #ifdef USE_SENSOR | #ifdef USE_SENSOR | ||||||
| bool APIServerConnectionBase::send_sensor_state_response(const SensorStateResponse &msg) { | bool APIServerConnectionBase::send_sensor_state_response(const SensorStateResponse &msg) { | ||||||
|   ESP_LOGVV(TAG, "send_sensor_state_response: %s", msg.dump().c_str()); |   ESP_LOGVV(TAG, "send_sensor_state_response: %s", msg.dump().c_str()); | ||||||
|   this->set_nodelay(true); |  | ||||||
|   return this->send_message_<SensorStateResponse>(msg, 25); |   return this->send_message_<SensorStateResponse>(msg, 25); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| #ifdef USE_SWITCH | #ifdef USE_SWITCH | ||||||
| bool APIServerConnectionBase::send_list_entities_switch_response(const ListEntitiesSwitchResponse &msg) { | bool APIServerConnectionBase::send_list_entities_switch_response(const ListEntitiesSwitchResponse &msg) { | ||||||
|   ESP_LOGVV(TAG, "send_list_entities_switch_response: %s", msg.dump().c_str()); |   ESP_LOGVV(TAG, "send_list_entities_switch_response: %s", msg.dump().c_str()); | ||||||
|   this->set_nodelay(false); |  | ||||||
|   return this->send_message_<ListEntitiesSwitchResponse>(msg, 17); |   return this->send_message_<ListEntitiesSwitchResponse>(msg, 17); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| #ifdef USE_SWITCH | #ifdef USE_SWITCH | ||||||
| bool APIServerConnectionBase::send_switch_state_response(const SwitchStateResponse &msg) { | bool APIServerConnectionBase::send_switch_state_response(const SwitchStateResponse &msg) { | ||||||
|   ESP_LOGVV(TAG, "send_switch_state_response: %s", msg.dump().c_str()); |   ESP_LOGVV(TAG, "send_switch_state_response: %s", msg.dump().c_str()); | ||||||
|   this->set_nodelay(true); |  | ||||||
|   return this->send_message_<SwitchStateResponse>(msg, 26); |   return this->send_message_<SwitchStateResponse>(msg, 26); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| @@ -141,58 +123,48 @@ bool APIServerConnectionBase::send_switch_state_response(const SwitchStateRespon | |||||||
| #ifdef USE_TEXT_SENSOR | #ifdef USE_TEXT_SENSOR | ||||||
| bool APIServerConnectionBase::send_list_entities_text_sensor_response(const ListEntitiesTextSensorResponse &msg) { | bool APIServerConnectionBase::send_list_entities_text_sensor_response(const ListEntitiesTextSensorResponse &msg) { | ||||||
|   ESP_LOGVV(TAG, "send_list_entities_text_sensor_response: %s", msg.dump().c_str()); |   ESP_LOGVV(TAG, "send_list_entities_text_sensor_response: %s", msg.dump().c_str()); | ||||||
|   this->set_nodelay(false); |  | ||||||
|   return this->send_message_<ListEntitiesTextSensorResponse>(msg, 18); |   return this->send_message_<ListEntitiesTextSensorResponse>(msg, 18); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| #ifdef USE_TEXT_SENSOR | #ifdef USE_TEXT_SENSOR | ||||||
| bool APIServerConnectionBase::send_text_sensor_state_response(const TextSensorStateResponse &msg) { | bool APIServerConnectionBase::send_text_sensor_state_response(const TextSensorStateResponse &msg) { | ||||||
|   ESP_LOGVV(TAG, "send_text_sensor_state_response: %s", msg.dump().c_str()); |   ESP_LOGVV(TAG, "send_text_sensor_state_response: %s", msg.dump().c_str()); | ||||||
|   this->set_nodelay(true); |  | ||||||
|   return this->send_message_<TextSensorStateResponse>(msg, 27); |   return this->send_message_<TextSensorStateResponse>(msg, 27); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| bool APIServerConnectionBase::send_subscribe_logs_response(const SubscribeLogsResponse &msg) { | bool APIServerConnectionBase::send_subscribe_logs_response(const SubscribeLogsResponse &msg) { | ||||||
|   this->set_nodelay(false); |  | ||||||
|   return this->send_message_<SubscribeLogsResponse>(msg, 29); |   return this->send_message_<SubscribeLogsResponse>(msg, 29); | ||||||
| } | } | ||||||
| bool APIServerConnectionBase::send_homeassistant_service_response(const HomeassistantServiceResponse &msg) { | bool APIServerConnectionBase::send_homeassistant_service_response(const HomeassistantServiceResponse &msg) { | ||||||
|   ESP_LOGVV(TAG, "send_homeassistant_service_response: %s", msg.dump().c_str()); |   ESP_LOGVV(TAG, "send_homeassistant_service_response: %s", msg.dump().c_str()); | ||||||
|   this->set_nodelay(true); |  | ||||||
|   return this->send_message_<HomeassistantServiceResponse>(msg, 35); |   return this->send_message_<HomeassistantServiceResponse>(msg, 35); | ||||||
| } | } | ||||||
| bool APIServerConnectionBase::send_subscribe_home_assistant_state_response( | bool APIServerConnectionBase::send_subscribe_home_assistant_state_response( | ||||||
|     const SubscribeHomeAssistantStateResponse &msg) { |     const SubscribeHomeAssistantStateResponse &msg) { | ||||||
|   ESP_LOGVV(TAG, "send_subscribe_home_assistant_state_response: %s", msg.dump().c_str()); |   ESP_LOGVV(TAG, "send_subscribe_home_assistant_state_response: %s", msg.dump().c_str()); | ||||||
|   this->set_nodelay(false); |  | ||||||
|   return this->send_message_<SubscribeHomeAssistantStateResponse>(msg, 39); |   return this->send_message_<SubscribeHomeAssistantStateResponse>(msg, 39); | ||||||
| } | } | ||||||
| bool APIServerConnectionBase::send_get_time_request(const GetTimeRequest &msg) { | bool APIServerConnectionBase::send_get_time_request(const GetTimeRequest &msg) { | ||||||
|   ESP_LOGVV(TAG, "send_get_time_request: %s", msg.dump().c_str()); |   ESP_LOGVV(TAG, "send_get_time_request: %s", msg.dump().c_str()); | ||||||
|   this->set_nodelay(false); |  | ||||||
|   return this->send_message_<GetTimeRequest>(msg, 36); |   return this->send_message_<GetTimeRequest>(msg, 36); | ||||||
| } | } | ||||||
| bool APIServerConnectionBase::send_get_time_response(const GetTimeResponse &msg) { | bool APIServerConnectionBase::send_get_time_response(const GetTimeResponse &msg) { | ||||||
|   ESP_LOGVV(TAG, "send_get_time_response: %s", msg.dump().c_str()); |   ESP_LOGVV(TAG, "send_get_time_response: %s", msg.dump().c_str()); | ||||||
|   this->set_nodelay(true); |  | ||||||
|   return this->send_message_<GetTimeResponse>(msg, 37); |   return this->send_message_<GetTimeResponse>(msg, 37); | ||||||
| } | } | ||||||
| bool APIServerConnectionBase::send_list_entities_services_response(const ListEntitiesServicesResponse &msg) { | bool APIServerConnectionBase::send_list_entities_services_response(const ListEntitiesServicesResponse &msg) { | ||||||
|   ESP_LOGVV(TAG, "send_list_entities_services_response: %s", msg.dump().c_str()); |   ESP_LOGVV(TAG, "send_list_entities_services_response: %s", msg.dump().c_str()); | ||||||
|   this->set_nodelay(false); |  | ||||||
|   return this->send_message_<ListEntitiesServicesResponse>(msg, 41); |   return this->send_message_<ListEntitiesServicesResponse>(msg, 41); | ||||||
| } | } | ||||||
| #ifdef USE_ESP32_CAMERA | #ifdef USE_ESP32_CAMERA | ||||||
| bool APIServerConnectionBase::send_list_entities_camera_response(const ListEntitiesCameraResponse &msg) { | bool APIServerConnectionBase::send_list_entities_camera_response(const ListEntitiesCameraResponse &msg) { | ||||||
|   ESP_LOGVV(TAG, "send_list_entities_camera_response: %s", msg.dump().c_str()); |   ESP_LOGVV(TAG, "send_list_entities_camera_response: %s", msg.dump().c_str()); | ||||||
|   this->set_nodelay(false); |  | ||||||
|   return this->send_message_<ListEntitiesCameraResponse>(msg, 43); |   return this->send_message_<ListEntitiesCameraResponse>(msg, 43); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| #ifdef USE_ESP32_CAMERA | #ifdef USE_ESP32_CAMERA | ||||||
| bool APIServerConnectionBase::send_camera_image_response(const CameraImageResponse &msg) { | bool APIServerConnectionBase::send_camera_image_response(const CameraImageResponse &msg) { | ||||||
|   ESP_LOGVV(TAG, "send_camera_image_response: %s", msg.dump().c_str()); |   ESP_LOGVV(TAG, "send_camera_image_response: %s", msg.dump().c_str()); | ||||||
|   this->set_nodelay(false); |  | ||||||
|   return this->send_message_<CameraImageResponse>(msg, 44); |   return this->send_message_<CameraImageResponse>(msg, 44); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| @@ -201,14 +173,12 @@ bool APIServerConnectionBase::send_camera_image_response(const CameraImageRespon | |||||||
| #ifdef USE_CLIMATE | #ifdef USE_CLIMATE | ||||||
| bool APIServerConnectionBase::send_list_entities_climate_response(const ListEntitiesClimateResponse &msg) { | bool APIServerConnectionBase::send_list_entities_climate_response(const ListEntitiesClimateResponse &msg) { | ||||||
|   ESP_LOGVV(TAG, "send_list_entities_climate_response: %s", msg.dump().c_str()); |   ESP_LOGVV(TAG, "send_list_entities_climate_response: %s", msg.dump().c_str()); | ||||||
|   this->set_nodelay(false); |  | ||||||
|   return this->send_message_<ListEntitiesClimateResponse>(msg, 46); |   return this->send_message_<ListEntitiesClimateResponse>(msg, 46); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| #ifdef USE_CLIMATE | #ifdef USE_CLIMATE | ||||||
| bool APIServerConnectionBase::send_climate_state_response(const ClimateStateResponse &msg) { | bool APIServerConnectionBase::send_climate_state_response(const ClimateStateResponse &msg) { | ||||||
|   ESP_LOGVV(TAG, "send_climate_state_response: %s", msg.dump().c_str()); |   ESP_LOGVV(TAG, "send_climate_state_response: %s", msg.dump().c_str()); | ||||||
|   this->set_nodelay(true); |  | ||||||
|   return this->send_message_<ClimateStateResponse>(msg, 47); |   return this->send_message_<ClimateStateResponse>(msg, 47); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
|   | |||||||
| @@ -1,3 +1,5 @@ | |||||||
|  | // This file was automatically generated with a tool. | ||||||
|  | // See scripts/api_protobuf/api_protobuf.py | ||||||
| #pragma once | #pragma once | ||||||
|  |  | ||||||
| #include "api_pb2.h" | #include "api_pb2.h" | ||||||
|   | |||||||
| @@ -29,6 +29,7 @@ template<typename... Ts> class HomeAssistantServiceCallAction : public Action<Ts | |||||||
|   template<typename T> void add_variable(std::string key, T value) { |   template<typename T> void add_variable(std::string key, T value) { | ||||||
|     this->variables_.push_back(TemplatableKeyValuePair<Ts...>(key, value)); |     this->variables_.push_back(TemplatableKeyValuePair<Ts...>(key, value)); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   void play(Ts... x) override { |   void play(Ts... x) override { | ||||||
|     HomeassistantServiceResponse resp; |     HomeassistantServiceResponse resp; | ||||||
|     resp.service = this->service_.value(x...); |     resp.service = this->service_.value(x...); | ||||||
|   | |||||||
| @@ -266,7 +266,6 @@ class ProtoService { | |||||||
|   virtual ProtoWriteBuffer create_buffer() = 0; |   virtual ProtoWriteBuffer create_buffer() = 0; | ||||||
|   virtual bool send_buffer(ProtoWriteBuffer buffer, uint32_t message_type) = 0; |   virtual bool send_buffer(ProtoWriteBuffer buffer, uint32_t message_type) = 0; | ||||||
|   virtual bool read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) = 0; |   virtual bool read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) = 0; | ||||||
|   virtual void set_nodelay(bool nodelay) = 0; |  | ||||||
|  |  | ||||||
|   template<class C> bool send_message_(const C &msg, uint32_t message_type) { |   template<class C> bool send_message_(const C &msg, uint32_t message_type) { | ||||||
|     auto buffer = this->create_buffer(); |     auto buffer = this->create_buffer(); | ||||||
|   | |||||||
| @@ -25,7 +25,7 @@ AS3935_SCHEMA = cv.Schema({ | |||||||
|     cv.Optional(CONF_SPIKE_REJECTION, default=2): cv.int_range(min=1, max=11), |     cv.Optional(CONF_SPIKE_REJECTION, default=2): cv.int_range(min=1, max=11), | ||||||
|     cv.Optional(CONF_LIGHTNING_THRESHOLD, default=1): cv.one_of(1, 5, 9, 16, int=True), |     cv.Optional(CONF_LIGHTNING_THRESHOLD, default=1): cv.one_of(1, 5, 9, 16, int=True), | ||||||
|     cv.Optional(CONF_MASK_DISTURBER, default=False): cv.boolean, |     cv.Optional(CONF_MASK_DISTURBER, default=False): cv.boolean, | ||||||
|     cv.Optional(CONF_DIV_RATIO, default=0): cv.one_of(0, 16, 22, 64, 128, int=True), |     cv.Optional(CONF_DIV_RATIO, default=0): cv.one_of(0, 16, 32, 64, 128, int=True), | ||||||
|     cv.Optional(CONF_CAPACITANCE, default=0): cv.int_range(min=0, max=15), |     cv.Optional(CONF_CAPACITANCE, default=0): cv.int_range(min=0, max=15), | ||||||
| }) | }) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -26,6 +26,9 @@ void AS3935Component::setup() { | |||||||
| void AS3935Component::dump_config() { | void AS3935Component::dump_config() { | ||||||
|   ESP_LOGCONFIG(TAG, "AS3935:"); |   ESP_LOGCONFIG(TAG, "AS3935:"); | ||||||
|   LOG_PIN("  Interrupt Pin: ", this->irq_pin_); |   LOG_PIN("  Interrupt Pin: ", this->irq_pin_); | ||||||
|  |   LOG_BINARY_SENSOR("  ", "Thunder alert", this->thunder_alert_binary_sensor_); | ||||||
|  |   LOG_SENSOR("  ", "Distance", this->distance_sensor_); | ||||||
|  |   LOG_SENSOR("  ", "Lightning energy", this->energy_sensor_); | ||||||
| } | } | ||||||
|  |  | ||||||
| float AS3935Component::get_setup_priority() const { return setup_priority::DATA; } | float AS3935Component::get_setup_priority() const { return setup_priority::DATA; } | ||||||
|   | |||||||
| @@ -27,4 +27,4 @@ def to_code(config): | |||||||
|     if CONF_LIGHTNING_ENERGY in config: |     if CONF_LIGHTNING_ENERGY in config: | ||||||
|         conf = config[CONF_LIGHTNING_ENERGY] |         conf = config[CONF_LIGHTNING_ENERGY] | ||||||
|         lightning_energy_sensor = yield sensor.new_sensor(conf) |         lightning_energy_sensor = yield sensor.new_sensor(conf) | ||||||
|         cg.add(hub.set_distance_sensor(lightning_energy_sensor)) |         cg.add(hub.set_energy_sensor(lightning_energy_sensor)) | ||||||
|   | |||||||
| @@ -10,8 +10,8 @@ as3935_spi_ns = cg.esphome_ns.namespace('as3935_spi') | |||||||
| SPIAS3935 = as3935_spi_ns.class_('SPIAS3935Component', as3935.AS3935, spi.SPIDevice) | SPIAS3935 = as3935_spi_ns.class_('SPIAS3935Component', as3935.AS3935, spi.SPIDevice) | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = cv.All(as3935.AS3935_SCHEMA.extend({ | CONFIG_SCHEMA = cv.All(as3935.AS3935_SCHEMA.extend({ | ||||||
|     cv.GenerateID(): cv.declare_id(SPIAS3935) |     cv.GenerateID(): cv.declare_id(SPIAS3935), | ||||||
| }).extend(cv.COMPONENT_SCHEMA).extend(spi.SPI_DEVICE_SCHEMA)) | }).extend(cv.COMPONENT_SCHEMA).extend(spi.spi_device_schema(cs_pin_required=True))) | ||||||
|  |  | ||||||
|  |  | ||||||
| def to_code(config): | def to_code(config): | ||||||
|   | |||||||
| @@ -2,6 +2,8 @@ | |||||||
| import esphome.codegen as cg | import esphome.codegen as cg | ||||||
| from esphome.core import CORE, coroutine_with_priority | from esphome.core import CORE, coroutine_with_priority | ||||||
|  |  | ||||||
|  | CODEOWNERS = ['@OttoWinter'] | ||||||
|  |  | ||||||
|  |  | ||||||
| @coroutine_with_priority(200.0) | @coroutine_with_priority(200.0) | ||||||
| def to_code(config): | def to_code(config): | ||||||
| @@ -10,4 +12,4 @@ def to_code(config): | |||||||
|         cg.add_library('AsyncTCP-esphome', '1.1.1') |         cg.add_library('AsyncTCP-esphome', '1.1.1') | ||||||
|     elif CORE.is_esp8266: |     elif CORE.is_esp8266: | ||||||
|         # https://github.com/OttoWinter/ESPAsyncTCP |         # https://github.com/OttoWinter/ESPAsyncTCP | ||||||
|         cg.add_library('ESPAsyncTCP-esphome', '1.2.2') |         cg.add_library('ESPAsyncTCP-esphome', '1.2.3') | ||||||
|   | |||||||
| @@ -40,19 +40,45 @@ void ATM90E32Component::update() { | |||||||
|   if (this->phase_[2].power_sensor_ != nullptr) { |   if (this->phase_[2].power_sensor_ != nullptr) { | ||||||
|     this->phase_[2].power_sensor_->publish_state(this->get_active_power_c_()); |     this->phase_[2].power_sensor_->publish_state(this->get_active_power_c_()); | ||||||
|   } |   } | ||||||
|  |   if (this->phase_[0].reactive_power_sensor_ != nullptr) { | ||||||
|  |     this->phase_[0].reactive_power_sensor_->publish_state(this->get_reactive_power_a_()); | ||||||
|  |   } | ||||||
|  |   if (this->phase_[1].reactive_power_sensor_ != nullptr) { | ||||||
|  |     this->phase_[1].reactive_power_sensor_->publish_state(this->get_reactive_power_b_()); | ||||||
|  |   } | ||||||
|  |   if (this->phase_[2].reactive_power_sensor_ != nullptr) { | ||||||
|  |     this->phase_[2].reactive_power_sensor_->publish_state(this->get_reactive_power_c_()); | ||||||
|  |   } | ||||||
|  |   if (this->phase_[0].power_factor_sensor_ != nullptr) { | ||||||
|  |     this->phase_[0].power_factor_sensor_->publish_state(this->get_power_factor_a_()); | ||||||
|  |   } | ||||||
|  |   if (this->phase_[1].power_factor_sensor_ != nullptr) { | ||||||
|  |     this->phase_[1].power_factor_sensor_->publish_state(this->get_power_factor_b_()); | ||||||
|  |   } | ||||||
|  |   if (this->phase_[2].power_factor_sensor_ != nullptr) { | ||||||
|  |     this->phase_[2].power_factor_sensor_->publish_state(this->get_power_factor_c_()); | ||||||
|  |   } | ||||||
|   if (this->freq_sensor_ != nullptr) { |   if (this->freq_sensor_ != nullptr) { | ||||||
|     this->freq_sensor_->publish_state(this->get_frequency_()); |     this->freq_sensor_->publish_state(this->get_frequency_()); | ||||||
|   } |   } | ||||||
|  |   if (this->chip_temperature_sensor_ != nullptr) { | ||||||
|  |     this->chip_temperature_sensor_->publish_state(this->get_chip_temperature_()); | ||||||
|  |   } | ||||||
|   this->status_clear_warning(); |   this->status_clear_warning(); | ||||||
| } | } | ||||||
|  |  | ||||||
| void ATM90E32Component::setup() { | void ATM90E32Component::setup() { | ||||||
|   ESP_LOGCONFIG(TAG, "Setting up ATM90E32Component..."); |   ESP_LOGCONFIG(TAG, "Setting up ATM90E32 Component..."); | ||||||
|   this->spi_setup(); |   this->spi_setup(); | ||||||
|  |  | ||||||
|   uint16_t mmode0 = 0x185; |   uint16_t mmode0 = 0x87;  // 3P4W 50Hz | ||||||
|   if (line_freq_ == 60) { |   if (line_freq_ == 60) { | ||||||
|     mmode0 |= 1 << 12; |     mmode0 |= 1 << 12;  // sets 12th bit to 1, 60Hz | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (current_phases_ == 2) { | ||||||
|  |     mmode0 |= 1 << 8;  // sets 8th bit to 1, 3P3W | ||||||
|  |     mmode0 |= 0 << 1;  // sets 1st bit to 0, phase b is not counted into the all-phase sum energy/power (P/Q/S) | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   this->write16_(ATM90E32_REGISTER_SOFTRESET, 0x789A);    // Perform soft reset |   this->write16_(ATM90E32_REGISTER_SOFTRESET, 0x789A);    // Perform soft reset | ||||||
| @@ -63,13 +89,15 @@ void ATM90E32Component::setup() { | |||||||
|     this->mark_failed(); |     this->mark_failed(); | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|  |   this->write16_(ATM90E32_REGISTER_PLCONSTH, 0x0861);   // PL Constant MSB (default) = 140625000 | ||||||
|   this->write16_(ATM90E32_REGISTER_ZXCONFIG, 0x0A55);                    // ZX2, ZX1, ZX0 pin config |   this->write16_(ATM90E32_REGISTER_PLCONSTL, 0xC468);   // PL Constant LSB (default) | ||||||
|   this->write16_(ATM90E32_REGISTER_MMODE0, mmode0);                      // Mode Config (frequency set in main program) |   this->write16_(ATM90E32_REGISTER_ZXCONFIG, 0xD654);   // ZX2, ZX1, ZX0 pin config | ||||||
|   this->write16_(ATM90E32_REGISTER_MMODE1, pga_gain_);                   // PGA Gain Configuration for Current Channels |   this->write16_(ATM90E32_REGISTER_MMODE0, mmode0);     // Mode Config (frequency set in main program) | ||||||
|   this->write16_(ATM90E32_REGISTER_PSTARTTH, 0x0AFC);                    // Active Startup Power Threshold = 50% |   this->write16_(ATM90E32_REGISTER_MMODE1, pga_gain_);  // PGA Gain Configuration for Current Channels | ||||||
|   this->write16_(ATM90E32_REGISTER_QSTARTTH, 0x0AEC);                    // Reactive Startup Power Threshold = 50% |   this->write16_(ATM90E32_REGISTER_PSTARTTH, 0x1D4C);   // All Active Startup Power Threshold - 0.02A/0.00032 = 7500 | ||||||
|   this->write16_(ATM90E32_REGISTER_PPHASETH, 0x00BC);                    // Active Phase Threshold = 10% |   this->write16_(ATM90E32_REGISTER_QSTARTTH, 0x1D4C);   // All Reactive Startup Power Threshold - 50% | ||||||
|  |   this->write16_(ATM90E32_REGISTER_PPHASETH, 0x02EE);   // Each Phase Active Phase Threshold - 0.002A/0.00032 = 750 | ||||||
|  |   this->write16_(ATM90E32_REGISTER_QPHASETH, 0x02EE);   // Each phase Reactive Phase Threshold - 10% | ||||||
|   this->write16_(ATM90E32_REGISTER_UGAINA, this->phase_[0].volt_gain_);  // A Voltage rms gain |   this->write16_(ATM90E32_REGISTER_UGAINA, this->phase_[0].volt_gain_);  // A Voltage rms gain | ||||||
|   this->write16_(ATM90E32_REGISTER_IGAINA, this->phase_[0].ct_gain_);    // A line current gain |   this->write16_(ATM90E32_REGISTER_IGAINA, this->phase_[0].ct_gain_);    // A line current gain | ||||||
|   this->write16_(ATM90E32_REGISTER_UGAINB, this->phase_[1].volt_gain_);  // B Voltage rms gain |   this->write16_(ATM90E32_REGISTER_UGAINB, this->phase_[1].volt_gain_);  // B Voltage rms gain | ||||||
| @@ -89,13 +117,20 @@ void ATM90E32Component::dump_config() { | |||||||
|   LOG_SENSOR("  ", "Voltage A", this->phase_[0].voltage_sensor_); |   LOG_SENSOR("  ", "Voltage A", this->phase_[0].voltage_sensor_); | ||||||
|   LOG_SENSOR("  ", "Current A", this->phase_[0].current_sensor_); |   LOG_SENSOR("  ", "Current A", this->phase_[0].current_sensor_); | ||||||
|   LOG_SENSOR("  ", "Power A", this->phase_[0].power_sensor_); |   LOG_SENSOR("  ", "Power A", this->phase_[0].power_sensor_); | ||||||
|  |   LOG_SENSOR("  ", "Reactive Power A", this->phase_[0].reactive_power_sensor_); | ||||||
|  |   LOG_SENSOR("  ", "PF A", this->phase_[0].power_factor_sensor_); | ||||||
|   LOG_SENSOR("  ", "Voltage B", this->phase_[1].voltage_sensor_); |   LOG_SENSOR("  ", "Voltage B", this->phase_[1].voltage_sensor_); | ||||||
|   LOG_SENSOR("  ", "Current B", this->phase_[1].current_sensor_); |   LOG_SENSOR("  ", "Current B", this->phase_[1].current_sensor_); | ||||||
|   LOG_SENSOR("  ", "Power B", this->phase_[1].power_sensor_); |   LOG_SENSOR("  ", "Power B", this->phase_[1].power_sensor_); | ||||||
|  |   LOG_SENSOR("  ", "Reactive Power B", this->phase_[1].reactive_power_sensor_); | ||||||
|  |   LOG_SENSOR("  ", "PF B", this->phase_[1].power_factor_sensor_); | ||||||
|   LOG_SENSOR("  ", "Voltage C", this->phase_[2].voltage_sensor_); |   LOG_SENSOR("  ", "Voltage C", this->phase_[2].voltage_sensor_); | ||||||
|   LOG_SENSOR("  ", "Current C", this->phase_[2].current_sensor_); |   LOG_SENSOR("  ", "Current C", this->phase_[2].current_sensor_); | ||||||
|   LOG_SENSOR("  ", "Power C", this->phase_[2].power_sensor_); |   LOG_SENSOR("  ", "Power C", this->phase_[2].power_sensor_); | ||||||
|   LOG_SENSOR("  ", "Frequency", this->freq_sensor_) |   LOG_SENSOR("  ", "Reactive Power C", this->phase_[2].reactive_power_sensor_); | ||||||
|  |   LOG_SENSOR("  ", "PF C", this->phase_[2].power_factor_sensor_); | ||||||
|  |   LOG_SENSOR("  ", "Frequency", this->freq_sensor_); | ||||||
|  |   LOG_SENSOR("  ", "Chip Temp", this->chip_temperature_sensor_); | ||||||
| } | } | ||||||
| float ATM90E32Component::get_setup_priority() const { return setup_priority::DATA; } | float ATM90E32Component::get_setup_priority() const { return setup_priority::DATA; } | ||||||
|  |  | ||||||
| @@ -180,9 +215,37 @@ float ATM90E32Component::get_active_power_c_() { | |||||||
|   int val = this->read32_(ATM90E32_REGISTER_PMEANC, ATM90E32_REGISTER_PMEANCLSB); |   int val = this->read32_(ATM90E32_REGISTER_PMEANC, ATM90E32_REGISTER_PMEANCLSB); | ||||||
|   return val * 0.00032f; |   return val * 0.00032f; | ||||||
| } | } | ||||||
|  | float ATM90E32Component::get_reactive_power_a_() { | ||||||
|  |   int val = this->read32_(ATM90E32_REGISTER_QMEANA, ATM90E32_REGISTER_QMEANALSB); | ||||||
|  |   return val * 0.00032f; | ||||||
|  | } | ||||||
|  | float ATM90E32Component::get_reactive_power_b_() { | ||||||
|  |   int val = this->read32_(ATM90E32_REGISTER_QMEANB, ATM90E32_REGISTER_QMEANBLSB); | ||||||
|  |   return val * 0.00032f; | ||||||
|  | } | ||||||
|  | float ATM90E32Component::get_reactive_power_c_() { | ||||||
|  |   int val = this->read32_(ATM90E32_REGISTER_QMEANC, ATM90E32_REGISTER_QMEANCLSB); | ||||||
|  |   return val * 0.00032f; | ||||||
|  | } | ||||||
|  | float ATM90E32Component::get_power_factor_a_() { | ||||||
|  |   int16_t pf = this->read16_(ATM90E32_REGISTER_PFMEANA); | ||||||
|  |   return (float) pf / 1000; | ||||||
|  | } | ||||||
|  | float ATM90E32Component::get_power_factor_b_() { | ||||||
|  |   int16_t pf = this->read16_(ATM90E32_REGISTER_PFMEANB); | ||||||
|  |   return (float) pf / 1000; | ||||||
|  | } | ||||||
|  | float ATM90E32Component::get_power_factor_c_() { | ||||||
|  |   int16_t pf = this->read16_(ATM90E32_REGISTER_PFMEANC); | ||||||
|  |   return (float) pf / 1000; | ||||||
|  | } | ||||||
| float ATM90E32Component::get_frequency_() { | float ATM90E32Component::get_frequency_() { | ||||||
|   uint16_t freq = this->read16_(ATM90E32_REGISTER_FREQ); |   uint16_t freq = this->read16_(ATM90E32_REGISTER_FREQ); | ||||||
|   return (float) freq / 100; |   return (float) freq / 100; | ||||||
| } | } | ||||||
|  | float ATM90E32Component::get_chip_temperature_() { | ||||||
|  |   uint16_t ctemp = this->read16_(ATM90E32_REGISTER_TEMP); | ||||||
|  |   return (float) ctemp; | ||||||
|  | } | ||||||
| }  // namespace atm90e32 | }  // namespace atm90e32 | ||||||
| }  // namespace esphome | }  // namespace esphome | ||||||
|   | |||||||
| @@ -19,11 +19,17 @@ class ATM90E32Component : public PollingComponent, | |||||||
|   void set_voltage_sensor(int phase, sensor::Sensor *obj) { this->phase_[phase].voltage_sensor_ = obj; } |   void set_voltage_sensor(int phase, sensor::Sensor *obj) { this->phase_[phase].voltage_sensor_ = obj; } | ||||||
|   void set_current_sensor(int phase, sensor::Sensor *obj) { this->phase_[phase].current_sensor_ = obj; } |   void set_current_sensor(int phase, sensor::Sensor *obj) { this->phase_[phase].current_sensor_ = obj; } | ||||||
|   void set_power_sensor(int phase, sensor::Sensor *obj) { this->phase_[phase].power_sensor_ = obj; } |   void set_power_sensor(int phase, sensor::Sensor *obj) { this->phase_[phase].power_sensor_ = obj; } | ||||||
|  |   void set_reactive_power_sensor(int phase, sensor::Sensor *obj) { this->phase_[phase].reactive_power_sensor_ = obj; } | ||||||
|  |   void set_power_factor_sensor(int phase, sensor::Sensor *obj) { this->phase_[phase].power_factor_sensor_ = obj; } | ||||||
|   void set_volt_gain(int phase, uint16_t gain) { this->phase_[phase].volt_gain_ = gain; } |   void set_volt_gain(int phase, uint16_t gain) { this->phase_[phase].volt_gain_ = gain; } | ||||||
|   void set_ct_gain(int phase, uint16_t gain) { this->phase_[phase].ct_gain_ = gain; } |   void set_ct_gain(int phase, uint16_t gain) { this->phase_[phase].ct_gain_ = gain; } | ||||||
|  |  | ||||||
|   void set_freq_sensor(sensor::Sensor *freq_sensor) { freq_sensor_ = freq_sensor; } |   void set_freq_sensor(sensor::Sensor *freq_sensor) { freq_sensor_ = freq_sensor; } | ||||||
|  |   void set_chip_temperature_sensor(sensor::Sensor *chip_temperature_sensor) { | ||||||
|  |     chip_temperature_sensor_ = chip_temperature_sensor; | ||||||
|  |   } | ||||||
|   void set_line_freq(int freq) { line_freq_ = freq; } |   void set_line_freq(int freq) { line_freq_ = freq; } | ||||||
|  |   void set_current_phases(int phases) { current_phases_ = phases; } | ||||||
|   void set_pga_gain(uint16_t gain) { pga_gain_ = gain; } |   void set_pga_gain(uint16_t gain) { pga_gain_ = gain; } | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
| @@ -40,18 +46,29 @@ class ATM90E32Component : public PollingComponent, | |||||||
|   float get_active_power_a_(); |   float get_active_power_a_(); | ||||||
|   float get_active_power_b_(); |   float get_active_power_b_(); | ||||||
|   float get_active_power_c_(); |   float get_active_power_c_(); | ||||||
|  |   float get_reactive_power_a_(); | ||||||
|  |   float get_reactive_power_b_(); | ||||||
|  |   float get_reactive_power_c_(); | ||||||
|  |   float get_power_factor_a_(); | ||||||
|  |   float get_power_factor_b_(); | ||||||
|  |   float get_power_factor_c_(); | ||||||
|   float get_frequency_(); |   float get_frequency_(); | ||||||
|  |   float get_chip_temperature_(); | ||||||
|  |  | ||||||
|   struct ATM90E32Phase { |   struct ATM90E32Phase { | ||||||
|     uint16_t volt_gain_{41820}; |     uint16_t volt_gain_{7305}; | ||||||
|     uint16_t ct_gain_{25498}; |     uint16_t ct_gain_{27961}; | ||||||
|     sensor::Sensor *voltage_sensor_{nullptr}; |     sensor::Sensor *voltage_sensor_{nullptr}; | ||||||
|     sensor::Sensor *current_sensor_{nullptr}; |     sensor::Sensor *current_sensor_{nullptr}; | ||||||
|     sensor::Sensor *power_sensor_{nullptr}; |     sensor::Sensor *power_sensor_{nullptr}; | ||||||
|  |     sensor::Sensor *reactive_power_sensor_{nullptr}; | ||||||
|  |     sensor::Sensor *power_factor_sensor_{nullptr}; | ||||||
|   } phase_[3]; |   } phase_[3]; | ||||||
|   sensor::Sensor *freq_sensor_{nullptr}; |   sensor::Sensor *freq_sensor_{nullptr}; | ||||||
|  |   sensor::Sensor *chip_temperature_sensor_{nullptr}; | ||||||
|   uint16_t pga_gain_{0x15}; |   uint16_t pga_gain_{0x15}; | ||||||
|   int line_freq_{60}; |   int line_freq_{60}; | ||||||
|  |   int current_phases_{3}; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| }  // namespace atm90e32 | }  // namespace atm90e32 | ||||||
|   | |||||||
| @@ -234,12 +234,12 @@ static const uint16_t ATM90E32_REGISTER_IRMSBLSB = 0xEE;    // Lower Word (B RMS | |||||||
| static const uint16_t ATM90E32_REGISTER_IRMSCLSB = 0xEF;    // Lower Word (C RMS Current) | static const uint16_t ATM90E32_REGISTER_IRMSCLSB = 0xEF;    // Lower Word (C RMS Current) | ||||||
|  |  | ||||||
| /* THD, FREQUENCY, ANGLE & TEMPTEMP REGISTERS*/ | /* THD, FREQUENCY, ANGLE & TEMPTEMP REGISTERS*/ | ||||||
| static const uint16_t ATM90E32_REGISTER_THDNUA = 0xF1;   // A Voltage THD+N | static const uint16_t ATM90E32_REGISTER_UPEAKA = 0xF1;   // A Voltage Peak | ||||||
| static const uint16_t ATM90E32_REGISTER_THDNUB = 0xF2;   // B Voltage THD+N | static const uint16_t ATM90E32_REGISTER_UPEAKB = 0xF2;   // B Voltage Peak | ||||||
| static const uint16_t ATM90E32_REGISTER_THDNUC = 0xF3;   // C Voltage THD+N | static const uint16_t ATM90E32_REGISTER_UPEAKC = 0xF3;   // C Voltage Peak | ||||||
| static const uint16_t ATM90E32_REGISTER_THDNIA = 0xF5;   // A Current THD+N | static const uint16_t ATM90E32_REGISTER_IPEAKA = 0xF5;   // A Current Peak | ||||||
| static const uint16_t ATM90E32_REGISTER_THDNIB = 0xF6;   // B Current THD+N | static const uint16_t ATM90E32_REGISTER_IPEAKB = 0xF6;   // B Current Peak | ||||||
| static const uint16_t ATM90E32_REGISTER_THDNIC = 0xF7;   // C Current THD+N | static const uint16_t ATM90E32_REGISTER_IPEAKC = 0xF7;   // C Current Peak | ||||||
| static const uint16_t ATM90E32_REGISTER_FREQ = 0xF8;     // Frequency | static const uint16_t ATM90E32_REGISTER_FREQ = 0xF8;     // Frequency | ||||||
| static const uint16_t ATM90E32_REGISTER_PANGLEA = 0xF9;  // A Mean Phase Angle | static const uint16_t ATM90E32_REGISTER_PANGLEA = 0xF9;  // A Mean Phase Angle | ||||||
| static const uint16_t ATM90E32_REGISTER_PANGLEB = 0xFA;  // B Mean Phase Angle | static const uint16_t ATM90E32_REGISTER_PANGLEB = 0xFA;  // B Mean Phase Angle | ||||||
|   | |||||||
| @@ -2,21 +2,29 @@ import esphome.codegen as cg | |||||||
| import esphome.config_validation as cv | import esphome.config_validation as cv | ||||||
| from esphome.components import sensor, spi | from esphome.components import sensor, spi | ||||||
| from esphome.const import \ | from esphome.const import \ | ||||||
|     CONF_ID, CONF_VOLTAGE, CONF_CURRENT, CONF_POWER, CONF_FREQUENCY, \ |     CONF_ID, CONF_VOLTAGE, CONF_CURRENT, CONF_POWER, CONF_POWER_FACTOR, CONF_FREQUENCY, \ | ||||||
|     ICON_FLASH, UNIT_VOLT, UNIT_AMPERE, UNIT_WATT, UNIT_HERTZ, ICON_CURRENT_AC |     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 | ||||||
|  |  | ||||||
| CONF_PHASE_A = 'phase_a' | CONF_PHASE_A = 'phase_a' | ||||||
| CONF_PHASE_B = 'phase_b' | CONF_PHASE_B = 'phase_b' | ||||||
| CONF_PHASE_C = 'phase_c' | CONF_PHASE_C = 'phase_c' | ||||||
|  |  | ||||||
|  | CONF_REACTIVE_POWER = 'reactive_power' | ||||||
| CONF_LINE_FREQUENCY = 'line_frequency' | CONF_LINE_FREQUENCY = 'line_frequency' | ||||||
|  | CONF_CHIP_TEMPERATURE = 'chip_temperature' | ||||||
| CONF_GAIN_PGA = 'gain_pga' | CONF_GAIN_PGA = 'gain_pga' | ||||||
|  | CONF_CURRENT_PHASES = 'current_phases' | ||||||
| CONF_GAIN_VOLTAGE = 'gain_voltage' | CONF_GAIN_VOLTAGE = 'gain_voltage' | ||||||
| CONF_GAIN_CT = 'gain_ct' | CONF_GAIN_CT = 'gain_ct' | ||||||
| LINE_FREQS = { | LINE_FREQS = { | ||||||
|     '50HZ': 50, |     '50HZ': 50, | ||||||
|     '60HZ': 60, |     '60HZ': 60, | ||||||
| } | } | ||||||
|  | CURRENT_PHASES = { | ||||||
|  |     '2': 2, | ||||||
|  |     '3': 3, | ||||||
|  | } | ||||||
| PGA_GAINS = { | PGA_GAINS = { | ||||||
|     '1X': 0x0, |     '1X': 0x0, | ||||||
|     '2X': 0x15, |     '2X': 0x15, | ||||||
| @@ -28,10 +36,13 @@ ATM90E32Component = atm90e32_ns.class_('ATM90E32Component', cg.PollingComponent, | |||||||
|  |  | ||||||
| ATM90E32_PHASE_SCHEMA = cv.Schema({ | ATM90E32_PHASE_SCHEMA = cv.Schema({ | ||||||
|     cv.Optional(CONF_VOLTAGE): sensor.sensor_schema(UNIT_VOLT, ICON_FLASH, 2), |     cv.Optional(CONF_VOLTAGE): sensor.sensor_schema(UNIT_VOLT, ICON_FLASH, 2), | ||||||
|     cv.Optional(CONF_CURRENT): sensor.sensor_schema(UNIT_AMPERE, 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_POWER): sensor.sensor_schema(UNIT_WATT, ICON_FLASH, 2), | ||||||
|     cv.Optional(CONF_GAIN_VOLTAGE, default=41820): cv.uint16_t, |     cv.Optional(CONF_REACTIVE_POWER): sensor.sensor_schema(UNIT_VOLT_AMPS_REACTIVE, | ||||||
|     cv.Optional(CONF_GAIN_CT, default=25498): cv.uint16_t, |                                                            ICON_LIGHTBULB, 2), | ||||||
|  |     cv.Optional(CONF_POWER_FACTOR): sensor.sensor_schema(UNIT_EMPTY, ICON_FLASH, 2), | ||||||
|  |     cv.Optional(CONF_GAIN_VOLTAGE, default=7305): cv.uint16_t, | ||||||
|  |     cv.Optional(CONF_GAIN_CT, default=27961): cv.uint16_t, | ||||||
| }) | }) | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = cv.Schema({ | CONFIG_SCHEMA = cv.Schema({ | ||||||
| @@ -40,9 +51,11 @@ CONFIG_SCHEMA = cv.Schema({ | |||||||
|     cv.Optional(CONF_PHASE_B): ATM90E32_PHASE_SCHEMA, |     cv.Optional(CONF_PHASE_B): ATM90E32_PHASE_SCHEMA, | ||||||
|     cv.Optional(CONF_PHASE_C): 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_FREQUENCY): sensor.sensor_schema(UNIT_HERTZ, ICON_CURRENT_AC, 1), | ||||||
|  |     cv.Optional(CONF_CHIP_TEMPERATURE): sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 1), | ||||||
|     cv.Required(CONF_LINE_FREQUENCY): cv.enum(LINE_FREQS, upper=True), |     cv.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), |     cv.Optional(CONF_GAIN_PGA, default='2X'): cv.enum(PGA_GAINS, upper=True), | ||||||
| }).extend(cv.polling_component_schema('60s')).extend(spi.SPI_DEVICE_SCHEMA) | }).extend(cv.polling_component_schema('60s')).extend(spi.spi_device_schema()) | ||||||
|  |  | ||||||
|  |  | ||||||
| def to_code(config): | def to_code(config): | ||||||
| @@ -65,8 +78,18 @@ def to_code(config): | |||||||
|         if CONF_POWER in conf: |         if CONF_POWER in conf: | ||||||
|             sens = yield sensor.new_sensor(conf[CONF_POWER]) |             sens = yield sensor.new_sensor(conf[CONF_POWER]) | ||||||
|             cg.add(var.set_power_sensor(i, sens)) |             cg.add(var.set_power_sensor(i, sens)) | ||||||
|  |         if CONF_REACTIVE_POWER in conf: | ||||||
|  |             sens = yield sensor.new_sensor(conf[CONF_REACTIVE_POWER]) | ||||||
|  |             cg.add(var.set_reactive_power_sensor(i, sens)) | ||||||
|  |         if CONF_POWER_FACTOR in conf: | ||||||
|  |             sens = yield sensor.new_sensor(conf[CONF_POWER_FACTOR]) | ||||||
|  |             cg.add(var.set_power_factor_sensor(i, sens)) | ||||||
|     if CONF_FREQUENCY in config: |     if CONF_FREQUENCY in config: | ||||||
|         sens = yield sensor.new_sensor(config[CONF_FREQUENCY]) |         sens = yield sensor.new_sensor(config[CONF_FREQUENCY]) | ||||||
|         cg.add(var.set_freq_sensor(sens)) |         cg.add(var.set_freq_sensor(sens)) | ||||||
|  |     if CONF_CHIP_TEMPERATURE in config: | ||||||
|  |         sens = yield sensor.new_sensor(config[CONF_CHIP_TEMPERATURE]) | ||||||
|  |         cg.add(var.set_chip_temperature_sensor(sens)) | ||||||
|     cg.add(var.set_line_freq(config[CONF_LINE_FREQUENCY])) |     cg.add(var.set_line_freq(config[CONF_LINE_FREQUENCY])) | ||||||
|  |     cg.add(var.set_current_phases(config[CONF_CURRENT_PHASES])) | ||||||
|     cg.add(var.set_pga_gain(config[CONF_GAIN_PGA])) |     cg.add(var.set_pga_gain(config[CONF_GAIN_PGA])) | ||||||
|   | |||||||
| @@ -0,0 +1 @@ | |||||||
|  | CODEOWNERS = ['@OttoWinter'] | ||||||
|   | |||||||
| @@ -51,12 +51,15 @@ climate::ClimateTraits BangBangClimate::traits() { | |||||||
| } | } | ||||||
| void BangBangClimate::compute_state_() { | void BangBangClimate::compute_state_() { | ||||||
|   if (this->mode != climate::CLIMATE_MODE_AUTO) { |   if (this->mode != climate::CLIMATE_MODE_AUTO) { | ||||||
|     // in non-auto mode |     // in non-auto mode, switch directly to appropriate action | ||||||
|  |     //  - HEAT mode -> HEATING action | ||||||
|  |     //  - COOL mode -> COOLING action | ||||||
|  |     //  - OFF mode -> OFF action (not IDLE!) | ||||||
|     this->switch_to_action_(static_cast<climate::ClimateAction>(this->mode)); |     this->switch_to_action_(static_cast<climate::ClimateAction>(this->mode)); | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|   if (isnan(this->current_temperature) || isnan(this->target_temperature_low) || isnan(this->target_temperature_high)) { |   if (isnan(this->current_temperature) || isnan(this->target_temperature_low) || isnan(this->target_temperature_high)) { | ||||||
|     // if any control values are nan, go to OFF (idle) mode |     // if any control parameters are nan, go to OFF action (not IDLE!) | ||||||
|     this->switch_to_action_(climate::CLIMATE_ACTION_OFF); |     this->switch_to_action_(climate::CLIMATE_ACTION_OFF); | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
| @@ -69,18 +72,18 @@ void BangBangClimate::compute_state_() { | |||||||
|     if (this->supports_heat_) |     if (this->supports_heat_) | ||||||
|       target_action = climate::CLIMATE_ACTION_HEATING; |       target_action = climate::CLIMATE_ACTION_HEATING; | ||||||
|     else |     else | ||||||
|       target_action = climate::CLIMATE_ACTION_OFF; |       target_action = climate::CLIMATE_ACTION_IDLE; | ||||||
|   } else if (too_hot) { |   } else if (too_hot) { | ||||||
|     // too hot -> enable cooling if possible, else idle |     // too hot -> enable cooling if possible, else idle | ||||||
|     if (this->supports_cool_) |     if (this->supports_cool_) | ||||||
|       target_action = climate::CLIMATE_ACTION_COOLING; |       target_action = climate::CLIMATE_ACTION_COOLING; | ||||||
|     else |     else | ||||||
|       target_action = climate::CLIMATE_ACTION_OFF; |       target_action = climate::CLIMATE_ACTION_IDLE; | ||||||
|   } else { |   } else { | ||||||
|     // neither too hot nor too cold -> in range |     // neither too hot nor too cold -> in range | ||||||
|     if (this->supports_cool_ && this->supports_heat_) { |     if (this->supports_cool_ && this->supports_heat_) { | ||||||
|       // if supports both ends, go to idle mode |       // if supports both ends, go to idle action | ||||||
|       target_action = climate::CLIMATE_ACTION_OFF; |       target_action = climate::CLIMATE_ACTION_IDLE; | ||||||
|     } else { |     } else { | ||||||
|       // else use current mode and don't change (hysteresis) |       // else use current mode and don't change (hysteresis) | ||||||
|       target_action = this->action; |       target_action = this->action; | ||||||
| @@ -94,13 +97,24 @@ void BangBangClimate::switch_to_action_(climate::ClimateAction action) { | |||||||
|     // already in target mode |     // already in target mode | ||||||
|     return; |     return; | ||||||
|  |  | ||||||
|  |   if ((action == climate::CLIMATE_ACTION_OFF && this->action == climate::CLIMATE_ACTION_IDLE) || | ||||||
|  |       (action == climate::CLIMATE_ACTION_IDLE && this->action == climate::CLIMATE_ACTION_OFF)) { | ||||||
|  |     // switching from OFF to IDLE or vice-versa | ||||||
|  |     // these only have visual difference. OFF means user manually disabled, | ||||||
|  |     // IDLE means it's in auto mode but value is in target range. | ||||||
|  |     this->action = action; | ||||||
|  |     this->publish_state(); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|   if (this->prev_trigger_ != nullptr) { |   if (this->prev_trigger_ != nullptr) { | ||||||
|     this->prev_trigger_->stop(); |     this->prev_trigger_->stop_action(); | ||||||
|     this->prev_trigger_ = nullptr; |     this->prev_trigger_ = nullptr; | ||||||
|   } |   } | ||||||
|   Trigger<> *trig; |   Trigger<> *trig; | ||||||
|   switch (action) { |   switch (action) { | ||||||
|     case climate::CLIMATE_ACTION_OFF: |     case climate::CLIMATE_ACTION_OFF: | ||||||
|  |     case climate::CLIMATE_ACTION_IDLE: | ||||||
|       trig = this->idle_trigger_; |       trig = this->idle_trigger_; | ||||||
|       break; |       break; | ||||||
|     case climate::CLIMATE_ACTION_COOLING: |     case climate::CLIMATE_ACTION_COOLING: | ||||||
| @@ -112,13 +126,11 @@ void BangBangClimate::switch_to_action_(climate::ClimateAction action) { | |||||||
|     default: |     default: | ||||||
|       trig = nullptr; |       trig = nullptr; | ||||||
|   } |   } | ||||||
|   if (trig != nullptr) { |   assert(trig != nullptr); | ||||||
|     // trig should never be null, but still check so that we don't crash |   trig->trigger(); | ||||||
|     trig->trigger(); |   this->action = action; | ||||||
|     this->action = action; |   this->prev_trigger_ = trig; | ||||||
|     this->prev_trigger_ = trig; |   this->publish_state(); | ||||||
|     this->publish_state(); |  | ||||||
|   } |  | ||||||
| } | } | ||||||
| void BangBangClimate::change_away_(bool away) { | void BangBangClimate::change_away_(bool away) { | ||||||
|   if (!away) { |   if (!away) { | ||||||
|   | |||||||
| @@ -7,6 +7,8 @@ namespace bh1750 { | |||||||
| static const char *TAG = "bh1750.sensor"; | static const char *TAG = "bh1750.sensor"; | ||||||
|  |  | ||||||
| static const uint8_t BH1750_COMMAND_POWER_ON = 0b00000001; | static const uint8_t BH1750_COMMAND_POWER_ON = 0b00000001; | ||||||
|  | static const uint8_t BH1750_COMMAND_MT_REG_HI = 0b01000000;  // last 3 bits | ||||||
|  | static const uint8_t BH1750_COMMAND_MT_REG_LO = 0b01100000;  // last 5 bits | ||||||
|  |  | ||||||
| void BH1750Sensor::setup() { | void BH1750Sensor::setup() { | ||||||
|   ESP_LOGCONFIG(TAG, "Setting up BH1750 '%s'...", this->name_.c_str()); |   ESP_LOGCONFIG(TAG, "Setting up BH1750 '%s'...", this->name_.c_str()); | ||||||
| @@ -14,7 +16,13 @@ void BH1750Sensor::setup() { | |||||||
|     this->mark_failed(); |     this->mark_failed(); | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   uint8_t mtreg_hi = (this->measurement_time_ >> 5) & 0b111; | ||||||
|  |   uint8_t mtreg_lo = (this->measurement_time_ >> 0) & 0b11111; | ||||||
|  |   this->write_bytes(BH1750_COMMAND_MT_REG_HI | mtreg_hi, nullptr, 0); | ||||||
|  |   this->write_bytes(BH1750_COMMAND_MT_REG_LO | mtreg_lo, nullptr, 0); | ||||||
| } | } | ||||||
|  |  | ||||||
| void BH1750Sensor::dump_config() { | void BH1750Sensor::dump_config() { | ||||||
|   LOG_SENSOR("", "BH1750", this); |   LOG_SENSOR("", "BH1750", this); | ||||||
|   LOG_I2C_DEVICE(this); |   LOG_I2C_DEVICE(this); | ||||||
| @@ -59,6 +67,7 @@ void BH1750Sensor::update() { | |||||||
|  |  | ||||||
|   this->set_timeout("illuminance", wait, [this]() { this->read_data_(); }); |   this->set_timeout("illuminance", wait, [this]() { this->read_data_(); }); | ||||||
| } | } | ||||||
|  |  | ||||||
| float BH1750Sensor::get_setup_priority() const { return setup_priority::DATA; } | float BH1750Sensor::get_setup_priority() const { return setup_priority::DATA; } | ||||||
| void BH1750Sensor::read_data_() { | void BH1750Sensor::read_data_() { | ||||||
|   uint16_t raw_value; |   uint16_t raw_value; | ||||||
| @@ -68,10 +77,12 @@ void BH1750Sensor::read_data_() { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   float lx = float(raw_value) / 1.2f; |   float lx = float(raw_value) / 1.2f; | ||||||
|  |   lx *= 69.0f / this->measurement_time_; | ||||||
|   ESP_LOGD(TAG, "'%s': Got illuminance=%.1flx", this->get_name().c_str(), lx); |   ESP_LOGD(TAG, "'%s': Got illuminance=%.1flx", this->get_name().c_str(), lx); | ||||||
|   this->publish_state(lx); |   this->publish_state(lx); | ||||||
|   this->status_clear_warning(); |   this->status_clear_warning(); | ||||||
| } | } | ||||||
|  |  | ||||||
| void BH1750Sensor::set_resolution(BH1750Resolution resolution) { this->resolution_ = resolution; } | void BH1750Sensor::set_resolution(BH1750Resolution resolution) { this->resolution_ = resolution; } | ||||||
|  |  | ||||||
| }  // namespace bh1750 | }  // namespace bh1750 | ||||||
|   | |||||||
| @@ -28,6 +28,7 @@ class BH1750Sensor : public sensor::Sensor, public PollingComponent, public i2c: | |||||||
|    * @param resolution The new resolution of the sensor. |    * @param resolution The new resolution of the sensor. | ||||||
|    */ |    */ | ||||||
|   void set_resolution(BH1750Resolution resolution); |   void set_resolution(BH1750Resolution resolution); | ||||||
|  |   void set_measurement_time(uint8_t measurement_time) { measurement_time_ = measurement_time; } | ||||||
|  |  | ||||||
|   // ========== INTERNAL METHODS ========== |   // ========== INTERNAL METHODS ========== | ||||||
|   // (In most use cases you won't need these) |   // (In most use cases you won't need these) | ||||||
| @@ -40,6 +41,7 @@ class BH1750Sensor : public sensor::Sensor, public PollingComponent, public i2c: | |||||||
|   void read_data_(); |   void read_data_(); | ||||||
|  |  | ||||||
|   BH1750Resolution resolution_{BH1750_RESOLUTION_0P5_LX}; |   BH1750Resolution resolution_{BH1750_RESOLUTION_0P5_LX}; | ||||||
|  |   uint8_t measurement_time_; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| }  // namespace bh1750 | }  // namespace bh1750 | ||||||
|   | |||||||
| @@ -15,9 +15,11 @@ BH1750_RESOLUTIONS = { | |||||||
|  |  | ||||||
| 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({ | CONFIG_SCHEMA = sensor.sensor_schema(UNIT_LUX, ICON_BRIGHTNESS_5, 1).extend({ | ||||||
|     cv.GenerateID(): cv.declare_id(BH1750Sensor), |     cv.GenerateID(): cv.declare_id(BH1750Sensor), | ||||||
|     cv.Optional(CONF_RESOLUTION, default=0.5): cv.enum(BH1750_RESOLUTIONS, float=True), |     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)) | }).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x23)) | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -28,3 +30,4 @@ def to_code(config): | |||||||
|     yield i2c.register_i2c_device(var, config) |     yield i2c.register_i2c_device(var, config) | ||||||
|  |  | ||||||
|     cg.add(var.set_resolution(config[CONF_RESOLUTION])) |     cg.add(var.set_resolution(config[CONF_RESOLUTION])) | ||||||
|  |     cg.add(var.set_measurement_time(config[CONF_MEASUREMENT_TIME])) | ||||||
|   | |||||||
| @@ -1,7 +1,8 @@ | |||||||
| import esphome.codegen as cg | import esphome.codegen as cg | ||||||
| import esphome.config_validation as cv | import esphome.config_validation as cv | ||||||
| from esphome.components import fan, output | from esphome.components import fan, output | ||||||
| from esphome.const import CONF_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 | from .. import binary_ns | ||||||
|  |  | ||||||
| BinaryFan = binary_ns.class_('BinaryFan', cg.Component) | BinaryFan = binary_ns.class_('BinaryFan', cg.Component) | ||||||
| @@ -9,6 +10,7 @@ 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.GenerateID(CONF_OUTPUT_ID): cv.declare_id(BinaryFan), | ||||||
|     cv.Required(CONF_OUTPUT): cv.use_id(output.BinaryOutput), |     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), |     cv.Optional(CONF_OSCILLATION_OUTPUT): cv.use_id(output.BinaryOutput), | ||||||
| }).extend(cv.COMPONENT_SCHEMA) | }).extend(cv.COMPONENT_SCHEMA) | ||||||
|  |  | ||||||
| @@ -25,3 +27,7 @@ def to_code(config): | |||||||
|     if CONF_OSCILLATION_OUTPUT in config: |     if CONF_OSCILLATION_OUTPUT in config: | ||||||
|         oscillation_output = yield cg.get_variable(config[CONF_OSCILLATION_OUTPUT]) |         oscillation_output = yield cg.get_variable(config[CONF_OSCILLATION_OUTPUT]) | ||||||
|         cg.add(var.set_oscillating(oscillation_output)) |         cg.add(var.set_oscillating(oscillation_output)) | ||||||
|  |  | ||||||
|  |     if CONF_DIRECTION_OUTPUT in config: | ||||||
|  |         direction_output = yield cg.get_variable(config[CONF_DIRECTION_OUTPUT]) | ||||||
|  |         cg.add(var.set_direction(direction_output)) | ||||||
|   | |||||||
| @@ -11,9 +11,12 @@ void binary::BinaryFan::dump_config() { | |||||||
|   if (this->fan_->get_traits().supports_oscillation()) { |   if (this->fan_->get_traits().supports_oscillation()) { | ||||||
|     ESP_LOGCONFIG(TAG, "  Oscillation: YES"); |     ESP_LOGCONFIG(TAG, "  Oscillation: YES"); | ||||||
|   } |   } | ||||||
|  |   if (this->fan_->get_traits().supports_direction()) { | ||||||
|  |     ESP_LOGCONFIG(TAG, "  Direction: YES"); | ||||||
|  |   } | ||||||
| } | } | ||||||
| void BinaryFan::setup() { | void BinaryFan::setup() { | ||||||
|   auto traits = fan::FanTraits(this->oscillating_ != nullptr, false); |   auto traits = fan::FanTraits(this->oscillating_ != nullptr, false, this->direction_ != nullptr); | ||||||
|   this->fan_->set_traits(traits); |   this->fan_->set_traits(traits); | ||||||
|   this->fan_->add_on_state_callback([this]() { this->next_update_ = true; }); |   this->fan_->add_on_state_callback([this]() { this->next_update_ = true; }); | ||||||
| } | } | ||||||
| @@ -41,6 +44,16 @@ void BinaryFan::loop() { | |||||||
|     } |     } | ||||||
|     ESP_LOGD(TAG, "Setting oscillation: %s", ONOFF(enable)); |     ESP_LOGD(TAG, "Setting oscillation: %s", ONOFF(enable)); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   if (this->direction_ != nullptr) { | ||||||
|  |     bool enable = this->fan_->direction == fan::FAN_DIRECTION_REVERSE; | ||||||
|  |     if (enable) { | ||||||
|  |       this->direction_->turn_on(); | ||||||
|  |     } else { | ||||||
|  |       this->direction_->turn_off(); | ||||||
|  |     } | ||||||
|  |     ESP_LOGD(TAG, "Setting reverse direction: %s", ONOFF(enable)); | ||||||
|  |   } | ||||||
| } | } | ||||||
| float BinaryFan::get_setup_priority() const { return setup_priority::DATA; } | float BinaryFan::get_setup_priority() const { return setup_priority::DATA; } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -16,11 +16,13 @@ class BinaryFan : public Component { | |||||||
|   void dump_config() override; |   void dump_config() override; | ||||||
|   float get_setup_priority() const override; |   float get_setup_priority() const override; | ||||||
|   void set_oscillating(output::BinaryOutput *oscillating) { this->oscillating_ = oscillating; } |   void set_oscillating(output::BinaryOutput *oscillating) { this->oscillating_ = oscillating; } | ||||||
|  |   void set_direction(output::BinaryOutput *direction) { this->direction_ = direction; } | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   fan::FanState *fan_; |   fan::FanState *fan_; | ||||||
|   output::BinaryOutput *output_; |   output::BinaryOutput *output_; | ||||||
|   output::BinaryOutput *oscillating_{nullptr}; |   output::BinaryOutput *oscillating_{nullptr}; | ||||||
|  |   output::BinaryOutput *direction_{nullptr}; | ||||||
|   bool next_update_{true}; |   bool next_update_{true}; | ||||||
| }; | }; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -9,9 +9,9 @@ from esphome.const import CONF_DEVICE_CLASS, CONF_FILTERS, \ | |||||||
|     CONF_ON_DOUBLE_CLICK, CONF_ON_MULTI_CLICK, CONF_ON_PRESS, CONF_ON_RELEASE, CONF_ON_STATE, \ |     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 |     CONF_STATE, CONF_TIMING, CONF_TRIGGER_ID, CONF_FOR, CONF_NAME, CONF_MQTT_ID | ||||||
| from esphome.core import CORE, coroutine, coroutine_with_priority | from esphome.core import CORE, coroutine, coroutine_with_priority | ||||||
| from esphome.py_compat import string_types |  | ||||||
| from esphome.util import Registry | from esphome.util import Registry | ||||||
|  |  | ||||||
|  | CODEOWNERS = ['@esphome/core'] | ||||||
| DEVICE_CLASSES = [ | DEVICE_CLASSES = [ | ||||||
|     '', 'battery', 'cold', 'connectivity', 'door', 'garage_door', 'gas', |     '', 'battery', 'cold', 'connectivity', 'door', 'garage_door', 'gas', | ||||||
|     'heat', 'light', 'lock', 'moisture', 'motion', 'moving', 'occupancy', |     'heat', 'light', 'lock', 'moisture', 'motion', 'moving', 'occupancy', | ||||||
| @@ -94,7 +94,7 @@ MULTI_CLICK_TIMING_SCHEMA = cv.Schema({ | |||||||
|  |  | ||||||
|  |  | ||||||
| def parse_multi_click_timing_str(value): | def parse_multi_click_timing_str(value): | ||||||
|     if not isinstance(value, string_types): |     if not isinstance(value, str): | ||||||
|         return value |         return value | ||||||
|  |  | ||||||
|     parts = value.lower().split(' ') |     parts = value.lower().split(' ') | ||||||
| @@ -104,10 +104,10 @@ def parse_multi_click_timing_str(value): | |||||||
|     try: |     try: | ||||||
|         state = cv.boolean(parts[0]) |         state = cv.boolean(parts[0]) | ||||||
|     except cv.Invalid: |     except cv.Invalid: | ||||||
|         raise cv.Invalid(u"First word must either be ON or OFF, not {}".format(parts[0])) |         raise cv.Invalid("First word must either be ON or OFF, not {}".format(parts[0])) | ||||||
|  |  | ||||||
|     if parts[1] != 'for': |     if parts[1] != 'for': | ||||||
|         raise cv.Invalid(u"Second word must be 'for', got {}".format(parts[1])) |         raise cv.Invalid("Second word must be 'for', got {}".format(parts[1])) | ||||||
|  |  | ||||||
|     if parts[2] == 'at': |     if parts[2] == 'at': | ||||||
|         if parts[3] == 'least': |         if parts[3] == 'least': | ||||||
| @@ -115,12 +115,12 @@ def parse_multi_click_timing_str(value): | |||||||
|         elif parts[3] == 'most': |         elif parts[3] == 'most': | ||||||
|             key = CONF_MAX_LENGTH |             key = CONF_MAX_LENGTH | ||||||
|         else: |         else: | ||||||
|             raise cv.Invalid(u"Third word after at must either be 'least' or 'most', got {}" |             raise cv.Invalid("Third word after at must either be 'least' or 'most', got {}" | ||||||
|                              u"".format(parts[3])) |                              "".format(parts[3])) | ||||||
|         try: |         try: | ||||||
|             length = cv.positive_time_period_milliseconds(parts[4]) |             length = cv.positive_time_period_milliseconds(parts[4]) | ||||||
|         except cv.Invalid as err: |         except cv.Invalid as err: | ||||||
|             raise cv.Invalid(u"Multi Click Grammar Parsing length failed: {}".format(err)) |             raise cv.Invalid(f"Multi Click Grammar Parsing length failed: {err}") | ||||||
|         return { |         return { | ||||||
|             CONF_STATE: state, |             CONF_STATE: state, | ||||||
|             key: str(length) |             key: str(length) | ||||||
| @@ -132,12 +132,12 @@ def parse_multi_click_timing_str(value): | |||||||
|     try: |     try: | ||||||
|         min_length = cv.positive_time_period_milliseconds(parts[2]) |         min_length = cv.positive_time_period_milliseconds(parts[2]) | ||||||
|     except cv.Invalid as err: |     except cv.Invalid as err: | ||||||
|         raise cv.Invalid(u"Multi Click Grammar Parsing minimum length failed: {}".format(err)) |         raise cv.Invalid(f"Multi Click Grammar Parsing minimum length failed: {err}") | ||||||
|  |  | ||||||
|     try: |     try: | ||||||
|         max_length = cv.positive_time_period_milliseconds(parts[4]) |         max_length = cv.positive_time_period_milliseconds(parts[4]) | ||||||
|     except cv.Invalid as err: |     except cv.Invalid as err: | ||||||
|         raise cv.Invalid(u"Multi Click Grammar Parsing minimum length failed: {}".format(err)) |         raise cv.Invalid(f"Multi Click Grammar Parsing minimum length failed: {err}") | ||||||
|  |  | ||||||
|     return { |     return { | ||||||
|         CONF_STATE: state, |         CONF_STATE: state, | ||||||
| @@ -225,7 +225,7 @@ BINARY_SENSOR_SCHEMA = cv.MQTT_COMPONENT_SCHEMA.extend({ | |||||||
| def setup_binary_sensor_core_(var, config): | def setup_binary_sensor_core_(var, config): | ||||||
|     cg.add(var.set_name(config[CONF_NAME])) |     cg.add(var.set_name(config[CONF_NAME])) | ||||||
|     if CONF_INTERNAL in config: |     if CONF_INTERNAL in config: | ||||||
|         cg.add(var.set_internal(CONF_INTERNAL)) |         cg.add(var.set_internal(config[CONF_INTERNAL])) | ||||||
|     if CONF_DEVICE_CLASS in config: |     if CONF_DEVICE_CLASS in config: | ||||||
|         cg.add(var.set_device_class(config[CONF_DEVICE_CLASS])) |         cg.add(var.set_device_class(config[CONF_DEVICE_CLASS])) | ||||||
|     if CONF_INVERTED in config: |     if CONF_INVERTED in config: | ||||||
|   | |||||||
| @@ -137,6 +137,7 @@ template<typename... Ts> class BinarySensorPublishAction : public Action<Ts...> | |||||||
|  public: |  public: | ||||||
|   explicit BinarySensorPublishAction(BinarySensor *sensor) : sensor_(sensor) {} |   explicit BinarySensorPublishAction(BinarySensor *sensor) : sensor_(sensor) {} | ||||||
|   TEMPLATABLE_VALUE(bool, state) |   TEMPLATABLE_VALUE(bool, state) | ||||||
|  |  | ||||||
|   void play(Ts... x) override { |   void play(Ts... x) override { | ||||||
|     auto val = this->state_.value(x...); |     auto val = this->state_.value(x...); | ||||||
|     this->sensor_->publish_state(val); |     this->sensor_->publish_state(val); | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| import esphome.codegen as cg | import esphome.codegen as cg | ||||||
| import esphome.config_validation as cv | import esphome.config_validation as cv | ||||||
| from esphome.components import binary_sensor, esp32_ble_tracker | from esphome.components import binary_sensor, esp32_ble_tracker | ||||||
| from esphome.const import CONF_MAC_ADDRESS, CONF_ID | from esphome.const import CONF_MAC_ADDRESS, CONF_SERVICE_UUID, CONF_ID | ||||||
|  |  | ||||||
| DEPENDENCIES = ['esp32_ble_tracker'] | DEPENDENCIES = ['esp32_ble_tracker'] | ||||||
|  |  | ||||||
| @@ -9,10 +9,12 @@ ble_presence_ns = cg.esphome_ns.namespace('ble_presence') | |||||||
| BLEPresenceDevice = ble_presence_ns.class_('BLEPresenceDevice', binary_sensor.BinarySensor, | BLEPresenceDevice = ble_presence_ns.class_('BLEPresenceDevice', binary_sensor.BinarySensor, | ||||||
|                                            cg.Component, esp32_ble_tracker.ESPBTDeviceListener) |                                            cg.Component, esp32_ble_tracker.ESPBTDeviceListener) | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend({ | CONFIG_SCHEMA = cv.All(binary_sensor.BINARY_SENSOR_SCHEMA.extend({ | ||||||
|     cv.GenerateID(): cv.declare_id(BLEPresenceDevice), |     cv.GenerateID(): cv.declare_id(BLEPresenceDevice), | ||||||
|     cv.Required(CONF_MAC_ADDRESS): cv.mac_address, |     cv.Optional(CONF_MAC_ADDRESS): cv.mac_address, | ||||||
| }).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA).extend(cv.COMPONENT_SCHEMA) |     cv.Optional(CONF_SERVICE_UUID): esp32_ble_tracker.bt_uuid, | ||||||
|  | }).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA).extend( | ||||||
|  |     cv.COMPONENT_SCHEMA), cv.has_exactly_one_key(CONF_MAC_ADDRESS, CONF_SERVICE_UUID)) | ||||||
|  |  | ||||||
|  |  | ||||||
| def to_code(config): | def to_code(config): | ||||||
| @@ -21,4 +23,14 @@ def to_code(config): | |||||||
|     yield esp32_ble_tracker.register_ble_device(var, config) |     yield esp32_ble_tracker.register_ble_device(var, config) | ||||||
|     yield binary_sensor.register_binary_sensor(var, config) |     yield binary_sensor.register_binary_sensor(var, config) | ||||||
|  |  | ||||||
|     cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex)) |     if CONF_MAC_ADDRESS in config: | ||||||
|  |         cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex)) | ||||||
|  |  | ||||||
|  |     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]))) | ||||||
|  |         elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid32_format): | ||||||
|  |             cg.add(var.set_service_uuid32(esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID]))) | ||||||
|  |         elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid128_format): | ||||||
|  |             uuid128 = esp32_ble_tracker.as_hex_array(config[CONF_SERVICE_UUID]) | ||||||
|  |             cg.add(var.set_service_uuid128(uuid128)) | ||||||
|   | |||||||
| @@ -13,17 +13,42 @@ class BLEPresenceDevice : public binary_sensor::BinarySensorInitiallyOff, | |||||||
|                           public esp32_ble_tracker::ESPBTDeviceListener, |                           public esp32_ble_tracker::ESPBTDeviceListener, | ||||||
|                           public Component { |                           public Component { | ||||||
|  public: |  public: | ||||||
|   void set_address(uint64_t address) { address_ = address; } |   void set_address(uint64_t address) { | ||||||
|  |     this->by_address_ = true; | ||||||
|  |     this->address_ = address; | ||||||
|  |   } | ||||||
|  |   void set_service_uuid16(uint16_t uuid) { | ||||||
|  |     this->by_address_ = false; | ||||||
|  |     this->uuid_ = esp32_ble_tracker::ESPBTUUID::from_uint16(uuid); | ||||||
|  |   } | ||||||
|  |   void set_service_uuid32(uint32_t uuid) { | ||||||
|  |     this->by_address_ = false; | ||||||
|  |     this->uuid_ = esp32_ble_tracker::ESPBTUUID::from_uint32(uuid); | ||||||
|  |   } | ||||||
|  |   void set_service_uuid128(uint8_t *uuid) { | ||||||
|  |     this->by_address_ = false; | ||||||
|  |     this->uuid_ = esp32_ble_tracker::ESPBTUUID::from_raw(uuid); | ||||||
|  |   } | ||||||
|   void on_scan_end() override { |   void on_scan_end() override { | ||||||
|     if (!this->found_) |     if (!this->found_) | ||||||
|       this->publish_state(false); |       this->publish_state(false); | ||||||
|     this->found_ = false; |     this->found_ = false; | ||||||
|   } |   } | ||||||
|   bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override { |   bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override { | ||||||
|     if (device.address_uint64() == this->address_) { |     if (this->by_address_) { | ||||||
|       this->publish_state(true); |       if (device.address_uint64() == this->address_) { | ||||||
|       this->found_ = true; |         this->publish_state(true); | ||||||
|       return true; |         this->found_ = true; | ||||||
|  |         return true; | ||||||
|  |       } | ||||||
|  |     } else { | ||||||
|  |       for (auto uuid : device.get_service_uuids()) { | ||||||
|  |         if (this->uuid_ == uuid) { | ||||||
|  |           this->publish_state(device.get_rssi()); | ||||||
|  |           this->found_ = true; | ||||||
|  |           return true; | ||||||
|  |         } | ||||||
|  |       } | ||||||
|     } |     } | ||||||
|     return false; |     return false; | ||||||
|   } |   } | ||||||
| @@ -32,7 +57,9 @@ class BLEPresenceDevice : public binary_sensor::BinarySensorInitiallyOff, | |||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   bool found_{false}; |   bool found_{false}; | ||||||
|  |   bool by_address_{false}; | ||||||
|   uint64_t address_; |   uint64_t address_; | ||||||
|  |   esp32_ble_tracker::ESPBTUUID uuid_; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| }  // namespace ble_presence | }  // namespace ble_presence | ||||||
|   | |||||||
| @@ -11,17 +11,42 @@ namespace ble_rssi { | |||||||
|  |  | ||||||
| class BLERSSISensor : public sensor::Sensor, public esp32_ble_tracker::ESPBTDeviceListener, public Component { | class BLERSSISensor : public sensor::Sensor, public esp32_ble_tracker::ESPBTDeviceListener, public Component { | ||||||
|  public: |  public: | ||||||
|   void set_address(uint64_t address) { address_ = address; } |   void set_address(uint64_t address) { | ||||||
|  |     this->by_address_ = true; | ||||||
|  |     this->address_ = address; | ||||||
|  |   } | ||||||
|  |   void set_service_uuid16(uint16_t uuid) { | ||||||
|  |     this->by_address_ = false; | ||||||
|  |     this->uuid_ = esp32_ble_tracker::ESPBTUUID::from_uint16(uuid); | ||||||
|  |   } | ||||||
|  |   void set_service_uuid32(uint32_t uuid) { | ||||||
|  |     this->by_address_ = false; | ||||||
|  |     this->uuid_ = esp32_ble_tracker::ESPBTUUID::from_uint32(uuid); | ||||||
|  |   } | ||||||
|  |   void set_service_uuid128(uint8_t *uuid) { | ||||||
|  |     this->by_address_ = false; | ||||||
|  |     this->uuid_ = esp32_ble_tracker::ESPBTUUID::from_raw(uuid); | ||||||
|  |   } | ||||||
|   void on_scan_end() override { |   void on_scan_end() override { | ||||||
|     if (!this->found_) |     if (!this->found_) | ||||||
|       this->publish_state(NAN); |       this->publish_state(NAN); | ||||||
|     this->found_ = false; |     this->found_ = false; | ||||||
|   } |   } | ||||||
|   bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override { |   bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override { | ||||||
|     if (device.address_uint64() == this->address_) { |     if (this->by_address_) { | ||||||
|       this->publish_state(device.get_rssi()); |       if (device.address_uint64() == this->address_) { | ||||||
|       this->found_ = true; |         this->publish_state(device.get_rssi()); | ||||||
|       return true; |         this->found_ = true; | ||||||
|  |         return true; | ||||||
|  |       } | ||||||
|  |     } else { | ||||||
|  |       for (auto uuid : device.get_service_uuids()) { | ||||||
|  |         if (this->uuid_ == uuid) { | ||||||
|  |           this->publish_state(device.get_rssi()); | ||||||
|  |           this->found_ = true; | ||||||
|  |           return true; | ||||||
|  |         } | ||||||
|  |       } | ||||||
|     } |     } | ||||||
|     return false; |     return false; | ||||||
|   } |   } | ||||||
| @@ -30,7 +55,9 @@ class BLERSSISensor : public sensor::Sensor, public esp32_ble_tracker::ESPBTDevi | |||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   bool found_{false}; |   bool found_{false}; | ||||||
|  |   bool by_address_{false}; | ||||||
|   uint64_t address_; |   uint64_t address_; | ||||||
|  |   esp32_ble_tracker::ESPBTUUID uuid_; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| }  // namespace ble_rssi | }  // namespace ble_rssi | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| import esphome.codegen as cg | import esphome.codegen as cg | ||||||
| import esphome.config_validation as cv | import esphome.config_validation as cv | ||||||
| from esphome.components import sensor, esp32_ble_tracker | from esphome.components import sensor, esp32_ble_tracker | ||||||
| from esphome.const import CONF_MAC_ADDRESS, CONF_ID, UNIT_DECIBEL, ICON_SIGNAL | from esphome.const import CONF_SERVICE_UUID, CONF_MAC_ADDRESS, CONF_ID, UNIT_DECIBEL, ICON_SIGNAL | ||||||
|  |  | ||||||
| DEPENDENCIES = ['esp32_ble_tracker'] | DEPENDENCIES = ['esp32_ble_tracker'] | ||||||
|  |  | ||||||
| @@ -9,10 +9,12 @@ ble_rssi_ns = cg.esphome_ns.namespace('ble_rssi') | |||||||
| BLERSSISensor = ble_rssi_ns.class_('BLERSSISensor', sensor.Sensor, cg.Component, | BLERSSISensor = ble_rssi_ns.class_('BLERSSISensor', sensor.Sensor, cg.Component, | ||||||
|                                    esp32_ble_tracker.ESPBTDeviceListener) |                                    esp32_ble_tracker.ESPBTDeviceListener) | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = sensor.sensor_schema(UNIT_DECIBEL, ICON_SIGNAL, 0).extend({ | CONFIG_SCHEMA = cv.All(sensor.sensor_schema(UNIT_DECIBEL, ICON_SIGNAL, 0).extend({ | ||||||
|     cv.GenerateID(): cv.declare_id(BLERSSISensor), |     cv.GenerateID(): cv.declare_id(BLERSSISensor), | ||||||
|     cv.Required(CONF_MAC_ADDRESS): cv.mac_address, |     cv.Optional(CONF_MAC_ADDRESS): cv.mac_address, | ||||||
| }).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA).extend(cv.COMPONENT_SCHEMA) |     cv.Optional(CONF_SERVICE_UUID): esp32_ble_tracker.bt_uuid, | ||||||
|  | }).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA).extend( | ||||||
|  |     cv.COMPONENT_SCHEMA), cv.has_exactly_one_key(CONF_MAC_ADDRESS, CONF_SERVICE_UUID)) | ||||||
|  |  | ||||||
|  |  | ||||||
| def to_code(config): | def to_code(config): | ||||||
| @@ -21,4 +23,14 @@ def to_code(config): | |||||||
|     yield esp32_ble_tracker.register_ble_device(var, config) |     yield esp32_ble_tracker.register_ble_device(var, config) | ||||||
|     yield sensor.register_sensor(var, config) |     yield sensor.register_sensor(var, config) | ||||||
|  |  | ||||||
|     cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex)) |     if CONF_MAC_ADDRESS in config: | ||||||
|  |         cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex)) | ||||||
|  |  | ||||||
|  |     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]))) | ||||||
|  |         elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid32_format): | ||||||
|  |             cg.add(var.set_service_uuid32(esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID]))) | ||||||
|  |         elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid128_format): | ||||||
|  |             uuid128 = esp32_ble_tracker.as_hex_array(config[CONF_SERVICE_UUID]) | ||||||
|  |             cg.add(var.set_service_uuid128(uuid128)) | ||||||
|   | |||||||
							
								
								
									
										0
									
								
								esphome/components/ble_scanner/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								esphome/components/ble_scanner/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										16
									
								
								esphome/components/ble_scanner/ble_scanner.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								esphome/components/ble_scanner/ble_scanner.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | |||||||
|  | #include "ble_scanner.h" | ||||||
|  | #include "esphome/core/log.h" | ||||||
|  |  | ||||||
|  | #ifdef ARDUINO_ARCH_ESP32 | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace ble_scanner { | ||||||
|  |  | ||||||
|  | static const char *TAG = "ble_scanner"; | ||||||
|  |  | ||||||
|  | void BLEScanner::dump_config() { LOG_TEXT_SENSOR("", "BLE Scanner", this); } | ||||||
|  |  | ||||||
|  | }  // namespace ble_scanner | ||||||
|  | }  // namespace esphome | ||||||
|  |  | ||||||
|  | #endif | ||||||
							
								
								
									
										38
									
								
								esphome/components/ble_scanner/ble_scanner.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								esphome/components/ble_scanner/ble_scanner.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,38 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include <ctime> | ||||||
|  | #include <string> | ||||||
|  |  | ||||||
|  | #include "esphome/core/component.h" | ||||||
|  | #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" | ||||||
|  | #include "esphome/components/text_sensor/text_sensor.h" | ||||||
|  |  | ||||||
|  | #ifdef ARDUINO_ARCH_ESP32 | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace ble_scanner { | ||||||
|  |  | ||||||
|  | class BLEScanner : public text_sensor::TextSensor, public esp32_ble_tracker::ESPBTDeviceListener, public Component { | ||||||
|  |  public: | ||||||
|  |   bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override { | ||||||
|  |     this->publish_state("{\"timestamp\":" + to_string(::time(NULL)) + | ||||||
|  |                         "," | ||||||
|  |                         "\"address\":\"" + | ||||||
|  |                         device.address_str() + | ||||||
|  |                         "\"," | ||||||
|  |                         "\"rssi\":" + | ||||||
|  |                         to_string(device.get_rssi()) + | ||||||
|  |                         "," | ||||||
|  |                         "\"name\":\"" + | ||||||
|  |                         device.get_name() + "\"}"); | ||||||
|  |  | ||||||
|  |     return true; | ||||||
|  |   } | ||||||
|  |   void dump_config() override; | ||||||
|  |   float get_setup_priority() const override { return setup_priority::DATA; } | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | }  // namespace ble_scanner | ||||||
|  | }  // namespace esphome | ||||||
|  |  | ||||||
|  | #endif | ||||||
							
								
								
									
										22
									
								
								esphome/components/ble_scanner/text_sensor.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								esphome/components/ble_scanner/text_sensor.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,22 @@ | |||||||
|  | import esphome.codegen as cg | ||||||
|  | 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'] | ||||||
|  |  | ||||||
|  | ble_scanner_ns = cg.esphome_ns.namespace('ble_scanner') | ||||||
|  | BLEScanner = ble_scanner_ns.class_('BLEScanner', text_sensor.TextSensor, cg.Component, | ||||||
|  |                                    esp32_ble_tracker.ESPBTDeviceListener) | ||||||
|  |  | ||||||
|  | CONFIG_SCHEMA = cv.All(text_sensor.TEXT_SENSOR_SCHEMA.extend({ | ||||||
|  |     cv.GenerateID(): cv.declare_id(BLEScanner), | ||||||
|  | }).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA).extend( | ||||||
|  |     cv.COMPONENT_SCHEMA)) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def to_code(config): | ||||||
|  |     var = cg.new_Pvariable(config[CONF_ID]) | ||||||
|  |     yield cg.register_component(var, config) | ||||||
|  |     yield esp32_ble_tracker.register_ble_device(var, config) | ||||||
|  |     yield text_sensor.register_text_sensor(var, config) | ||||||
| @@ -146,7 +146,7 @@ void BME280Component::dump_config() { | |||||||
|       ESP_LOGE(TAG, "Communication with BME280 failed!"); |       ESP_LOGE(TAG, "Communication with BME280 failed!"); | ||||||
|       break; |       break; | ||||||
|     case WRONG_CHIP_ID: |     case WRONG_CHIP_ID: | ||||||
|       ESP_LOGE(TAG, "BMP280 has wrong chip ID! Is it a BMP280?"); |       ESP_LOGE(TAG, "BME280 has wrong chip ID! Is it a BME280?"); | ||||||
|       break; |       break; | ||||||
|     case NONE: |     case NONE: | ||||||
|     default: |     default: | ||||||
| @@ -172,7 +172,7 @@ void BME280Component::update() { | |||||||
|   uint8_t meas_register = 0; |   uint8_t meas_register = 0; | ||||||
|   meas_register |= (this->temperature_oversampling_ & 0b111) << 5; |   meas_register |= (this->temperature_oversampling_ & 0b111) << 5; | ||||||
|   meas_register |= (this->pressure_oversampling_ & 0b111) << 2; |   meas_register |= (this->pressure_oversampling_ & 0b111) << 2; | ||||||
|   meas_register |= 0b01;  // Forced mode |   meas_register |= BME280_MODE_FORCED; | ||||||
|   if (!this->write_byte(BME280_REGISTER_CONTROL, meas_register)) { |   if (!this->write_byte(BME280_REGISTER_CONTROL, meas_register)) { | ||||||
|     this->status_set_warning(); |     this->status_set_warning(); | ||||||
|     return; |     return; | ||||||
|   | |||||||
| @@ -7,6 +7,7 @@ from esphome.core import coroutine_with_priority | |||||||
|  |  | ||||||
| AUTO_LOAD = ['web_server_base'] | AUTO_LOAD = ['web_server_base'] | ||||||
| DEPENDENCIES = ['wifi'] | DEPENDENCIES = ['wifi'] | ||||||
|  | CODEOWNERS = ['@OttoWinter'] | ||||||
|  |  | ||||||
| captive_portal_ns = cg.esphome_ns.namespace('captive_portal') | captive_portal_ns = cg.esphome_ns.namespace('captive_portal') | ||||||
| CaptivePortal = captive_portal_ns.class_('CaptivePortal', cg.Component) | CaptivePortal = captive_portal_ns.class_('CaptivePortal', cg.Component) | ||||||
|   | |||||||
| @@ -2,7 +2,7 @@ import esphome.codegen as cg | |||||||
| import esphome.config_validation as cv | import esphome.config_validation as cv | ||||||
| from esphome.components import i2c, sensor | from esphome.components import i2c, sensor | ||||||
| from esphome.const import CONF_ID, ICON_RADIATOR, UNIT_PARTS_PER_MILLION, \ | from esphome.const import CONF_ID, ICON_RADIATOR, UNIT_PARTS_PER_MILLION, \ | ||||||
|     UNIT_PARTS_PER_BILLION, CONF_TEMPERATURE, CONF_HUMIDITY, ICON_PERIODIC_TABLE_CO2 |     UNIT_PARTS_PER_BILLION, CONF_TEMPERATURE, CONF_HUMIDITY, ICON_MOLECULE_CO2 | ||||||
|  |  | ||||||
| DEPENDENCIES = ['i2c'] | DEPENDENCIES = ['i2c'] | ||||||
|  |  | ||||||
| @@ -15,7 +15,7 @@ CONF_BASELINE = 'baseline' | |||||||
|  |  | ||||||
| CONFIG_SCHEMA = cv.Schema({ | CONFIG_SCHEMA = cv.Schema({ | ||||||
|     cv.GenerateID(): cv.declare_id(CCS811Component), |     cv.GenerateID(): cv.declare_id(CCS811Component), | ||||||
|     cv.Required(CONF_ECO2): sensor.sensor_schema(UNIT_PARTS_PER_MILLION, ICON_PERIODIC_TABLE_CO2, |     cv.Required(CONF_ECO2): sensor.sensor_schema(UNIT_PARTS_PER_MILLION, ICON_MOLECULE_CO2, | ||||||
|                                                  0), |                                                  0), | ||||||
|     cv.Required(CONF_TVOC): sensor.sensor_schema(UNIT_PARTS_PER_BILLION, ICON_RADIATOR, 0), |     cv.Required(CONF_TVOC): sensor.sensor_schema(UNIT_PARTS_PER_BILLION, ICON_RADIATOR, 0), | ||||||
|  |  | ||||||
|   | |||||||
| @@ -5,11 +5,12 @@ from esphome.components import mqtt | |||||||
| from esphome.const import CONF_AWAY, CONF_ID, CONF_INTERNAL, CONF_MAX_TEMPERATURE, \ | from esphome.const import CONF_AWAY, CONF_ID, CONF_INTERNAL, CONF_MAX_TEMPERATURE, \ | ||||||
|     CONF_MIN_TEMPERATURE, CONF_MODE, CONF_TARGET_TEMPERATURE, \ |     CONF_MIN_TEMPERATURE, CONF_MODE, CONF_TARGET_TEMPERATURE, \ | ||||||
|     CONF_TARGET_TEMPERATURE_HIGH, CONF_TARGET_TEMPERATURE_LOW, CONF_TEMPERATURE_STEP, CONF_VISUAL, \ |     CONF_TARGET_TEMPERATURE_HIGH, CONF_TARGET_TEMPERATURE_LOW, CONF_TEMPERATURE_STEP, CONF_VISUAL, \ | ||||||
|     CONF_MQTT_ID, CONF_NAME |     CONF_MQTT_ID, CONF_NAME, CONF_FAN_MODE, CONF_SWING_MODE | ||||||
| from esphome.core import CORE, coroutine, coroutine_with_priority | from esphome.core import CORE, coroutine, coroutine_with_priority | ||||||
|  |  | ||||||
| IS_PLATFORM_COMPONENT = True | IS_PLATFORM_COMPONENT = True | ||||||
|  |  | ||||||
|  | CODEOWNERS = ['@esphome/core'] | ||||||
| climate_ns = cg.esphome_ns.namespace('climate') | climate_ns = cg.esphome_ns.namespace('climate') | ||||||
|  |  | ||||||
| Climate = climate_ns.class_('Climate', cg.Nameable) | Climate = climate_ns.class_('Climate', cg.Nameable) | ||||||
| @@ -22,9 +23,35 @@ CLIMATE_MODES = { | |||||||
|     'AUTO': ClimateMode.CLIMATE_MODE_AUTO, |     'AUTO': ClimateMode.CLIMATE_MODE_AUTO, | ||||||
|     'COOL': ClimateMode.CLIMATE_MODE_COOL, |     'COOL': ClimateMode.CLIMATE_MODE_COOL, | ||||||
|     'HEAT': ClimateMode.CLIMATE_MODE_HEAT, |     'HEAT': ClimateMode.CLIMATE_MODE_HEAT, | ||||||
|  |     'DRY': ClimateMode.CLIMATE_MODE_DRY, | ||||||
|  |     'FAN_ONLY': ClimateMode.CLIMATE_MODE_FAN_ONLY, | ||||||
|  | } | ||||||
|  | validate_climate_mode = cv.enum(CLIMATE_MODES, upper=True) | ||||||
|  |  | ||||||
|  | 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, | ||||||
| } | } | ||||||
|  |  | ||||||
| validate_climate_mode = cv.enum(CLIMATE_MODES, upper=True) | validate_climate_fan_mode = cv.enum(CLIMATE_FAN_MODES, upper=True) | ||||||
|  |  | ||||||
|  | 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, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | validate_climate_swing_mode = cv.enum(CLIMATE_SWING_MODES, upper=True) | ||||||
|  |  | ||||||
| # Actions | # Actions | ||||||
| ControlAction = climate_ns.class_('ControlAction', automation.Action) | ControlAction = climate_ns.class_('ControlAction', automation.Action) | ||||||
| @@ -74,6 +101,8 @@ CLIMATE_CONTROL_ACTION_SCHEMA = cv.Schema({ | |||||||
|     cv.Optional(CONF_TARGET_TEMPERATURE_LOW): cv.templatable(cv.temperature), |     cv.Optional(CONF_TARGET_TEMPERATURE_LOW): cv.templatable(cv.temperature), | ||||||
|     cv.Optional(CONF_TARGET_TEMPERATURE_HIGH): cv.templatable(cv.temperature), |     cv.Optional(CONF_TARGET_TEMPERATURE_HIGH): cv.templatable(cv.temperature), | ||||||
|     cv.Optional(CONF_AWAY): cv.templatable(cv.boolean), |     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), | ||||||
| }) | }) | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -96,6 +125,12 @@ def climate_control_to_code(config, action_id, template_arg, args): | |||||||
|     if CONF_AWAY in config: |     if CONF_AWAY in config: | ||||||
|         template_ = yield cg.templatable(config[CONF_AWAY], args, bool) |         template_ = yield cg.templatable(config[CONF_AWAY], args, bool) | ||||||
|         cg.add(var.set_away(template_)) |         cg.add(var.set_away(template_)) | ||||||
|  |     if CONF_FAN_MODE in config: | ||||||
|  |         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) | ||||||
|  |         cg.add(var.set_swing_mode(template_)) | ||||||
|     yield var |     yield var | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -15,6 +15,8 @@ template<typename... Ts> class ControlAction : public Action<Ts...> { | |||||||
|   TEMPLATABLE_VALUE(float, target_temperature_low) |   TEMPLATABLE_VALUE(float, target_temperature_low) | ||||||
|   TEMPLATABLE_VALUE(float, target_temperature_high) |   TEMPLATABLE_VALUE(float, target_temperature_high) | ||||||
|   TEMPLATABLE_VALUE(bool, away) |   TEMPLATABLE_VALUE(bool, away) | ||||||
|  |   TEMPLATABLE_VALUE(ClimateFanMode, fan_mode) | ||||||
|  |   TEMPLATABLE_VALUE(ClimateSwingMode, swing_mode) | ||||||
|  |  | ||||||
|   void play(Ts... x) override { |   void play(Ts... x) override { | ||||||
|     auto call = this->climate_->make_call(); |     auto call = this->climate_->make_call(); | ||||||
| @@ -23,6 +25,8 @@ template<typename... Ts> class ControlAction : public Action<Ts...> { | |||||||
|     call.set_target_temperature_low(this->target_temperature_low_.optional_value(x...)); |     call.set_target_temperature_low(this->target_temperature_low_.optional_value(x...)); | ||||||
|     call.set_target_temperature_high(this->target_temperature_high_.optional_value(x...)); |     call.set_target_temperature_high(this->target_temperature_high_.optional_value(x...)); | ||||||
|     call.set_away(this->away_.optional_value(x...)); |     call.set_away(this->away_.optional_value(x...)); | ||||||
|  |     call.set_fan_mode(this->fan_mode_.optional_value(x...)); | ||||||
|  |     call.set_swing_mode(this->swing_mode_.optional_value(x...)); | ||||||
|     call.perform(); |     call.perform(); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -13,6 +13,14 @@ void ClimateCall::perform() { | |||||||
|     const char *mode_s = climate_mode_to_string(*this->mode_); |     const char *mode_s = climate_mode_to_string(*this->mode_); | ||||||
|     ESP_LOGD(TAG, "  Mode: %s", mode_s); |     ESP_LOGD(TAG, "  Mode: %s", mode_s); | ||||||
|   } |   } | ||||||
|  |   if (this->fan_mode_.has_value()) { | ||||||
|  |     const char *fan_mode_s = climate_fan_mode_to_string(*this->fan_mode_); | ||||||
|  |     ESP_LOGD(TAG, "  Fan: %s", fan_mode_s); | ||||||
|  |   } | ||||||
|  |   if (this->swing_mode_.has_value()) { | ||||||
|  |     const char *swing_mode_s = climate_swing_mode_to_string(*this->swing_mode_); | ||||||
|  |     ESP_LOGD(TAG, "  Swing: %s", swing_mode_s); | ||||||
|  |   } | ||||||
|   if (this->target_temperature_.has_value()) { |   if (this->target_temperature_.has_value()) { | ||||||
|     ESP_LOGD(TAG, "  Target Temperature: %.2f", *this->target_temperature_); |     ESP_LOGD(TAG, "  Target Temperature: %.2f", *this->target_temperature_); | ||||||
|   } |   } | ||||||
| @@ -36,6 +44,20 @@ void ClimateCall::validate_() { | |||||||
|       this->mode_.reset(); |       this->mode_.reset(); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |   if (this->fan_mode_.has_value()) { | ||||||
|  |     auto fan_mode = *this->fan_mode_; | ||||||
|  |     if (!traits.supports_fan_mode(fan_mode)) { | ||||||
|  |       ESP_LOGW(TAG, "  Fan Mode %s is not supported by this device!", climate_fan_mode_to_string(fan_mode)); | ||||||
|  |       this->fan_mode_.reset(); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   if (this->swing_mode_.has_value()) { | ||||||
|  |     auto swing_mode = *this->swing_mode_; | ||||||
|  |     if (!traits.supports_swing_mode(swing_mode)) { | ||||||
|  |       ESP_LOGW(TAG, "  Swing Mode %s is not supported by this device!", climate_swing_mode_to_string(swing_mode)); | ||||||
|  |       this->swing_mode_.reset(); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|   if (this->target_temperature_.has_value()) { |   if (this->target_temperature_.has_value()) { | ||||||
|     auto target = *this->target_temperature_; |     auto target = *this->target_temperature_; | ||||||
|     if (traits.get_supports_two_point_target_temperature()) { |     if (traits.get_supports_two_point_target_temperature()) { | ||||||
| @@ -91,11 +113,63 @@ ClimateCall &ClimateCall::set_mode(const std::string &mode) { | |||||||
|     this->set_mode(CLIMATE_MODE_COOL); |     this->set_mode(CLIMATE_MODE_COOL); | ||||||
|   } else if (str_equals_case_insensitive(mode, "HEAT")) { |   } else if (str_equals_case_insensitive(mode, "HEAT")) { | ||||||
|     this->set_mode(CLIMATE_MODE_HEAT); |     this->set_mode(CLIMATE_MODE_HEAT); | ||||||
|  |   } else if (str_equals_case_insensitive(mode, "FAN_ONLY")) { | ||||||
|  |     this->set_mode(CLIMATE_MODE_FAN_ONLY); | ||||||
|  |   } else if (str_equals_case_insensitive(mode, "DRY")) { | ||||||
|  |     this->set_mode(CLIMATE_MODE_DRY); | ||||||
|   } else { |   } else { | ||||||
|     ESP_LOGW(TAG, "'%s' - Unrecognized mode %s", this->parent_->get_name().c_str(), mode.c_str()); |     ESP_LOGW(TAG, "'%s' - Unrecognized mode %s", this->parent_->get_name().c_str(), mode.c_str()); | ||||||
|   } |   } | ||||||
|   return *this; |   return *this; | ||||||
| } | } | ||||||
|  | ClimateCall &ClimateCall::set_fan_mode(ClimateFanMode fan_mode) { | ||||||
|  |   this->fan_mode_ = fan_mode; | ||||||
|  |   return *this; | ||||||
|  | } | ||||||
|  | ClimateCall &ClimateCall::set_fan_mode(const std::string &fan_mode) { | ||||||
|  |   if (str_equals_case_insensitive(fan_mode, "ON")) { | ||||||
|  |     this->set_fan_mode(CLIMATE_FAN_ON); | ||||||
|  |   } else if (str_equals_case_insensitive(fan_mode, "OFF")) { | ||||||
|  |     this->set_fan_mode(CLIMATE_FAN_OFF); | ||||||
|  |   } else if (str_equals_case_insensitive(fan_mode, "AUTO")) { | ||||||
|  |     this->set_fan_mode(CLIMATE_FAN_AUTO); | ||||||
|  |   } else if (str_equals_case_insensitive(fan_mode, "LOW")) { | ||||||
|  |     this->set_fan_mode(CLIMATE_FAN_LOW); | ||||||
|  |   } else if (str_equals_case_insensitive(fan_mode, "MEDIUM")) { | ||||||
|  |     this->set_fan_mode(CLIMATE_FAN_MEDIUM); | ||||||
|  |   } else if (str_equals_case_insensitive(fan_mode, "HIGH")) { | ||||||
|  |     this->set_fan_mode(CLIMATE_FAN_HIGH); | ||||||
|  |   } else if (str_equals_case_insensitive(fan_mode, "MIDDLE")) { | ||||||
|  |     this->set_fan_mode(CLIMATE_FAN_MIDDLE); | ||||||
|  |   } else if (str_equals_case_insensitive(fan_mode, "FOCUS")) { | ||||||
|  |     this->set_fan_mode(CLIMATE_FAN_FOCUS); | ||||||
|  |   } else if (str_equals_case_insensitive(fan_mode, "DIFFUSE")) { | ||||||
|  |     this->set_fan_mode(CLIMATE_FAN_DIFFUSE); | ||||||
|  |   } else { | ||||||
|  |     ESP_LOGW(TAG, "'%s' - Unrecognized fan mode %s", this->parent_->get_name().c_str(), fan_mode.c_str()); | ||||||
|  |   } | ||||||
|  |   return *this; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | ClimateCall &ClimateCall::set_swing_mode(ClimateSwingMode swing_mode) { | ||||||
|  |   this->swing_mode_ = swing_mode; | ||||||
|  |   return *this; | ||||||
|  | } | ||||||
|  | ClimateCall &ClimateCall::set_swing_mode(const std::string &swing_mode) { | ||||||
|  |   if (str_equals_case_insensitive(swing_mode, "OFF")) { | ||||||
|  |     this->set_swing_mode(CLIMATE_SWING_OFF); | ||||||
|  |   } else if (str_equals_case_insensitive(swing_mode, "BOTH")) { | ||||||
|  |     this->set_swing_mode(CLIMATE_SWING_BOTH); | ||||||
|  |   } else if (str_equals_case_insensitive(swing_mode, "VERTICAL")) { | ||||||
|  |     this->set_swing_mode(CLIMATE_SWING_VERTICAL); | ||||||
|  |   } else if (str_equals_case_insensitive(swing_mode, "HORIZONTAL")) { | ||||||
|  |     this->set_swing_mode(CLIMATE_SWING_HORIZONTAL); | ||||||
|  |   } else { | ||||||
|  |     ESP_LOGW(TAG, "'%s' - Unrecognized swing mode %s", this->parent_->get_name().c_str(), swing_mode.c_str()); | ||||||
|  |   } | ||||||
|  |   return *this; | ||||||
|  | } | ||||||
|  |  | ||||||
| ClimateCall &ClimateCall::set_target_temperature(float target_temperature) { | ClimateCall &ClimateCall::set_target_temperature(float target_temperature) { | ||||||
|   this->target_temperature_ = target_temperature; |   this->target_temperature_ = target_temperature; | ||||||
|   return *this; |   return *this; | ||||||
| @@ -113,6 +187,8 @@ const optional<float> &ClimateCall::get_target_temperature() const { return this | |||||||
| const optional<float> &ClimateCall::get_target_temperature_low() const { return this->target_temperature_low_; } | const optional<float> &ClimateCall::get_target_temperature_low() const { return this->target_temperature_low_; } | ||||||
| const optional<float> &ClimateCall::get_target_temperature_high() const { return this->target_temperature_high_; } | const optional<float> &ClimateCall::get_target_temperature_high() const { return this->target_temperature_high_; } | ||||||
| const optional<bool> &ClimateCall::get_away() const { return this->away_; } | const optional<bool> &ClimateCall::get_away() const { return this->away_; } | ||||||
|  | const optional<ClimateFanMode> &ClimateCall::get_fan_mode() const { return this->fan_mode_; } | ||||||
|  | const optional<ClimateSwingMode> &ClimateCall::get_swing_mode() const { return this->swing_mode_; } | ||||||
| ClimateCall &ClimateCall::set_away(bool away) { | ClimateCall &ClimateCall::set_away(bool away) { | ||||||
|   this->away_ = away; |   this->away_ = away; | ||||||
|   return *this; |   return *this; | ||||||
| @@ -137,6 +213,14 @@ ClimateCall &ClimateCall::set_mode(optional<ClimateMode> mode) { | |||||||
|   this->mode_ = mode; |   this->mode_ = mode; | ||||||
|   return *this; |   return *this; | ||||||
| } | } | ||||||
|  | ClimateCall &ClimateCall::set_fan_mode(optional<ClimateFanMode> fan_mode) { | ||||||
|  |   this->fan_mode_ = fan_mode; | ||||||
|  |   return *this; | ||||||
|  | } | ||||||
|  | ClimateCall &ClimateCall::set_swing_mode(optional<ClimateSwingMode> swing_mode) { | ||||||
|  |   this->swing_mode_ = swing_mode; | ||||||
|  |   return *this; | ||||||
|  | } | ||||||
|  |  | ||||||
| void Climate::add_on_state_callback(std::function<void()> &&callback) { | void Climate::add_on_state_callback(std::function<void()> &&callback) { | ||||||
|   this->state_callback_.add(std::move(callback)); |   this->state_callback_.add(std::move(callback)); | ||||||
| @@ -165,6 +249,12 @@ void Climate::save_state_() { | |||||||
|   if (traits.get_supports_away()) { |   if (traits.get_supports_away()) { | ||||||
|     state.away = this->away; |     state.away = this->away; | ||||||
|   } |   } | ||||||
|  |   if (traits.get_supports_fan_modes()) { | ||||||
|  |     state.fan_mode = this->fan_mode; | ||||||
|  |   } | ||||||
|  |   if (traits.get_supports_swing_modes()) { | ||||||
|  |     state.swing_mode = this->swing_mode; | ||||||
|  |   } | ||||||
|  |  | ||||||
|   this->rtc_.save(&state); |   this->rtc_.save(&state); | ||||||
| } | } | ||||||
| @@ -176,6 +266,12 @@ void Climate::publish_state() { | |||||||
|   if (traits.get_supports_action()) { |   if (traits.get_supports_action()) { | ||||||
|     ESP_LOGD(TAG, "  Action: %s", climate_action_to_string(this->action)); |     ESP_LOGD(TAG, "  Action: %s", climate_action_to_string(this->action)); | ||||||
|   } |   } | ||||||
|  |   if (traits.get_supports_fan_modes()) { | ||||||
|  |     ESP_LOGD(TAG, "  Fan Mode: %s", climate_fan_mode_to_string(this->fan_mode)); | ||||||
|  |   } | ||||||
|  |   if (traits.get_supports_swing_modes()) { | ||||||
|  |     ESP_LOGD(TAG, "  Swing Mode: %s", climate_swing_mode_to_string(this->swing_mode)); | ||||||
|  |   } | ||||||
|   if (traits.get_supports_current_temperature()) { |   if (traits.get_supports_current_temperature()) { | ||||||
|     ESP_LOGD(TAG, "  Current Temperature: %.2f°C", this->current_temperature); |     ESP_LOGD(TAG, "  Current Temperature: %.2f°C", this->current_temperature); | ||||||
|   } |   } | ||||||
| @@ -236,6 +332,12 @@ ClimateCall ClimateDeviceRestoreState::to_call(Climate *climate) { | |||||||
|   if (traits.get_supports_away()) { |   if (traits.get_supports_away()) { | ||||||
|     call.set_away(this->away); |     call.set_away(this->away); | ||||||
|   } |   } | ||||||
|  |   if (traits.get_supports_fan_modes()) { | ||||||
|  |     call.set_fan_mode(this->fan_mode); | ||||||
|  |   } | ||||||
|  |   if (traits.get_supports_swing_modes()) { | ||||||
|  |     call.set_swing_mode(this->swing_mode); | ||||||
|  |   } | ||||||
|   return call; |   return call; | ||||||
| } | } | ||||||
| void ClimateDeviceRestoreState::apply(Climate *climate) { | void ClimateDeviceRestoreState::apply(Climate *climate) { | ||||||
| @@ -250,6 +352,12 @@ void ClimateDeviceRestoreState::apply(Climate *climate) { | |||||||
|   if (traits.get_supports_away()) { |   if (traits.get_supports_away()) { | ||||||
|     climate->away = this->away; |     climate->away = this->away; | ||||||
|   } |   } | ||||||
|  |   if (traits.get_supports_fan_modes()) { | ||||||
|  |     climate->fan_mode = this->fan_mode; | ||||||
|  |   } | ||||||
|  |   if (traits.get_supports_swing_modes()) { | ||||||
|  |     climate->swing_mode = this->swing_mode; | ||||||
|  |   } | ||||||
|   climate->publish_state(); |   climate->publish_state(); | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -64,6 +64,18 @@ class ClimateCall { | |||||||
|   ClimateCall &set_target_temperature_high(optional<float> target_temperature_high); |   ClimateCall &set_target_temperature_high(optional<float> target_temperature_high); | ||||||
|   ClimateCall &set_away(bool away); |   ClimateCall &set_away(bool away); | ||||||
|   ClimateCall &set_away(optional<bool> away); |   ClimateCall &set_away(optional<bool> away); | ||||||
|  |   /// Set the fan mode of the climate device. | ||||||
|  |   ClimateCall &set_fan_mode(ClimateFanMode fan_mode); | ||||||
|  |   /// Set the fan mode of the climate device. | ||||||
|  |   ClimateCall &set_fan_mode(optional<ClimateFanMode> fan_mode); | ||||||
|  |   /// Set the fan mode of the climate device based on a string. | ||||||
|  |   ClimateCall &set_fan_mode(const std::string &fan_mode); | ||||||
|  |   /// Set the swing mode of the climate device. | ||||||
|  |   ClimateCall &set_swing_mode(ClimateSwingMode swing_mode); | ||||||
|  |   /// Set the swing mode of the climate device. | ||||||
|  |   ClimateCall &set_swing_mode(optional<ClimateSwingMode> swing_mode); | ||||||
|  |   /// Set the swing mode of the climate device based on a string. | ||||||
|  |   ClimateCall &set_swing_mode(const std::string &swing_mode); | ||||||
|  |  | ||||||
|   void perform(); |   void perform(); | ||||||
|  |  | ||||||
| @@ -72,6 +84,8 @@ class ClimateCall { | |||||||
|   const optional<float> &get_target_temperature_low() const; |   const optional<float> &get_target_temperature_low() const; | ||||||
|   const optional<float> &get_target_temperature_high() const; |   const optional<float> &get_target_temperature_high() const; | ||||||
|   const optional<bool> &get_away() const; |   const optional<bool> &get_away() const; | ||||||
|  |   const optional<ClimateFanMode> &get_fan_mode() const; | ||||||
|  |   const optional<ClimateSwingMode> &get_swing_mode() const; | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   void validate_(); |   void validate_(); | ||||||
| @@ -82,12 +96,16 @@ class ClimateCall { | |||||||
|   optional<float> target_temperature_low_; |   optional<float> target_temperature_low_; | ||||||
|   optional<float> target_temperature_high_; |   optional<float> target_temperature_high_; | ||||||
|   optional<bool> away_; |   optional<bool> away_; | ||||||
|  |   optional<ClimateFanMode> fan_mode_; | ||||||
|  |   optional<ClimateSwingMode> swing_mode_; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| /// Struct used to save the state of the climate device in restore memory. | /// Struct used to save the state of the climate device in restore memory. | ||||||
| struct ClimateDeviceRestoreState { | struct ClimateDeviceRestoreState { | ||||||
|   ClimateMode mode; |   ClimateMode mode; | ||||||
|   bool away; |   bool away; | ||||||
|  |   ClimateFanMode fan_mode; | ||||||
|  |   ClimateSwingMode swing_mode; | ||||||
|   union { |   union { | ||||||
|     float target_temperature; |     float target_temperature; | ||||||
|     struct { |     struct { | ||||||
| @@ -149,6 +167,12 @@ class Climate : public Nameable { | |||||||
|    */ |    */ | ||||||
|   bool away{false}; |   bool away{false}; | ||||||
|  |  | ||||||
|  |   /// The active fan mode of the climate device. | ||||||
|  |   ClimateFanMode fan_mode; | ||||||
|  |  | ||||||
|  |   /// The active swing mode of the climate device. | ||||||
|  |   ClimateSwingMode swing_mode; | ||||||
|  |  | ||||||
|   /** Add a callback for the climate device state, each time the state of the climate device is updated |   /** Add a callback for the climate device state, each time the state of the climate device is updated | ||||||
|    * (using publish_state), this callback will be called. |    * (using publish_state), this callback will be called. | ||||||
|    * |    * | ||||||
|   | |||||||
| @@ -13,6 +13,10 @@ const char *climate_mode_to_string(ClimateMode mode) { | |||||||
|       return "COOL"; |       return "COOL"; | ||||||
|     case CLIMATE_MODE_HEAT: |     case CLIMATE_MODE_HEAT: | ||||||
|       return "HEAT"; |       return "HEAT"; | ||||||
|  |     case CLIMATE_MODE_FAN_ONLY: | ||||||
|  |       return "FAN_ONLY"; | ||||||
|  |     case CLIMATE_MODE_DRY: | ||||||
|  |       return "DRY"; | ||||||
|     default: |     default: | ||||||
|       return "UNKNOWN"; |       return "UNKNOWN"; | ||||||
|   } |   } | ||||||
| @@ -25,6 +29,52 @@ const char *climate_action_to_string(ClimateAction action) { | |||||||
|       return "COOLING"; |       return "COOLING"; | ||||||
|     case CLIMATE_ACTION_HEATING: |     case CLIMATE_ACTION_HEATING: | ||||||
|       return "HEATING"; |       return "HEATING"; | ||||||
|  |     case CLIMATE_ACTION_IDLE: | ||||||
|  |       return "IDLE"; | ||||||
|  |     case CLIMATE_ACTION_DRYING: | ||||||
|  |       return "DRYING"; | ||||||
|  |     case CLIMATE_ACTION_FAN: | ||||||
|  |       return "FAN"; | ||||||
|  |     default: | ||||||
|  |       return "UNKNOWN"; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | const char *climate_fan_mode_to_string(ClimateFanMode fan_mode) { | ||||||
|  |   switch (fan_mode) { | ||||||
|  |     case climate::CLIMATE_FAN_ON: | ||||||
|  |       return "ON"; | ||||||
|  |     case climate::CLIMATE_FAN_OFF: | ||||||
|  |       return "OFF"; | ||||||
|  |     case climate::CLIMATE_FAN_AUTO: | ||||||
|  |       return "AUTO"; | ||||||
|  |     case climate::CLIMATE_FAN_LOW: | ||||||
|  |       return "LOW"; | ||||||
|  |     case climate::CLIMATE_FAN_MEDIUM: | ||||||
|  |       return "MEDIUM"; | ||||||
|  |     case climate::CLIMATE_FAN_HIGH: | ||||||
|  |       return "HIGH"; | ||||||
|  |     case climate::CLIMATE_FAN_MIDDLE: | ||||||
|  |       return "MIDDLE"; | ||||||
|  |     case climate::CLIMATE_FAN_FOCUS: | ||||||
|  |       return "FOCUS"; | ||||||
|  |     case climate::CLIMATE_FAN_DIFFUSE: | ||||||
|  |       return "DIFFUSE"; | ||||||
|  |     default: | ||||||
|  |       return "UNKNOWN"; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | const char *climate_swing_mode_to_string(ClimateSwingMode swing_mode) { | ||||||
|  |   switch (swing_mode) { | ||||||
|  |     case climate::CLIMATE_SWING_OFF: | ||||||
|  |       return "OFF"; | ||||||
|  |     case climate::CLIMATE_SWING_BOTH: | ||||||
|  |       return "BOTH"; | ||||||
|  |     case climate::CLIMATE_SWING_VERTICAL: | ||||||
|  |       return "VERTICAL"; | ||||||
|  |     case climate::CLIMATE_SWING_HORIZONTAL: | ||||||
|  |       return "HORIZONTAL"; | ||||||
|     default: |     default: | ||||||
|       return "UNKNOWN"; |       return "UNKNOWN"; | ||||||
|   } |   } | ||||||
|   | |||||||
| @@ -15,6 +15,10 @@ enum ClimateMode : uint8_t { | |||||||
|   CLIMATE_MODE_COOL = 2, |   CLIMATE_MODE_COOL = 2, | ||||||
|   /// The climate device is manually set to heat mode (not in auto mode!) |   /// The climate device is manually set to heat mode (not in auto mode!) | ||||||
|   CLIMATE_MODE_HEAT = 3, |   CLIMATE_MODE_HEAT = 3, | ||||||
|  |   /// The climate device is manually set to fan only mode | ||||||
|  |   CLIMATE_MODE_FAN_ONLY = 4, | ||||||
|  |   /// The climate device is manually set to dry mode | ||||||
|  |   CLIMATE_MODE_DRY = 5, | ||||||
| }; | }; | ||||||
|  |  | ||||||
| /// Enum for the current action of the climate device. Values match those of ClimateMode. | /// Enum for the current action of the climate device. Values match those of ClimateMode. | ||||||
| @@ -25,11 +29,59 @@ enum ClimateAction : uint8_t { | |||||||
|   CLIMATE_ACTION_COOLING = 2, |   CLIMATE_ACTION_COOLING = 2, | ||||||
|   /// The climate device is actively heating (usually in heat or auto mode) |   /// The climate device is actively heating (usually in heat or auto mode) | ||||||
|   CLIMATE_ACTION_HEATING = 3, |   CLIMATE_ACTION_HEATING = 3, | ||||||
|  |   /// The climate device is idle (monitoring climate but no action needed) | ||||||
|  |   CLIMATE_ACTION_IDLE = 4, | ||||||
|  |   /// The climate device is drying (either mode DRY or AUTO) | ||||||
|  |   CLIMATE_ACTION_DRYING = 5, | ||||||
|  |   /// The climate device is in fan only mode (either mode FAN_ONLY or AUTO) | ||||||
|  |   CLIMATE_ACTION_FAN = 6, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /// Enum for all modes a climate fan can be in | ||||||
|  | enum ClimateFanMode : uint8_t { | ||||||
|  |   /// The fan mode is set to On | ||||||
|  |   CLIMATE_FAN_ON = 0, | ||||||
|  |   /// The fan mode is set to Off | ||||||
|  |   CLIMATE_FAN_OFF = 1, | ||||||
|  |   /// The fan mode is set to Auto | ||||||
|  |   CLIMATE_FAN_AUTO = 2, | ||||||
|  |   /// The fan mode is set to Low | ||||||
|  |   CLIMATE_FAN_LOW = 3, | ||||||
|  |   /// The fan mode is set to Medium | ||||||
|  |   CLIMATE_FAN_MEDIUM = 4, | ||||||
|  |   /// The fan mode is set to High | ||||||
|  |   CLIMATE_FAN_HIGH = 5, | ||||||
|  |   /// The fan mode is set to Middle | ||||||
|  |   CLIMATE_FAN_MIDDLE = 6, | ||||||
|  |   /// The fan mode is set to Focus | ||||||
|  |   CLIMATE_FAN_FOCUS = 7, | ||||||
|  |   /// The fan mode is set to Diffuse | ||||||
|  |   CLIMATE_FAN_DIFFUSE = 8, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /// Enum for all modes a climate swing can be in | ||||||
|  | enum ClimateSwingMode : uint8_t { | ||||||
|  |   /// The sing mode is set to Off | ||||||
|  |   CLIMATE_SWING_OFF = 0, | ||||||
|  |   /// The fan mode is set to Both | ||||||
|  |   CLIMATE_SWING_BOTH = 1, | ||||||
|  |   /// The fan mode is set to Vertical | ||||||
|  |   CLIMATE_SWING_VERTICAL = 2, | ||||||
|  |   /// The fan mode is set to Horizontal | ||||||
|  |   CLIMATE_SWING_HORIZONTAL = 3, | ||||||
| }; | }; | ||||||
|  |  | ||||||
| /// Convert the given ClimateMode to a human-readable string. | /// Convert the given ClimateMode to a human-readable string. | ||||||
| const char *climate_mode_to_string(ClimateMode mode); | const char *climate_mode_to_string(ClimateMode mode); | ||||||
|  |  | ||||||
|  | /// Convert the given ClimateAction to a human-readable string. | ||||||
| const char *climate_action_to_string(ClimateAction action); | const char *climate_action_to_string(ClimateAction action); | ||||||
|  |  | ||||||
|  | /// Convert the given ClimateFanMode to a human-readable string. | ||||||
|  | const char *climate_fan_mode_to_string(ClimateFanMode mode); | ||||||
|  |  | ||||||
|  | /// Convert the given ClimateSwingMode to a human-readable string. | ||||||
|  | const char *climate_swing_mode_to_string(ClimateSwingMode mode); | ||||||
|  |  | ||||||
| }  // namespace climate | }  // namespace climate | ||||||
| }  // namespace esphome | }  // namespace esphome | ||||||
|   | |||||||
| @@ -14,6 +14,10 @@ bool ClimateTraits::supports_mode(ClimateMode mode) const { | |||||||
|       return this->supports_cool_mode_; |       return this->supports_cool_mode_; | ||||||
|     case CLIMATE_MODE_HEAT: |     case CLIMATE_MODE_HEAT: | ||||||
|       return this->supports_heat_mode_; |       return this->supports_heat_mode_; | ||||||
|  |     case CLIMATE_MODE_FAN_ONLY: | ||||||
|  |       return this->supports_fan_only_mode_; | ||||||
|  |     case CLIMATE_MODE_DRY: | ||||||
|  |       return this->supports_dry_mode_; | ||||||
|     default: |     default: | ||||||
|       return false; |       return false; | ||||||
|   } |   } | ||||||
| @@ -29,6 +33,10 @@ void ClimateTraits::set_supports_two_point_target_temperature(bool supports_two_ | |||||||
| void ClimateTraits::set_supports_auto_mode(bool supports_auto_mode) { supports_auto_mode_ = supports_auto_mode; } | void ClimateTraits::set_supports_auto_mode(bool supports_auto_mode) { supports_auto_mode_ = supports_auto_mode; } | ||||||
| void ClimateTraits::set_supports_cool_mode(bool supports_cool_mode) { supports_cool_mode_ = supports_cool_mode; } | void ClimateTraits::set_supports_cool_mode(bool supports_cool_mode) { supports_cool_mode_ = supports_cool_mode; } | ||||||
| void ClimateTraits::set_supports_heat_mode(bool supports_heat_mode) { supports_heat_mode_ = supports_heat_mode; } | void ClimateTraits::set_supports_heat_mode(bool supports_heat_mode) { supports_heat_mode_ = supports_heat_mode; } | ||||||
|  | void ClimateTraits::set_supports_fan_only_mode(bool supports_fan_only_mode) { | ||||||
|  |   supports_fan_only_mode_ = supports_fan_only_mode; | ||||||
|  | } | ||||||
|  | void ClimateTraits::set_supports_dry_mode(bool supports_dry_mode) { supports_dry_mode_ = supports_dry_mode; } | ||||||
| void ClimateTraits::set_supports_away(bool supports_away) { supports_away_ = supports_away; } | void ClimateTraits::set_supports_away(bool supports_away) { supports_away_ = supports_away; } | ||||||
| void ClimateTraits::set_supports_action(bool supports_action) { supports_action_ = supports_action; } | void ClimateTraits::set_supports_action(bool supports_action) { supports_action_ = supports_action; } | ||||||
| float ClimateTraits::get_visual_min_temperature() const { return visual_min_temperature_; } | float ClimateTraits::get_visual_min_temperature() const { return visual_min_temperature_; } | ||||||
| @@ -55,5 +63,91 @@ void ClimateTraits::set_visual_temperature_step(float temperature_step) { visual | |||||||
| bool ClimateTraits::get_supports_away() const { return supports_away_; } | bool ClimateTraits::get_supports_away() const { return supports_away_; } | ||||||
| bool ClimateTraits::get_supports_action() const { return supports_action_; } | bool ClimateTraits::get_supports_action() const { return supports_action_; } | ||||||
|  |  | ||||||
|  | void ClimateTraits::set_supports_fan_mode_on(bool supports_fan_mode_on) { | ||||||
|  |   this->supports_fan_mode_on_ = supports_fan_mode_on; | ||||||
|  | } | ||||||
|  | void ClimateTraits::set_supports_fan_mode_off(bool supports_fan_mode_off) { | ||||||
|  |   this->supports_fan_mode_off_ = supports_fan_mode_off; | ||||||
|  | } | ||||||
|  | void ClimateTraits::set_supports_fan_mode_auto(bool supports_fan_mode_auto) { | ||||||
|  |   this->supports_fan_mode_auto_ = supports_fan_mode_auto; | ||||||
|  | } | ||||||
|  | void ClimateTraits::set_supports_fan_mode_low(bool supports_fan_mode_low) { | ||||||
|  |   this->supports_fan_mode_low_ = supports_fan_mode_low; | ||||||
|  | } | ||||||
|  | void ClimateTraits::set_supports_fan_mode_medium(bool supports_fan_mode_medium) { | ||||||
|  |   this->supports_fan_mode_medium_ = supports_fan_mode_medium; | ||||||
|  | } | ||||||
|  | void ClimateTraits::set_supports_fan_mode_high(bool supports_fan_mode_high) { | ||||||
|  |   this->supports_fan_mode_high_ = supports_fan_mode_high; | ||||||
|  | } | ||||||
|  | void ClimateTraits::set_supports_fan_mode_middle(bool supports_fan_mode_middle) { | ||||||
|  |   this->supports_fan_mode_middle_ = supports_fan_mode_middle; | ||||||
|  | } | ||||||
|  | void ClimateTraits::set_supports_fan_mode_focus(bool supports_fan_mode_focus) { | ||||||
|  |   this->supports_fan_mode_focus_ = supports_fan_mode_focus; | ||||||
|  | } | ||||||
|  | void ClimateTraits::set_supports_fan_mode_diffuse(bool supports_fan_mode_diffuse) { | ||||||
|  |   this->supports_fan_mode_diffuse_ = supports_fan_mode_diffuse; | ||||||
|  | } | ||||||
|  | bool ClimateTraits::supports_fan_mode(ClimateFanMode fan_mode) const { | ||||||
|  |   switch (fan_mode) { | ||||||
|  |     case climate::CLIMATE_FAN_ON: | ||||||
|  |       return this->supports_fan_mode_on_; | ||||||
|  |     case climate::CLIMATE_FAN_OFF: | ||||||
|  |       return this->supports_fan_mode_off_; | ||||||
|  |     case climate::CLIMATE_FAN_AUTO: | ||||||
|  |       return this->supports_fan_mode_auto_; | ||||||
|  |     case climate::CLIMATE_FAN_LOW: | ||||||
|  |       return this->supports_fan_mode_low_; | ||||||
|  |     case climate::CLIMATE_FAN_MEDIUM: | ||||||
|  |       return this->supports_fan_mode_medium_; | ||||||
|  |     case climate::CLIMATE_FAN_HIGH: | ||||||
|  |       return this->supports_fan_mode_high_; | ||||||
|  |     case climate::CLIMATE_FAN_MIDDLE: | ||||||
|  |       return this->supports_fan_mode_middle_; | ||||||
|  |     case climate::CLIMATE_FAN_FOCUS: | ||||||
|  |       return this->supports_fan_mode_focus_; | ||||||
|  |     case climate::CLIMATE_FAN_DIFFUSE: | ||||||
|  |       return this->supports_fan_mode_diffuse_; | ||||||
|  |     default: | ||||||
|  |       return false; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | bool ClimateTraits::get_supports_fan_modes() const { | ||||||
|  |   return this->supports_fan_mode_on_ || this->supports_fan_mode_off_ || this->supports_fan_mode_auto_ || | ||||||
|  |          this->supports_fan_mode_low_ || this->supports_fan_mode_medium_ || this->supports_fan_mode_high_ || | ||||||
|  |          this->supports_fan_mode_middle_ || this->supports_fan_mode_focus_ || this->supports_fan_mode_diffuse_; | ||||||
|  | } | ||||||
|  | void ClimateTraits::set_supports_swing_mode_off(bool supports_swing_mode_off) { | ||||||
|  |   this->supports_swing_mode_off_ = supports_swing_mode_off; | ||||||
|  | } | ||||||
|  | void ClimateTraits::set_supports_swing_mode_both(bool supports_swing_mode_both) { | ||||||
|  |   this->supports_swing_mode_both_ = supports_swing_mode_both; | ||||||
|  | } | ||||||
|  | void ClimateTraits::set_supports_swing_mode_vertical(bool supports_swing_mode_vertical) { | ||||||
|  |   this->supports_swing_mode_vertical_ = supports_swing_mode_vertical; | ||||||
|  | } | ||||||
|  | void ClimateTraits::set_supports_swing_mode_horizontal(bool supports_swing_mode_horizontal) { | ||||||
|  |   this->supports_swing_mode_horizontal_ = supports_swing_mode_horizontal; | ||||||
|  | } | ||||||
|  | bool ClimateTraits::supports_swing_mode(ClimateSwingMode swing_mode) const { | ||||||
|  |   switch (swing_mode) { | ||||||
|  |     case climate::CLIMATE_SWING_OFF: | ||||||
|  |       return this->supports_swing_mode_off_; | ||||||
|  |     case climate::CLIMATE_SWING_BOTH: | ||||||
|  |       return this->supports_swing_mode_both_; | ||||||
|  |     case climate::CLIMATE_SWING_VERTICAL: | ||||||
|  |       return this->supports_swing_mode_vertical_; | ||||||
|  |     case climate::CLIMATE_SWING_HORIZONTAL: | ||||||
|  |       return this->supports_swing_mode_horizontal_; | ||||||
|  |     default: | ||||||
|  |       return false; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | bool ClimateTraits::get_supports_swing_modes() const { | ||||||
|  |   return this->supports_swing_mode_off_ || this->supports_swing_mode_both_ || supports_swing_mode_vertical_ || | ||||||
|  |          supports_swing_mode_horizontal_; | ||||||
|  | } | ||||||
| }  // namespace climate | }  // namespace climate | ||||||
| }  // namespace esphome | }  // namespace esphome | ||||||
|   | |||||||
| @@ -21,10 +21,16 @@ namespace climate { | |||||||
|  *    - auto mode (automatic control) |  *    - auto mode (automatic control) | ||||||
|  *    - cool mode (lowers current temperature) |  *    - cool mode (lowers current temperature) | ||||||
|  *    - heat mode (increases current temperature) |  *    - heat mode (increases current temperature) | ||||||
|  |  *    - dry mode (removes humidity from air) | ||||||
|  |  *    - fan mode (only turns on fan) | ||||||
|  *  - supports away - away mode means that the climate device supports two different |  *  - supports away - away mode means that the climate device supports two different | ||||||
|  *      target temperature settings: one target temp setting for "away" mode and one for non-away mode. |  *      target temperature settings: one target temp setting for "away" mode and one for non-away mode. | ||||||
|  *  - supports action - if the climate device supports reporting the active |  *  - supports action - if the climate device supports reporting the active | ||||||
|  *    current action of the device with the action property. |  *    current action of the device with the action property. | ||||||
|  |  *  - supports fan modes - optionally, if it has a fan which can be configured in different ways: | ||||||
|  |  *    - on, off, auto, high, medium, low, middle, focus, diffuse | ||||||
|  |  *  - supports swing modes - optionally, if it has a swing which can be configured in different ways: | ||||||
|  |  *    - off, both, vertical, horizontal | ||||||
|  * |  * | ||||||
|  * This class also contains static data for the climate device display: |  * This class also contains static data for the climate device display: | ||||||
|  *  - visual min/max temperature - tells the frontend what range of temperatures the climate device |  *  - visual min/max temperature - tells the frontend what range of temperatures the climate device | ||||||
| @@ -41,11 +47,30 @@ class ClimateTraits { | |||||||
|   void set_supports_auto_mode(bool supports_auto_mode); |   void set_supports_auto_mode(bool supports_auto_mode); | ||||||
|   void set_supports_cool_mode(bool supports_cool_mode); |   void set_supports_cool_mode(bool supports_cool_mode); | ||||||
|   void set_supports_heat_mode(bool supports_heat_mode); |   void set_supports_heat_mode(bool supports_heat_mode); | ||||||
|  |   void set_supports_fan_only_mode(bool supports_fan_only_mode); | ||||||
|  |   void set_supports_dry_mode(bool supports_dry_mode); | ||||||
|   void set_supports_away(bool supports_away); |   void set_supports_away(bool supports_away); | ||||||
|   bool get_supports_away() const; |   bool get_supports_away() const; | ||||||
|   void set_supports_action(bool supports_action); |   void set_supports_action(bool supports_action); | ||||||
|   bool get_supports_action() const; |   bool get_supports_action() const; | ||||||
|   bool supports_mode(ClimateMode mode) const; |   bool supports_mode(ClimateMode mode) const; | ||||||
|  |   void set_supports_fan_mode_on(bool supports_fan_mode_on); | ||||||
|  |   void set_supports_fan_mode_off(bool supports_fan_mode_off); | ||||||
|  |   void set_supports_fan_mode_auto(bool supports_fan_mode_auto); | ||||||
|  |   void set_supports_fan_mode_low(bool supports_fan_mode_low); | ||||||
|  |   void set_supports_fan_mode_medium(bool supports_fan_mode_medium); | ||||||
|  |   void set_supports_fan_mode_high(bool supports_fan_mode_high); | ||||||
|  |   void set_supports_fan_mode_middle(bool supports_fan_mode_middle); | ||||||
|  |   void set_supports_fan_mode_focus(bool supports_fan_mode_focus); | ||||||
|  |   void set_supports_fan_mode_diffuse(bool supports_fan_mode_diffuse); | ||||||
|  |   bool supports_fan_mode(ClimateFanMode fan_mode) const; | ||||||
|  |   bool get_supports_fan_modes() const; | ||||||
|  |   void set_supports_swing_mode_off(bool supports_swing_mode_off); | ||||||
|  |   void set_supports_swing_mode_both(bool supports_swing_mode_both); | ||||||
|  |   void set_supports_swing_mode_vertical(bool supports_swing_mode_vertical); | ||||||
|  |   void set_supports_swing_mode_horizontal(bool supports_swing_mode_horizontal); | ||||||
|  |   bool supports_swing_mode(ClimateSwingMode swing_mode) const; | ||||||
|  |   bool get_supports_swing_modes() const; | ||||||
|  |  | ||||||
|   float get_visual_min_temperature() const; |   float get_visual_min_temperature() const; | ||||||
|   void set_visual_min_temperature(float visual_min_temperature); |   void set_visual_min_temperature(float visual_min_temperature); | ||||||
| @@ -61,8 +86,23 @@ class ClimateTraits { | |||||||
|   bool supports_auto_mode_{false}; |   bool supports_auto_mode_{false}; | ||||||
|   bool supports_cool_mode_{false}; |   bool supports_cool_mode_{false}; | ||||||
|   bool supports_heat_mode_{false}; |   bool supports_heat_mode_{false}; | ||||||
|  |   bool supports_fan_only_mode_{false}; | ||||||
|  |   bool supports_dry_mode_{false}; | ||||||
|   bool supports_away_{false}; |   bool supports_away_{false}; | ||||||
|   bool supports_action_{false}; |   bool supports_action_{false}; | ||||||
|  |   bool supports_fan_mode_on_{false}; | ||||||
|  |   bool supports_fan_mode_off_{false}; | ||||||
|  |   bool supports_fan_mode_auto_{false}; | ||||||
|  |   bool supports_fan_mode_low_{false}; | ||||||
|  |   bool supports_fan_mode_medium_{false}; | ||||||
|  |   bool supports_fan_mode_high_{false}; | ||||||
|  |   bool supports_fan_mode_middle_{false}; | ||||||
|  |   bool supports_fan_mode_focus_{false}; | ||||||
|  |   bool supports_fan_mode_diffuse_{false}; | ||||||
|  |   bool supports_swing_mode_off_{false}; | ||||||
|  |   bool supports_swing_mode_both_{false}; | ||||||
|  |   bool supports_swing_mode_vertical_{false}; | ||||||
|  |   bool supports_swing_mode_horizontal_{false}; | ||||||
|  |  | ||||||
|   float visual_min_temperature_{10}; |   float visual_min_temperature_{10}; | ||||||
|   float visual_max_temperature_{30}; |   float visual_max_temperature_{30}; | ||||||
|   | |||||||
| @@ -6,6 +6,7 @@ from esphome.const import CONF_SUPPORTS_COOL, CONF_SUPPORTS_HEAT, CONF_SENSOR | |||||||
| from esphome.core import coroutine | from esphome.core import coroutine | ||||||
|  |  | ||||||
| AUTO_LOAD = ['sensor', 'remote_base'] | AUTO_LOAD = ['sensor', 'remote_base'] | ||||||
|  | CODEOWNERS = ['@glmnet'] | ||||||
|  |  | ||||||
| climate_ir_ns = cg.esphome_ns.namespace('climate_ir') | climate_ir_ns = cg.esphome_ns.namespace('climate_ir') | ||||||
| ClimateIR = climate_ir_ns.class_('ClimateIR', climate.Climate, cg.Component, | ClimateIR = climate_ir_ns.class_('ClimateIR', climate.Climate, cg.Component, | ||||||
|   | |||||||
| @@ -12,11 +12,60 @@ climate::ClimateTraits ClimateIR::traits() { | |||||||
|   traits.set_supports_auto_mode(true); |   traits.set_supports_auto_mode(true); | ||||||
|   traits.set_supports_cool_mode(this->supports_cool_); |   traits.set_supports_cool_mode(this->supports_cool_); | ||||||
|   traits.set_supports_heat_mode(this->supports_heat_); |   traits.set_supports_heat_mode(this->supports_heat_); | ||||||
|  |   traits.set_supports_dry_mode(this->supports_dry_); | ||||||
|  |   traits.set_supports_fan_only_mode(this->supports_fan_only_); | ||||||
|   traits.set_supports_two_point_target_temperature(false); |   traits.set_supports_two_point_target_temperature(false); | ||||||
|   traits.set_supports_away(false); |   traits.set_supports_away(false); | ||||||
|   traits.set_visual_min_temperature(this->minimum_temperature_); |   traits.set_visual_min_temperature(this->minimum_temperature_); | ||||||
|   traits.set_visual_max_temperature(this->maximum_temperature_); |   traits.set_visual_max_temperature(this->maximum_temperature_); | ||||||
|   traits.set_visual_temperature_step(this->temperature_step_); |   traits.set_visual_temperature_step(this->temperature_step_); | ||||||
|  |   for (auto fan_mode : this->fan_modes_) { | ||||||
|  |     switch (fan_mode) { | ||||||
|  |       case climate::CLIMATE_FAN_AUTO: | ||||||
|  |         traits.set_supports_fan_mode_auto(true); | ||||||
|  |         break; | ||||||
|  |       case climate::CLIMATE_FAN_DIFFUSE: | ||||||
|  |         traits.set_supports_fan_mode_diffuse(true); | ||||||
|  |         break; | ||||||
|  |       case climate::CLIMATE_FAN_FOCUS: | ||||||
|  |         traits.set_supports_fan_mode_focus(true); | ||||||
|  |         break; | ||||||
|  |       case climate::CLIMATE_FAN_HIGH: | ||||||
|  |         traits.set_supports_fan_mode_high(true); | ||||||
|  |         break; | ||||||
|  |       case climate::CLIMATE_FAN_LOW: | ||||||
|  |         traits.set_supports_fan_mode_low(true); | ||||||
|  |         break; | ||||||
|  |       case climate::CLIMATE_FAN_MEDIUM: | ||||||
|  |         traits.set_supports_fan_mode_medium(true); | ||||||
|  |         break; | ||||||
|  |       case climate::CLIMATE_FAN_MIDDLE: | ||||||
|  |         traits.set_supports_fan_mode_middle(true); | ||||||
|  |         break; | ||||||
|  |       case climate::CLIMATE_FAN_OFF: | ||||||
|  |         traits.set_supports_fan_mode_off(true); | ||||||
|  |         break; | ||||||
|  |       case climate::CLIMATE_FAN_ON: | ||||||
|  |         traits.set_supports_fan_mode_on(true); | ||||||
|  |         break; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   for (auto swing_mode : this->swing_modes_) { | ||||||
|  |     switch (swing_mode) { | ||||||
|  |       case climate::CLIMATE_SWING_OFF: | ||||||
|  |         traits.set_supports_swing_mode_off(true); | ||||||
|  |         break; | ||||||
|  |       case climate::CLIMATE_SWING_BOTH: | ||||||
|  |         traits.set_supports_swing_mode_both(true); | ||||||
|  |         break; | ||||||
|  |       case climate::CLIMATE_SWING_VERTICAL: | ||||||
|  |         traits.set_supports_swing_mode_vertical(true); | ||||||
|  |         break; | ||||||
|  |       case climate::CLIMATE_SWING_HORIZONTAL: | ||||||
|  |         traits.set_supports_swing_mode_horizontal(true); | ||||||
|  |         break; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|   return traits; |   return traits; | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -40,6 +89,8 @@ void ClimateIR::setup() { | |||||||
|     // initialize target temperature to some value so that it's not NAN |     // initialize target temperature to some value so that it's not NAN | ||||||
|     this->target_temperature = |     this->target_temperature = | ||||||
|         roundf(clamp(this->current_temperature, this->minimum_temperature_, this->maximum_temperature_)); |         roundf(clamp(this->current_temperature, this->minimum_temperature_, this->maximum_temperature_)); | ||||||
|  |     this->fan_mode = climate::CLIMATE_FAN_AUTO; | ||||||
|  |     this->swing_mode = climate::CLIMATE_SWING_OFF; | ||||||
|   } |   } | ||||||
|   // Never send nan to HA |   // Never send nan to HA | ||||||
|   if (isnan(this->target_temperature)) |   if (isnan(this->target_temperature)) | ||||||
| @@ -51,7 +102,10 @@ void ClimateIR::control(const climate::ClimateCall &call) { | |||||||
|     this->mode = *call.get_mode(); |     this->mode = *call.get_mode(); | ||||||
|   if (call.get_target_temperature().has_value()) |   if (call.get_target_temperature().has_value()) | ||||||
|     this->target_temperature = *call.get_target_temperature(); |     this->target_temperature = *call.get_target_temperature(); | ||||||
|  |   if (call.get_fan_mode().has_value()) | ||||||
|  |     this->fan_mode = *call.get_fan_mode(); | ||||||
|  |   if (call.get_swing_mode().has_value()) | ||||||
|  |     this->swing_mode = *call.get_swing_mode(); | ||||||
|   this->transmit_state(); |   this->transmit_state(); | ||||||
|   this->publish_state(); |   this->publish_state(); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -18,10 +18,17 @@ namespace climate_ir { | |||||||
| */ | */ | ||||||
| class ClimateIR : public climate::Climate, public Component, public remote_base::RemoteReceiverListener { | class ClimateIR : public climate::Climate, public Component, public remote_base::RemoteReceiverListener { | ||||||
|  public: |  public: | ||||||
|   ClimateIR(float minimum_temperature, float maximum_temperature, float temperature_step = 1.0f) { |   ClimateIR(float minimum_temperature, float maximum_temperature, float temperature_step = 1.0f, | ||||||
|  |             bool supports_dry = false, bool supports_fan_only = false, | ||||||
|  |             std::vector<climate::ClimateFanMode> fan_modes = {}, | ||||||
|  |             std::vector<climate::ClimateSwingMode> swing_modes = {}) { | ||||||
|     this->minimum_temperature_ = minimum_temperature; |     this->minimum_temperature_ = minimum_temperature; | ||||||
|     this->maximum_temperature_ = maximum_temperature; |     this->maximum_temperature_ = maximum_temperature; | ||||||
|     this->temperature_step_ = temperature_step; |     this->temperature_step_ = temperature_step; | ||||||
|  |     this->supports_dry_ = supports_dry; | ||||||
|  |     this->supports_fan_only_ = supports_fan_only; | ||||||
|  |     this->fan_modes_ = fan_modes; | ||||||
|  |     this->swing_modes_ = swing_modes; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   void setup() override; |   void setup() override; | ||||||
| @@ -49,6 +56,10 @@ class ClimateIR : public climate::Climate, public Component, public remote_base: | |||||||
|  |  | ||||||
|   bool supports_cool_{true}; |   bool supports_cool_{true}; | ||||||
|   bool supports_heat_{true}; |   bool supports_heat_{true}; | ||||||
|  |   bool supports_dry_{false}; | ||||||
|  |   bool supports_fan_only_{false}; | ||||||
|  |   std::vector<climate::ClimateFanMode> fan_modes_ = {}; | ||||||
|  |   std::vector<climate::ClimateSwingMode> swing_modes_ = {}; | ||||||
|  |  | ||||||
|   remote_transmitter::RemoteTransmitterComponent *transmitter_; |   remote_transmitter::RemoteTransmitterComponent *transmitter_; | ||||||
|   sensor::Sensor *sensor_{nullptr}; |   sensor::Sensor *sensor_{nullptr}; | ||||||
|   | |||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user