mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 07:03:55 +00:00 
			
		
		
		
	Merge branch 'dev' into ble-server-controller
This commit is contained in:
		
							
								
								
									
										15
									
								
								.clang-tidy
									
									
									
									
									
								
							
							
						
						
									
										15
									
								
								.clang-tidy
									
									
									
									
									
								
							| @@ -5,11 +5,8 @@ Checks: >- | |||||||
|   -altera-*, |   -altera-*, | ||||||
|   -android-*, |   -android-*, | ||||||
|   -boost-*, |   -boost-*, | ||||||
|   -bugprone-branch-clone, |  | ||||||
|   -bugprone-easily-swappable-parameters, |  | ||||||
|   -bugprone-narrowing-conversions, |   -bugprone-narrowing-conversions, | ||||||
|   -bugprone-signed-char-misuse, |   -bugprone-signed-char-misuse, | ||||||
|   -bugprone-too-small-loop-variable, |  | ||||||
|   -cert-dcl50-cpp, |   -cert-dcl50-cpp, | ||||||
|   -cert-err58-cpp, |   -cert-err58-cpp, | ||||||
|   -cert-oop57-cpp, |   -cert-oop57-cpp, | ||||||
| @@ -19,12 +16,10 @@ Checks: >- | |||||||
|   -clang-diagnostic-delete-abstract-non-virtual-dtor, |   -clang-diagnostic-delete-abstract-non-virtual-dtor, | ||||||
|   -clang-diagnostic-delete-non-abstract-non-virtual-dtor, |   -clang-diagnostic-delete-non-abstract-non-virtual-dtor, | ||||||
|   -clang-diagnostic-shadow-field, |   -clang-diagnostic-shadow-field, | ||||||
|   -clang-diagnostic-sign-compare, |  | ||||||
|   -clang-diagnostic-unused-variable, |  | ||||||
|   -clang-diagnostic-unused-const-variable, |   -clang-diagnostic-unused-const-variable, | ||||||
|  |   -clang-diagnostic-unused-parameter, | ||||||
|   -concurrency-*, |   -concurrency-*, | ||||||
|   -cppcoreguidelines-avoid-c-arrays, |   -cppcoreguidelines-avoid-c-arrays, | ||||||
|   -cppcoreguidelines-avoid-goto, |  | ||||||
|   -cppcoreguidelines-avoid-magic-numbers, |   -cppcoreguidelines-avoid-magic-numbers, | ||||||
|   -cppcoreguidelines-init-variables, |   -cppcoreguidelines-init-variables, | ||||||
|   -cppcoreguidelines-macro-usage, |   -cppcoreguidelines-macro-usage, | ||||||
| @@ -41,7 +36,6 @@ Checks: >- | |||||||
|   -cppcoreguidelines-pro-type-union-access, |   -cppcoreguidelines-pro-type-union-access, | ||||||
|   -cppcoreguidelines-pro-type-vararg, |   -cppcoreguidelines-pro-type-vararg, | ||||||
|   -cppcoreguidelines-special-member-functions, |   -cppcoreguidelines-special-member-functions, | ||||||
|   -fuchsia-default-arguments, |  | ||||||
|   -fuchsia-multiple-inheritance, |   -fuchsia-multiple-inheritance, | ||||||
|   -fuchsia-overloaded-operator, |   -fuchsia-overloaded-operator, | ||||||
|   -fuchsia-statically-constructed-objects, |   -fuchsia-statically-constructed-objects, | ||||||
| @@ -51,6 +45,7 @@ Checks: >- | |||||||
|   -google-explicit-constructor, |   -google-explicit-constructor, | ||||||
|   -google-readability-braces-around-statements, |   -google-readability-braces-around-statements, | ||||||
|   -google-readability-casting, |   -google-readability-casting, | ||||||
|  |   -google-readability-namespace-comments, | ||||||
|   -google-readability-todo, |   -google-readability-todo, | ||||||
|   -google-runtime-references, |   -google-runtime-references, | ||||||
|   -hicpp-*, |   -hicpp-*, | ||||||
| @@ -97,9 +92,11 @@ CheckOptions: | |||||||
|     value:           '1' |     value:           '1' | ||||||
|   - key:             google-readability-function-size.StatementThreshold |   - key:             google-readability-function-size.StatementThreshold | ||||||
|     value:           '800' |     value:           '800' | ||||||
|   - key:             google-readability-namespace-comments.ShortNamespaceLines |   - key:             google-runtime-int.TypeSuffix | ||||||
|  |     value:           '_t' | ||||||
|  |   - key:             llvm-namespace-comment.ShortNamespaceLines | ||||||
|     value:           '10' |     value:           '10' | ||||||
|   - key:             google-readability-namespace-comments.SpacesBeforeComments |   - key:             llvm-namespace-comment.SpacesBeforeComments | ||||||
|     value:           '2' |     value:           '2' | ||||||
|   - key:             modernize-loop-convert.MaxCopySize |   - key:             modernize-loop-convert.MaxCopySize | ||||||
|     value:           '16' |     value:           '16' | ||||||
|   | |||||||
							
								
								
									
										1
									
								
								.github/PULL_REQUEST_TEMPLATE.md
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.github/PULL_REQUEST_TEMPLATE.md
									
									
									
									
										vendored
									
									
								
							| @@ -16,6 +16,7 @@ Quick description and explanation of changes | |||||||
| ## Test Environment | ## Test Environment | ||||||
|  |  | ||||||
| - [ ] ESP32 | - [ ] ESP32 | ||||||
|  | - [ ] ESP32 IDF | ||||||
| - [ ] ESP8266 | - [ ] ESP8266 | ||||||
|  |  | ||||||
| ## Example entry for `config.yaml`: | ## Example entry for `config.yaml`: | ||||||
|   | |||||||
							
								
								
									
										7
									
								
								.github/issue-close-app.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										7
									
								
								.github/issue-close-app.yml
									
									
									
									
										vendored
									
									
								
							| @@ -1,7 +0,0 @@ | |||||||
| comment: >- |  | ||||||
|   https://github.com/esphome/esphome/issues/430 |  | ||||||
| issueConfigs: |  | ||||||
| - content: |  | ||||||
|   - "OTHERWISE THE ISSUE WILL BE CLOSED AUTOMATICALLY" |  | ||||||
|  |  | ||||||
| caseInsensitive: false |  | ||||||
							
								
								
									
										36
									
								
								.github/lock.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										36
									
								
								.github/lock.yml
									
									
									
									
										vendored
									
									
								
							| @@ -1,36 +0,0 @@ | |||||||
| # 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 |  | ||||||
							
								
								
									
										4
									
								
								.github/workflows/ci-docker.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/workflows/ci-docker.yml
									
									
									
									
										vendored
									
									
								
							| @@ -17,6 +17,10 @@ on: | |||||||
|       - 'requirements*.txt' |       - 'requirements*.txt' | ||||||
|       - 'platformio.ini' |       - 'platformio.ini' | ||||||
|  |  | ||||||
|  | permissions: | ||||||
|  |   contents: read | ||||||
|  |   packages: read | ||||||
|  |  | ||||||
| jobs: | jobs: | ||||||
|   check-docker: |   check-docker: | ||||||
|     name: Build docker containers |     name: Build docker containers | ||||||
|   | |||||||
							
								
								
									
										50
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										50
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							| @@ -1,5 +1,3 @@ | |||||||
| # THESE JOBS ARE COPIED IN release.yml and release-dev.yml |  | ||||||
| # PLEASE ALSO UPDATE THOSE FILES WHEN CHANGING LINES HERE |  | ||||||
| name: CI | name: CI | ||||||
|  |  | ||||||
| on: | on: | ||||||
| @@ -8,6 +6,13 @@ on: | |||||||
|  |  | ||||||
|   pull_request: |   pull_request: | ||||||
|  |  | ||||||
|  | permissions: | ||||||
|  |   contents: read | ||||||
|  |  | ||||||
|  | concurrency: | ||||||
|  |   group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} | ||||||
|  |   cancel-in-progress: true | ||||||
|  |  | ||||||
| jobs: | jobs: | ||||||
|   ci: |   ci: | ||||||
|     name: ${{ matrix.name }} |     name: ${{ matrix.name }} | ||||||
| @@ -31,7 +36,7 @@ jobs: | |||||||
|           - id: test |           - id: test | ||||||
|             file: tests/test3.yaml |             file: tests/test3.yaml | ||||||
|             name: Test tests/test3.yaml |             name: Test tests/test3.yaml | ||||||
|             pio_cache_key: test1 |             pio_cache_key: test3 | ||||||
|           - id: test |           - id: test | ||||||
|             file: tests/test4.yaml |             file: tests/test4.yaml | ||||||
|             name: Test tests/test4.yaml |             name: Test tests/test4.yaml | ||||||
| @@ -46,26 +51,26 @@ jobs: | |||||||
|             name: Run script/clang-format |             name: Run script/clang-format | ||||||
|           - id: clang-tidy |           - id: clang-tidy | ||||||
|             name: Run script/clang-tidy for ESP8266 |             name: Run script/clang-tidy for ESP8266 | ||||||
|             options: --environment esp8266-tidy --grep USE_ESP8266 |             options: --environment esp8266-arduino-tidy --grep USE_ESP8266 | ||||||
|             pio_cache_key: tidyesp8266 |             pio_cache_key: tidyesp8266 | ||||||
|           - id: clang-tidy |           - id: clang-tidy | ||||||
|             name: Run script/clang-tidy for ESP32 1/4 |             name: Run script/clang-tidy for ESP32 Arduino 1/4 | ||||||
|             options: --environment esp32-tidy --split-num 4 --split-at 1 |             options: --environment esp32-arduino-tidy --split-num 4 --split-at 1 | ||||||
|             pio_cache_key: tidyesp32 |             pio_cache_key: tidyesp32 | ||||||
|           - id: clang-tidy |           - id: clang-tidy | ||||||
|             name: Run script/clang-tidy for ESP32 2/4 |             name: Run script/clang-tidy for ESP32 Arduino 2/4 | ||||||
|             options: --environment esp32-tidy --split-num 4 --split-at 2 |             options: --environment esp32-arduino-tidy --split-num 4 --split-at 2 | ||||||
|             pio_cache_key: tidyesp32 |             pio_cache_key: tidyesp32 | ||||||
|           - id: clang-tidy |           - id: clang-tidy | ||||||
|             name: Run script/clang-tidy for ESP32 3/4 |             name: Run script/clang-tidy for ESP32 Arduino 3/4 | ||||||
|             options: --environment esp32-tidy --split-num 4 --split-at 3 |             options: --environment esp32-arduino-tidy --split-num 4 --split-at 3 | ||||||
|             pio_cache_key: tidyesp32 |             pio_cache_key: tidyesp32 | ||||||
|           - id: clang-tidy |           - id: clang-tidy | ||||||
|             name: Run script/clang-tidy for ESP32 4/4 |             name: Run script/clang-tidy for ESP32 Arduino 4/4 | ||||||
|             options: --environment esp32-tidy --split-num 4 --split-at 4 |             options: --environment esp32-arduino-tidy --split-num 4 --split-at 4 | ||||||
|             pio_cache_key: tidyesp32 |             pio_cache_key: tidyesp32 | ||||||
|           - id: clang-tidy |           - id: clang-tidy | ||||||
|             name: Run script/clang-tidy for ESP32 esp-idf |             name: Run script/clang-tidy for ESP32 IDF | ||||||
|             options: --environment esp32-idf-tidy --grep USE_ESP_IDF |             options: --environment esp32-idf-tidy --grep USE_ESP_IDF | ||||||
|             pio_cache_key: tidyesp32-idf |             pio_cache_key: tidyesp32-idf | ||||||
|  |  | ||||||
| @@ -77,18 +82,23 @@ jobs: | |||||||
|         with: |         with: | ||||||
|           python-version: '3.7' |           python-version: '3.7' | ||||||
|  |  | ||||||
|       - name: Cache pip modules |       - name: Cache virtualenv | ||||||
|         uses: actions/cache@v2 |         uses: actions/cache@v2 | ||||||
|         with: |         with: | ||||||
|           path: ~/.cache/pip |           path: .venv | ||||||
|           key: pip-${{ steps.python.outputs.python-version }}-${{ hashFiles('requirements*.txt') }} |           key: venv-${{ steps.python.outputs.python-version }}-${{ hashFiles('requirements*.txt') }} | ||||||
|           restore-keys: | |           restore-keys: | | ||||||
|             pip-${{ steps.python.outputs.python-version }}- |             venv-${{ steps.python.outputs.python-version }}- | ||||||
|  |  | ||||||
|       - name: Set up python environment |       - name: Set up virtualenv | ||||||
|         run: | |         run: | | ||||||
|           pip3 install -r requirements.txt -r requirements_optional.txt -r requirements_test.txt |           python -m venv .venv | ||||||
|           pip3 install -e . |           source .venv/bin/activate | ||||||
|  |           pip install -U pip | ||||||
|  |           pip install -r requirements.txt -r requirements_optional.txt -r requirements_test.txt | ||||||
|  |           pip install -e . | ||||||
|  |           echo "$GITHUB_WORKSPACE/.venv/bin" >> $GITHUB_PATH | ||||||
|  |           echo "VIRTUAL_ENV=$GITHUB_WORKSPACE/.venv" >> $GITHUB_ENV | ||||||
|  |  | ||||||
|       # Use per check platformio cache because checks use different parts |       # Use per check platformio cache because checks use different parts | ||||||
|       - name: Cache platformio |       - name: Cache platformio | ||||||
|   | |||||||
							
								
								
									
										14
									
								
								.github/workflows/lock.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										14
									
								
								.github/workflows/lock.yml
									
									
									
									
										vendored
									
									
								
							| @@ -9,13 +9,19 @@ permissions: | |||||||
|   issues: write |   issues: write | ||||||
|   pull-requests: write |   pull-requests: write | ||||||
|  |  | ||||||
|  | concurrency: | ||||||
|  |   group: lock | ||||||
|  |  | ||||||
| jobs: | jobs: | ||||||
|   lock: |   lock: | ||||||
|     runs-on: ubuntu-latest |     runs-on: ubuntu-latest | ||||||
|     steps: |     steps: | ||||||
|       - uses: dessant/lock-threads@v2 |       - uses: dessant/lock-threads@v3 | ||||||
|         with: |         with: | ||||||
|           github-token: ${{ github.token }} |           pr-inactive-days: "1" | ||||||
|           pr-lock-inactive-days: "1" |  | ||||||
|           pr-lock-reason: "" |           pr-lock-reason: "" | ||||||
|           process-only: prs |           exclude-any-pr-labels: keep-open | ||||||
|  |  | ||||||
|  |           issue-inactive-days: "7" | ||||||
|  |           issue-lock-reason: "" | ||||||
|  |           exclude-any-issue-labels: keep-open | ||||||
|   | |||||||
							
								
								
									
										2
									
								
								.github/workflows/matchers/ci-custom.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/matchers/ci-custom.json
									
									
									
									
										vendored
									
									
								
							| @@ -4,7 +4,7 @@ | |||||||
|             "owner": "ci-custom", |             "owner": "ci-custom", | ||||||
|             "pattern": [ |             "pattern": [ | ||||||
|                 { |                 { | ||||||
|                     "regexp": "^ERROR (.*):(\\d+):(\\d+) - (.*)$", |                     "regexp": "^(.*):(\\d+):(\\d+):\\s+lint:\\s+(.*)$", | ||||||
|                     "file": 1, |                     "file": 1, | ||||||
|                     "line": 2, |                     "line": 2, | ||||||
|                     "column": 3, |                     "column": 3, | ||||||
|   | |||||||
							
								
								
									
										2
									
								
								.github/workflows/matchers/gcc.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/matchers/gcc.json
									
									
									
									
										vendored
									
									
								
							| @@ -5,7 +5,7 @@ | |||||||
|       "severity": "error", |       "severity": "error", | ||||||
|       "pattern": [ |       "pattern": [ | ||||||
|         { |         { | ||||||
|           "regexp": "^(.*):(\\d+):(\\d+):\\s+(?:fatal\\s+)?(warning|error):\\s+(.*)$", |           "regexp": "^src/(.*):(\\d+):(\\d+):\\s+(?:fatal\\s+)?(warning|error):\\s+(.*)$", | ||||||
|           "file": 1, |           "file": 1, | ||||||
|           "line": 2, |           "line": 2, | ||||||
|           "column": 3, |           "column": 3, | ||||||
|   | |||||||
							
								
								
									
										15
									
								
								.github/workflows/matchers/lint-python.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										15
									
								
								.github/workflows/matchers/lint-python.json
									
									
									
									
										vendored
									
									
								
							| @@ -1,11 +1,22 @@ | |||||||
| { | { | ||||||
|   "problemMatcher": [ |   "problemMatcher": [ | ||||||
|  |     { | ||||||
|  |       "owner": "black", | ||||||
|  |       "severity": "error", | ||||||
|  |       "pattern": [ | ||||||
|  |         { | ||||||
|  |           "regexp": "^(.*): (Please format this file with the black formatter)", | ||||||
|  |           "file": 1, | ||||||
|  |           "message": 2 | ||||||
|  |         } | ||||||
|  |       ] | ||||||
|  |     }, | ||||||
|     { |     { | ||||||
|       "owner": "flake8", |       "owner": "flake8", | ||||||
|       "severity": "error", |       "severity": "error", | ||||||
|       "pattern": [ |       "pattern": [ | ||||||
|           { |           { | ||||||
|           "regexp": "^(.*):(\\d+) - ([EFCDNW]\\d{3}.*)$", |           "regexp": "^(.*):(\\d+): ([EFCDNW]\\d{3}.*)$", | ||||||
|           "file": 1, |           "file": 1, | ||||||
|           "line": 2, |           "line": 2, | ||||||
|           "message": 3 |           "message": 3 | ||||||
| @@ -17,7 +28,7 @@ | |||||||
|       "severity": "error", |       "severity": "error", | ||||||
|       "pattern": [ |       "pattern": [ | ||||||
|         { |         { | ||||||
|           "regexp": "^(.*):(\\d+) - (\\[[EFCRW]\\d{4}\\(.*\\),.*\\].*)$", |           "regexp": "^(.*):(\\d+): (\\[[EFCRW]\\d{4}\\(.*\\),.*\\].*)$", | ||||||
|           "file": 1, |           "file": 1, | ||||||
|           "line": 2, |           "line": 2, | ||||||
|           "message": 3 |           "message": 3 | ||||||
|   | |||||||
							
								
								
									
										9
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										9
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							| @@ -7,6 +7,9 @@ on: | |||||||
|   schedule: |   schedule: | ||||||
|     - cron: "0 2 * * *" |     - cron: "0 2 * * *" | ||||||
|  |  | ||||||
|  | permissions: | ||||||
|  |   contents: read | ||||||
|  |  | ||||||
| jobs: | jobs: | ||||||
|   init: |   init: | ||||||
|     name: Initialize build |     name: Initialize build | ||||||
| @@ -52,6 +55,9 @@ jobs: | |||||||
|   deploy-docker: |   deploy-docker: | ||||||
|     name: Build and publish docker containers |     name: Build and publish docker containers | ||||||
|     if: github.repository == 'esphome/esphome' |     if: github.repository == 'esphome/esphome' | ||||||
|  |     permissions: | ||||||
|  |       contents: read | ||||||
|  |       packages: write | ||||||
|     runs-on: ubuntu-latest |     runs-on: ubuntu-latest | ||||||
|     needs: [init] |     needs: [init] | ||||||
|     strategy: |     strategy: | ||||||
| @@ -93,6 +99,9 @@ jobs: | |||||||
|  |  | ||||||
|   deploy-docker-manifest: |   deploy-docker-manifest: | ||||||
|     if: github.repository == 'esphome/esphome' |     if: github.repository == 'esphome/esphome' | ||||||
|  |     permissions: | ||||||
|  |       contents: read | ||||||
|  |       packages: write | ||||||
|     runs-on: ubuntu-latest |     runs-on: ubuntu-latest | ||||||
|     needs: [init, deploy-docker] |     needs: [init, deploy-docker] | ||||||
|     strategy: |     strategy: | ||||||
|   | |||||||
							
								
								
									
										20
									
								
								.github/workflows/stale.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										20
									
								
								.github/workflows/stale.yml
									
									
									
									
										vendored
									
									
								
							| @@ -9,13 +9,15 @@ permissions: | |||||||
|   issues: write |   issues: write | ||||||
|   pull-requests: write |   pull-requests: write | ||||||
|  |  | ||||||
|  | concurrency: | ||||||
|  |   group: lock | ||||||
|  |  | ||||||
| jobs: | jobs: | ||||||
|   stale: |   stale: | ||||||
|     runs-on: ubuntu-latest |     runs-on: ubuntu-latest | ||||||
|     steps: |     steps: | ||||||
|       - uses: actions/stale@v4 |       - uses: actions/stale@v4 | ||||||
|         with: |         with: | ||||||
|           repo-token: ${{ github.token }} |  | ||||||
|           days-before-pr-stale: 90 |           days-before-pr-stale: 90 | ||||||
|           days-before-pr-close: 7 |           days-before-pr-close: 7 | ||||||
|           days-before-issue-stale: -1 |           days-before-issue-stale: -1 | ||||||
| @@ -28,3 +30,19 @@ jobs: | |||||||
|             pull request has been automatically marked as stale because of that |             pull request has been automatically marked as stale because of that | ||||||
|             and will be closed if no further activity occurs within 7 days. |             and will be closed if no further activity occurs within 7 days. | ||||||
|             Thank you for your contributions. |             Thank you for your contributions. | ||||||
|  |  | ||||||
|  |   # Use stale to automatically close issues with a reference to the issue tracker | ||||||
|  |   close-issues: | ||||||
|  |     runs-on: ubuntu-latest | ||||||
|  |     steps: | ||||||
|  |       - uses: actions/stale@v4 | ||||||
|  |         with: | ||||||
|  |           days-before-pr-stale: -1 | ||||||
|  |           days-before-pr-close: -1 | ||||||
|  |           days-before-issue-stale: 1 | ||||||
|  |           days-before-issue-close: 1 | ||||||
|  |           remove-stale-when-updated: true | ||||||
|  |           stale-issue-label: "stale" | ||||||
|  |           exempt-issue-labels: "not-stale" | ||||||
|  |           stale-issue-message: > | ||||||
|  |             https://github.com/esphome/esphome/issues/430 | ||||||
|   | |||||||
| @@ -3,4 +3,4 @@ ports: | |||||||
|   onOpen: open-preview |   onOpen: open-preview | ||||||
| tasks: | tasks: | ||||||
| - before: pyenv local $(pyenv version | grep '^3\.' | cut -d ' ' -f 1) && 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 dashboard config | ||||||
|   | |||||||
							
								
								
									
										24
									
								
								CODEOWNERS
									
									
									
									
									
								
							
							
						
						
									
										24
									
								
								CODEOWNERS
									
									
									
									
									
								
							| @@ -28,17 +28,23 @@ esphome/components/b_parasite/* @rbaron | |||||||
| esphome/components/ballu/* @bazuchan | esphome/components/ballu/* @bazuchan | ||||||
| esphome/components/bang_bang/* @OttoWinter | esphome/components/bang_bang/* @OttoWinter | ||||||
| esphome/components/binary_sensor/* @esphome/core | esphome/components/binary_sensor/* @esphome/core | ||||||
|  | esphome/components/bl0940/* @tobias- | ||||||
| esphome/components/ble_client/* @buxtronix | esphome/components/ble_client/* @buxtronix | ||||||
| esphome/components/bme680_bsec/* @trvrnrth | esphome/components/bme680_bsec/* @trvrnrth | ||||||
|  | esphome/components/bmp3xx/* @martgras | ||||||
|  | esphome/components/button/* @esphome/core | ||||||
| esphome/components/canbus/* @danielschramm @mvturnho | esphome/components/canbus/* @danielschramm @mvturnho | ||||||
|  | esphome/components/cap1188/* @MrEditor97 | ||||||
| esphome/components/captive_portal/* @OttoWinter | esphome/components/captive_portal/* @OttoWinter | ||||||
| esphome/components/ccs811/* @habbie | esphome/components/ccs811/* @habbie | ||||||
|  | esphome/components/cd74hc4067/* @asoehlke | ||||||
| esphome/components/climate/* @esphome/core | esphome/components/climate/* @esphome/core | ||||||
| esphome/components/climate_ir/* @glmnet | esphome/components/climate_ir/* @glmnet | ||||||
| esphome/components/color_temperature/* @jesserockz | esphome/components/color_temperature/* @jesserockz | ||||||
| esphome/components/coolix/* @glmnet | esphome/components/coolix/* @glmnet | ||||||
| esphome/components/cover/* @esphome/core | esphome/components/cover/* @esphome/core | ||||||
| esphome/components/cs5460a/* @balrog-kun | esphome/components/cs5460a/* @balrog-kun | ||||||
|  | esphome/components/cse7761/* @berfenger | ||||||
| esphome/components/ct_clamp/* @jesserockz | esphome/components/ct_clamp/* @jesserockz | ||||||
| esphome/components/current_based/* @djwmarcx | esphome/components/current_based/* @djwmarcx | ||||||
| esphome/components/daly_bms/* @s1lvi0 | esphome/components/daly_bms/* @s1lvi0 | ||||||
| @@ -52,6 +58,8 @@ esphome/components/esp32/* @esphome/core | |||||||
| esphome/components/esp32_ble/* @jesserockz | esphome/components/esp32_ble/* @jesserockz | ||||||
| esphome/components/esp32_ble_controller/* @jesserockz | esphome/components/esp32_ble_controller/* @jesserockz | ||||||
| esphome/components/esp32_ble_server/* @jesserockz | esphome/components/esp32_ble_server/* @jesserockz | ||||||
|  | esphome/components/esp32_camera_web_server/* @ayufan | ||||||
|  | esphome/components/esp32_can/* @Sympatron | ||||||
| esphome/components/esp32_improv/* @jesserockz | esphome/components/esp32_improv/* @jesserockz | ||||||
| esphome/components/esp8266/* @esphome/core | esphome/components/esp8266/* @esphome/core | ||||||
| esphome/components/exposure_notifications/* @OttoWinter | esphome/components/exposure_notifications/* @OttoWinter | ||||||
| @@ -62,6 +70,7 @@ esphome/components/globals/* @esphome/core | |||||||
| esphome/components/gpio/* @esphome/core | esphome/components/gpio/* @esphome/core | ||||||
| esphome/components/gps/* @coogle | esphome/components/gps/* @coogle | ||||||
| esphome/components/graph/* @synco | esphome/components/graph/* @synco | ||||||
|  | esphome/components/growatt_solar/* @leeuwte | ||||||
| esphome/components/havells_solar/* @sourabhjaiswal | esphome/components/havells_solar/* @sourabhjaiswal | ||||||
| esphome/components/hbridge/fan/* @WeekendWarrior | esphome/components/hbridge/fan/* @WeekendWarrior | ||||||
| esphome/components/hbridge/light/* @DotNetDann | esphome/components/hbridge/light/* @DotNetDann | ||||||
| @@ -70,12 +79,14 @@ esphome/components/hitachi_ac424/* @sourabhjaiswal | |||||||
| esphome/components/homeassistant/* @OttoWinter | esphome/components/homeassistant/* @OttoWinter | ||||||
| esphome/components/hrxl_maxsonar_wr/* @netmikey | esphome/components/hrxl_maxsonar_wr/* @netmikey | ||||||
| esphome/components/i2c/* @esphome/core | esphome/components/i2c/* @esphome/core | ||||||
| esphome/components/improv/* @jesserockz | esphome/components/improv_serial/* @esphome/core | ||||||
|  | esphome/components/ina260/* @MrEditor97 | ||||||
| esphome/components/inkbird_ibsth1_mini/* @fkirill | esphome/components/inkbird_ibsth1_mini/* @fkirill | ||||||
| esphome/components/inkplate6/* @jesserockz | esphome/components/inkplate6/* @jesserockz | ||||||
| esphome/components/integration/* @OttoWinter | esphome/components/integration/* @OttoWinter | ||||||
| esphome/components/interval/* @esphome/core | esphome/components/interval/* @esphome/core | ||||||
| esphome/components/json/* @OttoWinter | esphome/components/json/* @OttoWinter | ||||||
|  | esphome/components/kalman_combinator/* @Cat-Ion | ||||||
| esphome/components/ledc/* @OttoWinter | esphome/components/ledc/* @OttoWinter | ||||||
| esphome/components/light/* @esphome/core | esphome/components/light/* @esphome/core | ||||||
| esphome/components/logger/* @esphome/core | esphome/components/logger/* @esphome/core | ||||||
| @@ -89,9 +100,13 @@ esphome/components/mcp23x08_base/* @jesserockz | |||||||
| esphome/components/mcp23x17_base/* @jesserockz | esphome/components/mcp23x17_base/* @jesserockz | ||||||
| esphome/components/mcp23xxx_base/* @jesserockz | esphome/components/mcp23xxx_base/* @jesserockz | ||||||
| esphome/components/mcp2515/* @danielschramm @mvturnho | esphome/components/mcp2515/* @danielschramm @mvturnho | ||||||
|  | esphome/components/mcp3204/* @rsumner | ||||||
|  | esphome/components/mcp47a1/* @jesserockz | ||||||
| esphome/components/mcp9808/* @k7hpn | esphome/components/mcp9808/* @k7hpn | ||||||
|  | esphome/components/md5/* @esphome/core | ||||||
| esphome/components/mdns/* @esphome/core | esphome/components/mdns/* @esphome/core | ||||||
| esphome/components/midea/* @dudanov | esphome/components/midea/* @dudanov | ||||||
|  | esphome/components/midea_ir/* @dudanov | ||||||
| esphome/components/mitsubishi/* @RubyBailey | esphome/components/mitsubishi/* @RubyBailey | ||||||
| esphome/components/modbus_controller/* @martgras | esphome/components/modbus_controller/* @martgras | ||||||
| esphome/components/modbus_controller/binary_sensor/* @martgras | esphome/components/modbus_controller/binary_sensor/* @martgras | ||||||
| @@ -119,6 +134,7 @@ esphome/components/pn532_i2c/* @OttoWinter @jesserockz | |||||||
| esphome/components/pn532_spi/* @OttoWinter @jesserockz | esphome/components/pn532_spi/* @OttoWinter @jesserockz | ||||||
| esphome/components/power_supply/* @esphome/core | esphome/components/power_supply/* @esphome/core | ||||||
| esphome/components/preferences/* @esphome/core | esphome/components/preferences/* @esphome/core | ||||||
|  | esphome/components/psram/* @esphome/core | ||||||
| esphome/components/pulse_meter/* @stevebaxter | esphome/components/pulse_meter/* @stevebaxter | ||||||
| esphome/components/pvvx_mithermometer/* @pasiz | esphome/components/pvvx_mithermometer/* @pasiz | ||||||
| esphome/components/rc522/* @glmnet | esphome/components/rc522/* @glmnet | ||||||
| @@ -128,7 +144,7 @@ esphome/components/restart/* @esphome/core | |||||||
| esphome/components/rf_bridge/* @jesserockz | esphome/components/rf_bridge/* @jesserockz | ||||||
| esphome/components/rgbct/* @jesserockz | esphome/components/rgbct/* @jesserockz | ||||||
| esphome/components/rtttl/* @glmnet | esphome/components/rtttl/* @glmnet | ||||||
| esphome/components/safe_mode/* @paulmonigatti | esphome/components/safe_mode/* @jsuanet @paulmonigatti | ||||||
| esphome/components/scd4x/* @sjtrny | esphome/components/scd4x/* @sjtrny | ||||||
| esphome/components/script/* @esphome/core | esphome/components/script/* @esphome/core | ||||||
| esphome/components/sdm_meter/* @jesserockz @polyfaces | esphome/components/sdm_meter/* @jesserockz @polyfaces | ||||||
| @@ -138,7 +154,7 @@ esphome/components/select/* @esphome/core | |||||||
| esphome/components/sensor/* @esphome/core | esphome/components/sensor/* @esphome/core | ||||||
| esphome/components/sgp40/* @SenexCrenshaw | esphome/components/sgp40/* @SenexCrenshaw | ||||||
| esphome/components/sht4x/* @sjtrny | esphome/components/sht4x/* @sjtrny | ||||||
| esphome/components/shutdown/* @esphome/core | esphome/components/shutdown/* @esphome/core @jsuanet | ||||||
| esphome/components/sim800l/* @glmnet | esphome/components/sim800l/* @glmnet | ||||||
| esphome/components/sm2135/* @BoukeHaarsma23 | esphome/components/sm2135/* @BoukeHaarsma23 | ||||||
| esphome/components/socket/* @esphome/core | esphome/components/socket/* @esphome/core | ||||||
| @@ -175,8 +191,10 @@ esphome/components/toshiba/* @kbx81 | |||||||
| esphome/components/tsl2591/* @wjcarpenter | esphome/components/tsl2591/* @wjcarpenter | ||||||
| esphome/components/tuya/binary_sensor/* @jesserockz | esphome/components/tuya/binary_sensor/* @jesserockz | ||||||
| esphome/components/tuya/climate/* @jesserockz | esphome/components/tuya/climate/* @jesserockz | ||||||
|  | esphome/components/tuya/number/* @frankiboy1 | ||||||
| esphome/components/tuya/sensor/* @jesserockz | esphome/components/tuya/sensor/* @jesserockz | ||||||
| esphome/components/tuya/switch/* @jesserockz | esphome/components/tuya/switch/* @jesserockz | ||||||
|  | esphome/components/tuya/text_sensor/* @dentra | ||||||
| esphome/components/uart/* @esphome/core | esphome/components/uart/* @esphome/core | ||||||
| esphome/components/ultrasonic/* @OttoWinter | esphome/components/ultrasonic/* @OttoWinter | ||||||
| esphome/components/version/* @esphome/core | esphome/components/version/* @esphome/core | ||||||
|   | |||||||
| @@ -5,12 +5,12 @@ | |||||||
| # One of "docker", "hassio" | # One of "docker", "hassio" | ||||||
| ARG BASEIMGTYPE=docker | ARG BASEIMGTYPE=docker | ||||||
|  |  | ||||||
| FROM ghcr.io/hassio-addons/debian-base/amd64:5.1.0 AS base-hassio-amd64 | FROM ghcr.io/hassio-addons/debian-base/amd64:5.2.3 AS base-hassio-amd64 | ||||||
| FROM ghcr.io/hassio-addons/debian-base/aarch64:5.1.0 AS base-hassio-arm64 | FROM ghcr.io/hassio-addons/debian-base/aarch64:5.2.3 AS base-hassio-arm64 | ||||||
| FROM ghcr.io/hassio-addons/debian-base/armv7:5.1.0 AS base-hassio-armv7 | FROM ghcr.io/hassio-addons/debian-base/armv7:5.2.3 AS base-hassio-armv7 | ||||||
| FROM debian:bullseye-20210902-slim AS base-docker-amd64 | FROM debian:bullseye-20211220-slim AS base-docker-amd64 | ||||||
| FROM debian:bullseye-20210902-slim AS base-docker-arm64 | FROM debian:bullseye-20211220-slim AS base-docker-arm64 | ||||||
| FROM debian:bullseye-20210902-slim AS base-docker-armv7 | FROM debian:bullseye-20211220-slim AS base-docker-armv7 | ||||||
|  |  | ||||||
| # Use TARGETARCH/TARGETVARIANT defined by docker | # Use TARGETARCH/TARGETVARIANT defined by docker | ||||||
| # https://docs.docker.com/engine/reference/builder/#automatic-platform-args-in-the-global-scope | # https://docs.docker.com/engine/reference/builder/#automatic-platform-args-in-the-global-scope | ||||||
| @@ -27,7 +27,7 @@ RUN \ | |||||||
|         python3-cryptography=3.3.2-1 \ |         python3-cryptography=3.3.2-1 \ | ||||||
|         iputils-ping=3:20210202-1 \ |         iputils-ping=3:20210202-1 \ | ||||||
|         git=1:2.30.2-1 \ |         git=1:2.30.2-1 \ | ||||||
|         curl=7.74.0-1.3+b1 \ |         curl=7.74.0-1.3+deb11u1 \ | ||||||
|     && rm -rf \ |     && rm -rf \ | ||||||
|         /tmp/* \ |         /tmp/* \ | ||||||
|         /var/{cache,log}/* \ |         /var/{cache,log}/* \ | ||||||
| @@ -42,8 +42,8 @@ ENV \ | |||||||
| RUN \ | RUN \ | ||||||
|     # Ubuntu python3-pip is missing wheel |     # Ubuntu python3-pip is missing wheel | ||||||
|     pip3 install --no-cache-dir \ |     pip3 install --no-cache-dir \ | ||||||
|         wheel==0.36.2 \ |         wheel==0.37.1 \ | ||||||
|         platformio==5.2.0 \ |         platformio==5.2.4 \ | ||||||
|     # Change some platformio settings |     # Change some platformio settings | ||||||
|     && platformio settings set enable_telemetry No \ |     && platformio settings set enable_telemetry No \ | ||||||
|     && platformio settings set check_libraries_interval 1000000 \ |     && platformio settings set check_libraries_interval 1000000 \ | ||||||
| @@ -64,7 +64,7 @@ RUN \ | |||||||
|  |  | ||||||
| # Copy esphome and install | # Copy esphome and install | ||||||
| COPY . /esphome | COPY . /esphome | ||||||
| RUN pip3 install --no-cache-dir -e /esphome | RUN pip3 install --no-cache-dir --no-use-pep517 -e /esphome | ||||||
|  |  | ||||||
| # Settings for dashboard | # Settings for dashboard | ||||||
| ENV USERNAME="" PASSWORD="" | ENV USERNAME="" PASSWORD="" | ||||||
| @@ -112,7 +112,7 @@ RUN \ | |||||||
|  |  | ||||||
| # Copy esphome and install | # Copy esphome and install | ||||||
| COPY . /esphome | COPY . /esphome | ||||||
| RUN pip3 install --no-cache-dir -e /esphome | RUN pip3 install --no-cache-dir --no-use-pep517 -e /esphome | ||||||
|  |  | ||||||
| # Labels | # Labels | ||||||
| LABEL \ | LABEL \ | ||||||
| @@ -147,9 +147,9 @@ RUN \ | |||||||
|         /var/{cache,log}/* \ |         /var/{cache,log}/* \ | ||||||
|         /var/lib/apt/lists/* |         /var/lib/apt/lists/* | ||||||
|  |  | ||||||
| COPY requirements.txt requirements_optional.txt docker/platformio_install_deps.py platformio.ini / | COPY requirements.txt requirements_optional.txt requirements_test.txt docker/platformio_install_deps.py platformio.ini / | ||||||
| RUN \ | RUN \ | ||||||
|     pip3 install --no-cache-dir -r /requirements.txt -r /requirements_optional.txt \ |     pip3 install --no-cache-dir -r /requirements.txt -r /requirements_optional.txt -r /requirements_test.txt \ | ||||||
|     && /platformio_install_deps.py /platformio.ini |     && /platformio_install_deps.py /platformio.ini | ||||||
|  |  | ||||||
| VOLUME ["/esphome"] | VOLUME ["/esphome"] | ||||||
|   | |||||||
| @@ -32,6 +32,7 @@ parser.add_argument("--dry-run", action="store_true", help="Don't run any comman | |||||||
| subparsers = parser.add_subparsers(help="Action to perform", dest="command", required=True) | subparsers = parser.add_subparsers(help="Action to perform", dest="command", required=True) | ||||||
| build_parser = subparsers.add_parser("build", help="Build the image") | build_parser = subparsers.add_parser("build", help="Build the image") | ||||||
| build_parser.add_argument("--push", help="Also push the images", action="store_true") | build_parser.add_argument("--push", help="Also push the images", action="store_true") | ||||||
|  | build_parser.add_argument("--load", help="Load the docker image locally", action="store_true") | ||||||
| manifest_parser = subparsers.add_parser("manifest", help="Create a manifest from already pushed images") | manifest_parser = subparsers.add_parser("manifest", help="Create a manifest from already pushed images") | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -132,6 +133,8 @@ def main(): | |||||||
|             cmd += ["--tag", img] |             cmd += ["--tag", img] | ||||||
|         if args.push: |         if args.push: | ||||||
|             cmd += ["--push", "--cache-to", f"type=registry,ref={cache_img},mode=max"] |             cmd += ["--push", "--cache-to", f"type=registry,ref={cache_img},mode=max"] | ||||||
|  |         if args.load: | ||||||
|  |             cmd += ["--load"] | ||||||
|  |  | ||||||
|         run_command(*cmd, ".") |         run_command(*cmd, ".") | ||||||
|     elif args.command == "manifest": |     elif args.command == "manifest": | ||||||
|   | |||||||
| @@ -8,6 +8,23 @@ import sys | |||||||
|  |  | ||||||
| config = configparser.ConfigParser(inline_comment_prefixes=(';', )) | config = configparser.ConfigParser(inline_comment_prefixes=(';', )) | ||||||
| config.read(sys.argv[1]) | config.read(sys.argv[1]) | ||||||
| libs = [x for x in config['common']['lib_deps'].splitlines() if len(x) != 0] |  | ||||||
|  | libs = [] | ||||||
|  | # Extract from every lib_deps key in all sections | ||||||
|  | for section in config.sections(): | ||||||
|  |     conf = config[section] | ||||||
|  |     if "lib_deps" not in conf: | ||||||
|  |         continue | ||||||
|  |     for lib_dep in conf["lib_deps"].splitlines(): | ||||||
|  |         if not lib_dep: | ||||||
|  |             # Empty line or comment | ||||||
|  |             continue | ||||||
|  |         if lib_dep.startswith("${"): | ||||||
|  |             # Extending from another section | ||||||
|  |             continue | ||||||
|  |         if "@" not in lib_dep: | ||||||
|  |             # No version pinned, this is an internal lib | ||||||
|  |             continue | ||||||
|  |         libs.append(lib_dep) | ||||||
|  |  | ||||||
| subprocess.check_call(['platformio', 'lib', '-g', 'install', *libs]) | subprocess.check_call(['platformio', 'lib', '-g', 'install', *libs]) | ||||||
|   | |||||||
| @@ -18,6 +18,7 @@ from esphome.const import ( | |||||||
|     CONF_PORT, |     CONF_PORT, | ||||||
|     CONF_ESPHOME, |     CONF_ESPHOME, | ||||||
|     CONF_PLATFORMIO_OPTIONS, |     CONF_PLATFORMIO_OPTIONS, | ||||||
|  |     SECRETS_FILES, | ||||||
| ) | ) | ||||||
| from esphome.core import CORE, EsphomeError, coroutine | from esphome.core import CORE, EsphomeError, coroutine | ||||||
| from esphome.helpers import indent | from esphome.helpers import indent | ||||||
| @@ -144,6 +145,8 @@ def wrap_to_code(name, comp): | |||||||
|         if comp.config_schema is not None: |         if comp.config_schema is not None: | ||||||
|             conf_str = yaml_util.dump(conf) |             conf_str = yaml_util.dump(conf) | ||||||
|             conf_str = conf_str.replace("//", "") |             conf_str = conf_str.replace("//", "") | ||||||
|  |             # remove tailing \ to avoid multi-line comment warning | ||||||
|  |             conf_str = conf_str.replace("\\\n", "\n") | ||||||
|             cg.add(cg.LineComment(indent(conf_str))) |             cg.add(cg.LineComment(indent(conf_str))) | ||||||
|         await coro(conf) |         await coro(conf) | ||||||
|  |  | ||||||
| @@ -180,7 +183,11 @@ def compile_program(args, config): | |||||||
|     from esphome import platformio_api |     from esphome import platformio_api | ||||||
|  |  | ||||||
|     _LOGGER.info("Compiling app...") |     _LOGGER.info("Compiling app...") | ||||||
|     return platformio_api.run_compile(config, CORE.verbose) |     rc = platformio_api.run_compile(config, CORE.verbose) | ||||||
|  |     if rc != 0: | ||||||
|  |         return rc | ||||||
|  |     idedata = platformio_api.get_idedata(config) | ||||||
|  |     return 0 if idedata is not None else 1 | ||||||
|  |  | ||||||
|  |  | ||||||
| def upload_using_esptool(config, port): | def upload_using_esptool(config, port): | ||||||
| @@ -196,8 +203,7 @@ def upload_using_esptool(config, port): | |||||||
|         firmware_offset = "0x10000" if CORE.is_esp32 else "0x0" |         firmware_offset = "0x10000" if CORE.is_esp32 else "0x0" | ||||||
|         flash_images = [ |         flash_images = [ | ||||||
|             platformio_api.FlashImage( |             platformio_api.FlashImage( | ||||||
|                 path=idedata.firmware_bin_path, |                 path=idedata.firmware_bin_path, offset=firmware_offset | ||||||
|                 offset=firmware_offset, |  | ||||||
|             ), |             ), | ||||||
|             *idedata.extra_flash_images, |             *idedata.extra_flash_images, | ||||||
|         ] |         ] | ||||||
| @@ -222,6 +228,8 @@ def upload_using_esptool(config, port): | |||||||
|             mcu, |             mcu, | ||||||
|             "write_flash", |             "write_flash", | ||||||
|             "-z", |             "-z", | ||||||
|  |             "--flash_size", | ||||||
|  |             "detect", | ||||||
|         ] |         ] | ||||||
|         for img in flash_images: |         for img in flash_images: | ||||||
|             cmd += [img.offset, img.path] |             cmd += [img.offset, img.path] | ||||||
| @@ -458,6 +466,21 @@ def command_update_all(args): | |||||||
|     return failed |     return failed | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def command_idedata(args, config): | ||||||
|  |     from esphome import platformio_api | ||||||
|  |     import json | ||||||
|  |  | ||||||
|  |     logging.disable(logging.INFO) | ||||||
|  |     logging.disable(logging.WARNING) | ||||||
|  |  | ||||||
|  |     idedata = platformio_api.get_idedata(config) | ||||||
|  |     if idedata is None: | ||||||
|  |         return 1 | ||||||
|  |  | ||||||
|  |     print(json.dumps(idedata.raw, indent=2) + "\n") | ||||||
|  |     return 0 | ||||||
|  |  | ||||||
|  |  | ||||||
| PRE_CONFIG_ACTIONS = { | PRE_CONFIG_ACTIONS = { | ||||||
|     "wizard": command_wizard, |     "wizard": command_wizard, | ||||||
|     "version": command_version, |     "version": command_version, | ||||||
| @@ -475,6 +498,7 @@ POST_CONFIG_ACTIONS = { | |||||||
|     "clean-mqtt": command_clean_mqtt, |     "clean-mqtt": command_clean_mqtt, | ||||||
|     "mqtt-fingerprint": command_mqtt_fingerprint, |     "mqtt-fingerprint": command_mqtt_fingerprint, | ||||||
|     "clean": command_clean, |     "clean": command_clean, | ||||||
|  |     "idedata": command_idedata, | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -585,10 +609,7 @@ def parse_args(argv): | |||||||
|         "wizard", |         "wizard", | ||||||
|         help="A helpful setup wizard that will guide you through setting up ESPHome.", |         help="A helpful setup wizard that will guide you through setting up ESPHome.", | ||||||
|     ) |     ) | ||||||
|     parser_wizard.add_argument( |     parser_wizard.add_argument("configuration", help="Your YAML configuration file.") | ||||||
|         "configuration", |  | ||||||
|         help="Your YAML configuration file.", |  | ||||||
|     ) |  | ||||||
|  |  | ||||||
|     parser_fingerprint = subparsers.add_parser( |     parser_fingerprint = subparsers.add_parser( | ||||||
|         "mqtt-fingerprint", help="Get the SSL fingerprint from a MQTT broker." |         "mqtt-fingerprint", help="Get the SSL fingerprint from a MQTT broker." | ||||||
| @@ -610,8 +631,7 @@ def parse_args(argv): | |||||||
|         "dashboard", help="Create a simple web server for a dashboard." |         "dashboard", help="Create a simple web server for a dashboard." | ||||||
|     ) |     ) | ||||||
|     parser_dashboard.add_argument( |     parser_dashboard.add_argument( | ||||||
|         "configuration", |         "configuration", help="Your YAML configuration file directory." | ||||||
|         help="Your YAML configuration file directory.", |  | ||||||
|     ) |     ) | ||||||
|     parser_dashboard.add_argument( |     parser_dashboard.add_argument( | ||||||
|         "--port", |         "--port", | ||||||
| @@ -619,6 +639,12 @@ def parse_args(argv): | |||||||
|         type=int, |         type=int, | ||||||
|         default=6052, |         default=6052, | ||||||
|     ) |     ) | ||||||
|  |     parser_dashboard.add_argument( | ||||||
|  |         "--address", | ||||||
|  |         help="The address to bind to.", | ||||||
|  |         type=str, | ||||||
|  |         default="0.0.0.0", | ||||||
|  |     ) | ||||||
|     parser_dashboard.add_argument( |     parser_dashboard.add_argument( | ||||||
|         "--username", |         "--username", | ||||||
|         help="The optional username to require for authentication.", |         help="The optional username to require for authentication.", | ||||||
| @@ -650,6 +676,11 @@ def parse_args(argv): | |||||||
|         "configuration", help="Your YAML configuration file directories.", nargs="+" |         "configuration", help="Your YAML configuration file directories.", nargs="+" | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
|  |     parser_idedata = subparsers.add_parser("idedata") | ||||||
|  |     parser_idedata.add_argument( | ||||||
|  |         "configuration", help="Your YAML configuration file(s).", nargs=1 | ||||||
|  |     ) | ||||||
|  |  | ||||||
|     # Keep backward compatibility with the old command line format of |     # Keep backward compatibility with the old command line format of | ||||||
|     # esphome <config> <command>. |     # esphome <config> <command>. | ||||||
|     # |     # | ||||||
| @@ -733,7 +764,12 @@ def run_esphome(argv): | |||||||
|     args = parse_args(argv) |     args = parse_args(argv) | ||||||
|     CORE.dashboard = args.dashboard |     CORE.dashboard = args.dashboard | ||||||
|  |  | ||||||
|     setup_log(args.verbose, args.quiet) |     setup_log( | ||||||
|  |         args.verbose, | ||||||
|  |         args.quiet, | ||||||
|  |         # Show timestamp for dashboard access logs | ||||||
|  |         args.command == "dashboard", | ||||||
|  |     ) | ||||||
|     if args.deprecated_argv_suggestion is not None and args.command != "vscode": |     if args.deprecated_argv_suggestion is not None and args.command != "vscode": | ||||||
|         _LOGGER.warning( |         _LOGGER.warning( | ||||||
|             "Calling ESPHome with the configuration before the command is deprecated " |             "Calling ESPHome with the configuration before the command is deprecated " | ||||||
| @@ -757,12 +793,16 @@ def run_esphome(argv): | |||||||
|             return 1 |             return 1 | ||||||
|  |  | ||||||
|     for conf_path in args.configuration: |     for conf_path in args.configuration: | ||||||
|  |         if any(os.path.basename(conf_path) == x for x in SECRETS_FILES): | ||||||
|  |             _LOGGER.warning("Skipping secrets file %s", conf_path) | ||||||
|  |             continue | ||||||
|  |  | ||||||
|         CORE.config_path = conf_path |         CORE.config_path = conf_path | ||||||
|         CORE.dashboard = args.dashboard |         CORE.dashboard = args.dashboard | ||||||
|  |  | ||||||
|         config = read_config(dict(args.substitution) if args.substitution else {}) |         config = read_config(dict(args.substitution) if args.substitution else {}) | ||||||
|         if config is None: |         if config is None: | ||||||
|             return 1 |             return 2 | ||||||
|         CORE.config = config |         CORE.config = config | ||||||
|  |  | ||||||
|         if args.command not in POST_CONFIG_ACTIONS: |         if args.command not in POST_CONFIG_ACTIONS: | ||||||
|   | |||||||
| @@ -3,6 +3,7 @@ import esphome.config_validation as cv | |||||||
| from esphome.const import ( | from esphome.const import ( | ||||||
|     CONF_AUTOMATION_ID, |     CONF_AUTOMATION_ID, | ||||||
|     CONF_CONDITION, |     CONF_CONDITION, | ||||||
|  |     CONF_COUNT, | ||||||
|     CONF_ELSE, |     CONF_ELSE, | ||||||
|     CONF_ID, |     CONF_ID, | ||||||
|     CONF_THEN, |     CONF_THEN, | ||||||
| @@ -66,6 +67,7 @@ DelayAction = cg.esphome_ns.class_("DelayAction", Action, cg.Component) | |||||||
| LambdaAction = cg.esphome_ns.class_("LambdaAction", Action) | LambdaAction = cg.esphome_ns.class_("LambdaAction", Action) | ||||||
| IfAction = cg.esphome_ns.class_("IfAction", Action) | IfAction = cg.esphome_ns.class_("IfAction", Action) | ||||||
| WhileAction = cg.esphome_ns.class_("WhileAction", Action) | WhileAction = cg.esphome_ns.class_("WhileAction", Action) | ||||||
|  | RepeatAction = cg.esphome_ns.class_("RepeatAction", Action) | ||||||
| WaitUntilAction = cg.esphome_ns.class_("WaitUntilAction", Action, cg.Component) | WaitUntilAction = cg.esphome_ns.class_("WaitUntilAction", Action, cg.Component) | ||||||
| UpdateComponentAction = cg.esphome_ns.class_("UpdateComponentAction", Action) | UpdateComponentAction = cg.esphome_ns.class_("UpdateComponentAction", Action) | ||||||
| Automation = cg.esphome_ns.class_("Automation") | Automation = cg.esphome_ns.class_("Automation") | ||||||
| @@ -241,6 +243,25 @@ async def while_action_to_code(config, action_id, template_arg, args): | |||||||
|     return var |     return var | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @register_action( | ||||||
|  |     "repeat", | ||||||
|  |     RepeatAction, | ||||||
|  |     cv.Schema( | ||||||
|  |         { | ||||||
|  |             cv.Required(CONF_COUNT): cv.templatable(cv.positive_not_null_int), | ||||||
|  |             cv.Required(CONF_THEN): validate_action_list, | ||||||
|  |         } | ||||||
|  |     ), | ||||||
|  | ) | ||||||
|  | async def repeat_action_to_code(config, action_id, template_arg, args): | ||||||
|  |     var = cg.new_Pvariable(action_id, template_arg) | ||||||
|  |     count_template = await cg.templatable(config[CONF_COUNT], args, cg.uint32) | ||||||
|  |     cg.add(var.set_count(count_template)) | ||||||
|  |     actions = await build_action_list(config[CONF_THEN], template_arg, args) | ||||||
|  |     cg.add(var.add_then(actions)) | ||||||
|  |     return var | ||||||
|  |  | ||||||
|  |  | ||||||
| def validate_wait_until(value): | def validate_wait_until(value): | ||||||
|     schema = cv.Schema( |     schema = cv.Schema( | ||||||
|         { |         { | ||||||
|   | |||||||
| @@ -75,10 +75,10 @@ from esphome.cpp_types import (  # noqa | |||||||
|     optional, |     optional, | ||||||
|     arduino_json_ns, |     arduino_json_ns, | ||||||
|     JsonObject, |     JsonObject, | ||||||
|     JsonObjectRef, |     JsonObjectConst, | ||||||
|     JsonObjectConstRef, |  | ||||||
|     Controller, |     Controller, | ||||||
|     GPIOPin, |     GPIOPin, | ||||||
|     InternalGPIOPin, |     InternalGPIOPin, | ||||||
|     gpio_Flags, |     gpio_Flags, | ||||||
|  |     EntityCategory, | ||||||
| ) | ) | ||||||
|   | |||||||
| @@ -25,7 +25,7 @@ void AdalightLightEffect::stop() { | |||||||
|   AddressableLightEffect::stop(); |   AddressableLightEffect::stop(); | ||||||
| } | } | ||||||
|  |  | ||||||
| int AdalightLightEffect::get_frame_size_(int led_count) const { | unsigned int AdalightLightEffect::get_frame_size_(int led_count) const { | ||||||
|   // 3 bytes: Ada |   // 3 bytes: Ada | ||||||
|   // 2 bytes: LED count |   // 2 bytes: LED count | ||||||
|   // 1 byte: checksum |   // 1 byte: checksum | ||||||
|   | |||||||
| @@ -25,7 +25,7 @@ class AdalightLightEffect : public light::AddressableLightEffect, public uart::U | |||||||
|     CONSUMED, |     CONSUMED, | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
|   int get_frame_size_(int led_count) const; |   unsigned int get_frame_size_(int led_count) const; | ||||||
|   void reset_frame_(light::AddressableLight &it); |   void reset_frame_(light::AddressableLight &it); | ||||||
|   void blank_all_leds_(light::AddressableLight &it); |   void blank_all_leds_(light::AddressableLight &it); | ||||||
|   Frame parse_frame_(light::AddressableLight &it); |   Frame parse_frame_(light::AddressableLight &it); | ||||||
|   | |||||||
| @@ -1,5 +1,6 @@ | |||||||
| #include "adc_sensor.h" | #include "adc_sensor.h" | ||||||
| #include "esphome/core/log.h" | #include "esphome/core/log.h" | ||||||
|  | #include "esphome/core/helpers.h" | ||||||
|  |  | ||||||
| #ifdef USE_ESP8266 | #ifdef USE_ESP8266 | ||||||
| #ifdef USE_ADC_SENSOR_VCC | #ifdef USE_ADC_SENSOR_VCC | ||||||
| @@ -15,50 +16,6 @@ namespace adc { | |||||||
|  |  | ||||||
| static const char *const TAG = "adc"; | static const char *const TAG = "adc"; | ||||||
|  |  | ||||||
| #ifdef USE_ESP32 |  | ||||||
| void ADCSensor::set_attenuation(adc_atten_t attenuation) { this->attenuation_ = attenuation; } |  | ||||||
|  |  | ||||||
| inline adc1_channel_t gpio_to_adc1(uint8_t pin) { |  | ||||||
| #if CONFIG_IDF_TARGET_ESP32 |  | ||||||
|   switch (pin) { |  | ||||||
|     case 36: |  | ||||||
|       return ADC1_CHANNEL_0; |  | ||||||
|     case 37: |  | ||||||
|       return ADC1_CHANNEL_1; |  | ||||||
|     case 38: |  | ||||||
|       return ADC1_CHANNEL_2; |  | ||||||
|     case 39: |  | ||||||
|       return ADC1_CHANNEL_3; |  | ||||||
|     case 32: |  | ||||||
|       return ADC1_CHANNEL_4; |  | ||||||
|     case 33: |  | ||||||
|       return ADC1_CHANNEL_5; |  | ||||||
|     case 34: |  | ||||||
|       return ADC1_CHANNEL_6; |  | ||||||
|     case 35: |  | ||||||
|       return ADC1_CHANNEL_7; |  | ||||||
|     default: |  | ||||||
|       return ADC1_CHANNEL_MAX; |  | ||||||
|   } |  | ||||||
| #elif CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32H2 |  | ||||||
|   switch (pin) { |  | ||||||
|     case 0: |  | ||||||
|       return ADC1_CHANNEL_0; |  | ||||||
|     case 1: |  | ||||||
|       return ADC1_CHANNEL_1; |  | ||||||
|     case 2: |  | ||||||
|       return ADC1_CHANNEL_2; |  | ||||||
|     case 3: |  | ||||||
|       return ADC1_CHANNEL_3; |  | ||||||
|     case 4: |  | ||||||
|       return ADC1_CHANNEL_4; |  | ||||||
|     default: |  | ||||||
|       return ADC1_CHANNEL_MAX; |  | ||||||
|   } |  | ||||||
| #endif |  | ||||||
| } |  | ||||||
| #endif |  | ||||||
|  |  | ||||||
| void ADCSensor::setup() { | void ADCSensor::setup() { | ||||||
|   ESP_LOGCONFIG(TAG, "Setting up ADC '%s'...", this->get_name().c_str()); |   ESP_LOGCONFIG(TAG, "Setting up ADC '%s'...", this->get_name().c_str()); | ||||||
| #ifndef USE_ADC_SENSOR_VCC | #ifndef USE_ADC_SENSOR_VCC | ||||||
| @@ -66,13 +23,36 @@ void ADCSensor::setup() { | |||||||
| #endif | #endif | ||||||
|  |  | ||||||
| #ifdef USE_ESP32 | #ifdef USE_ESP32 | ||||||
|   adc1_config_channel_atten(gpio_to_adc1(pin_->get_pin()), attenuation_); |  | ||||||
|   adc1_config_width(ADC_WIDTH_BIT_12); |   adc1_config_width(ADC_WIDTH_BIT_12); | ||||||
| #if !CONFIG_IDF_TARGET_ESP32C3 && !CONFIG_IDF_TARGET_ESP32H2 |   if (!autorange_) { | ||||||
|   adc_gpio_init(ADC_UNIT_1, (adc_channel_t) gpio_to_adc1(pin_->get_pin())); |     adc1_config_channel_atten(channel_, attenuation_); | ||||||
| #endif |   } | ||||||
|  |  | ||||||
|  |   // load characteristics for each attenuation | ||||||
|  |   for (int i = 0; i < (int) ADC_ATTEN_MAX; i++) { | ||||||
|  |     auto cal_value = esp_adc_cal_characterize(ADC_UNIT_1, (adc_atten_t) i, ADC_WIDTH_BIT_12, | ||||||
|  |                                               1100,  // default vref | ||||||
|  |                                               &cal_characteristics_[i]); | ||||||
|  |     switch (cal_value) { | ||||||
|  |       case ESP_ADC_CAL_VAL_EFUSE_VREF: | ||||||
|  |         ESP_LOGV(TAG, "Using eFuse Vref for calibration"); | ||||||
|  |         break; | ||||||
|  |       case ESP_ADC_CAL_VAL_EFUSE_TP: | ||||||
|  |         ESP_LOGV(TAG, "Using two-point eFuse Vref for calibration"); | ||||||
|  |         break; | ||||||
|  |       case ESP_ADC_CAL_VAL_DEFAULT_VREF: | ||||||
|  |       default: | ||||||
|  |         break; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // adc_gpio_init doesn't exist on ESP32-C3 or ESP32-H2 | ||||||
|  | #if !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32H2) | ||||||
|  |   adc_gpio_init(ADC_UNIT_1, (adc_channel_t) channel_); | ||||||
| #endif | #endif | ||||||
|  | #endif  // USE_ESP32 | ||||||
| } | } | ||||||
|  |  | ||||||
| void ADCSensor::dump_config() { | void ADCSensor::dump_config() { | ||||||
|   LOG_SENSOR("", "ADC Sensor", this); |   LOG_SENSOR("", "ADC Sensor", this); | ||||||
| #ifdef USE_ESP8266 | #ifdef USE_ESP8266 | ||||||
| @@ -81,84 +61,107 @@ void ADCSensor::dump_config() { | |||||||
| #else | #else | ||||||
|   LOG_PIN("  Pin: ", pin_); |   LOG_PIN("  Pin: ", pin_); | ||||||
| #endif | #endif | ||||||
| #endif | #endif  // USE_ESP8266 | ||||||
|  |  | ||||||
| #ifdef USE_ESP32 | #ifdef USE_ESP32 | ||||||
|   LOG_PIN("  Pin: ", pin_); |   LOG_PIN("  Pin: ", pin_); | ||||||
|   switch (this->attenuation_) { |   if (autorange_) | ||||||
|     case ADC_ATTEN_DB_0: |     ESP_LOGCONFIG(TAG, " Attenuation: auto"); | ||||||
|       ESP_LOGCONFIG(TAG, " Attenuation: 0db (max 1.1V)"); |   else | ||||||
|       break; |     switch (this->attenuation_) { | ||||||
|     case ADC_ATTEN_DB_2_5: |       case ADC_ATTEN_DB_0: | ||||||
|       ESP_LOGCONFIG(TAG, " Attenuation: 2.5db (max 1.5V)"); |         ESP_LOGCONFIG(TAG, " Attenuation: 0db (max 1.1V)"); | ||||||
|       break; |         break; | ||||||
|     case ADC_ATTEN_DB_6: |       case ADC_ATTEN_DB_2_5: | ||||||
|       ESP_LOGCONFIG(TAG, " Attenuation: 6db (max 2.2V)"); |         ESP_LOGCONFIG(TAG, " Attenuation: 2.5db (max 1.5V)"); | ||||||
|       break; |         break; | ||||||
|     case ADC_ATTEN_DB_11: |       case ADC_ATTEN_DB_6: | ||||||
|       ESP_LOGCONFIG(TAG, " Attenuation: 11db (max 3.9V)"); |         ESP_LOGCONFIG(TAG, " Attenuation: 6db (max 2.2V)"); | ||||||
|       break; |         break; | ||||||
|     default:  // This is to satisfy the unused ADC_ATTEN_MAX |       case ADC_ATTEN_DB_11: | ||||||
|       break; |         ESP_LOGCONFIG(TAG, " Attenuation: 11db (max 3.9V)"); | ||||||
|   } |         break; | ||||||
| #endif |       default:  // This is to satisfy the unused ADC_ATTEN_MAX | ||||||
|  |         break; | ||||||
|  |     } | ||||||
|  | #endif  // USE_ESP32 | ||||||
|   LOG_UPDATE_INTERVAL(this); |   LOG_UPDATE_INTERVAL(this); | ||||||
| } | } | ||||||
|  |  | ||||||
| float ADCSensor::get_setup_priority() const { return setup_priority::DATA; } | float ADCSensor::get_setup_priority() const { return setup_priority::DATA; } | ||||||
| void ADCSensor::update() { | void ADCSensor::update() { | ||||||
|   float value_v = this->sample(); |   float value_v = this->sample(); | ||||||
|   ESP_LOGD(TAG, "'%s': Got voltage=%.2fV", this->get_name().c_str(), value_v); |   ESP_LOGV(TAG, "'%s': Got voltage=%.4fV", this->get_name().c_str(), value_v); | ||||||
|   this->publish_state(value_v); |   this->publish_state(value_v); | ||||||
| } | } | ||||||
| float ADCSensor::sample() { |  | ||||||
| #ifdef USE_ESP32 |  | ||||||
|   int raw = adc1_get_raw(gpio_to_adc1(pin_->get_pin())); |  | ||||||
|   float value_v = raw / 4095.0f; |  | ||||||
| #if CONFIG_IDF_TARGET_ESP32 |  | ||||||
|   switch (this->attenuation_) { |  | ||||||
|     case ADC_ATTEN_DB_0: |  | ||||||
|       value_v *= 1.1; |  | ||||||
|       break; |  | ||||||
|     case ADC_ATTEN_DB_2_5: |  | ||||||
|       value_v *= 1.5; |  | ||||||
|       break; |  | ||||||
|     case ADC_ATTEN_DB_6: |  | ||||||
|       value_v *= 2.2; |  | ||||||
|       break; |  | ||||||
|     case ADC_ATTEN_DB_11: |  | ||||||
|       value_v *= 3.9; |  | ||||||
|       break; |  | ||||||
|     default:  // This is to satisfy the unused ADC_ATTEN_MAX |  | ||||||
|       break; |  | ||||||
|   } |  | ||||||
| #elif CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32H2 |  | ||||||
|   switch (this->attenuation_) { |  | ||||||
|     case ADC_ATTEN_DB_0: |  | ||||||
|       value_v *= 0.84; |  | ||||||
|       break; |  | ||||||
|     case ADC_ATTEN_DB_2_5: |  | ||||||
|       value_v *= 1.13; |  | ||||||
|       break; |  | ||||||
|     case ADC_ATTEN_DB_6: |  | ||||||
|       value_v *= 1.56; |  | ||||||
|       break; |  | ||||||
|     case ADC_ATTEN_DB_11: |  | ||||||
|       value_v *= 3.0; |  | ||||||
|       break; |  | ||||||
|     default:  // This is to satisfy the unused ADC_ATTEN_MAX |  | ||||||
|       break; |  | ||||||
|   } |  | ||||||
| #endif |  | ||||||
|   return value_v; |  | ||||||
| #endif |  | ||||||
|  |  | ||||||
| #ifdef USE_ESP8266 | #ifdef USE_ESP8266 | ||||||
|  | float ADCSensor::sample() { | ||||||
| #ifdef USE_ADC_SENSOR_VCC | #ifdef USE_ADC_SENSOR_VCC | ||||||
|   return ESP.getVcc() / 1024.0f;  // NOLINT(readability-static-accessed-through-instance) |   int raw = ESP.getVcc();  // NOLINT(readability-static-accessed-through-instance) | ||||||
| #else | #else | ||||||
|   return analogRead(this->pin_->get_pin()) / 1024.0f;  // NOLINT |   int raw = analogRead(this->pin_->get_pin());  // NOLINT | ||||||
| #endif |  | ||||||
| #endif | #endif | ||||||
|  |   if (output_raw_) { | ||||||
|  |     return raw; | ||||||
|  |   } | ||||||
|  |   return raw / 1024.0f; | ||||||
| } | } | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  | #ifdef USE_ESP32 | ||||||
|  | float ADCSensor::sample() { | ||||||
|  |   if (!autorange_) { | ||||||
|  |     int raw = adc1_get_raw(channel_); | ||||||
|  |     if (raw == -1) { | ||||||
|  |       return NAN; | ||||||
|  |     } | ||||||
|  |     if (output_raw_) { | ||||||
|  |       return raw; | ||||||
|  |     } | ||||||
|  |     uint32_t mv = esp_adc_cal_raw_to_voltage(raw, &cal_characteristics_[(int) attenuation_]); | ||||||
|  |     return mv / 1000.0f; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   int raw11, raw6 = 4095, raw2 = 4095, raw0 = 4095; | ||||||
|  |   adc1_config_channel_atten(channel_, ADC_ATTEN_DB_11); | ||||||
|  |   raw11 = adc1_get_raw(channel_); | ||||||
|  |   if (raw11 < 4095) { | ||||||
|  |     adc1_config_channel_atten(channel_, ADC_ATTEN_DB_6); | ||||||
|  |     raw6 = adc1_get_raw(channel_); | ||||||
|  |     if (raw6 < 4095) { | ||||||
|  |       adc1_config_channel_atten(channel_, ADC_ATTEN_DB_2_5); | ||||||
|  |       raw2 = adc1_get_raw(channel_); | ||||||
|  |       if (raw2 < 4095) { | ||||||
|  |         adc1_config_channel_atten(channel_, ADC_ATTEN_DB_0); | ||||||
|  |         raw0 = adc1_get_raw(channel_); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (raw0 == -1 || raw2 == -1 || raw6 == -1 || raw11 == -1) { | ||||||
|  |     return NAN; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   uint32_t mv11 = esp_adc_cal_raw_to_voltage(raw11, &cal_characteristics_[(int) ADC_ATTEN_DB_11]); | ||||||
|  |   uint32_t mv6 = esp_adc_cal_raw_to_voltage(raw6, &cal_characteristics_[(int) ADC_ATTEN_DB_6]); | ||||||
|  |   uint32_t mv2 = esp_adc_cal_raw_to_voltage(raw2, &cal_characteristics_[(int) ADC_ATTEN_DB_2_5]); | ||||||
|  |   uint32_t mv0 = esp_adc_cal_raw_to_voltage(raw0, &cal_characteristics_[(int) ADC_ATTEN_DB_0]); | ||||||
|  |  | ||||||
|  |   // Contribution of each value, in range 0-2048 | ||||||
|  |   uint32_t c11 = std::min(raw11, 2048); | ||||||
|  |   uint32_t c6 = 2048 - std::abs(raw6 - 2048); | ||||||
|  |   uint32_t c2 = 2048 - std::abs(raw2 - 2048); | ||||||
|  |   uint32_t c0 = std::min(4095 - raw0, 2048); | ||||||
|  |   // max theoretical csum value is 2048*4 = 8192 | ||||||
|  |   uint32_t csum = c11 + c6 + c2 + c0; | ||||||
|  |  | ||||||
|  |   // each mv is max 3900; so max value is 3900*2048*4, fits in unsigned | ||||||
|  |   uint32_t mv_scaled = (mv11 * c11) + (mv6 * c6) + (mv2 * c2) + (mv0 * c0); | ||||||
|  |   return mv_scaled / (float) (csum * 1000U); | ||||||
|  | } | ||||||
|  | #endif  // USE_ESP32 | ||||||
|  |  | ||||||
| #ifdef USE_ESP8266 | #ifdef USE_ESP8266 | ||||||
| std::string ADCSensor::unique_id() { return get_mac_address() + "-adc"; } | std::string ADCSensor::unique_id() { return get_mac_address() + "-adc"; } | ||||||
| #endif | #endif | ||||||
|   | |||||||
| @@ -8,6 +8,7 @@ | |||||||
|  |  | ||||||
| #ifdef USE_ESP32 | #ifdef USE_ESP32 | ||||||
| #include "driver/adc.h" | #include "driver/adc.h" | ||||||
|  | #include <esp_adc_cal.h> | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
| namespace esphome { | namespace esphome { | ||||||
| @@ -17,7 +18,9 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage | |||||||
|  public: |  public: | ||||||
| #ifdef USE_ESP32 | #ifdef USE_ESP32 | ||||||
|   /// Set the attenuation for this pin. Only available on the ESP32. |   /// Set the attenuation for this pin. Only available on the ESP32. | ||||||
|   void set_attenuation(adc_atten_t attenuation); |   void set_attenuation(adc_atten_t attenuation) { attenuation_ = attenuation; } | ||||||
|  |   void set_channel(adc1_channel_t channel) { channel_ = channel; } | ||||||
|  |   void set_autorange(bool autorange) { autorange_ = autorange; } | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
|   /// Update adc values. |   /// Update adc values. | ||||||
| @@ -28,6 +31,7 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage | |||||||
|   /// `HARDWARE_LATE` setup priority. |   /// `HARDWARE_LATE` setup priority. | ||||||
|   float get_setup_priority() const override; |   float get_setup_priority() const override; | ||||||
|   void set_pin(InternalGPIOPin *pin) { this->pin_ = pin; } |   void set_pin(InternalGPIOPin *pin) { this->pin_ = pin; } | ||||||
|  |   void set_output_raw(bool output_raw) { output_raw_ = output_raw; } | ||||||
|   float sample() override; |   float sample() override; | ||||||
|  |  | ||||||
| #ifdef USE_ESP8266 | #ifdef USE_ESP8266 | ||||||
| @@ -36,9 +40,13 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage | |||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   InternalGPIOPin *pin_; |   InternalGPIOPin *pin_; | ||||||
|  |   bool output_raw_{false}; | ||||||
|  |  | ||||||
| #ifdef USE_ESP32 | #ifdef USE_ESP32 | ||||||
|   adc_atten_t attenuation_{ADC_ATTEN_DB_0}; |   adc_atten_t attenuation_{ADC_ATTEN_DB_0}; | ||||||
|  |   adc1_channel_t channel_{}; | ||||||
|  |   bool autorange_{false}; | ||||||
|  |   esp_adc_cal_characteristics_t cal_characteristics_[(int) ADC_ATTEN_MAX] = {}; | ||||||
| #endif | #endif | ||||||
| }; | }; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -4,14 +4,24 @@ from esphome import pins | |||||||
| from esphome.components import sensor, voltage_sampler | from esphome.components import sensor, voltage_sampler | ||||||
| from esphome.const import ( | from esphome.const import ( | ||||||
|     CONF_ATTENUATION, |     CONF_ATTENUATION, | ||||||
|  |     CONF_RAW, | ||||||
|     CONF_ID, |     CONF_ID, | ||||||
|     CONF_INPUT, |     CONF_INPUT, | ||||||
|  |     CONF_NUMBER, | ||||||
|     CONF_PIN, |     CONF_PIN, | ||||||
|     DEVICE_CLASS_VOLTAGE, |     DEVICE_CLASS_VOLTAGE, | ||||||
|     STATE_CLASS_MEASUREMENT, |     STATE_CLASS_MEASUREMENT, | ||||||
|     UNIT_VOLT, |     UNIT_VOLT, | ||||||
| ) | ) | ||||||
| from esphome.core import CORE | from esphome.core import CORE | ||||||
|  | from esphome.components.esp32 import get_esp32_variant | ||||||
|  | from esphome.components.esp32.const import ( | ||||||
|  |     VARIANT_ESP32, | ||||||
|  |     VARIANT_ESP32C3, | ||||||
|  |     VARIANT_ESP32H2, | ||||||
|  |     VARIANT_ESP32S2, | ||||||
|  |     VARIANT_ESP32S3, | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
| AUTO_LOAD = ["voltage_sampler"] | AUTO_LOAD = ["voltage_sampler"] | ||||||
| @@ -21,6 +31,62 @@ ATTENUATION_MODES = { | |||||||
|     "2.5db": cg.global_ns.ADC_ATTEN_DB_2_5, |     "2.5db": cg.global_ns.ADC_ATTEN_DB_2_5, | ||||||
|     "6db": cg.global_ns.ADC_ATTEN_DB_6, |     "6db": cg.global_ns.ADC_ATTEN_DB_6, | ||||||
|     "11db": cg.global_ns.ADC_ATTEN_DB_11, |     "11db": cg.global_ns.ADC_ATTEN_DB_11, | ||||||
|  |     "auto": "auto", | ||||||
|  | } | ||||||
|  |  | ||||||
|  | adc1_channel_t = cg.global_ns.enum("adc1_channel_t") | ||||||
|  |  | ||||||
|  | # From https://github.com/espressif/esp-idf/blob/master/components/driver/include/driver/adc_common.h | ||||||
|  | # pin to adc1 channel mapping | ||||||
|  | ESP32_VARIANT_ADC1_PIN_TO_CHANNEL = { | ||||||
|  |     VARIANT_ESP32: { | ||||||
|  |         36: adc1_channel_t.ADC1_CHANNEL_0, | ||||||
|  |         37: adc1_channel_t.ADC1_CHANNEL_1, | ||||||
|  |         38: adc1_channel_t.ADC1_CHANNEL_2, | ||||||
|  |         39: adc1_channel_t.ADC1_CHANNEL_3, | ||||||
|  |         32: adc1_channel_t.ADC1_CHANNEL_4, | ||||||
|  |         33: adc1_channel_t.ADC1_CHANNEL_5, | ||||||
|  |         34: adc1_channel_t.ADC1_CHANNEL_6, | ||||||
|  |         35: adc1_channel_t.ADC1_CHANNEL_7, | ||||||
|  |     }, | ||||||
|  |     VARIANT_ESP32S2: { | ||||||
|  |         1: adc1_channel_t.ADC1_CHANNEL_0, | ||||||
|  |         2: adc1_channel_t.ADC1_CHANNEL_1, | ||||||
|  |         3: adc1_channel_t.ADC1_CHANNEL_2, | ||||||
|  |         4: adc1_channel_t.ADC1_CHANNEL_3, | ||||||
|  |         5: adc1_channel_t.ADC1_CHANNEL_4, | ||||||
|  |         6: adc1_channel_t.ADC1_CHANNEL_5, | ||||||
|  |         7: adc1_channel_t.ADC1_CHANNEL_6, | ||||||
|  |         8: adc1_channel_t.ADC1_CHANNEL_7, | ||||||
|  |         9: adc1_channel_t.ADC1_CHANNEL_8, | ||||||
|  |         10: adc1_channel_t.ADC1_CHANNEL_9, | ||||||
|  |     }, | ||||||
|  |     VARIANT_ESP32S3: { | ||||||
|  |         1: adc1_channel_t.ADC1_CHANNEL_0, | ||||||
|  |         2: adc1_channel_t.ADC1_CHANNEL_1, | ||||||
|  |         3: adc1_channel_t.ADC1_CHANNEL_2, | ||||||
|  |         4: adc1_channel_t.ADC1_CHANNEL_3, | ||||||
|  |         5: adc1_channel_t.ADC1_CHANNEL_4, | ||||||
|  |         6: adc1_channel_t.ADC1_CHANNEL_5, | ||||||
|  |         7: adc1_channel_t.ADC1_CHANNEL_6, | ||||||
|  |         8: adc1_channel_t.ADC1_CHANNEL_7, | ||||||
|  |         9: adc1_channel_t.ADC1_CHANNEL_8, | ||||||
|  |         10: adc1_channel_t.ADC1_CHANNEL_9, | ||||||
|  |     }, | ||||||
|  |     VARIANT_ESP32C3: { | ||||||
|  |         0: adc1_channel_t.ADC1_CHANNEL_0, | ||||||
|  |         1: adc1_channel_t.ADC1_CHANNEL_1, | ||||||
|  |         2: adc1_channel_t.ADC1_CHANNEL_2, | ||||||
|  |         3: adc1_channel_t.ADC1_CHANNEL_3, | ||||||
|  |         4: adc1_channel_t.ADC1_CHANNEL_4, | ||||||
|  |     }, | ||||||
|  |     VARIANT_ESP32H2: { | ||||||
|  |         0: adc1_channel_t.ADC1_CHANNEL_0, | ||||||
|  |         1: adc1_channel_t.ADC1_CHANNEL_1, | ||||||
|  |         2: adc1_channel_t.ADC1_CHANNEL_2, | ||||||
|  |         3: adc1_channel_t.ADC1_CHANNEL_3, | ||||||
|  |         4: adc1_channel_t.ADC1_CHANNEL_4, | ||||||
|  |     }, | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -29,15 +95,16 @@ def validate_adc_pin(value): | |||||||
|         return cv.only_on_esp8266("VCC") |         return cv.only_on_esp8266("VCC") | ||||||
|  |  | ||||||
|     if CORE.is_esp32: |     if CORE.is_esp32: | ||||||
|         from esphome.components.esp32 import is_esp32c3 |  | ||||||
|  |  | ||||||
|         value = pins.internal_gpio_input_pin_number(value) |         value = pins.internal_gpio_input_pin_number(value) | ||||||
|         if is_esp32c3(): |         variant = get_esp32_variant() | ||||||
|             if not (0 <= value <= 4):  # ADC1 |         if variant not in ESP32_VARIANT_ADC1_PIN_TO_CHANNEL: | ||||||
|                 raise cv.Invalid("ESP32-C3: Only pins 0 though 4 support ADC.") |             raise cv.Invalid(f"This ESP32 variant ({variant}) is not supported") | ||||||
|         if not (32 <= value <= 39):  # ADC1 |  | ||||||
|             raise cv.Invalid("ESP32: Only pins 32 though 39 support ADC.") |         if value not in ESP32_VARIANT_ADC1_PIN_TO_CHANNEL[variant]: | ||||||
|     elif CORE.is_esp8266: |             raise cv.Invalid(f"{variant} doesn't support ADC on this pin") | ||||||
|  |         return pins.internal_gpio_input_pin_schema(value) | ||||||
|  |  | ||||||
|  |     if CORE.is_esp8266: | ||||||
|         from esphome.components.esp8266.gpio import CONF_ANALOG |         from esphome.components.esp8266.gpio import CONF_ANALOG | ||||||
|  |  | ||||||
|         value = pins.internal_gpio_pin_number({CONF_ANALOG: True, CONF_INPUT: True})( |         value = pins.internal_gpio_pin_number({CONF_ANALOG: True, CONF_INPUT: True})( | ||||||
| @@ -49,10 +116,14 @@ def validate_adc_pin(value): | |||||||
|         return pins.gpio_pin_schema( |         return pins.gpio_pin_schema( | ||||||
|             {CONF_ANALOG: True, CONF_INPUT: True}, internal=True |             {CONF_ANALOG: True, CONF_INPUT: True}, internal=True | ||||||
|         )(value) |         )(value) | ||||||
|     else: |  | ||||||
|         raise NotImplementedError |  | ||||||
|  |  | ||||||
|     return pins.internal_gpio_input_pin_schema(value) |     raise NotImplementedError | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def validate_config(config): | ||||||
|  |     if config[CONF_RAW] and config.get(CONF_ATTENUATION, None) == "auto": | ||||||
|  |         raise cv.Invalid("Automatic attenuation cannot be used when raw output is set.") | ||||||
|  |     return config | ||||||
|  |  | ||||||
|  |  | ||||||
| adc_ns = cg.esphome_ns.namespace("adc") | adc_ns = cg.esphome_ns.namespace("adc") | ||||||
| @@ -60,7 +131,7 @@ ADCSensor = adc_ns.class_( | |||||||
|     "ADCSensor", sensor.Sensor, cg.PollingComponent, voltage_sampler.VoltageSampler |     "ADCSensor", sensor.Sensor, cg.PollingComponent, voltage_sampler.VoltageSampler | ||||||
| ) | ) | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = ( | CONFIG_SCHEMA = cv.All( | ||||||
|     sensor.sensor_schema( |     sensor.sensor_schema( | ||||||
|         unit_of_measurement=UNIT_VOLT, |         unit_of_measurement=UNIT_VOLT, | ||||||
|         accuracy_decimals=2, |         accuracy_decimals=2, | ||||||
| @@ -71,12 +142,14 @@ CONFIG_SCHEMA = ( | |||||||
|         { |         { | ||||||
|             cv.GenerateID(): cv.declare_id(ADCSensor), |             cv.GenerateID(): cv.declare_id(ADCSensor), | ||||||
|             cv.Required(CONF_PIN): validate_adc_pin, |             cv.Required(CONF_PIN): validate_adc_pin, | ||||||
|  |             cv.Optional(CONF_RAW, default=False): cv.boolean, | ||||||
|             cv.SplitDefault(CONF_ATTENUATION, esp32="0db"): cv.All( |             cv.SplitDefault(CONF_ATTENUATION, esp32="0db"): cv.All( | ||||||
|                 cv.only_on_esp32, cv.enum(ATTENUATION_MODES, lower=True) |                 cv.only_on_esp32, cv.enum(ATTENUATION_MODES, lower=True) | ||||||
|             ), |             ), | ||||||
|         } |         } | ||||||
|     ) |     ) | ||||||
|     .extend(cv.polling_component_schema("60s")) |     .extend(cv.polling_component_schema("60s")), | ||||||
|  |     validate_config, | ||||||
| ) | ) | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -91,5 +164,17 @@ async def to_code(config): | |||||||
|         pin = await cg.gpio_pin_expression(config[CONF_PIN]) |         pin = await cg.gpio_pin_expression(config[CONF_PIN]) | ||||||
|         cg.add(var.set_pin(pin)) |         cg.add(var.set_pin(pin)) | ||||||
|  |  | ||||||
|  |     if CONF_RAW in config: | ||||||
|  |         cg.add(var.set_output_raw(config[CONF_RAW])) | ||||||
|  |  | ||||||
|     if CONF_ATTENUATION in config: |     if CONF_ATTENUATION in config: | ||||||
|         cg.add(var.set_attenuation(config[CONF_ATTENUATION])) |         if config[CONF_ATTENUATION] == "auto": | ||||||
|  |             cg.add(var.set_autorange(cg.global_ns.true)) | ||||||
|  |         else: | ||||||
|  |             cg.add(var.set_attenuation(config[CONF_ATTENUATION])) | ||||||
|  |  | ||||||
|  |     if CORE.is_esp32: | ||||||
|  |         variant = get_esp32_variant() | ||||||
|  |         pin_num = config[CONF_PIN][CONF_NUMBER] | ||||||
|  |         chan = ESP32_VARIANT_ADC1_PIN_TO_CHANNEL[variant][pin_num] | ||||||
|  |         cg.add(var.set_channel(chan)) | ||||||
|   | |||||||
| @@ -76,9 +76,9 @@ class ADE7953 : public i2c::I2CDevice, public PollingComponent { | |||||||
|       return err; |       return err; | ||||||
|     *value = 0; |     *value = 0; | ||||||
|     *value |= ((uint32_t) recv[0]) << 24; |     *value |= ((uint32_t) recv[0]) << 24; | ||||||
|     *value |= ((uint32_t) recv[1]) << 24; |     *value |= ((uint32_t) recv[1]) << 16; | ||||||
|     *value |= ((uint32_t) recv[2]) << 24; |     *value |= ((uint32_t) recv[2]) << 8; | ||||||
|     *value |= ((uint32_t) recv[3]) << 24; |     *value |= ((uint32_t) recv[3]); | ||||||
|     return i2c::ERROR_OK; |     return i2c::ERROR_OK; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -73,13 +73,6 @@ void AHT10Component::update() { | |||||||
|   bool success = false; |   bool success = false; | ||||||
|   for (int i = 0; i < AHT10_ATTEMPTS; ++i) { |   for (int i = 0; i < AHT10_ATTEMPTS; ++i) { | ||||||
|     ESP_LOGVV(TAG, "Attempt %d at %6u", i, millis()); |     ESP_LOGVV(TAG, "Attempt %d at %6u", i, millis()); | ||||||
|     delay_microseconds_accurate(4); |  | ||||||
|  |  | ||||||
|     uint8_t reg = 0; |  | ||||||
|     if (this->write(®, 1) != i2c::ERROR_OK) { |  | ||||||
|       ESP_LOGD(TAG, "Communication with AHT10 failed, waiting..."); |  | ||||||
|       continue; |  | ||||||
|     } |  | ||||||
|     delay(delay_ms); |     delay(delay_ms); | ||||||
|     if (this->read(data, 6) != i2c::ERROR_OK) { |     if (this->read(data, 6) != i2c::ERROR_OK) { | ||||||
|       ESP_LOGD(TAG, "Communication with AHT10 failed, waiting..."); |       ESP_LOGD(TAG, "Communication with AHT10 failed, waiting..."); | ||||||
| @@ -117,12 +110,12 @@ void AHT10Component::update() { | |||||||
|   uint32_t raw_temperature = ((data[3] & 0x0F) << 16) | (data[4] << 8) | data[5]; |   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; |   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 temperature = ((200.0f * (float) raw_temperature) / 1048576.0f) - 50.0f; | ||||||
|   float humidity; |   float humidity; | ||||||
|   if (raw_humidity == 0) {  // unrealistic value |   if (raw_humidity == 0) {  // unrealistic value | ||||||
|     humidity = NAN; |     humidity = NAN; | ||||||
|   } else { |   } else { | ||||||
|     humidity = (float) raw_humidity * 100.0 / 1048576.0; |     humidity = (float) raw_humidity * 100.0f / 1048576.0f; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   if (this->temperature_sensor_ != nullptr) { |   if (this->temperature_sensor_ != nullptr) { | ||||||
|   | |||||||
| @@ -38,9 +38,9 @@ void AM2320Component::update() { | |||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   float temperature = (((data[4] & 0x7F) << 8) + data[5]) / 10.0; |   float temperature = (((data[4] & 0x7F) << 8) + data[5]) / 10.0f; | ||||||
|   temperature = (data[4] & 0x80) ? -temperature : temperature; |   temperature = (data[4] & 0x80) ? -temperature : temperature; | ||||||
|   float humidity = ((data[2] << 8) + data[3]) / 10.0; |   float humidity = ((data[2] << 8) + data[3]) / 10.0f; | ||||||
|  |  | ||||||
|   ESP_LOGD(TAG, "Got temperature=%.1f°C humidity=%.1f%%", temperature, humidity); |   ESP_LOGD(TAG, "Got temperature=%.1f°C humidity=%.1f%%", temperature, humidity); | ||||||
|   if (this->temperature_sensor_ != nullptr) |   if (this->temperature_sensor_ != nullptr) | ||||||
|   | |||||||
| @@ -4,7 +4,8 @@ from esphome.components import sensor, ble_client | |||||||
| from esphome.const import ( | from esphome.const import ( | ||||||
|     CONF_ID, |     CONF_ID, | ||||||
|     CONF_BATTERY_LEVEL, |     CONF_BATTERY_LEVEL, | ||||||
|     ICON_BATTERY, |     DEVICE_CLASS_BATTERY, | ||||||
|  |     ENTITY_CATEGORY_DIAGNOSTIC, | ||||||
|     CONF_ILLUMINANCE, |     CONF_ILLUMINANCE, | ||||||
|     ICON_BRIGHTNESS_5, |     ICON_BRIGHTNESS_5, | ||||||
|     UNIT_PERCENT, |     UNIT_PERCENT, | ||||||
| @@ -20,10 +21,15 @@ CONFIG_SCHEMA = ( | |||||||
|         { |         { | ||||||
|             cv.GenerateID(): cv.declare_id(Am43), |             cv.GenerateID(): cv.declare_id(Am43), | ||||||
|             cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema( |             cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema( | ||||||
|                 UNIT_PERCENT, ICON_BATTERY, 0 |                 unit_of_measurement=UNIT_PERCENT, | ||||||
|  |                 device_class=DEVICE_CLASS_BATTERY, | ||||||
|  |                 accuracy_decimals=0, | ||||||
|  |                 entity_category=ENTITY_CATEGORY_DIAGNOSTIC, | ||||||
|             ), |             ), | ||||||
|             cv.Optional(CONF_ILLUMINANCE): sensor.sensor_schema( |             cv.Optional(CONF_ILLUMINANCE): sensor.sensor_schema( | ||||||
|                 UNIT_PERCENT, ICON_BRIGHTNESS_5, 0 |                 unit_of_measurement=UNIT_PERCENT, | ||||||
|  |                 icon=ICON_BRIGHTNESS_5, | ||||||
|  |                 accuracy_decimals=0, | ||||||
|             ), |             ), | ||||||
|         } |         } | ||||||
|     ) |     ) | ||||||
|   | |||||||
| @@ -44,8 +44,9 @@ async def to_code(config): | |||||||
|     width, height = image.size |     width, height = image.size | ||||||
|     frames = image.n_frames |     frames = image.n_frames | ||||||
|     if CONF_RESIZE in config: |     if CONF_RESIZE in config: | ||||||
|         image.thumbnail(config[CONF_RESIZE]) |         new_width_max, new_height_max = config[CONF_RESIZE] | ||||||
|         width, height = image.size |         ratio = min(new_width_max / width, new_height_max / height) | ||||||
|  |         width, height = int(width * ratio), int(height * ratio) | ||||||
|     else: |     else: | ||||||
|         if width > 500 or height > 500: |         if width > 500 or height > 500: | ||||||
|             _LOGGER.warning( |             _LOGGER.warning( | ||||||
| @@ -59,7 +60,13 @@ async def to_code(config): | |||||||
|         for frameIndex in range(frames): |         for frameIndex in range(frames): | ||||||
|             image.seek(frameIndex) |             image.seek(frameIndex) | ||||||
|             frame = image.convert("L", dither=Image.NONE) |             frame = image.convert("L", dither=Image.NONE) | ||||||
|  |             if CONF_RESIZE in config: | ||||||
|  |                 frame = frame.resize([width, height]) | ||||||
|             pixels = list(frame.getdata()) |             pixels = list(frame.getdata()) | ||||||
|  |             if len(pixels) != height * width: | ||||||
|  |                 raise core.EsphomeError( | ||||||
|  |                     f"Unexpected number of pixels in {path} frame {frameIndex}: ({len(pixels)} != {height*width})" | ||||||
|  |                 ) | ||||||
|             for pix in pixels: |             for pix in pixels: | ||||||
|                 data[pos] = pix |                 data[pos] = pix | ||||||
|                 pos += 1 |                 pos += 1 | ||||||
| @@ -70,7 +77,13 @@ async def to_code(config): | |||||||
|         for frameIndex in range(frames): |         for frameIndex in range(frames): | ||||||
|             image.seek(frameIndex) |             image.seek(frameIndex) | ||||||
|             frame = image.convert("RGB") |             frame = image.convert("RGB") | ||||||
|  |             if CONF_RESIZE in config: | ||||||
|  |                 frame = frame.resize([width, height]) | ||||||
|             pixels = list(frame.getdata()) |             pixels = list(frame.getdata()) | ||||||
|  |             if len(pixels) != height * width: | ||||||
|  |                 raise core.EsphomeError( | ||||||
|  |                     f"Unexpected number of pixels in {path} frame {frameIndex}: ({len(pixels)} != {height*width})" | ||||||
|  |                 ) | ||||||
|             for pix in pixels: |             for pix in pixels: | ||||||
|                 data[pos] = pix[0] |                 data[pos] = pix[0] | ||||||
|                 pos += 1 |                 pos += 1 | ||||||
| @@ -85,6 +98,8 @@ async def to_code(config): | |||||||
|         for frameIndex in range(frames): |         for frameIndex in range(frames): | ||||||
|             image.seek(frameIndex) |             image.seek(frameIndex) | ||||||
|             frame = image.convert("1", dither=Image.NONE) |             frame = image.convert("1", dither=Image.NONE) | ||||||
|  |             if CONF_RESIZE in config: | ||||||
|  |                 frame = frame.resize([width, height]) | ||||||
|             for y in range(height): |             for y in range(height): | ||||||
|                 for x in range(width): |                 for x in range(width): | ||||||
|                     if frame.getpixel((x, y)): |                     if frame.getpixel((x, y)): | ||||||
|   | |||||||
| @@ -30,7 +30,7 @@ class Anova : public climate::Climate, public esphome::ble_client::BLEClientNode | |||||||
|   climate::ClimateTraits traits() override { |   climate::ClimateTraits traits() override { | ||||||
|     auto traits = climate::ClimateTraits(); |     auto traits = climate::ClimateTraits(); | ||||||
|     traits.set_supports_current_temperature(true); |     traits.set_supports_current_temperature(true); | ||||||
|     traits.set_supports_heat_mode(true); |     traits.set_supported_modes({climate::CLIMATE_MODE_OFF, climate::ClimateMode::CLIMATE_MODE_HEAT}); | ||||||
|     traits.set_visual_min_temperature(25.0); |     traits.set_visual_min_temperature(25.0); | ||||||
|     traits.set_visual_max_temperature(100.0); |     traits.set_visual_max_temperature(100.0); | ||||||
|     traits.set_visual_temperature_step(0.1); |     traits.set_visual_temperature_step(0.1); | ||||||
|   | |||||||
| @@ -73,51 +73,46 @@ AnovaPacket *AnovaCodec::get_stop_request() { | |||||||
| } | } | ||||||
|  |  | ||||||
| void AnovaCodec::decode(const uint8_t *data, uint16_t length) { | void AnovaCodec::decode(const uint8_t *data, uint16_t length) { | ||||||
|   memset(this->buf_, 0, 32); |   char buf[32]; | ||||||
|   strncpy(this->buf_, (char *) data, length); |   memset(buf, 0, sizeof(buf)); | ||||||
|  |   strncpy(buf, (char *) data, std::min<uint16_t>(length, sizeof(buf) - 1)); | ||||||
|   this->has_target_temp_ = this->has_current_temp_ = this->has_unit_ = this->has_running_ = false; |   this->has_target_temp_ = this->has_current_temp_ = this->has_unit_ = this->has_running_ = false; | ||||||
|   switch (this->current_query_) { |   switch (this->current_query_) { | ||||||
|     case READ_DEVICE_STATUS: { |     case READ_DEVICE_STATUS: { | ||||||
|       if (!strncmp(this->buf_, "stopped", 7)) { |       if (!strncmp(buf, "stopped", 7)) { | ||||||
|         this->has_running_ = true; |         this->has_running_ = true; | ||||||
|         this->running_ = false; |         this->running_ = false; | ||||||
|       } |       } | ||||||
|       if (!strncmp(this->buf_, "running", 7)) { |       if (!strncmp(buf, "running", 7)) { | ||||||
|         this->has_running_ = true; |         this->has_running_ = true; | ||||||
|         this->running_ = true; |         this->running_ = true; | ||||||
|       } |       } | ||||||
|       break; |       break; | ||||||
|     } |     } | ||||||
|     case START: { |     case START: { | ||||||
|       if (!strncmp(this->buf_, "start", 5)) { |       if (!strncmp(buf, "start", 5)) { | ||||||
|         this->has_running_ = true; |         this->has_running_ = true; | ||||||
|         this->running_ = true; |         this->running_ = true; | ||||||
|       } |       } | ||||||
|       break; |       break; | ||||||
|     } |     } | ||||||
|     case STOP: { |     case STOP: { | ||||||
|       if (!strncmp(this->buf_, "stop", 4)) { |       if (!strncmp(buf, "stop", 4)) { | ||||||
|         this->has_running_ = true; |         this->has_running_ = true; | ||||||
|         this->running_ = false; |         this->running_ = false; | ||||||
|       } |       } | ||||||
|       break; |       break; | ||||||
|     } |     } | ||||||
|     case READ_TARGET_TEMPERATURE: { |     case READ_TARGET_TEMPERATURE: | ||||||
|       this->target_temp_ = strtof(this->buf_, nullptr); |  | ||||||
|       if (this->fahrenheit_) |  | ||||||
|         this->target_temp_ = ftoc(this->target_temp_); |  | ||||||
|       this->has_target_temp_ = true; |  | ||||||
|       break; |  | ||||||
|     } |  | ||||||
|     case SET_TARGET_TEMPERATURE: { |     case SET_TARGET_TEMPERATURE: { | ||||||
|       this->target_temp_ = strtof(this->buf_, nullptr); |       this->target_temp_ = parse_number<float>(str_until(buf, '\r')).value_or(0.0f); | ||||||
|       if (this->fahrenheit_) |       if (this->fahrenheit_) | ||||||
|         this->target_temp_ = ftoc(this->target_temp_); |         this->target_temp_ = ftoc(this->target_temp_); | ||||||
|       this->has_target_temp_ = true; |       this->has_target_temp_ = true; | ||||||
|       break; |       break; | ||||||
|     } |     } | ||||||
|     case READ_CURRENT_TEMPERATURE: { |     case READ_CURRENT_TEMPERATURE: { | ||||||
|       this->current_temp_ = strtof(this->buf_, nullptr); |       this->current_temp_ = parse_number<float>(str_until(buf, '\r')).value_or(0.0f); | ||||||
|       if (this->fahrenheit_) |       if (this->fahrenheit_) | ||||||
|         this->current_temp_ = ftoc(this->current_temp_); |         this->current_temp_ = ftoc(this->current_temp_); | ||||||
|       this->has_current_temp_ = true; |       this->has_current_temp_ = true; | ||||||
| @@ -125,8 +120,8 @@ void AnovaCodec::decode(const uint8_t *data, uint16_t length) { | |||||||
|     } |     } | ||||||
|     case SET_UNIT: |     case SET_UNIT: | ||||||
|     case READ_UNIT: { |     case READ_UNIT: { | ||||||
|       this->unit_ = this->buf_[0]; |       this->unit_ = buf[0]; | ||||||
|       this->fahrenheit_ = this->buf_[0] == 'f'; |       this->fahrenheit_ = buf[0] == 'f'; | ||||||
|       this->has_unit_ = true; |       this->has_unit_ = true; | ||||||
|       break; |       break; | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -70,7 +70,6 @@ class AnovaCodec { | |||||||
|   bool has_current_temp_; |   bool has_current_temp_; | ||||||
|   bool has_unit_; |   bool has_unit_; | ||||||
|   bool has_running_; |   bool has_running_; | ||||||
|   char buf_[32]; |  | ||||||
|   bool fahrenheit_; |   bool fahrenheit_; | ||||||
|  |  | ||||||
|   CurrentQuery current_query_; |   CurrentQuery current_query_; | ||||||
|   | |||||||
| @@ -121,7 +121,7 @@ async def to_code(config): | |||||||
|         decoded = base64.b64decode(conf[CONF_KEY]) |         decoded = base64.b64decode(conf[CONF_KEY]) | ||||||
|         cg.add(var.set_noise_psk(list(decoded))) |         cg.add(var.set_noise_psk(list(decoded))) | ||||||
|         cg.add_define("USE_API_NOISE") |         cg.add_define("USE_API_NOISE") | ||||||
|         cg.add_library("esphome/noise-c", "0.1.3") |         cg.add_library("esphome/noise-c", "0.1.4") | ||||||
|     else: |     else: | ||||||
|         cg.add_define("USE_API_PLAINTEXT") |         cg.add_define("USE_API_PLAINTEXT") | ||||||
|  |  | ||||||
|   | |||||||
| @@ -40,6 +40,7 @@ service APIConnection { | |||||||
|   rpc climate_command (ClimateCommandRequest) returns (void) {} |   rpc climate_command (ClimateCommandRequest) returns (void) {} | ||||||
|   rpc number_command (NumberCommandRequest) returns (void) {} |   rpc number_command (NumberCommandRequest) returns (void) {} | ||||||
|   rpc select_command (SelectCommandRequest) returns (void) {} |   rpc select_command (SelectCommandRequest) returns (void) {} | ||||||
|  |   rpc button_command (ButtonCommandRequest) returns (void) {} | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -182,6 +183,8 @@ message DeviceInfoResponse { | |||||||
|   // The esphome project details if set |   // The esphome project details if set | ||||||
|   string project_name = 8; |   string project_name = 8; | ||||||
|   string project_version = 9; |   string project_version = 9; | ||||||
|  |  | ||||||
|  |   uint32 webserver_port = 10; | ||||||
| } | } | ||||||
|  |  | ||||||
| message ListEntitiesRequest { | message ListEntitiesRequest { | ||||||
| @@ -201,6 +204,14 @@ message SubscribeStatesRequest { | |||||||
|   // Empty |   // Empty | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // ==================== COMMON ===================== | ||||||
|  |  | ||||||
|  | enum EntityCategory { | ||||||
|  |   ENTITY_CATEGORY_NONE = 0; | ||||||
|  |   ENTITY_CATEGORY_CONFIG = 1; | ||||||
|  |   ENTITY_CATEGORY_DIAGNOSTIC = 2; | ||||||
|  | } | ||||||
|  |  | ||||||
| // ==================== BINARY SENSOR ==================== | // ==================== BINARY SENSOR ==================== | ||||||
| message ListEntitiesBinarySensorResponse { | message ListEntitiesBinarySensorResponse { | ||||||
|   option (id) = 12; |   option (id) = 12; | ||||||
| @@ -216,6 +227,7 @@ message ListEntitiesBinarySensorResponse { | |||||||
|   bool is_status_binary_sensor = 6; |   bool is_status_binary_sensor = 6; | ||||||
|   bool disabled_by_default = 7; |   bool disabled_by_default = 7; | ||||||
|   string icon = 8; |   string icon = 8; | ||||||
|  |   EntityCategory entity_category = 9; | ||||||
| } | } | ||||||
| message BinarySensorStateResponse { | message BinarySensorStateResponse { | ||||||
|   option (id) = 21; |   option (id) = 21; | ||||||
| @@ -247,6 +259,7 @@ message ListEntitiesCoverResponse { | |||||||
|   string device_class = 8; |   string device_class = 8; | ||||||
|   bool disabled_by_default = 9; |   bool disabled_by_default = 9; | ||||||
|   string icon = 10; |   string icon = 10; | ||||||
|  |   EntityCategory entity_category = 11; | ||||||
| } | } | ||||||
|  |  | ||||||
| enum LegacyCoverState { | enum LegacyCoverState { | ||||||
| @@ -316,6 +329,7 @@ message ListEntitiesFanResponse { | |||||||
|   int32 supported_speed_count = 8; |   int32 supported_speed_count = 8; | ||||||
|   bool disabled_by_default = 9; |   bool disabled_by_default = 9; | ||||||
|   string icon = 10; |   string icon = 10; | ||||||
|  |   EntityCategory entity_category = 11; | ||||||
| } | } | ||||||
| enum FanSpeed { | enum FanSpeed { | ||||||
|   FAN_SPEED_LOW = 0; |   FAN_SPEED_LOW = 0; | ||||||
| @@ -392,6 +406,7 @@ message ListEntitiesLightResponse { | |||||||
|   repeated string effects = 11; |   repeated string effects = 11; | ||||||
|   bool disabled_by_default = 13; |   bool disabled_by_default = 13; | ||||||
|   string icon = 14; |   string icon = 14; | ||||||
|  |   EntityCategory entity_category = 15; | ||||||
| } | } | ||||||
| message LightStateResponse { | message LightStateResponse { | ||||||
|   option (id) = 24; |   option (id) = 24; | ||||||
| @@ -480,6 +495,7 @@ message ListEntitiesSensorResponse { | |||||||
|   // Last reset type removed in 2021.9.0 |   // Last reset type removed in 2021.9.0 | ||||||
|   SensorLastResetType legacy_last_reset_type = 11; |   SensorLastResetType legacy_last_reset_type = 11; | ||||||
|   bool disabled_by_default = 12; |   bool disabled_by_default = 12; | ||||||
|  |   EntityCategory entity_category = 13; | ||||||
| } | } | ||||||
| message SensorStateResponse { | message SensorStateResponse { | ||||||
|   option (id) = 25; |   option (id) = 25; | ||||||
| @@ -508,6 +524,7 @@ message ListEntitiesSwitchResponse { | |||||||
|   string icon = 5; |   string icon = 5; | ||||||
|   bool assumed_state = 6; |   bool assumed_state = 6; | ||||||
|   bool disabled_by_default = 7; |   bool disabled_by_default = 7; | ||||||
|  |   EntityCategory entity_category = 8; | ||||||
| } | } | ||||||
| message SwitchStateResponse { | message SwitchStateResponse { | ||||||
|   option (id) = 26; |   option (id) = 26; | ||||||
| @@ -541,6 +558,7 @@ message ListEntitiesTextSensorResponse { | |||||||
|  |  | ||||||
|   string icon = 5; |   string icon = 5; | ||||||
|   bool disabled_by_default = 6; |   bool disabled_by_default = 6; | ||||||
|  |   EntityCategory entity_category = 7; | ||||||
| } | } | ||||||
| message TextSensorStateResponse { | message TextSensorStateResponse { | ||||||
|   option (id) = 27; |   option (id) = 27; | ||||||
| @@ -701,6 +719,8 @@ message ListEntitiesCameraResponse { | |||||||
|   string name = 3; |   string name = 3; | ||||||
|   string unique_id = 4; |   string unique_id = 4; | ||||||
|   bool disabled_by_default = 5; |   bool disabled_by_default = 5; | ||||||
|  |   string icon = 6; | ||||||
|  |   EntityCategory entity_category = 7; | ||||||
| } | } | ||||||
|  |  | ||||||
| message CameraImageResponse { | message CameraImageResponse { | ||||||
| @@ -795,6 +815,7 @@ message ListEntitiesClimateResponse { | |||||||
|   repeated string supported_custom_presets = 17; |   repeated string supported_custom_presets = 17; | ||||||
|   bool disabled_by_default = 18; |   bool disabled_by_default = 18; | ||||||
|   string icon = 19; |   string icon = 19; | ||||||
|  |   EntityCategory entity_category = 20; | ||||||
| } | } | ||||||
| message ClimateStateResponse { | message ClimateStateResponse { | ||||||
|   option (id) = 47; |   option (id) = 47; | ||||||
| @@ -848,6 +869,11 @@ message ClimateCommandRequest { | |||||||
| } | } | ||||||
|  |  | ||||||
| // ==================== NUMBER ==================== | // ==================== NUMBER ==================== | ||||||
|  | enum NumberMode { | ||||||
|  |   NUMBER_MODE_AUTO = 0; | ||||||
|  |   NUMBER_MODE_BOX = 1; | ||||||
|  |   NUMBER_MODE_SLIDER = 2; | ||||||
|  | } | ||||||
| message ListEntitiesNumberResponse { | message ListEntitiesNumberResponse { | ||||||
|   option (id) = 49; |   option (id) = 49; | ||||||
|   option (source) = SOURCE_SERVER; |   option (source) = SOURCE_SERVER; | ||||||
| @@ -863,6 +889,9 @@ message ListEntitiesNumberResponse { | |||||||
|   float max_value = 7; |   float max_value = 7; | ||||||
|   float step = 8; |   float step = 8; | ||||||
|   bool disabled_by_default = 9; |   bool disabled_by_default = 9; | ||||||
|  |   EntityCategory entity_category = 10; | ||||||
|  |   string unit_of_measurement = 11; | ||||||
|  |   NumberMode mode = 12; | ||||||
| } | } | ||||||
| message NumberStateResponse { | message NumberStateResponse { | ||||||
|   option (id) = 50; |   option (id) = 50; | ||||||
| @@ -900,6 +929,7 @@ message ListEntitiesSelectResponse { | |||||||
|   string icon = 5; |   string icon = 5; | ||||||
|   repeated string options = 6; |   repeated string options = 6; | ||||||
|   bool disabled_by_default = 7; |   bool disabled_by_default = 7; | ||||||
|  |   EntityCategory entity_category = 8; | ||||||
| } | } | ||||||
| message SelectStateResponse { | message SelectStateResponse { | ||||||
|   option (id) = 53; |   option (id) = 53; | ||||||
| @@ -922,3 +952,28 @@ message SelectCommandRequest { | |||||||
|   fixed32 key = 1; |   fixed32 key = 1; | ||||||
|   string state = 2; |   string state = 2; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // ==================== BUTTON ==================== | ||||||
|  | message ListEntitiesButtonResponse { | ||||||
|  |   option (id) = 61; | ||||||
|  |   option (source) = SOURCE_SERVER; | ||||||
|  |   option (ifdef) = "USE_BUTTON"; | ||||||
|  |  | ||||||
|  |   string object_id = 1; | ||||||
|  |   fixed32 key = 2; | ||||||
|  |   string name = 3; | ||||||
|  |   string unique_id = 4; | ||||||
|  |  | ||||||
|  |   string icon = 5; | ||||||
|  |   bool disabled_by_default = 6; | ||||||
|  |   EntityCategory entity_category = 7; | ||||||
|  |   string device_class = 8; | ||||||
|  | } | ||||||
|  | message ButtonCommandRequest { | ||||||
|  |   option (id) = 62; | ||||||
|  |   option (source) = SOURCE_CLIENT; | ||||||
|  |   option (ifdef) = "USE_BUTTON"; | ||||||
|  |   option (no_delay) = true; | ||||||
|  |  | ||||||
|  |   fixed32 key = 1; | ||||||
|  | } | ||||||
|   | |||||||
| @@ -20,6 +20,7 @@ namespace esphome { | |||||||
| namespace api { | namespace api { | ||||||
|  |  | ||||||
| static const char *const TAG = "api.connection"; | static const char *const TAG = "api.connection"; | ||||||
|  | static const int ESP32_CAMERA_STOP_STREAM = 5000; | ||||||
|  |  | ||||||
| APIConnection::APIConnection(std::unique_ptr<socket::Socket> sock, APIServer *parent) | APIConnection::APIConnection(std::unique_ptr<socket::Socket> sock, APIServer *parent) | ||||||
|     : parent_(parent), initial_state_iterator_(parent, this), list_entities_iterator_(parent, this) { |     : parent_(parent), initial_state_iterator_(parent, this), list_entities_iterator_(parent, this) { | ||||||
| @@ -78,6 +79,8 @@ void APIConnection::loop() { | |||||||
|     on_fatal_error(); |     on_fatal_error(); | ||||||
|     if (err == APIError::SOCKET_READ_FAILED && errno == ECONNRESET) { |     if (err == APIError::SOCKET_READ_FAILED && errno == ECONNRESET) { | ||||||
|       ESP_LOGW(TAG, "%s: Connection reset", client_info_.c_str()); |       ESP_LOGW(TAG, "%s: Connection reset", client_info_.c_str()); | ||||||
|  |     } else if (err == APIError::CONNECTION_CLOSED) { | ||||||
|  |       ESP_LOGW(TAG, "%s: Connection closed", client_info_.c_str()); | ||||||
|     } else { |     } else { | ||||||
|       ESP_LOGW(TAG, "%s: Reading failed: %s errno=%d", client_info_.c_str(), api_error_to_str(err), errno); |       ESP_LOGW(TAG, "%s: Reading failed: %s errno=%d", client_info_.c_str(), api_error_to_str(err), errno); | ||||||
|     } |     } | ||||||
| @@ -130,7 +133,7 @@ void APIConnection::loop() { | |||||||
|  |  | ||||||
|   if (state_subs_at_ != -1) { |   if (state_subs_at_ != -1) { | ||||||
|     const auto &subs = this->parent_->get_state_subs(); |     const auto &subs = this->parent_->get_state_subs(); | ||||||
|     if (state_subs_at_ >= subs.size()) { |     if (state_subs_at_ >= (int) subs.size()) { | ||||||
|       state_subs_at_ = -1; |       state_subs_at_ = -1; | ||||||
|     } else { |     } else { | ||||||
|       auto &it = subs[state_subs_at_]; |       auto &it = subs[state_subs_at_]; | ||||||
| @@ -182,6 +185,7 @@ bool APIConnection::send_binary_sensor_info(binary_sensor::BinarySensor *binary_ | |||||||
|   msg.is_status_binary_sensor = binary_sensor->is_status_binary_sensor(); |   msg.is_status_binary_sensor = binary_sensor->is_status_binary_sensor(); | ||||||
|   msg.disabled_by_default = binary_sensor->is_disabled_by_default(); |   msg.disabled_by_default = binary_sensor->is_disabled_by_default(); | ||||||
|   msg.icon = binary_sensor->get_icon(); |   msg.icon = binary_sensor->get_icon(); | ||||||
|  |   msg.entity_category = static_cast<enums::EntityCategory>(binary_sensor->get_entity_category()); | ||||||
|   return this->send_list_entities_binary_sensor_response(msg); |   return this->send_list_entities_binary_sensor_response(msg); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| @@ -215,6 +219,7 @@ bool APIConnection::send_cover_info(cover::Cover *cover) { | |||||||
|   msg.device_class = cover->get_device_class(); |   msg.device_class = cover->get_device_class(); | ||||||
|   msg.disabled_by_default = cover->is_disabled_by_default(); |   msg.disabled_by_default = cover->is_disabled_by_default(); | ||||||
|   msg.icon = cover->get_icon(); |   msg.icon = cover->get_icon(); | ||||||
|  |   msg.entity_category = static_cast<enums::EntityCategory>(cover->get_entity_category()); | ||||||
|   return this->send_list_entities_cover_response(msg); |   return this->send_list_entities_cover_response(msg); | ||||||
| } | } | ||||||
| void APIConnection::cover_command(const CoverCommandRequest &msg) { | void APIConnection::cover_command(const CoverCommandRequest &msg) { | ||||||
| @@ -281,6 +286,7 @@ bool APIConnection::send_fan_info(fan::FanState *fan) { | |||||||
|   msg.supported_speed_count = traits.supported_speed_count(); |   msg.supported_speed_count = traits.supported_speed_count(); | ||||||
|   msg.disabled_by_default = fan->is_disabled_by_default(); |   msg.disabled_by_default = fan->is_disabled_by_default(); | ||||||
|   msg.icon = fan->get_icon(); |   msg.icon = fan->get_icon(); | ||||||
|  |   msg.entity_category = static_cast<enums::EntityCategory>(fan->get_entity_category()); | ||||||
|   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) { | ||||||
| @@ -344,6 +350,7 @@ bool APIConnection::send_light_info(light::LightState *light) { | |||||||
|  |  | ||||||
|   msg.disabled_by_default = light->is_disabled_by_default(); |   msg.disabled_by_default = light->is_disabled_by_default(); | ||||||
|   msg.icon = light->get_icon(); |   msg.icon = light->get_icon(); | ||||||
|  |   msg.entity_category = static_cast<enums::EntityCategory>(light->get_entity_category()); | ||||||
|  |  | ||||||
|   for (auto mode : traits.get_supported_color_modes()) |   for (auto mode : traits.get_supported_color_modes()) | ||||||
|     msg.supported_color_modes.push_back(static_cast<enums::ColorMode>(mode)); |     msg.supported_color_modes.push_back(static_cast<enums::ColorMode>(mode)); | ||||||
| @@ -430,7 +437,7 @@ bool APIConnection::send_sensor_info(sensor::Sensor *sensor) { | |||||||
|   msg.device_class = sensor->get_device_class(); |   msg.device_class = sensor->get_device_class(); | ||||||
|   msg.state_class = static_cast<enums::SensorStateClass>(sensor->get_state_class()); |   msg.state_class = static_cast<enums::SensorStateClass>(sensor->get_state_class()); | ||||||
|   msg.disabled_by_default = sensor->is_disabled_by_default(); |   msg.disabled_by_default = sensor->is_disabled_by_default(); | ||||||
|  |   msg.entity_category = static_cast<enums::EntityCategory>(sensor->get_entity_category()); | ||||||
|   return this->send_list_entities_sensor_response(msg); |   return this->send_list_entities_sensor_response(msg); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| @@ -454,6 +461,7 @@ bool APIConnection::send_switch_info(switch_::Switch *a_switch) { | |||||||
|   msg.icon = a_switch->get_icon(); |   msg.icon = a_switch->get_icon(); | ||||||
|   msg.assumed_state = a_switch->assumed_state(); |   msg.assumed_state = a_switch->assumed_state(); | ||||||
|   msg.disabled_by_default = a_switch->is_disabled_by_default(); |   msg.disabled_by_default = a_switch->is_disabled_by_default(); | ||||||
|  |   msg.entity_category = static_cast<enums::EntityCategory>(a_switch->get_entity_category()); | ||||||
|   return this->send_list_entities_switch_response(msg); |   return this->send_list_entities_switch_response(msg); | ||||||
| } | } | ||||||
| void APIConnection::switch_command(const SwitchCommandRequest &msg) { | void APIConnection::switch_command(const SwitchCommandRequest &msg) { | ||||||
| @@ -489,6 +497,7 @@ bool APIConnection::send_text_sensor_info(text_sensor::TextSensor *text_sensor) | |||||||
|     msg.unique_id = get_default_unique_id("text_sensor", text_sensor); |     msg.unique_id = get_default_unique_id("text_sensor", text_sensor); | ||||||
|   msg.icon = text_sensor->get_icon(); |   msg.icon = text_sensor->get_icon(); | ||||||
|   msg.disabled_by_default = text_sensor->is_disabled_by_default(); |   msg.disabled_by_default = text_sensor->is_disabled_by_default(); | ||||||
|  |   msg.entity_category = static_cast<enums::EntityCategory>(text_sensor->get_entity_category()); | ||||||
|   return this->send_list_entities_text_sensor_response(msg); |   return this->send_list_entities_text_sensor_response(msg); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| @@ -535,6 +544,7 @@ bool APIConnection::send_climate_info(climate::Climate *climate) { | |||||||
|  |  | ||||||
|   msg.disabled_by_default = climate->is_disabled_by_default(); |   msg.disabled_by_default = climate->is_disabled_by_default(); | ||||||
|   msg.icon = climate->get_icon(); |   msg.icon = climate->get_icon(); | ||||||
|  |   msg.entity_category = static_cast<enums::EntityCategory>(climate->get_entity_category()); | ||||||
|  |  | ||||||
|   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(); | ||||||
| @@ -609,6 +619,9 @@ bool APIConnection::send_number_info(number::Number *number) { | |||||||
|   msg.unique_id = get_default_unique_id("number", number); |   msg.unique_id = get_default_unique_id("number", number); | ||||||
|   msg.icon = number->get_icon(); |   msg.icon = number->get_icon(); | ||||||
|   msg.disabled_by_default = number->is_disabled_by_default(); |   msg.disabled_by_default = number->is_disabled_by_default(); | ||||||
|  |   msg.entity_category = static_cast<enums::EntityCategory>(number->get_entity_category()); | ||||||
|  |   msg.unit_of_measurement = number->traits.get_unit_of_measurement(); | ||||||
|  |   msg.mode = static_cast<enums::NumberMode>(number->traits.get_mode()); | ||||||
|  |  | ||||||
|   msg.min_value = number->traits.get_min_value(); |   msg.min_value = number->traits.get_min_value(); | ||||||
|   msg.max_value = number->traits.get_max_value(); |   msg.max_value = number->traits.get_max_value(); | ||||||
| @@ -646,6 +659,7 @@ bool APIConnection::send_select_info(select::Select *select) { | |||||||
|   msg.unique_id = get_default_unique_id("select", select); |   msg.unique_id = get_default_unique_id("select", select); | ||||||
|   msg.icon = select->get_icon(); |   msg.icon = select->get_icon(); | ||||||
|   msg.disabled_by_default = select->is_disabled_by_default(); |   msg.disabled_by_default = select->is_disabled_by_default(); | ||||||
|  |   msg.entity_category = static_cast<enums::EntityCategory>(select->get_entity_category()); | ||||||
|  |  | ||||||
|   for (const auto &option : select->traits.get_options()) |   for (const auto &option : select->traits.get_options()) | ||||||
|     msg.options.push_back(option); |     msg.options.push_back(option); | ||||||
| @@ -663,13 +677,37 @@ void APIConnection::select_command(const SelectCommandRequest &msg) { | |||||||
| } | } | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
|  | #ifdef USE_BUTTON | ||||||
|  | bool APIConnection::send_button_info(button::Button *button) { | ||||||
|  |   ListEntitiesButtonResponse msg; | ||||||
|  |   msg.key = button->get_object_id_hash(); | ||||||
|  |   msg.object_id = button->get_object_id(); | ||||||
|  |   msg.name = button->get_name(); | ||||||
|  |   msg.unique_id = get_default_unique_id("button", button); | ||||||
|  |   msg.icon = button->get_icon(); | ||||||
|  |   msg.disabled_by_default = button->is_disabled_by_default(); | ||||||
|  |   msg.entity_category = static_cast<enums::EntityCategory>(button->get_entity_category()); | ||||||
|  |   msg.device_class = button->get_device_class(); | ||||||
|  |   return this->send_list_entities_button_response(msg); | ||||||
|  | } | ||||||
|  | void APIConnection::button_command(const ButtonCommandRequest &msg) { | ||||||
|  |   button::Button *button = App.get_button_by_key(msg.key); | ||||||
|  |   if (button == nullptr) | ||||||
|  |     return; | ||||||
|  |  | ||||||
|  |   button->press(); | ||||||
|  | } | ||||||
|  | #endif | ||||||
|  |  | ||||||
| #ifdef USE_ESP32_CAMERA | #ifdef USE_ESP32_CAMERA | ||||||
| void APIConnection::send_camera_state(std::shared_ptr<esp32_camera::CameraImage> image) { | void APIConnection::send_camera_state(std::shared_ptr<esp32_camera::CameraImage> image) { | ||||||
|   if (!this->state_subscription_) |   if (!this->state_subscription_) | ||||||
|     return; |     return; | ||||||
|   if (this->image_reader_.available()) |   if (this->image_reader_.available()) | ||||||
|     return; |     return; | ||||||
|   this->image_reader_.set_image(std::move(image)); |   if (image->was_requested_by(esphome::esp32_camera::API_REQUESTER) || | ||||||
|  |       image->was_requested_by(esphome::esp32_camera::IDLE)) | ||||||
|  |     this->image_reader_.set_image(std::move(image)); | ||||||
| } | } | ||||||
| bool APIConnection::send_camera_info(esp32_camera::ESP32Camera *camera) { | bool APIConnection::send_camera_info(esp32_camera::ESP32Camera *camera) { | ||||||
|   ListEntitiesCameraResponse msg; |   ListEntitiesCameraResponse msg; | ||||||
| @@ -678,6 +716,8 @@ bool APIConnection::send_camera_info(esp32_camera::ESP32Camera *camera) { | |||||||
|   msg.name = camera->get_name(); |   msg.name = camera->get_name(); | ||||||
|   msg.unique_id = get_default_unique_id("camera", camera); |   msg.unique_id = get_default_unique_id("camera", camera); | ||||||
|   msg.disabled_by_default = camera->is_disabled_by_default(); |   msg.disabled_by_default = camera->is_disabled_by_default(); | ||||||
|  |   msg.icon = camera->get_icon(); | ||||||
|  |   msg.entity_category = static_cast<enums::EntityCategory>(camera->get_entity_category()); | ||||||
|   return this->send_list_entities_camera_response(msg); |   return this->send_list_entities_camera_response(msg); | ||||||
| } | } | ||||||
| void APIConnection::camera_image(const CameraImageRequest &msg) { | void APIConnection::camera_image(const CameraImageRequest &msg) { | ||||||
| @@ -685,9 +725,14 @@ void APIConnection::camera_image(const CameraImageRequest &msg) { | |||||||
|     return; |     return; | ||||||
|  |  | ||||||
|   if (msg.single) |   if (msg.single) | ||||||
|     esp32_camera::global_esp32_camera->request_image(); |     esp32_camera::global_esp32_camera->request_image(esphome::esp32_camera::API_REQUESTER); | ||||||
|   if (msg.stream) |   if (msg.stream) { | ||||||
|     esp32_camera::global_esp32_camera->request_stream(); |     esp32_camera::global_esp32_camera->start_stream(esphome::esp32_camera::API_REQUESTER); | ||||||
|  |  | ||||||
|  |     App.scheduler.set_timeout(this->parent_, "api_esp32_camera_stop_stream", ESP32_CAMERA_STOP_STREAM, []() { | ||||||
|  |       esp32_camera::global_esp32_camera->stop_stream(esphome::esp32_camera::API_REQUESTER); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
| @@ -756,6 +801,9 @@ DeviceInfoResponse APIConnection::device_info(const DeviceInfoRequest &msg) { | |||||||
| #ifdef ESPHOME_PROJECT_NAME | #ifdef ESPHOME_PROJECT_NAME | ||||||
|   resp.project_name = ESPHOME_PROJECT_NAME; |   resp.project_name = ESPHOME_PROJECT_NAME; | ||||||
|   resp.project_version = ESPHOME_PROJECT_VERSION; |   resp.project_version = ESPHOME_PROJECT_VERSION; | ||||||
|  | #endif | ||||||
|  | #ifdef USE_WEBSERVER | ||||||
|  |   resp.webserver_port = WEBSERVER_PORT; | ||||||
| #endif | #endif | ||||||
|   return resp; |   return resp; | ||||||
| } | } | ||||||
|   | |||||||
| @@ -73,6 +73,10 @@ class APIConnection : public APIServerConnection { | |||||||
|   bool send_select_state(select::Select *select, std::string state); |   bool send_select_state(select::Select *select, std::string state); | ||||||
|   bool send_select_info(select::Select *select); |   bool send_select_info(select::Select *select); | ||||||
|   void select_command(const SelectCommandRequest &msg) override; |   void select_command(const SelectCommandRequest &msg) override; | ||||||
|  | #endif | ||||||
|  | #ifdef USE_BUTTON | ||||||
|  |   bool send_button_info(button::Button *button); | ||||||
|  |   void button_command(const ButtonCommandRequest &msg) override; | ||||||
| #endif | #endif | ||||||
|   bool send_log_message(int level, const char *tag, const char *line); |   bool send_log_message(int level, const char *tag, const char *line); | ||||||
|   void send_homeassistant_service_call(const HomeassistantServiceResponse &call) { |   void send_homeassistant_service_call(const HomeassistantServiceResponse &call) { | ||||||
|   | |||||||
| @@ -1,6 +1,7 @@ | |||||||
| #include "api_frame_helper.h" | #include "api_frame_helper.h" | ||||||
|  |  | ||||||
| #include "esphome/core/log.h" | #include "esphome/core/log.h" | ||||||
|  | #include "esphome/core/hal.h" | ||||||
| #include "esphome/core/helpers.h" | #include "esphome/core/helpers.h" | ||||||
| #include "proto.h" | #include "proto.h" | ||||||
| #include <cstring> | #include <cstring> | ||||||
| @@ -10,7 +11,7 @@ namespace api { | |||||||
|  |  | ||||||
| static const char *const TAG = "api.socket"; | static const char *const TAG = "api.socket"; | ||||||
|  |  | ||||||
| /// Is the given return value (from read/write syscalls) a wouldblock error? | /// Is the given return value (from write syscalls) a wouldblock error? | ||||||
| bool is_would_block(ssize_t ret) { | bool is_would_block(ssize_t ret) { | ||||||
|   if (ret == -1) { |   if (ret == -1) { | ||||||
|     return errno == EWOULDBLOCK || errno == EAGAIN; |     return errno == EWOULDBLOCK || errno == EAGAIN; | ||||||
| @@ -64,6 +65,8 @@ const char *api_error_to_str(APIError err) { | |||||||
|     return "HANDSHAKESTATE_SPLIT_FAILED"; |     return "HANDSHAKESTATE_SPLIT_FAILED"; | ||||||
|   } else if (err == APIError::BAD_HANDSHAKE_ERROR_BYTE) { |   } else if (err == APIError::BAD_HANDSHAKE_ERROR_BYTE) { | ||||||
|     return "BAD_HANDSHAKE_ERROR_BYTE"; |     return "BAD_HANDSHAKE_ERROR_BYTE"; | ||||||
|  |   } else if (err == APIError::CONNECTION_CLOSED) { | ||||||
|  |     return "CONNECTION_CLOSED"; | ||||||
|   } |   } | ||||||
|   return "UNKNOWN"; |   return "UNKNOWN"; | ||||||
| } | } | ||||||
| @@ -172,9 +175,6 @@ APIError APINoiseFrameHelper::loop() { | |||||||
|  * errno API_ERROR_HANDSHAKE_PACKET_LEN: Packet too big for this phase. |  * errno API_ERROR_HANDSHAKE_PACKET_LEN: Packet too big for this phase. | ||||||
|  */ |  */ | ||||||
| APIError APINoiseFrameHelper::try_read_frame_(ParsedFrame *frame) { | APIError APINoiseFrameHelper::try_read_frame_(ParsedFrame *frame) { | ||||||
|   int err; |  | ||||||
|   APIError aerr; |  | ||||||
|  |  | ||||||
|   if (frame == nullptr) { |   if (frame == nullptr) { | ||||||
|     HELPER_LOG("Bad argument for try_read_frame_"); |     HELPER_LOG("Bad argument for try_read_frame_"); | ||||||
|     return APIError::BAD_ARG; |     return APIError::BAD_ARG; | ||||||
| @@ -185,15 +185,20 @@ APIError APINoiseFrameHelper::try_read_frame_(ParsedFrame *frame) { | |||||||
|     // no header information yet |     // no header information yet | ||||||
|     size_t to_read = 3 - rx_header_buf_len_; |     size_t to_read = 3 - rx_header_buf_len_; | ||||||
|     ssize_t received = socket_->read(&rx_header_buf_[rx_header_buf_len_], to_read); |     ssize_t received = socket_->read(&rx_header_buf_[rx_header_buf_len_], to_read); | ||||||
|     if (is_would_block(received)) { |     if (received == -1) { | ||||||
|       return APIError::WOULD_BLOCK; |       if (errno == EWOULDBLOCK || errno == EAGAIN) { | ||||||
|     } else if (received == -1) { |         return APIError::WOULD_BLOCK; | ||||||
|  |       } | ||||||
|       state_ = State::FAILED; |       state_ = State::FAILED; | ||||||
|       HELPER_LOG("Socket read failed with errno %d", errno); |       HELPER_LOG("Socket read failed with errno %d", errno); | ||||||
|       return APIError::SOCKET_READ_FAILED; |       return APIError::SOCKET_READ_FAILED; | ||||||
|  |     } else if (received == 0) { | ||||||
|  |       state_ = State::FAILED; | ||||||
|  |       HELPER_LOG("Connection closed"); | ||||||
|  |       return APIError::CONNECTION_CLOSED; | ||||||
|     } |     } | ||||||
|     rx_header_buf_len_ += received; |     rx_header_buf_len_ += received; | ||||||
|     if (received != to_read) { |     if ((size_t) received != to_read) { | ||||||
|       // not a full read |       // not a full read | ||||||
|       return APIError::WOULD_BLOCK; |       return APIError::WOULD_BLOCK; | ||||||
|     } |     } | ||||||
| @@ -227,15 +232,20 @@ APIError APINoiseFrameHelper::try_read_frame_(ParsedFrame *frame) { | |||||||
|     // more data to read |     // more data to read | ||||||
|     size_t to_read = msg_size - rx_buf_len_; |     size_t to_read = msg_size - rx_buf_len_; | ||||||
|     ssize_t received = socket_->read(&rx_buf_[rx_buf_len_], to_read); |     ssize_t received = socket_->read(&rx_buf_[rx_buf_len_], to_read); | ||||||
|     if (is_would_block(received)) { |     if (received == -1) { | ||||||
|       return APIError::WOULD_BLOCK; |       if (errno == EWOULDBLOCK || errno == EAGAIN) { | ||||||
|     } else if (received == -1) { |         return APIError::WOULD_BLOCK; | ||||||
|  |       } | ||||||
|       state_ = State::FAILED; |       state_ = State::FAILED; | ||||||
|       HELPER_LOG("Socket read failed with errno %d", errno); |       HELPER_LOG("Socket read failed with errno %d", errno); | ||||||
|       return APIError::SOCKET_READ_FAILED; |       return APIError::SOCKET_READ_FAILED; | ||||||
|  |     } else if (received == 0) { | ||||||
|  |       state_ = State::FAILED; | ||||||
|  |       HELPER_LOG("Connection closed"); | ||||||
|  |       return APIError::CONNECTION_CLOSED; | ||||||
|     } |     } | ||||||
|     rx_buf_len_ += received; |     rx_buf_len_ += received; | ||||||
|     if (received != to_read) { |     if ((size_t) received != to_read) { | ||||||
|       // not all read |       // not all read | ||||||
|       return APIError::WOULD_BLOCK; |       return APIError::WOULD_BLOCK; | ||||||
|     } |     } | ||||||
| @@ -243,7 +253,7 @@ APIError APINoiseFrameHelper::try_read_frame_(ParsedFrame *frame) { | |||||||
|  |  | ||||||
|   // uncomment for even more debugging |   // uncomment for even more debugging | ||||||
| #ifdef HELPER_LOG_PACKETS | #ifdef HELPER_LOG_PACKETS | ||||||
|   ESP_LOGVV(TAG, "Received frame: %s", hexencode(rx_buf_).c_str()); |   ESP_LOGVV(TAG, "Received frame: %s", format_hex_pretty(rx_buf_).c_str()); | ||||||
| #endif | #endif | ||||||
|   frame->msg = std::move(rx_buf_); |   frame->msg = std::move(rx_buf_); | ||||||
|   // consume msg |   // consume msg | ||||||
| @@ -532,13 +542,13 @@ APIError APINoiseFrameHelper::try_send_tx_buf_() { | |||||||
| APIError APINoiseFrameHelper::write_raw_(const struct iovec *iov, int iovcnt) { | APIError APINoiseFrameHelper::write_raw_(const struct iovec *iov, int iovcnt) { | ||||||
|   if (iovcnt == 0) |   if (iovcnt == 0) | ||||||
|     return APIError::OK; |     return APIError::OK; | ||||||
|   int err; |  | ||||||
|   APIError aerr; |   APIError aerr; | ||||||
|  |  | ||||||
|   size_t total_write_len = 0; |   size_t total_write_len = 0; | ||||||
|   for (int i = 0; i < iovcnt; i++) { |   for (int i = 0; i < iovcnt; i++) { | ||||||
| #ifdef HELPER_LOG_PACKETS | #ifdef HELPER_LOG_PACKETS | ||||||
|     ESP_LOGVV(TAG, "Sending raw: %s", hexencode(reinterpret_cast<uint8_t *>(iov[i].iov_base), iov[i].iov_len).c_str()); |     ESP_LOGVV(TAG, "Sending raw: %s", | ||||||
|  |               format_hex_pretty(reinterpret_cast<uint8_t *>(iov[i].iov_base), iov[i].iov_len).c_str()); | ||||||
| #endif | #endif | ||||||
|     total_write_len += iov[i].iov_len; |     total_write_len += iov[i].iov_len; | ||||||
|   } |   } | ||||||
| @@ -572,7 +582,7 @@ APIError APINoiseFrameHelper::write_raw_(const struct iovec *iov, int iovcnt) { | |||||||
|     state_ = State::FAILED; |     state_ = State::FAILED; | ||||||
|     HELPER_LOG("Socket write failed with errno %d", errno); |     HELPER_LOG("Socket write failed with errno %d", errno); | ||||||
|     return APIError::SOCKET_WRITE_FAILED; |     return APIError::SOCKET_WRITE_FAILED; | ||||||
|   } else if (sent != total_write_len) { |   } else if ((size_t) sent != total_write_len) { | ||||||
|     // partially sent, add end to tx_buf |     // partially sent, add end to tx_buf | ||||||
|     size_t to_consume = sent; |     size_t to_consume = sent; | ||||||
|     for (int i = 0; i < iovcnt; i++) { |     for (int i = 0; i < iovcnt; i++) { | ||||||
| @@ -712,7 +722,12 @@ APIError APINoiseFrameHelper::shutdown(int how) { | |||||||
| } | } | ||||||
| extern "C" { | extern "C" { | ||||||
| // declare how noise generates random bytes (here with a good HWRNG based on the RF system) | // declare how noise generates random bytes (here with a good HWRNG based on the RF system) | ||||||
| void noise_rand_bytes(void *output, size_t len) { esphome::fill_random(reinterpret_cast<uint8_t *>(output), len); } | void noise_rand_bytes(void *output, size_t len) { | ||||||
|  |   if (!esphome::random_bytes(reinterpret_cast<uint8_t *>(output), len)) { | ||||||
|  |     ESP_LOGE(TAG, "Failed to acquire random bytes, rebooting!"); | ||||||
|  |     arch_restart(); | ||||||
|  |   } | ||||||
|  | } | ||||||
| } | } | ||||||
| #endif  // USE_API_NOISE | #endif  // USE_API_NOISE | ||||||
|  |  | ||||||
| @@ -766,9 +781,6 @@ APIError APIPlaintextFrameHelper::loop() { | |||||||
|  * error API_ERROR_BAD_INDICATOR: Bad indicator byte at start of frame. |  * error API_ERROR_BAD_INDICATOR: Bad indicator byte at start of frame. | ||||||
|  */ |  */ | ||||||
| APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) { | APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) { | ||||||
|   int err; |  | ||||||
|   APIError aerr; |  | ||||||
|  |  | ||||||
|   if (frame == nullptr) { |   if (frame == nullptr) { | ||||||
|     HELPER_LOG("Bad argument for try_read_frame_"); |     HELPER_LOG("Bad argument for try_read_frame_"); | ||||||
|     return APIError::BAD_ARG; |     return APIError::BAD_ARG; | ||||||
| @@ -778,12 +790,17 @@ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) { | |||||||
|   while (!rx_header_parsed_) { |   while (!rx_header_parsed_) { | ||||||
|     uint8_t data; |     uint8_t data; | ||||||
|     ssize_t received = socket_->read(&data, 1); |     ssize_t received = socket_->read(&data, 1); | ||||||
|     if (is_would_block(received)) { |     if (received == -1) { | ||||||
|       return APIError::WOULD_BLOCK; |       if (errno == EWOULDBLOCK || errno == EAGAIN) { | ||||||
|     } else if (received == -1) { |         return APIError::WOULD_BLOCK; | ||||||
|  |       } | ||||||
|       state_ = State::FAILED; |       state_ = State::FAILED; | ||||||
|       HELPER_LOG("Socket read failed with errno %d", errno); |       HELPER_LOG("Socket read failed with errno %d", errno); | ||||||
|       return APIError::SOCKET_READ_FAILED; |       return APIError::SOCKET_READ_FAILED; | ||||||
|  |     } else if (received == 0) { | ||||||
|  |       state_ = State::FAILED; | ||||||
|  |       HELPER_LOG("Connection closed"); | ||||||
|  |       return APIError::CONNECTION_CLOSED; | ||||||
|     } |     } | ||||||
|     rx_header_buf_.push_back(data); |     rx_header_buf_.push_back(data); | ||||||
|  |  | ||||||
| @@ -824,15 +841,20 @@ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) { | |||||||
|     // more data to read |     // more data to read | ||||||
|     size_t to_read = rx_header_parsed_len_ - rx_buf_len_; |     size_t to_read = rx_header_parsed_len_ - rx_buf_len_; | ||||||
|     ssize_t received = socket_->read(&rx_buf_[rx_buf_len_], to_read); |     ssize_t received = socket_->read(&rx_buf_[rx_buf_len_], to_read); | ||||||
|     if (is_would_block(received)) { |     if (received == -1) { | ||||||
|       return APIError::WOULD_BLOCK; |       if (errno == EWOULDBLOCK || errno == EAGAIN) { | ||||||
|     } else if (received == -1) { |         return APIError::WOULD_BLOCK; | ||||||
|  |       } | ||||||
|       state_ = State::FAILED; |       state_ = State::FAILED; | ||||||
|       HELPER_LOG("Socket read failed with errno %d", errno); |       HELPER_LOG("Socket read failed with errno %d", errno); | ||||||
|       return APIError::SOCKET_READ_FAILED; |       return APIError::SOCKET_READ_FAILED; | ||||||
|  |     } else if (received == 0) { | ||||||
|  |       state_ = State::FAILED; | ||||||
|  |       HELPER_LOG("Connection closed"); | ||||||
|  |       return APIError::CONNECTION_CLOSED; | ||||||
|     } |     } | ||||||
|     rx_buf_len_ += received; |     rx_buf_len_ += received; | ||||||
|     if (received != to_read) { |     if ((size_t) received != to_read) { | ||||||
|       // not all read |       // not all read | ||||||
|       return APIError::WOULD_BLOCK; |       return APIError::WOULD_BLOCK; | ||||||
|     } |     } | ||||||
| @@ -840,7 +862,7 @@ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) { | |||||||
|  |  | ||||||
|   // uncomment for even more debugging |   // uncomment for even more debugging | ||||||
| #ifdef HELPER_LOG_PACKETS | #ifdef HELPER_LOG_PACKETS | ||||||
|   ESP_LOGVV(TAG, "Received frame: %s", hexencode(rx_buf_).c_str()); |   ESP_LOGVV(TAG, "Received frame: %s", format_hex_pretty(rx_buf_).c_str()); | ||||||
| #endif | #endif | ||||||
|   frame->msg = std::move(rx_buf_); |   frame->msg = std::move(rx_buf_); | ||||||
|   // consume msg |   // consume msg | ||||||
| @@ -852,7 +874,6 @@ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) { | |||||||
| } | } | ||||||
|  |  | ||||||
| APIError APIPlaintextFrameHelper::read_packet(ReadPacketBuffer *buffer) { | APIError APIPlaintextFrameHelper::read_packet(ReadPacketBuffer *buffer) { | ||||||
|   int err; |  | ||||||
|   APIError aerr; |   APIError aerr; | ||||||
|  |  | ||||||
|   if (state_ != State::DATA) { |   if (state_ != State::DATA) { | ||||||
| @@ -872,9 +893,6 @@ APIError APIPlaintextFrameHelper::read_packet(ReadPacketBuffer *buffer) { | |||||||
| } | } | ||||||
| bool APIPlaintextFrameHelper::can_write_without_blocking() { return state_ == State::DATA && tx_buf_.empty(); } | bool APIPlaintextFrameHelper::can_write_without_blocking() { return state_ == State::DATA && tx_buf_.empty(); } | ||||||
| APIError APIPlaintextFrameHelper::write_packet(uint16_t type, const uint8_t *payload, size_t payload_len) { | APIError APIPlaintextFrameHelper::write_packet(uint16_t type, const uint8_t *payload, size_t payload_len) { | ||||||
|   int err; |  | ||||||
|   APIError aerr; |  | ||||||
|  |  | ||||||
|   if (state_ != State::DATA) { |   if (state_ != State::DATA) { | ||||||
|     return APIError::BAD_STATE; |     return APIError::BAD_STATE; | ||||||
|   } |   } | ||||||
| @@ -918,13 +936,13 @@ APIError APIPlaintextFrameHelper::try_send_tx_buf_() { | |||||||
| APIError APIPlaintextFrameHelper::write_raw_(const struct iovec *iov, int iovcnt) { | APIError APIPlaintextFrameHelper::write_raw_(const struct iovec *iov, int iovcnt) { | ||||||
|   if (iovcnt == 0) |   if (iovcnt == 0) | ||||||
|     return APIError::OK; |     return APIError::OK; | ||||||
|   int err; |  | ||||||
|   APIError aerr; |   APIError aerr; | ||||||
|  |  | ||||||
|   size_t total_write_len = 0; |   size_t total_write_len = 0; | ||||||
|   for (int i = 0; i < iovcnt; i++) { |   for (int i = 0; i < iovcnt; i++) { | ||||||
| #ifdef HELPER_LOG_PACKETS | #ifdef HELPER_LOG_PACKETS | ||||||
|     ESP_LOGVV(TAG, "Sending raw: %s", hexencode(reinterpret_cast<uint8_t *>(iov[i].iov_base), iov[i].iov_len).c_str()); |     ESP_LOGVV(TAG, "Sending raw: %s", | ||||||
|  |               format_hex_pretty(reinterpret_cast<uint8_t *>(iov[i].iov_base), iov[i].iov_len).c_str()); | ||||||
| #endif | #endif | ||||||
|     total_write_len += iov[i].iov_len; |     total_write_len += iov[i].iov_len; | ||||||
|   } |   } | ||||||
| @@ -958,7 +976,7 @@ APIError APIPlaintextFrameHelper::write_raw_(const struct iovec *iov, int iovcnt | |||||||
|     state_ = State::FAILED; |     state_ = State::FAILED; | ||||||
|     HELPER_LOG("Socket write failed with errno %d", errno); |     HELPER_LOG("Socket write failed with errno %d", errno); | ||||||
|     return APIError::SOCKET_WRITE_FAILED; |     return APIError::SOCKET_WRITE_FAILED; | ||||||
|   } else if (sent != total_write_len) { |   } else if ((size_t) sent != total_write_len) { | ||||||
|     // partially sent, add end to tx_buf |     // partially sent, add end to tx_buf | ||||||
|     size_t to_consume = sent; |     size_t to_consume = sent; | ||||||
|     for (int i = 0; i < iovcnt; i++) { |     for (int i = 0; i < iovcnt; i++) { | ||||||
|   | |||||||
| @@ -53,6 +53,7 @@ enum class APIError : int { | |||||||
|   HANDSHAKESTATE_SETUP_FAILED = 1019, |   HANDSHAKESTATE_SETUP_FAILED = 1019, | ||||||
|   HANDSHAKESTATE_SPLIT_FAILED = 1020, |   HANDSHAKESTATE_SPLIT_FAILED = 1020, | ||||||
|   BAD_HANDSHAKE_ERROR_BYTE = 1021, |   BAD_HANDSHAKE_ERROR_BYTE = 1021, | ||||||
|  |   CONNECTION_CLOSED = 1022, | ||||||
| }; | }; | ||||||
|  |  | ||||||
| const char *api_error_to_str(APIError err); | const char *api_error_to_str(APIError err); | ||||||
|   | |||||||
| @@ -6,6 +6,18 @@ | |||||||
| namespace esphome { | namespace esphome { | ||||||
| namespace api { | namespace api { | ||||||
|  |  | ||||||
|  | template<> const char *proto_enum_to_string<enums::EntityCategory>(enums::EntityCategory value) { | ||||||
|  |   switch (value) { | ||||||
|  |     case enums::ENTITY_CATEGORY_NONE: | ||||||
|  |       return "ENTITY_CATEGORY_NONE"; | ||||||
|  |     case enums::ENTITY_CATEGORY_CONFIG: | ||||||
|  |       return "ENTITY_CATEGORY_CONFIG"; | ||||||
|  |     case enums::ENTITY_CATEGORY_DIAGNOSTIC: | ||||||
|  |       return "ENTITY_CATEGORY_DIAGNOSTIC"; | ||||||
|  |     default: | ||||||
|  |       return "UNKNOWN"; | ||||||
|  |   } | ||||||
|  | } | ||||||
| template<> const char *proto_enum_to_string<enums::LegacyCoverState>(enums::LegacyCoverState value) { | template<> const char *proto_enum_to_string<enums::LegacyCoverState>(enums::LegacyCoverState value) { | ||||||
|   switch (value) { |   switch (value) { | ||||||
|     case enums::LEGACY_COVER_STATE_OPEN: |     case enums::LEGACY_COVER_STATE_OPEN: | ||||||
| @@ -254,6 +266,18 @@ template<> const char *proto_enum_to_string<enums::ClimatePreset>(enums::Climate | |||||||
|       return "UNKNOWN"; |       return "UNKNOWN"; | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  | template<> const char *proto_enum_to_string<enums::NumberMode>(enums::NumberMode value) { | ||||||
|  |   switch (value) { | ||||||
|  |     case enums::NUMBER_MODE_AUTO: | ||||||
|  |       return "NUMBER_MODE_AUTO"; | ||||||
|  |     case enums::NUMBER_MODE_BOX: | ||||||
|  |       return "NUMBER_MODE_BOX"; | ||||||
|  |     case enums::NUMBER_MODE_SLIDER: | ||||||
|  |       return "NUMBER_MODE_SLIDER"; | ||||||
|  |     default: | ||||||
|  |       return "UNKNOWN"; | ||||||
|  |   } | ||||||
|  | } | ||||||
| bool HelloRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { | bool HelloRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { | ||||||
|   switch (field_id) { |   switch (field_id) { | ||||||
|     case 1: { |     case 1: { | ||||||
| @@ -267,7 +291,7 @@ bool HelloRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) | |||||||
| void HelloRequest::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(1, this->client_info); } | void HelloRequest::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(1, this->client_info); } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void HelloRequest::dump_to(std::string &out) const { | void HelloRequest::dump_to(std::string &out) const { | ||||||
|   char buffer[64]; |   __attribute__((unused)) char buffer[64]; | ||||||
|   out.append("HelloRequest {\n"); |   out.append("HelloRequest {\n"); | ||||||
|   out.append("  client_info: "); |   out.append("  client_info: "); | ||||||
|   out.append("'").append(this->client_info).append("'"); |   out.append("'").append(this->client_info).append("'"); | ||||||
| @@ -306,7 +330,7 @@ void HelloResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void HelloResponse::dump_to(std::string &out) const { | void HelloResponse::dump_to(std::string &out) const { | ||||||
|   char buffer[64]; |   __attribute__((unused)) char buffer[64]; | ||||||
|   out.append("HelloResponse {\n"); |   out.append("HelloResponse {\n"); | ||||||
|   out.append("  api_version_major: "); |   out.append("  api_version_major: "); | ||||||
|   sprintf(buffer, "%u", this->api_version_major); |   sprintf(buffer, "%u", this->api_version_major); | ||||||
| @@ -337,7 +361,7 @@ bool ConnectRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value | |||||||
| void ConnectRequest::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(1, this->password); } | void ConnectRequest::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(1, this->password); } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void ConnectRequest::dump_to(std::string &out) const { | void ConnectRequest::dump_to(std::string &out) const { | ||||||
|   char buffer[64]; |   __attribute__((unused)) char buffer[64]; | ||||||
|   out.append("ConnectRequest {\n"); |   out.append("ConnectRequest {\n"); | ||||||
|   out.append("  password: "); |   out.append("  password: "); | ||||||
|   out.append("'").append(this->password).append("'"); |   out.append("'").append(this->password).append("'"); | ||||||
| @@ -358,7 +382,7 @@ bool ConnectResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { | |||||||
| void ConnectResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(1, this->invalid_password); } | void ConnectResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(1, this->invalid_password); } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void ConnectResponse::dump_to(std::string &out) const { | void ConnectResponse::dump_to(std::string &out) const { | ||||||
|   char buffer[64]; |   __attribute__((unused)) char buffer[64]; | ||||||
|   out.append("ConnectResponse {\n"); |   out.append("ConnectResponse {\n"); | ||||||
|   out.append("  invalid_password: "); |   out.append("  invalid_password: "); | ||||||
|   out.append(YESNO(this->invalid_password)); |   out.append(YESNO(this->invalid_password)); | ||||||
| @@ -396,6 +420,10 @@ bool DeviceInfoResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { | |||||||
|       this->has_deep_sleep = value.as_bool(); |       this->has_deep_sleep = value.as_bool(); | ||||||
|       return true; |       return true; | ||||||
|     } |     } | ||||||
|  |     case 10: { | ||||||
|  |       this->webserver_port = value.as_uint32(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|     default: |     default: | ||||||
|       return false; |       return false; | ||||||
|   } |   } | ||||||
| @@ -444,10 +472,11 @@ void DeviceInfoResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
|   buffer.encode_bool(7, this->has_deep_sleep); |   buffer.encode_bool(7, this->has_deep_sleep); | ||||||
|   buffer.encode_string(8, this->project_name); |   buffer.encode_string(8, this->project_name); | ||||||
|   buffer.encode_string(9, this->project_version); |   buffer.encode_string(9, this->project_version); | ||||||
|  |   buffer.encode_uint32(10, this->webserver_port); | ||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void DeviceInfoResponse::dump_to(std::string &out) const { | void DeviceInfoResponse::dump_to(std::string &out) const { | ||||||
|   char buffer[64]; |   __attribute__((unused)) char buffer[64]; | ||||||
|   out.append("DeviceInfoResponse {\n"); |   out.append("DeviceInfoResponse {\n"); | ||||||
|   out.append("  uses_password: "); |   out.append("  uses_password: "); | ||||||
|   out.append(YESNO(this->uses_password)); |   out.append(YESNO(this->uses_password)); | ||||||
| @@ -484,6 +513,11 @@ void DeviceInfoResponse::dump_to(std::string &out) const { | |||||||
|   out.append("  project_version: "); |   out.append("  project_version: "); | ||||||
|   out.append("'").append(this->project_version).append("'"); |   out.append("'").append(this->project_version).append("'"); | ||||||
|   out.append("\n"); |   out.append("\n"); | ||||||
|  |  | ||||||
|  |   out.append("  webserver_port: "); | ||||||
|  |   sprintf(buffer, "%u", this->webserver_port); | ||||||
|  |   out.append(buffer); | ||||||
|  |   out.append("\n"); | ||||||
|   out.append("}"); |   out.append("}"); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| @@ -509,6 +543,10 @@ bool ListEntitiesBinarySensorResponse::decode_varint(uint32_t field_id, ProtoVar | |||||||
|       this->disabled_by_default = value.as_bool(); |       this->disabled_by_default = value.as_bool(); | ||||||
|       return true; |       return true; | ||||||
|     } |     } | ||||||
|  |     case 9: { | ||||||
|  |       this->entity_category = value.as_enum<enums::EntityCategory>(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|     default: |     default: | ||||||
|       return false; |       return false; | ||||||
|   } |   } | ||||||
| @@ -558,10 +596,11 @@ void ListEntitiesBinarySensorResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
|   buffer.encode_bool(6, this->is_status_binary_sensor); |   buffer.encode_bool(6, this->is_status_binary_sensor); | ||||||
|   buffer.encode_bool(7, this->disabled_by_default); |   buffer.encode_bool(7, this->disabled_by_default); | ||||||
|   buffer.encode_string(8, this->icon); |   buffer.encode_string(8, this->icon); | ||||||
|  |   buffer.encode_enum<enums::EntityCategory>(9, this->entity_category); | ||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void ListEntitiesBinarySensorResponse::dump_to(std::string &out) const { | void ListEntitiesBinarySensorResponse::dump_to(std::string &out) const { | ||||||
|   char buffer[64]; |   __attribute__((unused)) char buffer[64]; | ||||||
|   out.append("ListEntitiesBinarySensorResponse {\n"); |   out.append("ListEntitiesBinarySensorResponse {\n"); | ||||||
|   out.append("  object_id: "); |   out.append("  object_id: "); | ||||||
|   out.append("'").append(this->object_id).append("'"); |   out.append("'").append(this->object_id).append("'"); | ||||||
| @@ -595,6 +634,10 @@ void ListEntitiesBinarySensorResponse::dump_to(std::string &out) const { | |||||||
|   out.append("  icon: "); |   out.append("  icon: "); | ||||||
|   out.append("'").append(this->icon).append("'"); |   out.append("'").append(this->icon).append("'"); | ||||||
|   out.append("\n"); |   out.append("\n"); | ||||||
|  |  | ||||||
|  |   out.append("  entity_category: "); | ||||||
|  |   out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category)); | ||||||
|  |   out.append("\n"); | ||||||
|   out.append("}"); |   out.append("}"); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| @@ -629,7 +672,7 @@ void BinarySensorStateResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void BinarySensorStateResponse::dump_to(std::string &out) const { | void BinarySensorStateResponse::dump_to(std::string &out) const { | ||||||
|   char buffer[64]; |   __attribute__((unused)) char buffer[64]; | ||||||
|   out.append("BinarySensorStateResponse {\n"); |   out.append("BinarySensorStateResponse {\n"); | ||||||
|   out.append("  key: "); |   out.append("  key: "); | ||||||
|   sprintf(buffer, "%u", this->key); |   sprintf(buffer, "%u", this->key); | ||||||
| @@ -664,6 +707,10 @@ bool ListEntitiesCoverResponse::decode_varint(uint32_t field_id, ProtoVarInt val | |||||||
|       this->disabled_by_default = value.as_bool(); |       this->disabled_by_default = value.as_bool(); | ||||||
|       return true; |       return true; | ||||||
|     } |     } | ||||||
|  |     case 11: { | ||||||
|  |       this->entity_category = value.as_enum<enums::EntityCategory>(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|     default: |     default: | ||||||
|       return false; |       return false; | ||||||
|   } |   } | ||||||
| @@ -715,10 +762,11 @@ void ListEntitiesCoverResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
|   buffer.encode_string(8, this->device_class); |   buffer.encode_string(8, this->device_class); | ||||||
|   buffer.encode_bool(9, this->disabled_by_default); |   buffer.encode_bool(9, this->disabled_by_default); | ||||||
|   buffer.encode_string(10, this->icon); |   buffer.encode_string(10, this->icon); | ||||||
|  |   buffer.encode_enum<enums::EntityCategory>(11, this->entity_category); | ||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void ListEntitiesCoverResponse::dump_to(std::string &out) const { | void ListEntitiesCoverResponse::dump_to(std::string &out) const { | ||||||
|   char buffer[64]; |   __attribute__((unused)) char buffer[64]; | ||||||
|   out.append("ListEntitiesCoverResponse {\n"); |   out.append("ListEntitiesCoverResponse {\n"); | ||||||
|   out.append("  object_id: "); |   out.append("  object_id: "); | ||||||
|   out.append("'").append(this->object_id).append("'"); |   out.append("'").append(this->object_id).append("'"); | ||||||
| @@ -760,6 +808,10 @@ void ListEntitiesCoverResponse::dump_to(std::string &out) const { | |||||||
|   out.append("  icon: "); |   out.append("  icon: "); | ||||||
|   out.append("'").append(this->icon).append("'"); |   out.append("'").append(this->icon).append("'"); | ||||||
|   out.append("\n"); |   out.append("\n"); | ||||||
|  |  | ||||||
|  |   out.append("  entity_category: "); | ||||||
|  |   out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category)); | ||||||
|  |   out.append("\n"); | ||||||
|   out.append("}"); |   out.append("}"); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| @@ -804,7 +856,7 @@ void CoverStateResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void CoverStateResponse::dump_to(std::string &out) const { | void CoverStateResponse::dump_to(std::string &out) const { | ||||||
|   char buffer[64]; |   __attribute__((unused)) char buffer[64]; | ||||||
|   out.append("CoverStateResponse {\n"); |   out.append("CoverStateResponse {\n"); | ||||||
|   out.append("  key: "); |   out.append("  key: "); | ||||||
|   sprintf(buffer, "%u", this->key); |   sprintf(buffer, "%u", this->key); | ||||||
| @@ -887,7 +939,7 @@ void CoverCommandRequest::encode(ProtoWriteBuffer buffer) const { | |||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void CoverCommandRequest::dump_to(std::string &out) const { | void CoverCommandRequest::dump_to(std::string &out) const { | ||||||
|   char buffer[64]; |   __attribute__((unused)) char buffer[64]; | ||||||
|   out.append("CoverCommandRequest {\n"); |   out.append("CoverCommandRequest {\n"); | ||||||
|   out.append("  key: "); |   out.append("  key: "); | ||||||
|   sprintf(buffer, "%u", this->key); |   sprintf(buffer, "%u", this->key); | ||||||
| @@ -948,6 +1000,10 @@ bool ListEntitiesFanResponse::decode_varint(uint32_t field_id, ProtoVarInt value | |||||||
|       this->disabled_by_default = value.as_bool(); |       this->disabled_by_default = value.as_bool(); | ||||||
|       return true; |       return true; | ||||||
|     } |     } | ||||||
|  |     case 11: { | ||||||
|  |       this->entity_category = value.as_enum<enums::EntityCategory>(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|     default: |     default: | ||||||
|       return false; |       return false; | ||||||
|   } |   } | ||||||
| @@ -995,10 +1051,11 @@ void ListEntitiesFanResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
|   buffer.encode_int32(8, this->supported_speed_count); |   buffer.encode_int32(8, this->supported_speed_count); | ||||||
|   buffer.encode_bool(9, this->disabled_by_default); |   buffer.encode_bool(9, this->disabled_by_default); | ||||||
|   buffer.encode_string(10, this->icon); |   buffer.encode_string(10, this->icon); | ||||||
|  |   buffer.encode_enum<enums::EntityCategory>(11, this->entity_category); | ||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void ListEntitiesFanResponse::dump_to(std::string &out) const { | void ListEntitiesFanResponse::dump_to(std::string &out) const { | ||||||
|   char buffer[64]; |   __attribute__((unused)) char buffer[64]; | ||||||
|   out.append("ListEntitiesFanResponse {\n"); |   out.append("ListEntitiesFanResponse {\n"); | ||||||
|   out.append("  object_id: "); |   out.append("  object_id: "); | ||||||
|   out.append("'").append(this->object_id).append("'"); |   out.append("'").append(this->object_id).append("'"); | ||||||
| @@ -1041,6 +1098,10 @@ void ListEntitiesFanResponse::dump_to(std::string &out) const { | |||||||
|   out.append("  icon: "); |   out.append("  icon: "); | ||||||
|   out.append("'").append(this->icon).append("'"); |   out.append("'").append(this->icon).append("'"); | ||||||
|   out.append("\n"); |   out.append("\n"); | ||||||
|  |  | ||||||
|  |   out.append("  entity_category: "); | ||||||
|  |   out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category)); | ||||||
|  |   out.append("\n"); | ||||||
|   out.append("}"); |   out.append("}"); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| @@ -1090,7 +1151,7 @@ void FanStateResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void FanStateResponse::dump_to(std::string &out) const { | void FanStateResponse::dump_to(std::string &out) const { | ||||||
|   char buffer[64]; |   __attribute__((unused)) char buffer[64]; | ||||||
|   out.append("FanStateResponse {\n"); |   out.append("FanStateResponse {\n"); | ||||||
|   out.append("  key: "); |   out.append("  key: "); | ||||||
|   sprintf(buffer, "%u", this->key); |   sprintf(buffer, "%u", this->key); | ||||||
| @@ -1191,7 +1252,7 @@ void FanCommandRequest::encode(ProtoWriteBuffer buffer) const { | |||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void FanCommandRequest::dump_to(std::string &out) const { | void FanCommandRequest::dump_to(std::string &out) const { | ||||||
|   char buffer[64]; |   __attribute__((unused)) char buffer[64]; | ||||||
|   out.append("FanCommandRequest {\n"); |   out.append("FanCommandRequest {\n"); | ||||||
|   out.append("  key: "); |   out.append("  key: "); | ||||||
|   sprintf(buffer, "%u", this->key); |   sprintf(buffer, "%u", this->key); | ||||||
| @@ -1267,6 +1328,10 @@ bool ListEntitiesLightResponse::decode_varint(uint32_t field_id, ProtoVarInt val | |||||||
|       this->disabled_by_default = value.as_bool(); |       this->disabled_by_default = value.as_bool(); | ||||||
|       return true; |       return true; | ||||||
|     } |     } | ||||||
|  |     case 15: { | ||||||
|  |       this->entity_category = value.as_enum<enums::EntityCategory>(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|     default: |     default: | ||||||
|       return false; |       return false; | ||||||
|   } |   } | ||||||
| @@ -1334,10 +1399,11 @@ void ListEntitiesLightResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
|   } |   } | ||||||
|   buffer.encode_bool(13, this->disabled_by_default); |   buffer.encode_bool(13, this->disabled_by_default); | ||||||
|   buffer.encode_string(14, this->icon); |   buffer.encode_string(14, this->icon); | ||||||
|  |   buffer.encode_enum<enums::EntityCategory>(15, this->entity_category); | ||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void ListEntitiesLightResponse::dump_to(std::string &out) const { | void ListEntitiesLightResponse::dump_to(std::string &out) const { | ||||||
|   char buffer[64]; |   __attribute__((unused)) char buffer[64]; | ||||||
|   out.append("ListEntitiesLightResponse {\n"); |   out.append("ListEntitiesLightResponse {\n"); | ||||||
|   out.append("  object_id: "); |   out.append("  object_id: "); | ||||||
|   out.append("'").append(this->object_id).append("'"); |   out.append("'").append(this->object_id).append("'"); | ||||||
| @@ -1401,6 +1467,10 @@ void ListEntitiesLightResponse::dump_to(std::string &out) const { | |||||||
|   out.append("  icon: "); |   out.append("  icon: "); | ||||||
|   out.append("'").append(this->icon).append("'"); |   out.append("'").append(this->icon).append("'"); | ||||||
|   out.append("\n"); |   out.append("\n"); | ||||||
|  |  | ||||||
|  |   out.append("  entity_category: "); | ||||||
|  |   out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category)); | ||||||
|  |   out.append("\n"); | ||||||
|   out.append("}"); |   out.append("}"); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| @@ -1491,7 +1561,7 @@ void LightStateResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void LightStateResponse::dump_to(std::string &out) const { | void LightStateResponse::dump_to(std::string &out) const { | ||||||
|   char buffer[64]; |   __attribute__((unused)) char buffer[64]; | ||||||
|   out.append("LightStateResponse {\n"); |   out.append("LightStateResponse {\n"); | ||||||
|   out.append("  key: "); |   out.append("  key: "); | ||||||
|   sprintf(buffer, "%u", this->key); |   sprintf(buffer, "%u", this->key); | ||||||
| @@ -1714,7 +1784,7 @@ void LightCommandRequest::encode(ProtoWriteBuffer buffer) const { | |||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void LightCommandRequest::dump_to(std::string &out) const { | void LightCommandRequest::dump_to(std::string &out) const { | ||||||
|   char buffer[64]; |   __attribute__((unused)) char buffer[64]; | ||||||
|   out.append("LightCommandRequest {\n"); |   out.append("LightCommandRequest {\n"); | ||||||
|   out.append("  key: "); |   out.append("  key: "); | ||||||
|   sprintf(buffer, "%u", this->key); |   sprintf(buffer, "%u", this->key); | ||||||
| @@ -1860,6 +1930,10 @@ bool ListEntitiesSensorResponse::decode_varint(uint32_t field_id, ProtoVarInt va | |||||||
|       this->disabled_by_default = value.as_bool(); |       this->disabled_by_default = value.as_bool(); | ||||||
|       return true; |       return true; | ||||||
|     } |     } | ||||||
|  |     case 13: { | ||||||
|  |       this->entity_category = value.as_enum<enums::EntityCategory>(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|     default: |     default: | ||||||
|       return false; |       return false; | ||||||
|   } |   } | ||||||
| @@ -1917,10 +1991,11 @@ void ListEntitiesSensorResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
|   buffer.encode_enum<enums::SensorStateClass>(10, this->state_class); |   buffer.encode_enum<enums::SensorStateClass>(10, this->state_class); | ||||||
|   buffer.encode_enum<enums::SensorLastResetType>(11, this->legacy_last_reset_type); |   buffer.encode_enum<enums::SensorLastResetType>(11, this->legacy_last_reset_type); | ||||||
|   buffer.encode_bool(12, this->disabled_by_default); |   buffer.encode_bool(12, this->disabled_by_default); | ||||||
|  |   buffer.encode_enum<enums::EntityCategory>(13, this->entity_category); | ||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void ListEntitiesSensorResponse::dump_to(std::string &out) const { | void ListEntitiesSensorResponse::dump_to(std::string &out) const { | ||||||
|   char buffer[64]; |   __attribute__((unused)) char buffer[64]; | ||||||
|   out.append("ListEntitiesSensorResponse {\n"); |   out.append("ListEntitiesSensorResponse {\n"); | ||||||
|   out.append("  object_id: "); |   out.append("  object_id: "); | ||||||
|   out.append("'").append(this->object_id).append("'"); |   out.append("'").append(this->object_id).append("'"); | ||||||
| @@ -1971,6 +2046,10 @@ void ListEntitiesSensorResponse::dump_to(std::string &out) const { | |||||||
|   out.append("  disabled_by_default: "); |   out.append("  disabled_by_default: "); | ||||||
|   out.append(YESNO(this->disabled_by_default)); |   out.append(YESNO(this->disabled_by_default)); | ||||||
|   out.append("\n"); |   out.append("\n"); | ||||||
|  |  | ||||||
|  |   out.append("  entity_category: "); | ||||||
|  |   out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category)); | ||||||
|  |   out.append("\n"); | ||||||
|   out.append("}"); |   out.append("}"); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| @@ -2005,7 +2084,7 @@ void SensorStateResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void SensorStateResponse::dump_to(std::string &out) const { | void SensorStateResponse::dump_to(std::string &out) const { | ||||||
|   char buffer[64]; |   __attribute__((unused)) char buffer[64]; | ||||||
|   out.append("SensorStateResponse {\n"); |   out.append("SensorStateResponse {\n"); | ||||||
|   out.append("  key: "); |   out.append("  key: "); | ||||||
|   sprintf(buffer, "%u", this->key); |   sprintf(buffer, "%u", this->key); | ||||||
| @@ -2033,6 +2112,10 @@ bool ListEntitiesSwitchResponse::decode_varint(uint32_t field_id, ProtoVarInt va | |||||||
|       this->disabled_by_default = value.as_bool(); |       this->disabled_by_default = value.as_bool(); | ||||||
|       return true; |       return true; | ||||||
|     } |     } | ||||||
|  |     case 8: { | ||||||
|  |       this->entity_category = value.as_enum<enums::EntityCategory>(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|     default: |     default: | ||||||
|       return false; |       return false; | ||||||
|   } |   } | ||||||
| @@ -2077,10 +2160,11 @@ void ListEntitiesSwitchResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
|   buffer.encode_string(5, this->icon); |   buffer.encode_string(5, this->icon); | ||||||
|   buffer.encode_bool(6, this->assumed_state); |   buffer.encode_bool(6, this->assumed_state); | ||||||
|   buffer.encode_bool(7, this->disabled_by_default); |   buffer.encode_bool(7, this->disabled_by_default); | ||||||
|  |   buffer.encode_enum<enums::EntityCategory>(8, this->entity_category); | ||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void ListEntitiesSwitchResponse::dump_to(std::string &out) const { | void ListEntitiesSwitchResponse::dump_to(std::string &out) const { | ||||||
|   char buffer[64]; |   __attribute__((unused)) char buffer[64]; | ||||||
|   out.append("ListEntitiesSwitchResponse {\n"); |   out.append("ListEntitiesSwitchResponse {\n"); | ||||||
|   out.append("  object_id: "); |   out.append("  object_id: "); | ||||||
|   out.append("'").append(this->object_id).append("'"); |   out.append("'").append(this->object_id).append("'"); | ||||||
| @@ -2110,6 +2194,10 @@ void ListEntitiesSwitchResponse::dump_to(std::string &out) const { | |||||||
|   out.append("  disabled_by_default: "); |   out.append("  disabled_by_default: "); | ||||||
|   out.append(YESNO(this->disabled_by_default)); |   out.append(YESNO(this->disabled_by_default)); | ||||||
|   out.append("\n"); |   out.append("\n"); | ||||||
|  |  | ||||||
|  |   out.append("  entity_category: "); | ||||||
|  |   out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category)); | ||||||
|  |   out.append("\n"); | ||||||
|   out.append("}"); |   out.append("}"); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| @@ -2139,7 +2227,7 @@ void SwitchStateResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void SwitchStateResponse::dump_to(std::string &out) const { | void SwitchStateResponse::dump_to(std::string &out) const { | ||||||
|   char buffer[64]; |   __attribute__((unused)) char buffer[64]; | ||||||
|   out.append("SwitchStateResponse {\n"); |   out.append("SwitchStateResponse {\n"); | ||||||
|   out.append("  key: "); |   out.append("  key: "); | ||||||
|   sprintf(buffer, "%u", this->key); |   sprintf(buffer, "%u", this->key); | ||||||
| @@ -2178,7 +2266,7 @@ void SwitchCommandRequest::encode(ProtoWriteBuffer buffer) const { | |||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void SwitchCommandRequest::dump_to(std::string &out) const { | void SwitchCommandRequest::dump_to(std::string &out) const { | ||||||
|   char buffer[64]; |   __attribute__((unused)) char buffer[64]; | ||||||
|   out.append("SwitchCommandRequest {\n"); |   out.append("SwitchCommandRequest {\n"); | ||||||
|   out.append("  key: "); |   out.append("  key: "); | ||||||
|   sprintf(buffer, "%u", this->key); |   sprintf(buffer, "%u", this->key); | ||||||
| @@ -2197,6 +2285,10 @@ bool ListEntitiesTextSensorResponse::decode_varint(uint32_t field_id, ProtoVarIn | |||||||
|       this->disabled_by_default = value.as_bool(); |       this->disabled_by_default = value.as_bool(); | ||||||
|       return true; |       return true; | ||||||
|     } |     } | ||||||
|  |     case 7: { | ||||||
|  |       this->entity_category = value.as_enum<enums::EntityCategory>(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|     default: |     default: | ||||||
|       return false; |       return false; | ||||||
|   } |   } | ||||||
| @@ -2240,10 +2332,11 @@ void ListEntitiesTextSensorResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
|   buffer.encode_string(4, this->unique_id); |   buffer.encode_string(4, this->unique_id); | ||||||
|   buffer.encode_string(5, this->icon); |   buffer.encode_string(5, this->icon); | ||||||
|   buffer.encode_bool(6, this->disabled_by_default); |   buffer.encode_bool(6, this->disabled_by_default); | ||||||
|  |   buffer.encode_enum<enums::EntityCategory>(7, this->entity_category); | ||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void ListEntitiesTextSensorResponse::dump_to(std::string &out) const { | void ListEntitiesTextSensorResponse::dump_to(std::string &out) const { | ||||||
|   char buffer[64]; |   __attribute__((unused)) char buffer[64]; | ||||||
|   out.append("ListEntitiesTextSensorResponse {\n"); |   out.append("ListEntitiesTextSensorResponse {\n"); | ||||||
|   out.append("  object_id: "); |   out.append("  object_id: "); | ||||||
|   out.append("'").append(this->object_id).append("'"); |   out.append("'").append(this->object_id).append("'"); | ||||||
| @@ -2269,6 +2362,10 @@ void ListEntitiesTextSensorResponse::dump_to(std::string &out) const { | |||||||
|   out.append("  disabled_by_default: "); |   out.append("  disabled_by_default: "); | ||||||
|   out.append(YESNO(this->disabled_by_default)); |   out.append(YESNO(this->disabled_by_default)); | ||||||
|   out.append("\n"); |   out.append("\n"); | ||||||
|  |  | ||||||
|  |   out.append("  entity_category: "); | ||||||
|  |   out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category)); | ||||||
|  |   out.append("\n"); | ||||||
|   out.append("}"); |   out.append("}"); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| @@ -2309,7 +2406,7 @@ void TextSensorStateResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void TextSensorStateResponse::dump_to(std::string &out) const { | void TextSensorStateResponse::dump_to(std::string &out) const { | ||||||
|   char buffer[64]; |   __attribute__((unused)) char buffer[64]; | ||||||
|   out.append("TextSensorStateResponse {\n"); |   out.append("TextSensorStateResponse {\n"); | ||||||
|   out.append("  key: "); |   out.append("  key: "); | ||||||
|   sprintf(buffer, "%u", this->key); |   sprintf(buffer, "%u", this->key); | ||||||
| @@ -2346,7 +2443,7 @@ void SubscribeLogsRequest::encode(ProtoWriteBuffer buffer) const { | |||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void SubscribeLogsRequest::dump_to(std::string &out) const { | void SubscribeLogsRequest::dump_to(std::string &out) const { | ||||||
|   char buffer[64]; |   __attribute__((unused)) char buffer[64]; | ||||||
|   out.append("SubscribeLogsRequest {\n"); |   out.append("SubscribeLogsRequest {\n"); | ||||||
|   out.append("  level: "); |   out.append("  level: "); | ||||||
|   out.append(proto_enum_to_string<enums::LogLevel>(this->level)); |   out.append(proto_enum_to_string<enums::LogLevel>(this->level)); | ||||||
| @@ -2389,7 +2486,7 @@ void SubscribeLogsResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void SubscribeLogsResponse::dump_to(std::string &out) const { | void SubscribeLogsResponse::dump_to(std::string &out) const { | ||||||
|   char buffer[64]; |   __attribute__((unused)) char buffer[64]; | ||||||
|   out.append("SubscribeLogsResponse {\n"); |   out.append("SubscribeLogsResponse {\n"); | ||||||
|   out.append("  level: "); |   out.append("  level: "); | ||||||
|   out.append(proto_enum_to_string<enums::LogLevel>(this->level)); |   out.append(proto_enum_to_string<enums::LogLevel>(this->level)); | ||||||
| @@ -2431,7 +2528,7 @@ void HomeassistantServiceMap::encode(ProtoWriteBuffer buffer) const { | |||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void HomeassistantServiceMap::dump_to(std::string &out) const { | void HomeassistantServiceMap::dump_to(std::string &out) const { | ||||||
|   char buffer[64]; |   __attribute__((unused)) char buffer[64]; | ||||||
|   out.append("HomeassistantServiceMap {\n"); |   out.append("HomeassistantServiceMap {\n"); | ||||||
|   out.append("  key: "); |   out.append("  key: "); | ||||||
|   out.append("'").append(this->key).append("'"); |   out.append("'").append(this->key).append("'"); | ||||||
| @@ -2490,7 +2587,7 @@ void HomeassistantServiceResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void HomeassistantServiceResponse::dump_to(std::string &out) const { | void HomeassistantServiceResponse::dump_to(std::string &out) const { | ||||||
|   char buffer[64]; |   __attribute__((unused)) char buffer[64]; | ||||||
|   out.append("HomeassistantServiceResponse {\n"); |   out.append("HomeassistantServiceResponse {\n"); | ||||||
|   out.append("  service: "); |   out.append("  service: "); | ||||||
|   out.append("'").append(this->service).append("'"); |   out.append("'").append(this->service).append("'"); | ||||||
| @@ -2546,7 +2643,7 @@ void SubscribeHomeAssistantStateResponse::encode(ProtoWriteBuffer buffer) const | |||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void SubscribeHomeAssistantStateResponse::dump_to(std::string &out) const { | void SubscribeHomeAssistantStateResponse::dump_to(std::string &out) const { | ||||||
|   char buffer[64]; |   __attribute__((unused)) char buffer[64]; | ||||||
|   out.append("SubscribeHomeAssistantStateResponse {\n"); |   out.append("SubscribeHomeAssistantStateResponse {\n"); | ||||||
|   out.append("  entity_id: "); |   out.append("  entity_id: "); | ||||||
|   out.append("'").append(this->entity_id).append("'"); |   out.append("'").append(this->entity_id).append("'"); | ||||||
| @@ -2583,7 +2680,7 @@ void HomeAssistantStateResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void HomeAssistantStateResponse::dump_to(std::string &out) const { | void HomeAssistantStateResponse::dump_to(std::string &out) const { | ||||||
|   char buffer[64]; |   __attribute__((unused)) char buffer[64]; | ||||||
|   out.append("HomeAssistantStateResponse {\n"); |   out.append("HomeAssistantStateResponse {\n"); | ||||||
|   out.append("  entity_id: "); |   out.append("  entity_id: "); | ||||||
|   out.append("'").append(this->entity_id).append("'"); |   out.append("'").append(this->entity_id).append("'"); | ||||||
| @@ -2616,7 +2713,7 @@ bool GetTimeResponse::decode_32bit(uint32_t field_id, Proto32Bit value) { | |||||||
| void GetTimeResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_fixed32(1, this->epoch_seconds); } | void GetTimeResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_fixed32(1, this->epoch_seconds); } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void GetTimeResponse::dump_to(std::string &out) const { | void GetTimeResponse::dump_to(std::string &out) const { | ||||||
|   char buffer[64]; |   __attribute__((unused)) char buffer[64]; | ||||||
|   out.append("GetTimeResponse {\n"); |   out.append("GetTimeResponse {\n"); | ||||||
|   out.append("  epoch_seconds: "); |   out.append("  epoch_seconds: "); | ||||||
|   sprintf(buffer, "%u", this->epoch_seconds); |   sprintf(buffer, "%u", this->epoch_seconds); | ||||||
| @@ -2651,7 +2748,7 @@ void ListEntitiesServicesArgument::encode(ProtoWriteBuffer buffer) const { | |||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void ListEntitiesServicesArgument::dump_to(std::string &out) const { | void ListEntitiesServicesArgument::dump_to(std::string &out) const { | ||||||
|   char buffer[64]; |   __attribute__((unused)) char buffer[64]; | ||||||
|   out.append("ListEntitiesServicesArgument {\n"); |   out.append("ListEntitiesServicesArgument {\n"); | ||||||
|   out.append("  name: "); |   out.append("  name: "); | ||||||
|   out.append("'").append(this->name).append("'"); |   out.append("'").append(this->name).append("'"); | ||||||
| @@ -2696,7 +2793,7 @@ void ListEntitiesServicesResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void ListEntitiesServicesResponse::dump_to(std::string &out) const { | void ListEntitiesServicesResponse::dump_to(std::string &out) const { | ||||||
|   char buffer[64]; |   __attribute__((unused)) char buffer[64]; | ||||||
|   out.append("ListEntitiesServicesResponse {\n"); |   out.append("ListEntitiesServicesResponse {\n"); | ||||||
|   out.append("  name: "); |   out.append("  name: "); | ||||||
|   out.append("'").append(this->name).append("'"); |   out.append("'").append(this->name).append("'"); | ||||||
| @@ -2790,7 +2887,7 @@ void ExecuteServiceArgument::encode(ProtoWriteBuffer buffer) const { | |||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void ExecuteServiceArgument::dump_to(std::string &out) const { | void ExecuteServiceArgument::dump_to(std::string &out) const { | ||||||
|   char buffer[64]; |   __attribute__((unused)) char buffer[64]; | ||||||
|   out.append("ExecuteServiceArgument {\n"); |   out.append("ExecuteServiceArgument {\n"); | ||||||
|   out.append("  bool_: "); |   out.append("  bool_: "); | ||||||
|   out.append(YESNO(this->bool_)); |   out.append(YESNO(this->bool_)); | ||||||
| @@ -2871,7 +2968,7 @@ void ExecuteServiceRequest::encode(ProtoWriteBuffer buffer) const { | |||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void ExecuteServiceRequest::dump_to(std::string &out) const { | void ExecuteServiceRequest::dump_to(std::string &out) const { | ||||||
|   char buffer[64]; |   __attribute__((unused)) char buffer[64]; | ||||||
|   out.append("ExecuteServiceRequest {\n"); |   out.append("ExecuteServiceRequest {\n"); | ||||||
|   out.append("  key: "); |   out.append("  key: "); | ||||||
|   sprintf(buffer, "%u", this->key); |   sprintf(buffer, "%u", this->key); | ||||||
| @@ -2892,6 +2989,10 @@ bool ListEntitiesCameraResponse::decode_varint(uint32_t field_id, ProtoVarInt va | |||||||
|       this->disabled_by_default = value.as_bool(); |       this->disabled_by_default = value.as_bool(); | ||||||
|       return true; |       return true; | ||||||
|     } |     } | ||||||
|  |     case 7: { | ||||||
|  |       this->entity_category = value.as_enum<enums::EntityCategory>(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|     default: |     default: | ||||||
|       return false; |       return false; | ||||||
|   } |   } | ||||||
| @@ -2910,6 +3011,10 @@ bool ListEntitiesCameraResponse::decode_length(uint32_t field_id, ProtoLengthDel | |||||||
|       this->unique_id = value.as_string(); |       this->unique_id = value.as_string(); | ||||||
|       return true; |       return true; | ||||||
|     } |     } | ||||||
|  |     case 6: { | ||||||
|  |       this->icon = value.as_string(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|     default: |     default: | ||||||
|       return false; |       return false; | ||||||
|   } |   } | ||||||
| @@ -2930,10 +3035,12 @@ void ListEntitiesCameraResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
|   buffer.encode_string(3, this->name); |   buffer.encode_string(3, this->name); | ||||||
|   buffer.encode_string(4, this->unique_id); |   buffer.encode_string(4, this->unique_id); | ||||||
|   buffer.encode_bool(5, this->disabled_by_default); |   buffer.encode_bool(5, this->disabled_by_default); | ||||||
|  |   buffer.encode_string(6, this->icon); | ||||||
|  |   buffer.encode_enum<enums::EntityCategory>(7, this->entity_category); | ||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void ListEntitiesCameraResponse::dump_to(std::string &out) const { | void ListEntitiesCameraResponse::dump_to(std::string &out) const { | ||||||
|   char buffer[64]; |   __attribute__((unused)) char buffer[64]; | ||||||
|   out.append("ListEntitiesCameraResponse {\n"); |   out.append("ListEntitiesCameraResponse {\n"); | ||||||
|   out.append("  object_id: "); |   out.append("  object_id: "); | ||||||
|   out.append("'").append(this->object_id).append("'"); |   out.append("'").append(this->object_id).append("'"); | ||||||
| @@ -2955,6 +3062,14 @@ void ListEntitiesCameraResponse::dump_to(std::string &out) const { | |||||||
|   out.append("  disabled_by_default: "); |   out.append("  disabled_by_default: "); | ||||||
|   out.append(YESNO(this->disabled_by_default)); |   out.append(YESNO(this->disabled_by_default)); | ||||||
|   out.append("\n"); |   out.append("\n"); | ||||||
|  |  | ||||||
|  |   out.append("  icon: "); | ||||||
|  |   out.append("'").append(this->icon).append("'"); | ||||||
|  |   out.append("\n"); | ||||||
|  |  | ||||||
|  |   out.append("  entity_category: "); | ||||||
|  |   out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category)); | ||||||
|  |   out.append("\n"); | ||||||
|   out.append("}"); |   out.append("}"); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| @@ -2995,7 +3110,7 @@ void CameraImageResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void CameraImageResponse::dump_to(std::string &out) const { | void CameraImageResponse::dump_to(std::string &out) const { | ||||||
|   char buffer[64]; |   __attribute__((unused)) char buffer[64]; | ||||||
|   out.append("CameraImageResponse {\n"); |   out.append("CameraImageResponse {\n"); | ||||||
|   out.append("  key: "); |   out.append("  key: "); | ||||||
|   sprintf(buffer, "%u", this->key); |   sprintf(buffer, "%u", this->key); | ||||||
| @@ -3032,7 +3147,7 @@ void CameraImageRequest::encode(ProtoWriteBuffer buffer) const { | |||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void CameraImageRequest::dump_to(std::string &out) const { | void CameraImageRequest::dump_to(std::string &out) const { | ||||||
|   char buffer[64]; |   __attribute__((unused)) char buffer[64]; | ||||||
|   out.append("CameraImageRequest {\n"); |   out.append("CameraImageRequest {\n"); | ||||||
|   out.append("  single: "); |   out.append("  single: "); | ||||||
|   out.append(YESNO(this->single)); |   out.append(YESNO(this->single)); | ||||||
| @@ -3082,6 +3197,10 @@ bool ListEntitiesClimateResponse::decode_varint(uint32_t field_id, ProtoVarInt v | |||||||
|       this->disabled_by_default = value.as_bool(); |       this->disabled_by_default = value.as_bool(); | ||||||
|       return true; |       return true; | ||||||
|     } |     } | ||||||
|  |     case 20: { | ||||||
|  |       this->entity_category = value.as_enum<enums::EntityCategory>(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|     default: |     default: | ||||||
|       return false; |       return false; | ||||||
|   } |   } | ||||||
| @@ -3170,10 +3289,11 @@ void ListEntitiesClimateResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
|   } |   } | ||||||
|   buffer.encode_bool(18, this->disabled_by_default); |   buffer.encode_bool(18, this->disabled_by_default); | ||||||
|   buffer.encode_string(19, this->icon); |   buffer.encode_string(19, this->icon); | ||||||
|  |   buffer.encode_enum<enums::EntityCategory>(20, this->entity_category); | ||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void ListEntitiesClimateResponse::dump_to(std::string &out) const { | void ListEntitiesClimateResponse::dump_to(std::string &out) const { | ||||||
|   char buffer[64]; |   __attribute__((unused)) char buffer[64]; | ||||||
|   out.append("ListEntitiesClimateResponse {\n"); |   out.append("ListEntitiesClimateResponse {\n"); | ||||||
|   out.append("  object_id: "); |   out.append("  object_id: "); | ||||||
|   out.append("'").append(this->object_id).append("'"); |   out.append("'").append(this->object_id).append("'"); | ||||||
| @@ -3266,6 +3386,10 @@ void ListEntitiesClimateResponse::dump_to(std::string &out) const { | |||||||
|   out.append("  icon: "); |   out.append("  icon: "); | ||||||
|   out.append("'").append(this->icon).append("'"); |   out.append("'").append(this->icon).append("'"); | ||||||
|   out.append("\n"); |   out.append("\n"); | ||||||
|  |  | ||||||
|  |   out.append("  entity_category: "); | ||||||
|  |   out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category)); | ||||||
|  |   out.append("\n"); | ||||||
|   out.append("}"); |   out.append("}"); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| @@ -3356,7 +3480,7 @@ void ClimateStateResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void ClimateStateResponse::dump_to(std::string &out) const { | void ClimateStateResponse::dump_to(std::string &out) const { | ||||||
|   char buffer[64]; |   __attribute__((unused)) char buffer[64]; | ||||||
|   out.append("ClimateStateResponse {\n"); |   out.append("ClimateStateResponse {\n"); | ||||||
|   out.append("  key: "); |   out.append("  key: "); | ||||||
|   sprintf(buffer, "%u", this->key); |   sprintf(buffer, "%u", this->key); | ||||||
| @@ -3544,7 +3668,7 @@ void ClimateCommandRequest::encode(ProtoWriteBuffer buffer) const { | |||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void ClimateCommandRequest::dump_to(std::string &out) const { | void ClimateCommandRequest::dump_to(std::string &out) const { | ||||||
|   char buffer[64]; |   __attribute__((unused)) char buffer[64]; | ||||||
|   out.append("ClimateCommandRequest {\n"); |   out.append("ClimateCommandRequest {\n"); | ||||||
|   out.append("  key: "); |   out.append("  key: "); | ||||||
|   sprintf(buffer, "%u", this->key); |   sprintf(buffer, "%u", this->key); | ||||||
| @@ -3642,6 +3766,14 @@ bool ListEntitiesNumberResponse::decode_varint(uint32_t field_id, ProtoVarInt va | |||||||
|       this->disabled_by_default = value.as_bool(); |       this->disabled_by_default = value.as_bool(); | ||||||
|       return true; |       return true; | ||||||
|     } |     } | ||||||
|  |     case 10: { | ||||||
|  |       this->entity_category = value.as_enum<enums::EntityCategory>(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|  |     case 12: { | ||||||
|  |       this->mode = value.as_enum<enums::NumberMode>(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|     default: |     default: | ||||||
|       return false; |       return false; | ||||||
|   } |   } | ||||||
| @@ -3664,6 +3796,10 @@ bool ListEntitiesNumberResponse::decode_length(uint32_t field_id, ProtoLengthDel | |||||||
|       this->icon = value.as_string(); |       this->icon = value.as_string(); | ||||||
|       return true; |       return true; | ||||||
|     } |     } | ||||||
|  |     case 11: { | ||||||
|  |       this->unit_of_measurement = value.as_string(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|     default: |     default: | ||||||
|       return false; |       return false; | ||||||
|   } |   } | ||||||
| @@ -3700,10 +3836,13 @@ void ListEntitiesNumberResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
|   buffer.encode_float(7, this->max_value); |   buffer.encode_float(7, this->max_value); | ||||||
|   buffer.encode_float(8, this->step); |   buffer.encode_float(8, this->step); | ||||||
|   buffer.encode_bool(9, this->disabled_by_default); |   buffer.encode_bool(9, this->disabled_by_default); | ||||||
|  |   buffer.encode_enum<enums::EntityCategory>(10, this->entity_category); | ||||||
|  |   buffer.encode_string(11, this->unit_of_measurement); | ||||||
|  |   buffer.encode_enum<enums::NumberMode>(12, this->mode); | ||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void ListEntitiesNumberResponse::dump_to(std::string &out) const { | void ListEntitiesNumberResponse::dump_to(std::string &out) const { | ||||||
|   char buffer[64]; |   __attribute__((unused)) char buffer[64]; | ||||||
|   out.append("ListEntitiesNumberResponse {\n"); |   out.append("ListEntitiesNumberResponse {\n"); | ||||||
|   out.append("  object_id: "); |   out.append("  object_id: "); | ||||||
|   out.append("'").append(this->object_id).append("'"); |   out.append("'").append(this->object_id).append("'"); | ||||||
| @@ -3744,6 +3883,18 @@ void ListEntitiesNumberResponse::dump_to(std::string &out) const { | |||||||
|   out.append("  disabled_by_default: "); |   out.append("  disabled_by_default: "); | ||||||
|   out.append(YESNO(this->disabled_by_default)); |   out.append(YESNO(this->disabled_by_default)); | ||||||
|   out.append("\n"); |   out.append("\n"); | ||||||
|  |  | ||||||
|  |   out.append("  entity_category: "); | ||||||
|  |   out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category)); | ||||||
|  |   out.append("\n"); | ||||||
|  |  | ||||||
|  |   out.append("  unit_of_measurement: "); | ||||||
|  |   out.append("'").append(this->unit_of_measurement).append("'"); | ||||||
|  |   out.append("\n"); | ||||||
|  |  | ||||||
|  |   out.append("  mode: "); | ||||||
|  |   out.append(proto_enum_to_string<enums::NumberMode>(this->mode)); | ||||||
|  |   out.append("\n"); | ||||||
|   out.append("}"); |   out.append("}"); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| @@ -3778,7 +3929,7 @@ void NumberStateResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void NumberStateResponse::dump_to(std::string &out) const { | void NumberStateResponse::dump_to(std::string &out) const { | ||||||
|   char buffer[64]; |   __attribute__((unused)) char buffer[64]; | ||||||
|   out.append("NumberStateResponse {\n"); |   out.append("NumberStateResponse {\n"); | ||||||
|   out.append("  key: "); |   out.append("  key: "); | ||||||
|   sprintf(buffer, "%u", this->key); |   sprintf(buffer, "%u", this->key); | ||||||
| @@ -3816,7 +3967,7 @@ void NumberCommandRequest::encode(ProtoWriteBuffer buffer) const { | |||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void NumberCommandRequest::dump_to(std::string &out) const { | void NumberCommandRequest::dump_to(std::string &out) const { | ||||||
|   char buffer[64]; |   __attribute__((unused)) char buffer[64]; | ||||||
|   out.append("NumberCommandRequest {\n"); |   out.append("NumberCommandRequest {\n"); | ||||||
|   out.append("  key: "); |   out.append("  key: "); | ||||||
|   sprintf(buffer, "%u", this->key); |   sprintf(buffer, "%u", this->key); | ||||||
| @@ -3836,6 +3987,10 @@ bool ListEntitiesSelectResponse::decode_varint(uint32_t field_id, ProtoVarInt va | |||||||
|       this->disabled_by_default = value.as_bool(); |       this->disabled_by_default = value.as_bool(); | ||||||
|       return true; |       return true; | ||||||
|     } |     } | ||||||
|  |     case 8: { | ||||||
|  |       this->entity_category = value.as_enum<enums::EntityCategory>(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|     default: |     default: | ||||||
|       return false; |       return false; | ||||||
|   } |   } | ||||||
| @@ -3886,10 +4041,11 @@ void ListEntitiesSelectResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
|     buffer.encode_string(6, it, true); |     buffer.encode_string(6, it, true); | ||||||
|   } |   } | ||||||
|   buffer.encode_bool(7, this->disabled_by_default); |   buffer.encode_bool(7, this->disabled_by_default); | ||||||
|  |   buffer.encode_enum<enums::EntityCategory>(8, this->entity_category); | ||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void ListEntitiesSelectResponse::dump_to(std::string &out) const { | void ListEntitiesSelectResponse::dump_to(std::string &out) const { | ||||||
|   char buffer[64]; |   __attribute__((unused)) char buffer[64]; | ||||||
|   out.append("ListEntitiesSelectResponse {\n"); |   out.append("ListEntitiesSelectResponse {\n"); | ||||||
|   out.append("  object_id: "); |   out.append("  object_id: "); | ||||||
|   out.append("'").append(this->object_id).append("'"); |   out.append("'").append(this->object_id).append("'"); | ||||||
| @@ -3921,6 +4077,10 @@ void ListEntitiesSelectResponse::dump_to(std::string &out) const { | |||||||
|   out.append("  disabled_by_default: "); |   out.append("  disabled_by_default: "); | ||||||
|   out.append(YESNO(this->disabled_by_default)); |   out.append(YESNO(this->disabled_by_default)); | ||||||
|   out.append("\n"); |   out.append("\n"); | ||||||
|  |  | ||||||
|  |   out.append("  entity_category: "); | ||||||
|  |   out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category)); | ||||||
|  |   out.append("\n"); | ||||||
|   out.append("}"); |   out.append("}"); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| @@ -3961,7 +4121,7 @@ void SelectStateResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void SelectStateResponse::dump_to(std::string &out) const { | void SelectStateResponse::dump_to(std::string &out) const { | ||||||
|   char buffer[64]; |   __attribute__((unused)) char buffer[64]; | ||||||
|   out.append("SelectStateResponse {\n"); |   out.append("SelectStateResponse {\n"); | ||||||
|   out.append("  key: "); |   out.append("  key: "); | ||||||
|   sprintf(buffer, "%u", this->key); |   sprintf(buffer, "%u", this->key); | ||||||
| @@ -4004,7 +4164,7 @@ void SelectCommandRequest::encode(ProtoWriteBuffer buffer) const { | |||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void SelectCommandRequest::dump_to(std::string &out) const { | void SelectCommandRequest::dump_to(std::string &out) const { | ||||||
|   char buffer[64]; |   __attribute__((unused)) char buffer[64]; | ||||||
|   out.append("SelectCommandRequest {\n"); |   out.append("SelectCommandRequest {\n"); | ||||||
|   out.append("  key: "); |   out.append("  key: "); | ||||||
|   sprintf(buffer, "%u", this->key); |   sprintf(buffer, "%u", this->key); | ||||||
| @@ -4017,6 +4177,127 @@ void SelectCommandRequest::dump_to(std::string &out) const { | |||||||
|   out.append("}"); |   out.append("}"); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
|  | bool ListEntitiesButtonResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { | ||||||
|  |   switch (field_id) { | ||||||
|  |     case 6: { | ||||||
|  |       this->disabled_by_default = value.as_bool(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|  |     case 7: { | ||||||
|  |       this->entity_category = value.as_enum<enums::EntityCategory>(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|  |     default: | ||||||
|  |       return false; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | bool ListEntitiesButtonResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { | ||||||
|  |   switch (field_id) { | ||||||
|  |     case 1: { | ||||||
|  |       this->object_id = value.as_string(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|  |     case 3: { | ||||||
|  |       this->name = value.as_string(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|  |     case 4: { | ||||||
|  |       this->unique_id = value.as_string(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|  |     case 5: { | ||||||
|  |       this->icon = value.as_string(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|  |     case 8: { | ||||||
|  |       this->device_class = value.as_string(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|  |     default: | ||||||
|  |       return false; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | bool ListEntitiesButtonResponse::decode_32bit(uint32_t field_id, Proto32Bit value) { | ||||||
|  |   switch (field_id) { | ||||||
|  |     case 2: { | ||||||
|  |       this->key = value.as_fixed32(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|  |     default: | ||||||
|  |       return false; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | void ListEntitiesButtonResponse::encode(ProtoWriteBuffer buffer) const { | ||||||
|  |   buffer.encode_string(1, this->object_id); | ||||||
|  |   buffer.encode_fixed32(2, this->key); | ||||||
|  |   buffer.encode_string(3, this->name); | ||||||
|  |   buffer.encode_string(4, this->unique_id); | ||||||
|  |   buffer.encode_string(5, this->icon); | ||||||
|  |   buffer.encode_bool(6, this->disabled_by_default); | ||||||
|  |   buffer.encode_enum<enums::EntityCategory>(7, this->entity_category); | ||||||
|  |   buffer.encode_string(8, this->device_class); | ||||||
|  | } | ||||||
|  | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|  | void ListEntitiesButtonResponse::dump_to(std::string &out) const { | ||||||
|  |   char buffer[64]; | ||||||
|  |   out.append("ListEntitiesButtonResponse {\n"); | ||||||
|  |   out.append("  object_id: "); | ||||||
|  |   out.append("'").append(this->object_id).append("'"); | ||||||
|  |   out.append("\n"); | ||||||
|  |  | ||||||
|  |   out.append("  key: "); | ||||||
|  |   sprintf(buffer, "%u", this->key); | ||||||
|  |   out.append(buffer); | ||||||
|  |   out.append("\n"); | ||||||
|  |  | ||||||
|  |   out.append("  name: "); | ||||||
|  |   out.append("'").append(this->name).append("'"); | ||||||
|  |   out.append("\n"); | ||||||
|  |  | ||||||
|  |   out.append("  unique_id: "); | ||||||
|  |   out.append("'").append(this->unique_id).append("'"); | ||||||
|  |   out.append("\n"); | ||||||
|  |  | ||||||
|  |   out.append("  icon: "); | ||||||
|  |   out.append("'").append(this->icon).append("'"); | ||||||
|  |   out.append("\n"); | ||||||
|  |  | ||||||
|  |   out.append("  disabled_by_default: "); | ||||||
|  |   out.append(YESNO(this->disabled_by_default)); | ||||||
|  |   out.append("\n"); | ||||||
|  |  | ||||||
|  |   out.append("  entity_category: "); | ||||||
|  |   out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category)); | ||||||
|  |   out.append("\n"); | ||||||
|  |  | ||||||
|  |   out.append("  device_class: "); | ||||||
|  |   out.append("'").append(this->device_class).append("'"); | ||||||
|  |   out.append("\n"); | ||||||
|  |   out.append("}"); | ||||||
|  | } | ||||||
|  | #endif | ||||||
|  | bool ButtonCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) { | ||||||
|  |   switch (field_id) { | ||||||
|  |     case 1: { | ||||||
|  |       this->key = value.as_fixed32(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|  |     default: | ||||||
|  |       return false; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | void ButtonCommandRequest::encode(ProtoWriteBuffer buffer) const { buffer.encode_fixed32(1, this->key); } | ||||||
|  | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|  | void ButtonCommandRequest::dump_to(std::string &out) const { | ||||||
|  |   char buffer[64]; | ||||||
|  |   out.append("ButtonCommandRequest {\n"); | ||||||
|  |   out.append("  key: "); | ||||||
|  |   sprintf(buffer, "%u", this->key); | ||||||
|  |   out.append(buffer); | ||||||
|  |   out.append("\n"); | ||||||
|  |   out.append("}"); | ||||||
|  | } | ||||||
|  | #endif | ||||||
|  |  | ||||||
| }  // namespace api | }  // namespace api | ||||||
| }  // namespace esphome | }  // namespace esphome | ||||||
|   | |||||||
| @@ -9,6 +9,11 @@ namespace api { | |||||||
|  |  | ||||||
| namespace enums { | namespace enums { | ||||||
|  |  | ||||||
|  | enum EntityCategory : uint32_t { | ||||||
|  |   ENTITY_CATEGORY_NONE = 0, | ||||||
|  |   ENTITY_CATEGORY_CONFIG = 1, | ||||||
|  |   ENTITY_CATEGORY_DIAGNOSTIC = 2, | ||||||
|  | }; | ||||||
| enum LegacyCoverState : uint32_t { | enum LegacyCoverState : uint32_t { | ||||||
|   LEGACY_COVER_STATE_OPEN = 0, |   LEGACY_COVER_STATE_OPEN = 0, | ||||||
|   LEGACY_COVER_STATE_CLOSED = 1, |   LEGACY_COVER_STATE_CLOSED = 1, | ||||||
| @@ -118,6 +123,11 @@ enum ClimatePreset : uint32_t { | |||||||
|   CLIMATE_PRESET_SLEEP = 6, |   CLIMATE_PRESET_SLEEP = 6, | ||||||
|   CLIMATE_PRESET_ACTIVITY = 7, |   CLIMATE_PRESET_ACTIVITY = 7, | ||||||
| }; | }; | ||||||
|  | enum NumberMode : uint32_t { | ||||||
|  |   NUMBER_MODE_AUTO = 0, | ||||||
|  |   NUMBER_MODE_BOX = 1, | ||||||
|  |   NUMBER_MODE_SLIDER = 2, | ||||||
|  | }; | ||||||
|  |  | ||||||
| }  // namespace enums | }  // namespace enums | ||||||
|  |  | ||||||
| @@ -224,6 +234,7 @@ class DeviceInfoResponse : public ProtoMessage { | |||||||
|   bool has_deep_sleep{false}; |   bool has_deep_sleep{false}; | ||||||
|   std::string project_name{}; |   std::string project_name{}; | ||||||
|   std::string project_version{}; |   std::string project_version{}; | ||||||
|  |   uint32_t webserver_port{0}; | ||||||
|   void encode(ProtoWriteBuffer buffer) const override; |   void encode(ProtoWriteBuffer buffer) const override; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   void dump_to(std::string &out) const override; |   void dump_to(std::string &out) const override; | ||||||
| @@ -270,6 +281,7 @@ class ListEntitiesBinarySensorResponse : public ProtoMessage { | |||||||
|   bool is_status_binary_sensor{false}; |   bool is_status_binary_sensor{false}; | ||||||
|   bool disabled_by_default{false}; |   bool disabled_by_default{false}; | ||||||
|   std::string icon{}; |   std::string icon{}; | ||||||
|  |   enums::EntityCategory entity_category{}; | ||||||
|   void encode(ProtoWriteBuffer buffer) const override; |   void encode(ProtoWriteBuffer buffer) const override; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   void dump_to(std::string &out) const override; |   void dump_to(std::string &out) const override; | ||||||
| @@ -306,6 +318,7 @@ class ListEntitiesCoverResponse : public ProtoMessage { | |||||||
|   std::string device_class{}; |   std::string device_class{}; | ||||||
|   bool disabled_by_default{false}; |   bool disabled_by_default{false}; | ||||||
|   std::string icon{}; |   std::string icon{}; | ||||||
|  |   enums::EntityCategory entity_category{}; | ||||||
|   void encode(ProtoWriteBuffer buffer) const override; |   void encode(ProtoWriteBuffer buffer) const override; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   void dump_to(std::string &out) const override; |   void dump_to(std::string &out) const override; | ||||||
| @@ -363,6 +376,7 @@ class ListEntitiesFanResponse : public ProtoMessage { | |||||||
|   int32_t supported_speed_count{0}; |   int32_t supported_speed_count{0}; | ||||||
|   bool disabled_by_default{false}; |   bool disabled_by_default{false}; | ||||||
|   std::string icon{}; |   std::string icon{}; | ||||||
|  |   enums::EntityCategory entity_category{}; | ||||||
|   void encode(ProtoWriteBuffer buffer) const override; |   void encode(ProtoWriteBuffer buffer) const override; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   void dump_to(std::string &out) const override; |   void dump_to(std::string &out) const override; | ||||||
| @@ -428,6 +442,7 @@ class ListEntitiesLightResponse : public ProtoMessage { | |||||||
|   std::vector<std::string> effects{}; |   std::vector<std::string> effects{}; | ||||||
|   bool disabled_by_default{false}; |   bool disabled_by_default{false}; | ||||||
|   std::string icon{}; |   std::string icon{}; | ||||||
|  |   enums::EntityCategory entity_category{}; | ||||||
|   void encode(ProtoWriteBuffer buffer) const override; |   void encode(ProtoWriteBuffer buffer) const override; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   void dump_to(std::string &out) const override; |   void dump_to(std::string &out) const override; | ||||||
| @@ -516,6 +531,7 @@ class ListEntitiesSensorResponse : public ProtoMessage { | |||||||
|   enums::SensorStateClass state_class{}; |   enums::SensorStateClass state_class{}; | ||||||
|   enums::SensorLastResetType legacy_last_reset_type{}; |   enums::SensorLastResetType legacy_last_reset_type{}; | ||||||
|   bool disabled_by_default{false}; |   bool disabled_by_default{false}; | ||||||
|  |   enums::EntityCategory entity_category{}; | ||||||
|   void encode(ProtoWriteBuffer buffer) const override; |   void encode(ProtoWriteBuffer buffer) const override; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   void dump_to(std::string &out) const override; |   void dump_to(std::string &out) const override; | ||||||
| @@ -549,6 +565,7 @@ class ListEntitiesSwitchResponse : public ProtoMessage { | |||||||
|   std::string icon{}; |   std::string icon{}; | ||||||
|   bool assumed_state{false}; |   bool assumed_state{false}; | ||||||
|   bool disabled_by_default{false}; |   bool disabled_by_default{false}; | ||||||
|  |   enums::EntityCategory entity_category{}; | ||||||
|   void encode(ProtoWriteBuffer buffer) const override; |   void encode(ProtoWriteBuffer buffer) const override; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   void dump_to(std::string &out) const override; |   void dump_to(std::string &out) const override; | ||||||
| @@ -593,6 +610,7 @@ class ListEntitiesTextSensorResponse : public ProtoMessage { | |||||||
|   std::string unique_id{}; |   std::string unique_id{}; | ||||||
|   std::string icon{}; |   std::string icon{}; | ||||||
|   bool disabled_by_default{false}; |   bool disabled_by_default{false}; | ||||||
|  |   enums::EntityCategory entity_category{}; | ||||||
|   void encode(ProtoWriteBuffer buffer) const override; |   void encode(ProtoWriteBuffer buffer) const override; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   void dump_to(std::string &out) const override; |   void dump_to(std::string &out) const override; | ||||||
| @@ -803,6 +821,8 @@ class ListEntitiesCameraResponse : public ProtoMessage { | |||||||
|   std::string name{}; |   std::string name{}; | ||||||
|   std::string unique_id{}; |   std::string unique_id{}; | ||||||
|   bool disabled_by_default{false}; |   bool disabled_by_default{false}; | ||||||
|  |   std::string icon{}; | ||||||
|  |   enums::EntityCategory entity_category{}; | ||||||
|   void encode(ProtoWriteBuffer buffer) const override; |   void encode(ProtoWriteBuffer buffer) const override; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   void dump_to(std::string &out) const override; |   void dump_to(std::string &out) const override; | ||||||
| @@ -861,6 +881,7 @@ class ListEntitiesClimateResponse : public ProtoMessage { | |||||||
|   std::vector<std::string> supported_custom_presets{}; |   std::vector<std::string> supported_custom_presets{}; | ||||||
|   bool disabled_by_default{false}; |   bool disabled_by_default{false}; | ||||||
|   std::string icon{}; |   std::string icon{}; | ||||||
|  |   enums::EntityCategory entity_category{}; | ||||||
|   void encode(ProtoWriteBuffer buffer) const override; |   void encode(ProtoWriteBuffer buffer) const override; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   void dump_to(std::string &out) const override; |   void dump_to(std::string &out) const override; | ||||||
| @@ -940,6 +961,9 @@ class ListEntitiesNumberResponse : public ProtoMessage { | |||||||
|   float max_value{0.0f}; |   float max_value{0.0f}; | ||||||
|   float step{0.0f}; |   float step{0.0f}; | ||||||
|   bool disabled_by_default{false}; |   bool disabled_by_default{false}; | ||||||
|  |   enums::EntityCategory entity_category{}; | ||||||
|  |   std::string unit_of_measurement{}; | ||||||
|  |   enums::NumberMode mode{}; | ||||||
|   void encode(ProtoWriteBuffer buffer) const override; |   void encode(ProtoWriteBuffer buffer) const override; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   void dump_to(std::string &out) const override; |   void dump_to(std::string &out) const override; | ||||||
| @@ -985,6 +1009,7 @@ class ListEntitiesSelectResponse : public ProtoMessage { | |||||||
|   std::string icon{}; |   std::string icon{}; | ||||||
|   std::vector<std::string> options{}; |   std::vector<std::string> options{}; | ||||||
|   bool disabled_by_default{false}; |   bool disabled_by_default{false}; | ||||||
|  |   enums::EntityCategory entity_category{}; | ||||||
|   void encode(ProtoWriteBuffer buffer) const override; |   void encode(ProtoWriteBuffer buffer) const override; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   void dump_to(std::string &out) const override; |   void dump_to(std::string &out) const override; | ||||||
| @@ -1023,6 +1048,37 @@ class SelectCommandRequest : public ProtoMessage { | |||||||
|   bool decode_32bit(uint32_t field_id, Proto32Bit value) override; |   bool decode_32bit(uint32_t field_id, Proto32Bit value) override; | ||||||
|   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; |   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; | ||||||
| }; | }; | ||||||
|  | class ListEntitiesButtonResponse : public ProtoMessage { | ||||||
|  |  public: | ||||||
|  |   std::string object_id{}; | ||||||
|  |   uint32_t key{0}; | ||||||
|  |   std::string name{}; | ||||||
|  |   std::string unique_id{}; | ||||||
|  |   std::string icon{}; | ||||||
|  |   bool disabled_by_default{false}; | ||||||
|  |   enums::EntityCategory entity_category{}; | ||||||
|  |   std::string device_class{}; | ||||||
|  |   void encode(ProtoWriteBuffer buffer) const override; | ||||||
|  | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|  |   void dump_to(std::string &out) const override; | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   bool decode_32bit(uint32_t field_id, Proto32Bit value) override; | ||||||
|  |   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; | ||||||
|  |   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; | ||||||
|  | }; | ||||||
|  | class ButtonCommandRequest : public ProtoMessage { | ||||||
|  |  public: | ||||||
|  |   uint32_t key{0}; | ||||||
|  |   void encode(ProtoWriteBuffer buffer) const override; | ||||||
|  | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|  |   void dump_to(std::string &out) const override; | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   bool decode_32bit(uint32_t field_id, Proto32Bit value) override; | ||||||
|  | }; | ||||||
|  |  | ||||||
| }  // namespace api | }  // namespace api | ||||||
| }  // namespace esphome | }  // namespace esphome | ||||||
|   | |||||||
| @@ -282,6 +282,16 @@ bool APIServerConnectionBase::send_select_state_response(const SelectStateRespon | |||||||
| #endif | #endif | ||||||
| #ifdef USE_SELECT | #ifdef USE_SELECT | ||||||
| #endif | #endif | ||||||
|  | #ifdef USE_BUTTON | ||||||
|  | bool APIServerConnectionBase::send_list_entities_button_response(const ListEntitiesButtonResponse &msg) { | ||||||
|  | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|  |   ESP_LOGVV(TAG, "send_list_entities_button_response: %s", msg.dump().c_str()); | ||||||
|  | #endif | ||||||
|  |   return this->send_message_<ListEntitiesButtonResponse>(msg, 61); | ||||||
|  | } | ||||||
|  | #endif | ||||||
|  | #ifdef USE_BUTTON | ||||||
|  | #endif | ||||||
| bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) { | bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) { | ||||||
|   switch (msg_type) { |   switch (msg_type) { | ||||||
|     case 1: { |     case 1: { | ||||||
| @@ -513,6 +523,17 @@ bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, | |||||||
|       ESP_LOGVV(TAG, "on_select_command_request: %s", msg.dump().c_str()); |       ESP_LOGVV(TAG, "on_select_command_request: %s", msg.dump().c_str()); | ||||||
| #endif | #endif | ||||||
|       this->on_select_command_request(msg); |       this->on_select_command_request(msg); | ||||||
|  | #endif | ||||||
|  |       break; | ||||||
|  |     } | ||||||
|  |     case 62: { | ||||||
|  | #ifdef USE_BUTTON | ||||||
|  |       ButtonCommandRequest msg; | ||||||
|  |       msg.decode(msg_data, msg_size); | ||||||
|  | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|  |       ESP_LOGVV(TAG, "on_button_command_request: %s", msg.dump().c_str()); | ||||||
|  | #endif | ||||||
|  |       this->on_button_command_request(msg); | ||||||
| #endif | #endif | ||||||
|       break; |       break; | ||||||
|     } |     } | ||||||
| @@ -737,6 +758,19 @@ void APIServerConnection::on_select_command_request(const SelectCommandRequest & | |||||||
|   this->select_command(msg); |   this->select_command(msg); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
|  | #ifdef USE_BUTTON | ||||||
|  | void APIServerConnection::on_button_command_request(const ButtonCommandRequest &msg) { | ||||||
|  |   if (!this->is_connection_setup()) { | ||||||
|  |     this->on_no_setup_connection(); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   if (!this->is_authenticated()) { | ||||||
|  |     this->on_unauthenticated_access(); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   this->button_command(msg); | ||||||
|  | } | ||||||
|  | #endif | ||||||
|  |  | ||||||
| }  // namespace api | }  // namespace api | ||||||
| }  // namespace esphome | }  // namespace esphome | ||||||
|   | |||||||
| @@ -129,6 +129,12 @@ class APIServerConnectionBase : public ProtoService { | |||||||
| #endif | #endif | ||||||
| #ifdef USE_SELECT | #ifdef USE_SELECT | ||||||
|   virtual void on_select_command_request(const SelectCommandRequest &value){}; |   virtual void on_select_command_request(const SelectCommandRequest &value){}; | ||||||
|  | #endif | ||||||
|  | #ifdef USE_BUTTON | ||||||
|  |   bool send_list_entities_button_response(const ListEntitiesButtonResponse &msg); | ||||||
|  | #endif | ||||||
|  | #ifdef USE_BUTTON | ||||||
|  |   virtual void on_button_command_request(const ButtonCommandRequest &value){}; | ||||||
| #endif | #endif | ||||||
|  protected: |  protected: | ||||||
|   bool read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override; |   bool read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override; | ||||||
| @@ -171,6 +177,9 @@ class APIServerConnection : public APIServerConnectionBase { | |||||||
| #endif | #endif | ||||||
| #ifdef USE_SELECT | #ifdef USE_SELECT | ||||||
|   virtual void select_command(const SelectCommandRequest &msg) = 0; |   virtual void select_command(const SelectCommandRequest &msg) = 0; | ||||||
|  | #endif | ||||||
|  | #ifdef USE_BUTTON | ||||||
|  |   virtual void button_command(const ButtonCommandRequest &msg) = 0; | ||||||
| #endif | #endif | ||||||
|  protected: |  protected: | ||||||
|   void on_hello_request(const HelloRequest &msg) override; |   void on_hello_request(const HelloRequest &msg) override; | ||||||
| @@ -209,6 +218,9 @@ class APIServerConnection : public APIServerConnectionBase { | |||||||
| #ifdef USE_SELECT | #ifdef USE_SELECT | ||||||
|   void on_select_command_request(const SelectCommandRequest &msg) override; |   void on_select_command_request(const SelectCommandRequest &msg) override; | ||||||
| #endif | #endif | ||||||
|  | #ifdef USE_BUTTON | ||||||
|  |   void on_button_command_request(const ButtonCommandRequest &msg) override; | ||||||
|  | #endif | ||||||
| }; | }; | ||||||
|  |  | ||||||
| }  // namespace api | }  // namespace api | ||||||
|   | |||||||
| @@ -77,7 +77,7 @@ void APIServer::setup() { | |||||||
|   this->last_connected_ = millis(); |   this->last_connected_ = millis(); | ||||||
|  |  | ||||||
| #ifdef USE_ESP32_CAMERA | #ifdef USE_ESP32_CAMERA | ||||||
|   if (esp32_camera::global_esp32_camera != nullptr) { |   if (esp32_camera::global_esp32_camera != nullptr && !esp32_camera::global_esp32_camera->is_internal()) { | ||||||
|     esp32_camera::global_esp32_camera->add_image_callback( |     esp32_camera::global_esp32_camera->add_image_callback( | ||||||
|         [this](const std::shared_ptr<esp32_camera::CameraImage> &image) { |         [this](const std::shared_ptr<esp32_camera::CameraImage> &image) { | ||||||
|           for (auto &c : this->clients_) |           for (auto &c : this->clients_) | ||||||
|   | |||||||
| @@ -23,7 +23,6 @@ async def async_run_logs(config, address): | |||||||
|     _LOGGER.info("Starting log output from %s using esphome API", address) |     _LOGGER.info("Starting log output from %s using esphome API", address) | ||||||
|     zc = zeroconf.Zeroconf() |     zc = zeroconf.Zeroconf() | ||||||
|     cli = APIClient( |     cli = APIClient( | ||||||
|         asyncio.get_event_loop(), |  | ||||||
|         address, |         address, | ||||||
|         port, |         port, | ||||||
|         password, |         password, | ||||||
|   | |||||||
| @@ -8,6 +8,18 @@ | |||||||
| namespace esphome { | namespace esphome { | ||||||
| namespace api { | namespace api { | ||||||
|  |  | ||||||
|  | template<typename... X> class TemplatableStringValue : public TemplatableValue<std::string, X...> { | ||||||
|  |  public: | ||||||
|  |   TemplatableStringValue() : TemplatableValue<std::string, X...>() {} | ||||||
|  |  | ||||||
|  |   template<typename F, enable_if_t<!is_invocable<F, X...>::value, int> = 0> | ||||||
|  |   TemplatableStringValue(F value) : TemplatableValue<std::string, X...>(value) {} | ||||||
|  |  | ||||||
|  |   template<typename F, enable_if_t<is_invocable<F, X...>::value, int> = 0> | ||||||
|  |   TemplatableStringValue(F f) | ||||||
|  |       : TemplatableValue<std::string, X...>([f](X... x) -> std::string { return to_string(f(x...)); }) {} | ||||||
|  | }; | ||||||
|  |  | ||||||
| template<typename... Ts> class TemplatableKeyValuePair { | template<typename... Ts> class TemplatableKeyValuePair { | ||||||
|  public: |  public: | ||||||
|   template<typename T> TemplatableKeyValuePair(std::string key, T value) : key(std::move(key)), value(value) {} |   template<typename T> TemplatableKeyValuePair(std::string key, T value) : key(std::move(key)), value(value) {} | ||||||
| @@ -19,7 +31,8 @@ template<typename... Ts> class HomeAssistantServiceCallAction : public Action<Ts | |||||||
|  public: |  public: | ||||||
|   explicit HomeAssistantServiceCallAction(APIServer *parent, bool is_event) : parent_(parent), is_event_(is_event) {} |   explicit HomeAssistantServiceCallAction(APIServer *parent, bool is_event) : parent_(parent), is_event_(is_event) {} | ||||||
|  |  | ||||||
|   TEMPLATABLE_STRING_VALUE(service); |   template<typename T> void set_service(T service) { this->service_ = service; } | ||||||
|  |  | ||||||
|   template<typename T> void add_data(std::string key, T value) { |   template<typename T> void add_data(std::string key, T value) { | ||||||
|     this->data_.push_back(TemplatableKeyValuePair<Ts...>(key, value)); |     this->data_.push_back(TemplatableKeyValuePair<Ts...>(key, value)); | ||||||
|   } |   } | ||||||
| @@ -58,6 +71,7 @@ template<typename... Ts> class HomeAssistantServiceCallAction : public Action<Ts | |||||||
|  protected: |  protected: | ||||||
|   APIServer *parent_; |   APIServer *parent_; | ||||||
|   bool is_event_; |   bool is_event_; | ||||||
|  |   TemplatableStringValue<Ts...> service_{}; | ||||||
|   std::vector<TemplatableKeyValuePair<Ts...>> data_; |   std::vector<TemplatableKeyValuePair<Ts...>> data_; | ||||||
|   std::vector<TemplatableKeyValuePair<Ts...>> data_template_; |   std::vector<TemplatableKeyValuePair<Ts...>> data_template_; | ||||||
|   std::vector<TemplatableKeyValuePair<Ts...>> variables_; |   std::vector<TemplatableKeyValuePair<Ts...>> variables_; | ||||||
|   | |||||||
| @@ -27,6 +27,9 @@ bool ListEntitiesIterator::on_sensor(sensor::Sensor *sensor) { return this->clie | |||||||
| #ifdef USE_SWITCH | #ifdef USE_SWITCH | ||||||
| bool ListEntitiesIterator::on_switch(switch_::Switch *a_switch) { return this->client_->send_switch_info(a_switch); } | bool ListEntitiesIterator::on_switch(switch_::Switch *a_switch) { return this->client_->send_switch_info(a_switch); } | ||||||
| #endif | #endif | ||||||
|  | #ifdef USE_BUTTON | ||||||
|  | bool ListEntitiesIterator::on_button(button::Button *button) { return this->client_->send_button_info(button); } | ||||||
|  | #endif | ||||||
| #ifdef USE_TEXT_SENSOR | #ifdef USE_TEXT_SENSOR | ||||||
| bool ListEntitiesIterator::on_text_sensor(text_sensor::TextSensor *text_sensor) { | bool ListEntitiesIterator::on_text_sensor(text_sensor::TextSensor *text_sensor) { | ||||||
|   return this->client_->send_text_sensor_info(text_sensor); |   return this->client_->send_text_sensor_info(text_sensor); | ||||||
|   | |||||||
| @@ -30,6 +30,9 @@ class ListEntitiesIterator : public ComponentIterator { | |||||||
| #ifdef USE_SWITCH | #ifdef USE_SWITCH | ||||||
|   bool on_switch(switch_::Switch *a_switch) override; |   bool on_switch(switch_::Switch *a_switch) override; | ||||||
| #endif | #endif | ||||||
|  | #ifdef USE_BUTTON | ||||||
|  |   bool on_button(button::Button *button) override; | ||||||
|  | #endif | ||||||
| #ifdef USE_TEXT_SENSOR | #ifdef USE_TEXT_SENSOR | ||||||
|   bool on_text_sensor(text_sensor::TextSensor *text_sensor) override; |   bool on_text_sensor(text_sensor::TextSensor *text_sensor) override; | ||||||
| #endif | #endif | ||||||
|   | |||||||
| @@ -31,6 +31,9 @@ class InitialStateIterator : public ComponentIterator { | |||||||
| #ifdef USE_SWITCH | #ifdef USE_SWITCH | ||||||
|   bool on_switch(switch_::Switch *a_switch) override; |   bool on_switch(switch_::Switch *a_switch) override; | ||||||
| #endif | #endif | ||||||
|  | #ifdef USE_BUTTON | ||||||
|  |   bool on_button(button::Button *button) override { return true; }; | ||||||
|  | #endif | ||||||
| #ifdef USE_TEXT_SENSOR | #ifdef USE_TEXT_SENSOR | ||||||
|   bool on_text_sensor(text_sensor::TextSensor *text_sensor) override; |   bool on_text_sensor(text_sensor::TextSensor *text_sensor) override; | ||||||
| #endif | #endif | ||||||
|   | |||||||
| @@ -116,6 +116,21 @@ void ComponentIterator::advance() { | |||||||
|       } |       } | ||||||
|       break; |       break; | ||||||
| #endif | #endif | ||||||
|  | #ifdef USE_BUTTON | ||||||
|  |     case IteratorState::BUTTON: | ||||||
|  |       if (this->at_ >= App.get_buttons().size()) { | ||||||
|  |         advance_platform = true; | ||||||
|  |       } else { | ||||||
|  |         auto *button = App.get_buttons()[this->at_]; | ||||||
|  |         if (button->is_internal()) { | ||||||
|  |           success = true; | ||||||
|  |           break; | ||||||
|  |         } else { | ||||||
|  |           success = this->on_button(button); | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |       break; | ||||||
|  | #endif | ||||||
| #ifdef USE_TEXT_SENSOR | #ifdef USE_TEXT_SENSOR | ||||||
|     case IteratorState::TEXT_SENSOR: |     case IteratorState::TEXT_SENSOR: | ||||||
|       if (this->at_ >= App.get_text_sensors().size()) { |       if (this->at_ >= App.get_text_sensors().size()) { | ||||||
|   | |||||||
| @@ -38,6 +38,9 @@ class ComponentIterator { | |||||||
| #ifdef USE_SWITCH | #ifdef USE_SWITCH | ||||||
|   virtual bool on_switch(switch_::Switch *a_switch) = 0; |   virtual bool on_switch(switch_::Switch *a_switch) = 0; | ||||||
| #endif | #endif | ||||||
|  | #ifdef USE_BUTTON | ||||||
|  |   virtual bool on_button(button::Button *button) = 0; | ||||||
|  | #endif | ||||||
| #ifdef USE_TEXT_SENSOR | #ifdef USE_TEXT_SENSOR | ||||||
|   virtual bool on_text_sensor(text_sensor::TextSensor *text_sensor) = 0; |   virtual bool on_text_sensor(text_sensor::TextSensor *text_sensor) = 0; | ||||||
| #endif | #endif | ||||||
| @@ -78,6 +81,9 @@ class ComponentIterator { | |||||||
| #ifdef USE_SWITCH | #ifdef USE_SWITCH | ||||||
|     SWITCH, |     SWITCH, | ||||||
| #endif | #endif | ||||||
|  | #ifdef USE_BUTTON | ||||||
|  |     BUTTON, | ||||||
|  | #endif | ||||||
| #ifdef USE_TEXT_SENSOR | #ifdef USE_TEXT_SENSOR | ||||||
|     TEXT_SENSOR, |     TEXT_SENSOR, | ||||||
| #endif | #endif | ||||||
|   | |||||||
| @@ -45,6 +45,8 @@ bool ATCMiThermometer::parse_device(const esp32_ble_tracker::ESPBTDevice &device | |||||||
|       this->battery_voltage_->publish_state(*res->battery_voltage); |       this->battery_voltage_->publish_state(*res->battery_voltage); | ||||||
|     success = true; |     success = true; | ||||||
|   } |   } | ||||||
|  |   if (this->signal_strength_ != nullptr) | ||||||
|  |     this->signal_strength_->publish_state(device.get_rssi()); | ||||||
|  |  | ||||||
|   return success; |   return success; | ||||||
| } | } | ||||||
|   | |||||||
| @@ -28,6 +28,7 @@ class ATCMiThermometer : public Component, public esp32_ble_tracker::ESPBTDevice | |||||||
|   void set_humidity(sensor::Sensor *humidity) { humidity_ = humidity; } |   void set_humidity(sensor::Sensor *humidity) { humidity_ = humidity; } | ||||||
|   void set_battery_level(sensor::Sensor *battery_level) { battery_level_ = battery_level; } |   void set_battery_level(sensor::Sensor *battery_level) { battery_level_ = battery_level; } | ||||||
|   void set_battery_voltage(sensor::Sensor *battery_voltage) { battery_voltage_ = battery_voltage; } |   void set_battery_voltage(sensor::Sensor *battery_voltage) { battery_voltage_ = battery_voltage; } | ||||||
|  |   void set_signal_strength(sensor::Sensor *signal_strength) { signal_strength_ = signal_strength; } | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   uint64_t address_; |   uint64_t address_; | ||||||
| @@ -35,6 +36,7 @@ class ATCMiThermometer : public Component, public esp32_ble_tracker::ESPBTDevice | |||||||
|   sensor::Sensor *humidity_{nullptr}; |   sensor::Sensor *humidity_{nullptr}; | ||||||
|   sensor::Sensor *battery_level_{nullptr}; |   sensor::Sensor *battery_level_{nullptr}; | ||||||
|   sensor::Sensor *battery_voltage_{nullptr}; |   sensor::Sensor *battery_voltage_{nullptr}; | ||||||
|  |   sensor::Sensor *signal_strength_{nullptr}; | ||||||
|  |  | ||||||
|   optional<ParseResult> parse_header_(const esp32_ble_tracker::ServiceData &service_data); |   optional<ParseResult> parse_header_(const esp32_ble_tracker::ServiceData &service_data); | ||||||
|   bool parse_message_(const std::vector<uint8_t> &message, ParseResult &result); |   bool parse_message_(const std::vector<uint8_t> &message, ParseResult &result); | ||||||
|   | |||||||
| @@ -6,14 +6,18 @@ from esphome.const import ( | |||||||
|     CONF_BATTERY_VOLTAGE, |     CONF_BATTERY_VOLTAGE, | ||||||
|     CONF_MAC_ADDRESS, |     CONF_MAC_ADDRESS, | ||||||
|     CONF_HUMIDITY, |     CONF_HUMIDITY, | ||||||
|  |     CONF_SIGNAL_STRENGTH, | ||||||
|     CONF_TEMPERATURE, |     CONF_TEMPERATURE, | ||||||
|     CONF_ID, |     CONF_ID, | ||||||
|     DEVICE_CLASS_BATTERY, |     DEVICE_CLASS_BATTERY, | ||||||
|     DEVICE_CLASS_HUMIDITY, |     DEVICE_CLASS_HUMIDITY, | ||||||
|  |     DEVICE_CLASS_SIGNAL_STRENGTH, | ||||||
|     DEVICE_CLASS_TEMPERATURE, |     DEVICE_CLASS_TEMPERATURE, | ||||||
|     DEVICE_CLASS_VOLTAGE, |     DEVICE_CLASS_VOLTAGE, | ||||||
|  |     ENTITY_CATEGORY_DIAGNOSTIC, | ||||||
|     STATE_CLASS_MEASUREMENT, |     STATE_CLASS_MEASUREMENT, | ||||||
|     UNIT_CELSIUS, |     UNIT_CELSIUS, | ||||||
|  |     UNIT_DECIBEL_MILLIWATT, | ||||||
|     UNIT_PERCENT, |     UNIT_PERCENT, | ||||||
|     UNIT_VOLT, |     UNIT_VOLT, | ||||||
| ) | ) | ||||||
| @@ -49,12 +53,21 @@ CONFIG_SCHEMA = ( | |||||||
|                 accuracy_decimals=0, |                 accuracy_decimals=0, | ||||||
|                 device_class=DEVICE_CLASS_BATTERY, |                 device_class=DEVICE_CLASS_BATTERY, | ||||||
|                 state_class=STATE_CLASS_MEASUREMENT, |                 state_class=STATE_CLASS_MEASUREMENT, | ||||||
|  |                 entity_category=ENTITY_CATEGORY_DIAGNOSTIC, | ||||||
|             ), |             ), | ||||||
|             cv.Optional(CONF_BATTERY_VOLTAGE): sensor.sensor_schema( |             cv.Optional(CONF_BATTERY_VOLTAGE): sensor.sensor_schema( | ||||||
|                 unit_of_measurement=UNIT_VOLT, |                 unit_of_measurement=UNIT_VOLT, | ||||||
|                 accuracy_decimals=3, |                 accuracy_decimals=3, | ||||||
|                 device_class=DEVICE_CLASS_VOLTAGE, |                 device_class=DEVICE_CLASS_VOLTAGE, | ||||||
|                 state_class=STATE_CLASS_MEASUREMENT, |                 state_class=STATE_CLASS_MEASUREMENT, | ||||||
|  |                 entity_category=ENTITY_CATEGORY_DIAGNOSTIC, | ||||||
|  |             ), | ||||||
|  |             cv.Optional(CONF_SIGNAL_STRENGTH): sensor.sensor_schema( | ||||||
|  |                 unit_of_measurement=UNIT_DECIBEL_MILLIWATT, | ||||||
|  |                 accuracy_decimals=0, | ||||||
|  |                 device_class=DEVICE_CLASS_SIGNAL_STRENGTH, | ||||||
|  |                 state_class=STATE_CLASS_MEASUREMENT, | ||||||
|  |                 entity_category=ENTITY_CATEGORY_DIAGNOSTIC, | ||||||
|             ), |             ), | ||||||
|         } |         } | ||||||
|     ) |     ) | ||||||
| @@ -82,3 +95,6 @@ async def to_code(config): | |||||||
|     if CONF_BATTERY_VOLTAGE in config: |     if CONF_BATTERY_VOLTAGE in config: | ||||||
|         sens = await sensor.new_sensor(config[CONF_BATTERY_VOLTAGE]) |         sens = await sensor.new_sensor(config[CONF_BATTERY_VOLTAGE]) | ||||||
|         cg.add(var.set_battery_voltage(sens)) |         cg.add(var.set_battery_voltage(sens)) | ||||||
|  |     if CONF_SIGNAL_STRENGTH in config: | ||||||
|  |         sens = await sensor.new_sensor(config[CONF_SIGNAL_STRENGTH]) | ||||||
|  |         cg.add(var.set_signal_strength(sens)) | ||||||
|   | |||||||
| @@ -17,6 +17,7 @@ from esphome.const import ( | |||||||
|     DEVICE_CLASS_POWER_FACTOR, |     DEVICE_CLASS_POWER_FACTOR, | ||||||
|     DEVICE_CLASS_TEMPERATURE, |     DEVICE_CLASS_TEMPERATURE, | ||||||
|     DEVICE_CLASS_VOLTAGE, |     DEVICE_CLASS_VOLTAGE, | ||||||
|  |     ENTITY_CATEGORY_DIAGNOSTIC, | ||||||
|     ICON_LIGHTBULB, |     ICON_LIGHTBULB, | ||||||
|     ICON_CURRENT_AC, |     ICON_CURRENT_AC, | ||||||
|     STATE_CLASS_MEASUREMENT, |     STATE_CLASS_MEASUREMENT, | ||||||
| @@ -125,6 +126,7 @@ CONFIG_SCHEMA = ( | |||||||
|                 accuracy_decimals=1, |                 accuracy_decimals=1, | ||||||
|                 device_class=DEVICE_CLASS_TEMPERATURE, |                 device_class=DEVICE_CLASS_TEMPERATURE, | ||||||
|                 state_class=STATE_CLASS_MEASUREMENT, |                 state_class=STATE_CLASS_MEASUREMENT, | ||||||
|  |                 entity_category=ENTITY_CATEGORY_DIAGNOSTIC, | ||||||
|             ), |             ), | ||||||
|             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( |             cv.Optional(CONF_CURRENT_PHASES, default="3"): cv.enum( | ||||||
|   | |||||||
| @@ -13,6 +13,7 @@ from esphome.const import ( | |||||||
|     DEVICE_CLASS_ILLUMINANCE, |     DEVICE_CLASS_ILLUMINANCE, | ||||||
|     DEVICE_CLASS_TEMPERATURE, |     DEVICE_CLASS_TEMPERATURE, | ||||||
|     DEVICE_CLASS_VOLTAGE, |     DEVICE_CLASS_VOLTAGE, | ||||||
|  |     ENTITY_CATEGORY_DIAGNOSTIC, | ||||||
|     STATE_CLASS_MEASUREMENT, |     STATE_CLASS_MEASUREMENT, | ||||||
|     UNIT_CELSIUS, |     UNIT_CELSIUS, | ||||||
|     UNIT_LUX, |     UNIT_LUX, | ||||||
| @@ -51,6 +52,7 @@ CONFIG_SCHEMA = ( | |||||||
|                 accuracy_decimals=3, |                 accuracy_decimals=3, | ||||||
|                 device_class=DEVICE_CLASS_VOLTAGE, |                 device_class=DEVICE_CLASS_VOLTAGE, | ||||||
|                 state_class=STATE_CLASS_MEASUREMENT, |                 state_class=STATE_CLASS_MEASUREMENT, | ||||||
|  |                 entity_category=ENTITY_CATEGORY_DIAGNOSTIC, | ||||||
|             ), |             ), | ||||||
|             cv.Optional(CONF_MOISTURE): sensor.sensor_schema( |             cv.Optional(CONF_MOISTURE): sensor.sensor_schema( | ||||||
|                 unit_of_measurement=UNIT_PERCENT, |                 unit_of_measurement=UNIT_PERCENT, | ||||||
|   | |||||||
| @@ -80,21 +80,23 @@ void BangBangClimate::compute_state_() { | |||||||
|  |  | ||||||
|   climate::ClimateAction target_action; |   climate::ClimateAction target_action; | ||||||
|   if (too_cold) { |   if (too_cold) { | ||||||
|     // too cold -> enable heating if possible, else idle |     // too cold -> enable heating if possible and enabled, else idle | ||||||
|     if (this->supports_heat_) |     if (this->supports_heat_ && | ||||||
|  |         (this->mode == climate::CLIMATE_MODE_HEAT_COOL || this->mode == climate::CLIMATE_MODE_HEAT)) | ||||||
|       target_action = climate::CLIMATE_ACTION_HEATING; |       target_action = climate::CLIMATE_ACTION_HEATING; | ||||||
|     else |     else | ||||||
|       target_action = climate::CLIMATE_ACTION_IDLE; |       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 and enabled, else idle | ||||||
|     if (this->supports_cool_) |     if (this->supports_cool_ && | ||||||
|  |         (this->mode == climate::CLIMATE_MODE_HEAT_COOL || this->mode == climate::CLIMATE_MODE_COOL)) | ||||||
|       target_action = climate::CLIMATE_ACTION_COOLING; |       target_action = climate::CLIMATE_ACTION_COOLING; | ||||||
|     else |     else | ||||||
|       target_action = climate::CLIMATE_ACTION_IDLE; |       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_ && this->mode == climate::CLIMATE_MODE_HEAT_COOL) { | ||||||
|       // if supports both ends, go to idle action |       // if supports both ends and both cooling and heating enabled, go to idle action | ||||||
|       target_action = climate::CLIMATE_ACTION_IDLE; |       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) | ||||||
|   | |||||||
| @@ -79,6 +79,9 @@ void BH1750Sensor::read_data_() { | |||||||
|  |  | ||||||
|   float lx = float(raw_value) / 1.2f; |   float lx = float(raw_value) / 1.2f; | ||||||
|   lx *= 69.0f / this->measurement_duration_; |   lx *= 69.0f / this->measurement_duration_; | ||||||
|  |   if (this->resolution_ == BH1750_RESOLUTION_0P5_LX) { | ||||||
|  |     lx /= 2.0f; | ||||||
|  |   } | ||||||
|   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(); | ||||||
|   | |||||||
| @@ -44,9 +44,11 @@ from esphome.const import ( | |||||||
|     DEVICE_CLASS_POWER, |     DEVICE_CLASS_POWER, | ||||||
|     DEVICE_CLASS_PRESENCE, |     DEVICE_CLASS_PRESENCE, | ||||||
|     DEVICE_CLASS_PROBLEM, |     DEVICE_CLASS_PROBLEM, | ||||||
|  |     DEVICE_CLASS_RUNNING, | ||||||
|     DEVICE_CLASS_SAFETY, |     DEVICE_CLASS_SAFETY, | ||||||
|     DEVICE_CLASS_SMOKE, |     DEVICE_CLASS_SMOKE, | ||||||
|     DEVICE_CLASS_SOUND, |     DEVICE_CLASS_SOUND, | ||||||
|  |     DEVICE_CLASS_TAMPER, | ||||||
|     DEVICE_CLASS_UPDATE, |     DEVICE_CLASS_UPDATE, | ||||||
|     DEVICE_CLASS_VIBRATION, |     DEVICE_CLASS_VIBRATION, | ||||||
|     DEVICE_CLASS_WINDOW, |     DEVICE_CLASS_WINDOW, | ||||||
| @@ -76,9 +78,11 @@ DEVICE_CLASSES = [ | |||||||
|     DEVICE_CLASS_POWER, |     DEVICE_CLASS_POWER, | ||||||
|     DEVICE_CLASS_PRESENCE, |     DEVICE_CLASS_PRESENCE, | ||||||
|     DEVICE_CLASS_PROBLEM, |     DEVICE_CLASS_PROBLEM, | ||||||
|  |     DEVICE_CLASS_RUNNING, | ||||||
|     DEVICE_CLASS_SAFETY, |     DEVICE_CLASS_SAFETY, | ||||||
|     DEVICE_CLASS_SMOKE, |     DEVICE_CLASS_SMOKE, | ||||||
|     DEVICE_CLASS_SOUND, |     DEVICE_CLASS_SOUND, | ||||||
|  |     DEVICE_CLASS_TAMPER, | ||||||
|     DEVICE_CLASS_UPDATE, |     DEVICE_CLASS_UPDATE, | ||||||
|     DEVICE_CLASS_VIBRATION, |     DEVICE_CLASS_VIBRATION, | ||||||
|     DEVICE_CLASS_WINDOW, |     DEVICE_CLASS_WINDOW, | ||||||
| @@ -313,6 +317,7 @@ def validate_multi_click_timing(value): | |||||||
|  |  | ||||||
| device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space="_") | device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space="_") | ||||||
|  |  | ||||||
|  |  | ||||||
| BINARY_SENSOR_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend( | BINARY_SENSOR_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend( | ||||||
|     { |     { | ||||||
|         cv.GenerateID(): cv.declare_id(BinarySensor), |         cv.GenerateID(): cv.declare_id(BinarySensor), | ||||||
|   | |||||||
| @@ -48,7 +48,10 @@ void BinarySensor::set_device_class(const std::string &device_class) { this->dev | |||||||
| std::string BinarySensor::get_device_class() { | std::string BinarySensor::get_device_class() { | ||||||
|   if (this->device_class_.has_value()) |   if (this->device_class_.has_value()) | ||||||
|     return *this->device_class_; |     return *this->device_class_; | ||||||
|  | #pragma GCC diagnostic push | ||||||
|  | #pragma GCC diagnostic ignored "-Wdeprecated-declarations" | ||||||
|   return this->device_class(); |   return this->device_class(); | ||||||
|  | #pragma GCC diagnostic pop | ||||||
| } | } | ||||||
| void BinarySensor::add_filter(Filter *filter) { | void BinarySensor::add_filter(Filter *filter) { | ||||||
|   filter->parent_ = this; |   filter->parent_ = this; | ||||||
|   | |||||||
| @@ -74,7 +74,10 @@ class BinarySensor : public EntityBase { | |||||||
|  |  | ||||||
|   // ========== OVERRIDE METHODS ========== |   // ========== OVERRIDE METHODS ========== | ||||||
|   // (You'll only need this when creating your own custom binary sensor) |   // (You'll only need this when creating your own custom binary sensor) | ||||||
|   /// Get the default device class for this sensor, or empty string for no default. |   /** Override this to set the default device class. | ||||||
|  |    * | ||||||
|  |    * @deprecated This method is deprecated, set the property during config validation instead. (2022.1) | ||||||
|  |    */ | ||||||
|   virtual std::string device_class(); |   virtual std::string device_class(); | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   | |||||||
							
								
								
									
										1
									
								
								esphome/components/bl0940/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								esphome/components/bl0940/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | |||||||
|  | CODEOWNERS = ["@tobias-"] | ||||||
							
								
								
									
										137
									
								
								esphome/components/bl0940/bl0940.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										137
									
								
								esphome/components/bl0940/bl0940.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,137 @@ | |||||||
|  | #include "bl0940.h" | ||||||
|  | #include "esphome/core/log.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace bl0940 { | ||||||
|  |  | ||||||
|  | static const char *const TAG = "bl0940"; | ||||||
|  |  | ||||||
|  | static const uint8_t BL0940_READ_COMMAND = 0x50;  // 0x58 according to documentation | ||||||
|  | static const uint8_t BL0940_FULL_PACKET = 0xAA; | ||||||
|  | static const uint8_t BL0940_PACKET_HEADER = 0x55;  // 0x58 according to documentation | ||||||
|  |  | ||||||
|  | static const uint8_t BL0940_WRITE_COMMAND = 0xA0;  // 0xA8 according to documentation | ||||||
|  | static const uint8_t BL0940_REG_I_FAST_RMS_CTRL = 0x10; | ||||||
|  | static const uint8_t BL0940_REG_MODE = 0x18; | ||||||
|  | static const uint8_t BL0940_REG_SOFT_RESET = 0x19; | ||||||
|  | static const uint8_t BL0940_REG_USR_WRPROT = 0x1A; | ||||||
|  | static const uint8_t BL0940_REG_TPS_CTRL = 0x1B; | ||||||
|  |  | ||||||
|  | const uint8_t BL0940_INIT[5][6] = { | ||||||
|  |     // Reset to default | ||||||
|  |     {BL0940_WRITE_COMMAND, BL0940_REG_SOFT_RESET, 0x5A, 0x5A, 0x5A, 0x38}, | ||||||
|  |     // Enable User Operation Write | ||||||
|  |     {BL0940_WRITE_COMMAND, BL0940_REG_USR_WRPROT, 0x55, 0x00, 0x00, 0xF0}, | ||||||
|  |     // 0x0100 = CF_UNABLE energy pulse, AC_FREQ_SEL 50Hz, RMS_UPDATE_SEL 800mS | ||||||
|  |     {BL0940_WRITE_COMMAND, BL0940_REG_MODE, 0x00, 0x10, 0x00, 0x37}, | ||||||
|  |     // 0x47FF = Over-current and leakage alarm on, Automatic temperature measurement, Interval 100mS | ||||||
|  |     {BL0940_WRITE_COMMAND, BL0940_REG_TPS_CTRL, 0xFF, 0x47, 0x00, 0xFE}, | ||||||
|  |     // 0x181C = Half cycle, Fast RMS threshold 6172 | ||||||
|  |     {BL0940_WRITE_COMMAND, BL0940_REG_I_FAST_RMS_CTRL, 0x1C, 0x18, 0x00, 0x1B}}; | ||||||
|  |  | ||||||
|  | void BL0940::loop() { | ||||||
|  |   DataPacket buffer; | ||||||
|  |   if (!this->available()) { | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   if (read_array((uint8_t *) &buffer, sizeof(buffer))) { | ||||||
|  |     if (validate_checksum(&buffer)) { | ||||||
|  |       received_package_(&buffer); | ||||||
|  |     } | ||||||
|  |   } else { | ||||||
|  |     ESP_LOGW(TAG, "Junk on wire. Throwing away partial message"); | ||||||
|  |     while (read() >= 0) | ||||||
|  |       ; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool BL0940::validate_checksum(const DataPacket *data) { | ||||||
|  |   uint8_t checksum = BL0940_READ_COMMAND; | ||||||
|  |   // Whole package but checksum | ||||||
|  |   for (uint32_t i = 0; i < sizeof(data->raw) - 1; i++) { | ||||||
|  |     checksum += data->raw[i]; | ||||||
|  |   } | ||||||
|  |   checksum ^= 0xFF; | ||||||
|  |   if (checksum != data->checksum) { | ||||||
|  |     ESP_LOGW(TAG, "BL0940 invalid checksum! 0x%02X != 0x%02X", checksum, data->checksum); | ||||||
|  |   } | ||||||
|  |   return checksum == data->checksum; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void BL0940::update() { | ||||||
|  |   this->flush(); | ||||||
|  |   this->write_byte(BL0940_READ_COMMAND); | ||||||
|  |   this->write_byte(BL0940_FULL_PACKET); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void BL0940::setup() { | ||||||
|  |   for (auto i : BL0940_INIT) { | ||||||
|  |     this->write_array(i, 6); | ||||||
|  |     delay(1); | ||||||
|  |   } | ||||||
|  |   this->flush(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | float BL0940::update_temp_(sensor::Sensor *sensor, ube16_t temperature) const { | ||||||
|  |   auto tb = (float) (temperature.h << 8 | temperature.l); | ||||||
|  |   float converted_temp = ((float) 170 / 448) * (tb / 2 - 32) - 45; | ||||||
|  |   if (sensor != nullptr) { | ||||||
|  |     if (sensor->has_state() && std::abs(converted_temp - sensor->get_state()) > max_temperature_diff_) { | ||||||
|  |       ESP_LOGD("bl0940", "Invalid temperature change. Sensor: '%s', Old temperature: %f, New temperature: %f", | ||||||
|  |                sensor->get_name().c_str(), sensor->get_state(), converted_temp); | ||||||
|  |       return 0.0f; | ||||||
|  |     } | ||||||
|  |     sensor->publish_state(converted_temp); | ||||||
|  |   } | ||||||
|  |   return converted_temp; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void BL0940::received_package_(const DataPacket *data) const { | ||||||
|  |   // Bad header | ||||||
|  |   if (data->frame_header != BL0940_PACKET_HEADER) { | ||||||
|  |     ESP_LOGI("bl0940", "Invalid data. Header mismatch: %d", data->frame_header); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   float v_rms = (float) to_uint32_t(data->v_rms) / voltage_reference_; | ||||||
|  |   float i_rms = (float) to_uint32_t(data->i_rms) / current_reference_; | ||||||
|  |   float watt = (float) to_int32_t(data->watt) / power_reference_; | ||||||
|  |   uint32_t cf_cnt = to_uint32_t(data->cf_cnt); | ||||||
|  |   float total_energy_consumption = (float) cf_cnt / energy_reference_; | ||||||
|  |  | ||||||
|  |   float tps1 = update_temp_(internal_temperature_sensor_, data->tps1); | ||||||
|  |   float tps2 = update_temp_(external_temperature_sensor_, data->tps2); | ||||||
|  |  | ||||||
|  |   if (voltage_sensor_ != nullptr) { | ||||||
|  |     voltage_sensor_->publish_state(v_rms); | ||||||
|  |   } | ||||||
|  |   if (current_sensor_ != nullptr) { | ||||||
|  |     current_sensor_->publish_state(i_rms); | ||||||
|  |   } | ||||||
|  |   if (power_sensor_ != nullptr) { | ||||||
|  |     power_sensor_->publish_state(watt); | ||||||
|  |   } | ||||||
|  |   if (energy_sensor_ != nullptr) { | ||||||
|  |     energy_sensor_->publish_state(total_energy_consumption); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   ESP_LOGV("bl0940", "BL0940: U %fV, I %fA, P %fW, Cnt %d, ∫P %fkWh, T1 %f°C, T2 %f°C", v_rms, i_rms, watt, cf_cnt, | ||||||
|  |            total_energy_consumption, tps1, tps2); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void BL0940::dump_config() {  // NOLINT(readability-function-cognitive-complexity) | ||||||
|  |   ESP_LOGCONFIG(TAG, "BL0940:"); | ||||||
|  |   LOG_SENSOR("", "Voltage", this->voltage_sensor_); | ||||||
|  |   LOG_SENSOR("", "Current", this->current_sensor_); | ||||||
|  |   LOG_SENSOR("", "Power", this->power_sensor_); | ||||||
|  |   LOG_SENSOR("", "Energy", this->energy_sensor_); | ||||||
|  |   LOG_SENSOR("", "Internal temperature", this->internal_temperature_sensor_); | ||||||
|  |   LOG_SENSOR("", "External temperature", this->external_temperature_sensor_); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | uint32_t BL0940::to_uint32_t(ube24_t input) { return input.h << 16 | input.m << 8 | input.l; } | ||||||
|  |  | ||||||
|  | int32_t BL0940::to_int32_t(sbe24_t input) { return input.h << 16 | input.m << 8 | input.l; } | ||||||
|  |  | ||||||
|  | }  // namespace bl0940 | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										109
									
								
								esphome/components/bl0940/bl0940.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										109
									
								
								esphome/components/bl0940/bl0940.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,109 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include "esphome/core/component.h" | ||||||
|  | #include "esphome/components/uart/uart.h" | ||||||
|  | #include "esphome/components/sensor/sensor.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace bl0940 { | ||||||
|  |  | ||||||
|  | static const float BL0940_PREF = 1430; | ||||||
|  | static const float BL0940_UREF = 33000; | ||||||
|  | static const float BL0940_IREF = 275000;  // 2750 from tasmota. Seems to generate values 100 times too high | ||||||
|  |  | ||||||
|  | // Measured to 297J  per click according to power consumption of 5 minutes | ||||||
|  | // Converted to kWh (3.6MJ per kwH). Used to be 256 * 1638.4 | ||||||
|  | static const float BL0940_EREF = 3.6e6 / 297; | ||||||
|  |  | ||||||
|  | struct ube24_t {  // NOLINT(readability-identifier-naming,altera-struct-pack-align) | ||||||
|  |   uint8_t l; | ||||||
|  |   uint8_t m; | ||||||
|  |   uint8_t h; | ||||||
|  | } __attribute__((packed)); | ||||||
|  |  | ||||||
|  | struct ube16_t {  // NOLINT(readability-identifier-naming,altera-struct-pack-align) | ||||||
|  |   uint8_t l; | ||||||
|  |   uint8_t h; | ||||||
|  | } __attribute__((packed)); | ||||||
|  |  | ||||||
|  | struct sbe24_t {  // NOLINT(readability-identifier-naming,altera-struct-pack-align) | ||||||
|  |   uint8_t l; | ||||||
|  |   uint8_t m; | ||||||
|  |   int8_t h; | ||||||
|  | } __attribute__((packed)); | ||||||
|  |  | ||||||
|  | // Caveat: All these values are big endian (low - middle - high) | ||||||
|  |  | ||||||
|  | union DataPacket {  // NOLINT(altera-struct-pack-align) | ||||||
|  |   uint8_t raw[35]; | ||||||
|  |   struct { | ||||||
|  |     uint8_t frame_header;  // value of 0x58 according to docs. 0x55 according to Tasmota real world tests. Reality wins. | ||||||
|  |     ube24_t i_fast_rms;    // 0x00 | ||||||
|  |     ube24_t i_rms;         // 0x04 | ||||||
|  |     ube24_t RESERVED0;     // reserved | ||||||
|  |     ube24_t v_rms;         // 0x06 | ||||||
|  |     ube24_t RESERVED1;     // reserved | ||||||
|  |     sbe24_t watt;          // 0x08 | ||||||
|  |     ube24_t RESERVED2;     // reserved | ||||||
|  |     ube24_t cf_cnt;        // 0x0A | ||||||
|  |     ube24_t RESERVED3;     // reserved | ||||||
|  |     ube16_t tps1;          // 0x0c | ||||||
|  |     uint8_t RESERVED4;     // value of 0x00 | ||||||
|  |     ube16_t tps2;          // 0x0c | ||||||
|  |     uint8_t RESERVED5;     // value of 0x00 | ||||||
|  |     uint8_t checksum;      // checksum | ||||||
|  |   }; | ||||||
|  | } __attribute__((packed)); | ||||||
|  |  | ||||||
|  | class BL0940 : public PollingComponent, public uart::UARTDevice { | ||||||
|  |  public: | ||||||
|  |   void set_voltage_sensor(sensor::Sensor *voltage_sensor) { voltage_sensor_ = voltage_sensor; } | ||||||
|  |   void set_current_sensor(sensor::Sensor *current_sensor) { current_sensor_ = current_sensor; } | ||||||
|  |   void set_power_sensor(sensor::Sensor *power_sensor) { power_sensor_ = power_sensor; } | ||||||
|  |   void set_energy_sensor(sensor::Sensor *energy_sensor) { energy_sensor_ = energy_sensor; } | ||||||
|  |   void set_internal_temperature_sensor(sensor::Sensor *internal_temperature_sensor) { | ||||||
|  |     internal_temperature_sensor_ = internal_temperature_sensor; | ||||||
|  |   } | ||||||
|  |   void set_external_temperature_sensor(sensor::Sensor *external_temperature_sensor) { | ||||||
|  |     external_temperature_sensor_ = external_temperature_sensor; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   void loop() override; | ||||||
|  |  | ||||||
|  |   void update() override; | ||||||
|  |   void setup() override; | ||||||
|  |   void dump_config() override; | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   sensor::Sensor *voltage_sensor_; | ||||||
|  |   sensor::Sensor *current_sensor_; | ||||||
|  |   // NB This may be negative as the circuits is seemingly able to measure | ||||||
|  |   // power in both directions | ||||||
|  |   sensor::Sensor *power_sensor_; | ||||||
|  |   sensor::Sensor *energy_sensor_; | ||||||
|  |   sensor::Sensor *internal_temperature_sensor_; | ||||||
|  |   sensor::Sensor *external_temperature_sensor_; | ||||||
|  |  | ||||||
|  |   // Max difference between two measurements of the temperature. Used to avoid noise. | ||||||
|  |   float max_temperature_diff_{0}; | ||||||
|  |   // Divide by this to turn into Watt | ||||||
|  |   float power_reference_ = BL0940_PREF; | ||||||
|  |   // Divide by this to turn into Volt | ||||||
|  |   float voltage_reference_ = BL0940_UREF; | ||||||
|  |   // Divide by this to turn into Ampere | ||||||
|  |   float current_reference_ = BL0940_IREF; | ||||||
|  |   // Divide by this to turn into kWh | ||||||
|  |   float energy_reference_ = BL0940_EREF; | ||||||
|  |  | ||||||
|  |   float update_temp_(sensor::Sensor *sensor, ube16_t packed_temperature) const; | ||||||
|  |  | ||||||
|  |   static uint32_t to_uint32_t(ube24_t input); | ||||||
|  |  | ||||||
|  |   static int32_t to_int32_t(sbe24_t input); | ||||||
|  |  | ||||||
|  |   static bool validate_checksum(const DataPacket *data); | ||||||
|  |  | ||||||
|  |   void received_package_(const DataPacket *data) const; | ||||||
|  | }; | ||||||
|  | }  // namespace bl0940 | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										106
									
								
								esphome/components/bl0940/sensor.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										106
									
								
								esphome/components/bl0940/sensor.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,106 @@ | |||||||
|  | import esphome.codegen as cg | ||||||
|  | import esphome.config_validation as cv | ||||||
|  | from esphome.components import sensor, uart | ||||||
|  | from esphome.const import ( | ||||||
|  |     CONF_CURRENT, | ||||||
|  |     CONF_ENERGY, | ||||||
|  |     CONF_ID, | ||||||
|  |     CONF_POWER, | ||||||
|  |     CONF_VOLTAGE, | ||||||
|  |     DEVICE_CLASS_CURRENT, | ||||||
|  |     DEVICE_CLASS_ENERGY, | ||||||
|  |     DEVICE_CLASS_POWER, | ||||||
|  |     DEVICE_CLASS_VOLTAGE, | ||||||
|  |     DEVICE_CLASS_TEMPERATURE, | ||||||
|  |     ICON_EMPTY, | ||||||
|  |     STATE_CLASS_MEASUREMENT, | ||||||
|  |     STATE_CLASS_NONE, | ||||||
|  |     UNIT_AMPERE, | ||||||
|  |     UNIT_CELSIUS, | ||||||
|  |     UNIT_KILOWATT_HOURS, | ||||||
|  |     UNIT_VOLT, | ||||||
|  |     UNIT_WATT, | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | DEPENDENCIES = ["uart"] | ||||||
|  |  | ||||||
|  | CONF_INTERNAL_TEMPERATURE = "internal_temperature" | ||||||
|  | CONF_EXTERNAL_TEMPERATURE = "external_temperature" | ||||||
|  |  | ||||||
|  | bl0940_ns = cg.esphome_ns.namespace("bl0940") | ||||||
|  | BL0940 = bl0940_ns.class_("BL0940", cg.PollingComponent, uart.UARTDevice) | ||||||
|  |  | ||||||
|  | CONFIG_SCHEMA = ( | ||||||
|  |     cv.Schema( | ||||||
|  |         { | ||||||
|  |             cv.GenerateID(): cv.declare_id(BL0940), | ||||||
|  |             cv.Optional(CONF_VOLTAGE): sensor.sensor_schema( | ||||||
|  |                 UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE, STATE_CLASS_MEASUREMENT | ||||||
|  |             ), | ||||||
|  |             cv.Optional(CONF_CURRENT): sensor.sensor_schema( | ||||||
|  |                 UNIT_AMPERE, | ||||||
|  |                 ICON_EMPTY, | ||||||
|  |                 2, | ||||||
|  |                 DEVICE_CLASS_CURRENT, | ||||||
|  |                 STATE_CLASS_MEASUREMENT, | ||||||
|  |             ), | ||||||
|  |             cv.Optional(CONF_POWER): sensor.sensor_schema( | ||||||
|  |                 UNIT_WATT, ICON_EMPTY, 0, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT | ||||||
|  |             ), | ||||||
|  |             cv.Optional(CONF_ENERGY): sensor.sensor_schema( | ||||||
|  |                 UNIT_KILOWATT_HOURS, | ||||||
|  |                 ICON_EMPTY, | ||||||
|  |                 0, | ||||||
|  |                 DEVICE_CLASS_ENERGY, | ||||||
|  |                 STATE_CLASS_NONE, | ||||||
|  |             ), | ||||||
|  |             cv.Optional(CONF_INTERNAL_TEMPERATURE): sensor.sensor_schema( | ||||||
|  |                 UNIT_CELSIUS, | ||||||
|  |                 ICON_EMPTY, | ||||||
|  |                 0, | ||||||
|  |                 DEVICE_CLASS_TEMPERATURE, | ||||||
|  |                 STATE_CLASS_NONE, | ||||||
|  |             ), | ||||||
|  |             cv.Optional(CONF_EXTERNAL_TEMPERATURE): sensor.sensor_schema( | ||||||
|  |                 UNIT_CELSIUS, | ||||||
|  |                 ICON_EMPTY, | ||||||
|  |                 0, | ||||||
|  |                 DEVICE_CLASS_TEMPERATURE, | ||||||
|  |                 STATE_CLASS_NONE, | ||||||
|  |             ), | ||||||
|  |         } | ||||||
|  |     ) | ||||||
|  |     .extend(cv.polling_component_schema("60s")) | ||||||
|  |     .extend(uart.UART_DEVICE_SCHEMA) | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | async def to_code(config): | ||||||
|  |     var = cg.new_Pvariable(config[CONF_ID]) | ||||||
|  |     await cg.register_component(var, config) | ||||||
|  |     await uart.register_uart_device(var, config) | ||||||
|  |  | ||||||
|  |     if CONF_VOLTAGE in config: | ||||||
|  |         conf = config[CONF_VOLTAGE] | ||||||
|  |         sens = await sensor.new_sensor(conf) | ||||||
|  |         cg.add(var.set_voltage_sensor(sens)) | ||||||
|  |     if CONF_CURRENT in config: | ||||||
|  |         conf = config[CONF_CURRENT] | ||||||
|  |         sens = await sensor.new_sensor(conf) | ||||||
|  |         cg.add(var.set_current_sensor(sens)) | ||||||
|  |     if CONF_POWER in config: | ||||||
|  |         conf = config[CONF_POWER] | ||||||
|  |         sens = await sensor.new_sensor(conf) | ||||||
|  |         cg.add(var.set_power_sensor(sens)) | ||||||
|  |     if CONF_ENERGY in config: | ||||||
|  |         conf = config[CONF_ENERGY] | ||||||
|  |         sens = await sensor.new_sensor(conf) | ||||||
|  |         cg.add(var.set_energy_sensor(sens)) | ||||||
|  |     if CONF_INTERNAL_TEMPERATURE in config: | ||||||
|  |         conf = config[CONF_INTERNAL_TEMPERATURE] | ||||||
|  |         sens = await sensor.new_sensor(conf) | ||||||
|  |         cg.add(var.set_internal_temperature_sensor(sens)) | ||||||
|  |     if CONF_EXTERNAL_TEMPERATURE in config: | ||||||
|  |         conf = config[CONF_EXTERNAL_TEMPERATURE] | ||||||
|  |         sens = await sensor.new_sensor(conf) | ||||||
|  |         cg.add(var.set_external_temperature_sensor(sens)) | ||||||
| @@ -11,6 +11,8 @@ namespace ble_client { | |||||||
|  |  | ||||||
| static const char *const TAG = "ble_client"; | static const char *const TAG = "ble_client"; | ||||||
|  |  | ||||||
|  | float BLEClient::get_setup_priority() const { return setup_priority::AFTER_BLUETOOTH; } | ||||||
|  |  | ||||||
| void BLEClient::setup() { | void BLEClient::setup() { | ||||||
|   auto ret = esp_ble_gattc_app_register(this->app_id); |   auto ret = esp_ble_gattc_app_register(this->app_id); | ||||||
|   if (ret) { |   if (ret) { | ||||||
| @@ -386,6 +388,15 @@ BLEDescriptor *BLECharacteristic::get_descriptor(uint16_t uuid) { | |||||||
|   return this->get_descriptor(espbt::ESPBTUUID::from_uint16(uuid)); |   return this->get_descriptor(espbt::ESPBTUUID::from_uint16(uuid)); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | void BLECharacteristic::write_value(uint8_t *new_val, int16_t new_val_size) { | ||||||
|  |   auto client = this->service->client; | ||||||
|  |   auto status = esp_ble_gattc_write_char(client->gattc_if, client->conn_id, this->handle, new_val_size, new_val, | ||||||
|  |                                          ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); | ||||||
|  |   if (status) { | ||||||
|  |     ESP_LOGW(TAG, "Error sending write value to BLE gattc server, status=%d", status); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
| }  // namespace ble_client | }  // namespace ble_client | ||||||
| }  // namespace esphome | }  // namespace esphome | ||||||
|  |  | ||||||
|   | |||||||
| @@ -59,7 +59,7 @@ class BLECharacteristic { | |||||||
|   void parse_descriptors(); |   void parse_descriptors(); | ||||||
|   BLEDescriptor *get_descriptor(espbt::ESPBTUUID uuid); |   BLEDescriptor *get_descriptor(espbt::ESPBTUUID uuid); | ||||||
|   BLEDescriptor *get_descriptor(uint16_t uuid); |   BLEDescriptor *get_descriptor(uint16_t uuid); | ||||||
|  |   void write_value(uint8_t *new_val, int16_t new_val_size); | ||||||
|   BLEService *service; |   BLEService *service; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| @@ -81,6 +81,7 @@ class BLEClient : public espbt::ESPBTClient, public Component { | |||||||
|   void setup() override; |   void setup() override; | ||||||
|   void dump_config() override; |   void dump_config() override; | ||||||
|   void loop() override; |   void loop() override; | ||||||
|  |   float get_setup_priority() const override; | ||||||
|  |  | ||||||
|   void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, |   void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, | ||||||
|                            esp_ble_gattc_cb_param_t *param) override; |                            esp_ble_gattc_cb_param_t *param) override; | ||||||
|   | |||||||
							
								
								
									
										67
									
								
								esphome/components/ble_client/output/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								esphome/components/ble_client/output/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,67 @@ | |||||||
|  | import esphome.codegen as cg | ||||||
|  | import esphome.config_validation as cv | ||||||
|  | from esphome.components import output, ble_client, esp32_ble_tracker | ||||||
|  | from esphome.const import CONF_ID, CONF_SERVICE_UUID | ||||||
|  | from .. import ble_client_ns | ||||||
|  |  | ||||||
|  |  | ||||||
|  | DEPENDENCIES = ["ble_client"] | ||||||
|  |  | ||||||
|  | CONF_CHARACTERISTIC_UUID = "characteristic_uuid" | ||||||
|  |  | ||||||
|  | BLEBinaryOutput = ble_client_ns.class_( | ||||||
|  |     "BLEBinaryOutput", output.BinaryOutput, ble_client.BLEClientNode, cg.Component | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | CONFIG_SCHEMA = cv.All( | ||||||
|  |     output.BINARY_OUTPUT_SCHEMA.extend( | ||||||
|  |         { | ||||||
|  |             cv.Required(CONF_ID): cv.declare_id(BLEBinaryOutput), | ||||||
|  |             cv.Required(CONF_SERVICE_UUID): esp32_ble_tracker.bt_uuid, | ||||||
|  |             cv.Required(CONF_CHARACTERISTIC_UUID): esp32_ble_tracker.bt_uuid, | ||||||
|  |         } | ||||||
|  |     ) | ||||||
|  |     .extend(cv.COMPONENT_SCHEMA) | ||||||
|  |     .extend(ble_client.BLE_CLIENT_SCHEMA) | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def to_code(config): | ||||||
|  |     var = cg.new_Pvariable(config[CONF_ID]) | ||||||
|  |     if len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid16_format): | ||||||
|  |         cg.add( | ||||||
|  |             var.set_service_uuid16(esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID])) | ||||||
|  |         ) | ||||||
|  |     elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid32_format): | ||||||
|  |         cg.add( | ||||||
|  |             var.set_service_uuid32(esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID])) | ||||||
|  |         ) | ||||||
|  |     elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid128_format): | ||||||
|  |         uuid128 = esp32_ble_tracker.as_reversed_hex_array(config[CONF_SERVICE_UUID]) | ||||||
|  |         cg.add(var.set_service_uuid128(uuid128)) | ||||||
|  |  | ||||||
|  |     if len(config[CONF_CHARACTERISTIC_UUID]) == len(esp32_ble_tracker.bt_uuid16_format): | ||||||
|  |         cg.add( | ||||||
|  |             var.set_char_uuid16( | ||||||
|  |                 esp32_ble_tracker.as_hex(config[CONF_CHARACTERISTIC_UUID]) | ||||||
|  |             ) | ||||||
|  |         ) | ||||||
|  |     elif len(config[CONF_CHARACTERISTIC_UUID]) == len( | ||||||
|  |         esp32_ble_tracker.bt_uuid32_format | ||||||
|  |     ): | ||||||
|  |         cg.add( | ||||||
|  |             var.set_char_uuid32( | ||||||
|  |                 esp32_ble_tracker.as_hex(config[CONF_CHARACTERISTIC_UUID]) | ||||||
|  |             ) | ||||||
|  |         ) | ||||||
|  |     elif len(config[CONF_CHARACTERISTIC_UUID]) == len( | ||||||
|  |         esp32_ble_tracker.bt_uuid128_format | ||||||
|  |     ): | ||||||
|  |         uuid128 = esp32_ble_tracker.as_reversed_hex_array( | ||||||
|  |             config[CONF_CHARACTERISTIC_UUID] | ||||||
|  |         ) | ||||||
|  |         cg.add(var.set_char_uuid128(uuid128)) | ||||||
|  |  | ||||||
|  |     yield output.register_output(var, config) | ||||||
|  |     yield ble_client.register_ble_node(var, config) | ||||||
|  |     yield cg.register_component(var, config) | ||||||
							
								
								
									
										71
									
								
								esphome/components/ble_client/output/ble_binary_output.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								esphome/components/ble_client/output/ble_binary_output.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,71 @@ | |||||||
|  | #include "ble_binary_output.h" | ||||||
|  | #include "esphome/core/log.h" | ||||||
|  | #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" | ||||||
|  |  | ||||||
|  | #ifdef USE_ESP32 | ||||||
|  | namespace esphome { | ||||||
|  | namespace ble_client { | ||||||
|  |  | ||||||
|  | static const char *const TAG = "ble_binary_output"; | ||||||
|  |  | ||||||
|  | void BLEBinaryOutput::dump_config() { | ||||||
|  |   ESP_LOGCONFIG(TAG, "BLE Binary Output:"); | ||||||
|  |   ESP_LOGCONFIG(TAG, "  MAC address        : %s", this->parent_->address_str().c_str()); | ||||||
|  |   ESP_LOGCONFIG(TAG, "  Service UUID       : %s", this->service_uuid_.to_string().c_str()); | ||||||
|  |   ESP_LOGCONFIG(TAG, "  Characteristic UUID: %s", this->char_uuid_.to_string().c_str()); | ||||||
|  |   LOG_BINARY_OUTPUT(this); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void BLEBinaryOutput::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, | ||||||
|  |                                           esp_ble_gattc_cb_param_t *param) { | ||||||
|  |   switch (event) { | ||||||
|  |     case ESP_GATTC_OPEN_EVT: | ||||||
|  |       this->client_state_ = espbt::ClientState::ESTABLISHED; | ||||||
|  |       ESP_LOGW(TAG, "[%s] Connected successfully!", this->char_uuid_.to_string().c_str()); | ||||||
|  |       break; | ||||||
|  |     case ESP_GATTC_DISCONNECT_EVT: | ||||||
|  |       ESP_LOGW(TAG, "[%s] Disconnected", this->char_uuid_.to_string().c_str()); | ||||||
|  |       this->client_state_ = espbt::ClientState::IDLE; | ||||||
|  |       break; | ||||||
|  |     case ESP_GATTC_WRITE_CHAR_EVT: { | ||||||
|  |       if (param->write.status == 0) { | ||||||
|  |         break; | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       auto chr = this->parent()->get_characteristic(this->service_uuid_, this->char_uuid_); | ||||||
|  |       if (chr == nullptr) { | ||||||
|  |         ESP_LOGW(TAG, "[%s] Characteristic not found.", this->char_uuid_.to_string().c_str()); | ||||||
|  |         break; | ||||||
|  |       } | ||||||
|  |       if (param->write.handle == chr->handle) { | ||||||
|  |         ESP_LOGW(TAG, "[%s] Write error, status=%d", this->char_uuid_.to_string().c_str(), param->write.status); | ||||||
|  |       } | ||||||
|  |       break; | ||||||
|  |     } | ||||||
|  |     default: | ||||||
|  |       break; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void BLEBinaryOutput::write_state(bool state) { | ||||||
|  |   if (this->client_state_ != espbt::ClientState::ESTABLISHED) { | ||||||
|  |     ESP_LOGW(TAG, "[%s] Not connected to BLE client.  State update can not be written.", | ||||||
|  |              this->char_uuid_.to_string().c_str()); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   auto chr = this->parent()->get_characteristic(this->service_uuid_, this->char_uuid_); | ||||||
|  |   if (chr == nullptr) { | ||||||
|  |     ESP_LOGW(TAG, "[%s] Characteristic not found.  State update can not be written.", | ||||||
|  |              this->char_uuid_.to_string().c_str()); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   uint8_t state_as_uint = (uint8_t) state; | ||||||
|  |   ESP_LOGV(TAG, "[%s] Write State: %d", this->char_uuid_.to_string().c_str(), state_as_uint); | ||||||
|  |   chr->write_value(&state_as_uint, sizeof(state_as_uint)); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | }  // namespace ble_client | ||||||
|  | }  // namespace esphome | ||||||
|  | #endif | ||||||
							
								
								
									
										39
									
								
								esphome/components/ble_client/output/ble_binary_output.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								esphome/components/ble_client/output/ble_binary_output.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,39 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include "esphome/core/component.h" | ||||||
|  | #include "esphome/components/ble_client/ble_client.h" | ||||||
|  | #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" | ||||||
|  | #include "esphome/components/output/binary_output.h" | ||||||
|  |  | ||||||
|  | #ifdef USE_ESP32 | ||||||
|  | #include <esp_gattc_api.h> | ||||||
|  | namespace esphome { | ||||||
|  | namespace ble_client { | ||||||
|  |  | ||||||
|  | namespace espbt = esphome::esp32_ble_tracker; | ||||||
|  |  | ||||||
|  | class BLEBinaryOutput : public output::BinaryOutput, public BLEClientNode, public Component { | ||||||
|  |  public: | ||||||
|  |   void dump_config() override; | ||||||
|  |   void loop() override {} | ||||||
|  |   float get_setup_priority() const override { return setup_priority::DATA; } | ||||||
|  |   void set_service_uuid16(uint16_t uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_uint16(uuid); } | ||||||
|  |   void set_service_uuid32(uint32_t uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_uint32(uuid); } | ||||||
|  |   void set_service_uuid128(uint8_t *uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_raw(uuid); } | ||||||
|  |   void set_char_uuid16(uint16_t uuid) { this->char_uuid_ = espbt::ESPBTUUID::from_uint16(uuid); } | ||||||
|  |   void set_char_uuid32(uint32_t uuid) { this->char_uuid_ = espbt::ESPBTUUID::from_uint32(uuid); } | ||||||
|  |   void set_char_uuid128(uint8_t *uuid) { this->char_uuid_ = espbt::ESPBTUUID::from_raw(uuid); } | ||||||
|  |   void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, | ||||||
|  |                            esp_ble_gattc_cb_param_t *param) override; | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   void write_state(bool state) override; | ||||||
|  |   espbt::ESPBTUUID service_uuid_; | ||||||
|  |   espbt::ESPBTUUID char_uuid_; | ||||||
|  |   espbt::ClientState client_state_; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | }  // namespace ble_client | ||||||
|  | }  // namespace esphome | ||||||
|  |  | ||||||
|  | #endif | ||||||
| @@ -67,7 +67,7 @@ async def to_code(config): | |||||||
|             var.set_service_uuid32(esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID])) |             var.set_service_uuid32(esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID])) | ||||||
|         ) |         ) | ||||||
|     elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid128_format): |     elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid128_format): | ||||||
|         uuid128 = esp32_ble_tracker.as_hex_array(config[CONF_SERVICE_UUID]) |         uuid128 = esp32_ble_tracker.as_reversed_hex_array(config[CONF_SERVICE_UUID]) | ||||||
|         cg.add(var.set_service_uuid128(uuid128)) |         cg.add(var.set_service_uuid128(uuid128)) | ||||||
|  |  | ||||||
|     if len(config[CONF_CHARACTERISTIC_UUID]) == len(esp32_ble_tracker.bt_uuid16_format): |     if len(config[CONF_CHARACTERISTIC_UUID]) == len(esp32_ble_tracker.bt_uuid16_format): | ||||||
| @@ -87,7 +87,9 @@ async def to_code(config): | |||||||
|     elif len(config[CONF_CHARACTERISTIC_UUID]) == len( |     elif len(config[CONF_CHARACTERISTIC_UUID]) == len( | ||||||
|         esp32_ble_tracker.bt_uuid128_format |         esp32_ble_tracker.bt_uuid128_format | ||||||
|     ): |     ): | ||||||
|         uuid128 = esp32_ble_tracker.as_hex_array(config[CONF_CHARACTERISTIC_UUID]) |         uuid128 = esp32_ble_tracker.as_reversed_hex_array( | ||||||
|  |             config[CONF_CHARACTERISTIC_UUID] | ||||||
|  |         ) | ||||||
|         cg.add(var.set_char_uuid128(uuid128)) |         cg.add(var.set_char_uuid128(uuid128)) | ||||||
|  |  | ||||||
|     if CONF_DESCRIPTOR_UUID in config: |     if CONF_DESCRIPTOR_UUID in config: | ||||||
| @@ -108,7 +110,9 @@ async def to_code(config): | |||||||
|         elif len(config[CONF_DESCRIPTOR_UUID]) == len( |         elif len(config[CONF_DESCRIPTOR_UUID]) == len( | ||||||
|             esp32_ble_tracker.bt_uuid128_format |             esp32_ble_tracker.bt_uuid128_format | ||||||
|         ): |         ): | ||||||
|             uuid128 = esp32_ble_tracker.as_hex_array(config[CONF_DESCRIPTOR_UUID]) |             uuid128 = esp32_ble_tracker.as_reversed_hex_array( | ||||||
|  |                 config[CONF_DESCRIPTOR_UUID] | ||||||
|  |             ) | ||||||
|             cg.add(var.set_descr_uuid128(uuid128)) |             cg.add(var.set_descr_uuid128(uuid128)) | ||||||
|  |  | ||||||
|     if CONF_LAMBDA in config: |     if CONF_LAMBDA in config: | ||||||
|   | |||||||
| @@ -33,6 +33,7 @@ static const uint8_t BME280_REGISTER_CONTROLHUMID = 0xF2; | |||||||
| static const uint8_t BME280_REGISTER_STATUS = 0xF3; | static const uint8_t BME280_REGISTER_STATUS = 0xF3; | ||||||
| static const uint8_t BME280_REGISTER_CONTROL = 0xF4; | static const uint8_t BME280_REGISTER_CONTROL = 0xF4; | ||||||
| static const uint8_t BME280_REGISTER_CONFIG = 0xF5; | static const uint8_t BME280_REGISTER_CONFIG = 0xF5; | ||||||
|  | static const uint8_t BME280_REGISTER_MEASUREMENTS = 0xF7; | ||||||
| static const uint8_t BME280_REGISTER_PRESSUREDATA = 0xF7; | static const uint8_t BME280_REGISTER_PRESSUREDATA = 0xF7; | ||||||
| static const uint8_t BME280_REGISTER_TEMPDATA = 0xFA; | static const uint8_t BME280_REGISTER_TEMPDATA = 0xFA; | ||||||
| static const uint8_t BME280_REGISTER_HUMIDDATA = 0xFD; | static const uint8_t BME280_REGISTER_HUMIDDATA = 0xFD; | ||||||
| @@ -178,23 +179,29 @@ void BME280Component::update() { | |||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   float meas_time = 1.5; |   float meas_time = 1.5f; | ||||||
|   meas_time += 2.3f * oversampling_to_time(this->temperature_oversampling_); |   meas_time += 2.3f * oversampling_to_time(this->temperature_oversampling_); | ||||||
|   meas_time += 2.3f * oversampling_to_time(this->pressure_oversampling_) + 0.575f; |   meas_time += 2.3f * oversampling_to_time(this->pressure_oversampling_) + 0.575f; | ||||||
|   meas_time += 2.3f * oversampling_to_time(this->humidity_oversampling_) + 0.575f; |   meas_time += 2.3f * oversampling_to_time(this->humidity_oversampling_) + 0.575f; | ||||||
|  |  | ||||||
|   this->set_timeout("data", uint32_t(ceilf(meas_time)), [this]() { |   this->set_timeout("data", uint32_t(ceilf(meas_time)), [this]() { | ||||||
|  |     uint8_t data[8]; | ||||||
|  |     if (!this->read_bytes(BME280_REGISTER_MEASUREMENTS, data, 8)) { | ||||||
|  |       ESP_LOGW(TAG, "Error reading registers."); | ||||||
|  |       this->status_set_warning(); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|     int32_t t_fine = 0; |     int32_t t_fine = 0; | ||||||
|     float temperature = this->read_temperature_(&t_fine); |     float temperature = this->read_temperature_(data, &t_fine); | ||||||
|     if (std::isnan(temperature)) { |     if (std::isnan(temperature)) { | ||||||
|       ESP_LOGW(TAG, "Invalid temperature, cannot read pressure & humidity values."); |       ESP_LOGW(TAG, "Invalid temperature, cannot read pressure & humidity values."); | ||||||
|       this->status_set_warning(); |       this->status_set_warning(); | ||||||
|       return; |       return; | ||||||
|     } |     } | ||||||
|     float pressure = this->read_pressure_(t_fine); |     float pressure = this->read_pressure_(data, t_fine); | ||||||
|     float humidity = this->read_humidity_(t_fine); |     float humidity = this->read_humidity_(data, t_fine); | ||||||
|  |  | ||||||
|     ESP_LOGD(TAG, "Got temperature=%.1f°C pressure=%.1fhPa humidity=%.1f%%", temperature, pressure, humidity); |     ESP_LOGV(TAG, "Got temperature=%.1f°C pressure=%.1fhPa humidity=%.1f%%", temperature, pressure, humidity); | ||||||
|     if (this->temperature_sensor_ != nullptr) |     if (this->temperature_sensor_ != nullptr) | ||||||
|       this->temperature_sensor_->publish_state(temperature); |       this->temperature_sensor_->publish_state(temperature); | ||||||
|     if (this->pressure_sensor_ != nullptr) |     if (this->pressure_sensor_ != nullptr) | ||||||
| @@ -204,11 +211,8 @@ void BME280Component::update() { | |||||||
|     this->status_clear_warning(); |     this->status_clear_warning(); | ||||||
|   }); |   }); | ||||||
| } | } | ||||||
| float BME280Component::read_temperature_(int32_t *t_fine) { | float BME280Component::read_temperature_(const uint8_t *data, int32_t *t_fine) { | ||||||
|   uint8_t data[3]; |   int32_t adc = ((data[3] & 0xFF) << 16) | ((data[4] & 0xFF) << 8) | (data[5] & 0xFF); | ||||||
|   if (!this->read_bytes(BME280_REGISTER_TEMPDATA, data, 3)) |  | ||||||
|     return NAN; |  | ||||||
|   int32_t adc = ((data[0] & 0xFF) << 16) | ((data[1] & 0xFF) << 8) | (data[2] & 0xFF); |  | ||||||
|   adc >>= 4; |   adc >>= 4; | ||||||
|   if (adc == 0x80000) |   if (adc == 0x80000) | ||||||
|     // temperature was disabled |     // temperature was disabled | ||||||
| @@ -226,10 +230,7 @@ float BME280Component::read_temperature_(int32_t *t_fine) { | |||||||
|   return temperature / 100.0f; |   return temperature / 100.0f; | ||||||
| } | } | ||||||
|  |  | ||||||
| float BME280Component::read_pressure_(int32_t t_fine) { | float BME280Component::read_pressure_(const uint8_t *data, int32_t t_fine) { | ||||||
|   uint8_t data[3]; |  | ||||||
|   if (!this->read_bytes(BME280_REGISTER_PRESSUREDATA, data, 3)) |  | ||||||
|     return NAN; |  | ||||||
|   int32_t adc = ((data[0] & 0xFF) << 16) | ((data[1] & 0xFF) << 8) | (data[2] & 0xFF); |   int32_t adc = ((data[0] & 0xFF) << 16) | ((data[1] & 0xFF) << 8) | (data[2] & 0xFF); | ||||||
|   adc >>= 4; |   adc >>= 4; | ||||||
|   if (adc == 0x80000) |   if (adc == 0x80000) | ||||||
| @@ -265,9 +266,9 @@ float BME280Component::read_pressure_(int32_t t_fine) { | |||||||
|   return (p / 256.0f) / 100.0f; |   return (p / 256.0f) / 100.0f; | ||||||
| } | } | ||||||
|  |  | ||||||
| float BME280Component::read_humidity_(int32_t t_fine) { | float BME280Component::read_humidity_(const uint8_t *data, int32_t t_fine) { | ||||||
|   uint16_t raw_adc; |   uint16_t raw_adc = ((data[6] & 0xFF) << 8) | (data[7] & 0xFF); | ||||||
|   if (!this->read_byte_16(BME280_REGISTER_HUMIDDATA, &raw_adc) || raw_adc == 0x8000) |   if (raw_adc == 0x8000) | ||||||
|     return NAN; |     return NAN; | ||||||
|  |  | ||||||
|   int32_t adc = raw_adc; |   int32_t adc = raw_adc; | ||||||
|   | |||||||
| @@ -82,11 +82,11 @@ class BME280Component : public PollingComponent, public i2c::I2CDevice { | |||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   /// Read the temperature value and store the calculated ambient temperature in t_fine. |   /// Read the temperature value and store the calculated ambient temperature in t_fine. | ||||||
|   float read_temperature_(int32_t *t_fine); |   float read_temperature_(const uint8_t *data, int32_t *t_fine); | ||||||
|   /// Read the pressure value in hPa using the provided t_fine value. |   /// Read the pressure value in hPa using the provided t_fine value. | ||||||
|   float read_pressure_(int32_t t_fine); |   float read_pressure_(const uint8_t *data, int32_t t_fine); | ||||||
|   /// Read the humidity value in % using the provided t_fine value. |   /// Read the humidity value in % using the provided t_fine value. | ||||||
|   float read_humidity_(int32_t t_fine); |   float read_humidity_(const uint8_t *data, int32_t t_fine); | ||||||
|   uint8_t read_u8_(uint8_t a_register); |   uint8_t read_u8_(uint8_t a_register); | ||||||
|   uint16_t read_u16_le_(uint8_t a_register); |   uint16_t read_u16_le_(uint8_t a_register); | ||||||
|   int16_t read_s16_le_(uint8_t a_register); |   int16_t read_s16_le_(uint8_t a_register); | ||||||
|   | |||||||
| @@ -44,7 +44,8 @@ CONFIG_SCHEMA = cv.Schema( | |||||||
|         cv.Optional( |         cv.Optional( | ||||||
|             CONF_STATE_SAVE_INTERVAL, default="6hours" |             CONF_STATE_SAVE_INTERVAL, default="6hours" | ||||||
|         ): cv.positive_time_period_minutes, |         ): cv.positive_time_period_minutes, | ||||||
|     } |     }, | ||||||
|  |     cv.only_with_arduino, | ||||||
| ).extend(i2c.i2c_device_schema(0x76)) | ).extend(i2c.i2c_device_schema(0x76)) | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -60,5 +61,8 @@ async def to_code(config): | |||||||
|         var.set_state_save_interval(config[CONF_STATE_SAVE_INTERVAL].total_milliseconds) |         var.set_state_save_interval(config[CONF_STATE_SAVE_INTERVAL].total_milliseconds) | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
|  |     # Although this component does not use SPI, the BSEC library requires the SPI library | ||||||
|  |     cg.add_library("SPI", None) | ||||||
|  |  | ||||||
|     cg.add_define("USE_BSEC") |     cg.add_define("USE_BSEC") | ||||||
|     cg.add_library("BSEC Software Library", "1.6.1480") |     cg.add_library("boschsensortec/BSEC Software Library", "1.6.1480") | ||||||
|   | |||||||
							
								
								
									
										0
									
								
								esphome/components/bmp3xx/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								esphome/components/bmp3xx/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										388
									
								
								esphome/components/bmp3xx/bmp3xx.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										388
									
								
								esphome/components/bmp3xx/bmp3xx.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,388 @@ | |||||||
|  | /* | ||||||
|  |   based on BMP388_DEV by Martin Lindupp | ||||||
|  |   under MIT License (MIT) | ||||||
|  |   Copyright (C) Martin Lindupp 2020 | ||||||
|  |   http://github.com/MartinL1/BMP388_DEV | ||||||
|  | */ | ||||||
|  |  | ||||||
|  | #include "bmp3xx.h" | ||||||
|  | #include "esphome/core/log.h" | ||||||
|  | #include "esphome/core/hal.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace bmp3xx { | ||||||
|  |  | ||||||
|  | static const char *const TAG = "bmp3xx.sensor"; | ||||||
|  |  | ||||||
|  | static const LogString *chip_type_to_str(uint8_t chip_type) { | ||||||
|  |   switch (chip_type) { | ||||||
|  |     case BMP388_ID: | ||||||
|  |       return LOG_STR("BMP 388"); | ||||||
|  |     case BMP390_ID: | ||||||
|  |       return LOG_STR("BMP 390"); | ||||||
|  |     default: | ||||||
|  |       return LOG_STR("Unknown Chip Type"); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | static const LogString *oversampling_to_str(Oversampling oversampling) { | ||||||
|  |   switch (oversampling) { | ||||||
|  |     case Oversampling::OVERSAMPLING_NONE: | ||||||
|  |       return LOG_STR("None"); | ||||||
|  |     case Oversampling::OVERSAMPLING_X2: | ||||||
|  |       return LOG_STR("2x"); | ||||||
|  |     case Oversampling::OVERSAMPLING_X4: | ||||||
|  |       return LOG_STR("4x"); | ||||||
|  |     case Oversampling::OVERSAMPLING_X8: | ||||||
|  |       return LOG_STR("8x"); | ||||||
|  |     case Oversampling::OVERSAMPLING_X16: | ||||||
|  |       return LOG_STR("16x"); | ||||||
|  |     case Oversampling::OVERSAMPLING_X32: | ||||||
|  |       return LOG_STR("32x"); | ||||||
|  |     default: | ||||||
|  |       return LOG_STR(""); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | static const LogString *iir_filter_to_str(IIRFilter filter) { | ||||||
|  |   switch (filter) { | ||||||
|  |     case IIRFilter::IIR_FILTER_OFF: | ||||||
|  |       return LOG_STR("OFF"); | ||||||
|  |     case IIRFilter::IIR_FILTER_2: | ||||||
|  |       return LOG_STR("2x"); | ||||||
|  |     case IIRFilter::IIR_FILTER_4: | ||||||
|  |       return LOG_STR("4x"); | ||||||
|  |     case IIRFilter::IIR_FILTER_8: | ||||||
|  |       return LOG_STR("8x"); | ||||||
|  |     case IIRFilter::IIR_FILTER_16: | ||||||
|  |       return LOG_STR("16x"); | ||||||
|  |     case IIRFilter::IIR_FILTER_32: | ||||||
|  |       return LOG_STR("32x"); | ||||||
|  |     case IIRFilter::IIR_FILTER_64: | ||||||
|  |       return LOG_STR("64x"); | ||||||
|  |     case IIRFilter::IIR_FILTER_128: | ||||||
|  |       return LOG_STR("128x"); | ||||||
|  |     default: | ||||||
|  |       return LOG_STR(""); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void BMP3XXComponent::setup() { | ||||||
|  |   this->error_code_ = NONE; | ||||||
|  |   ESP_LOGCONFIG(TAG, "Setting up BMP3XX..."); | ||||||
|  |   // Call the Device base class "initialise" function | ||||||
|  |   if (!reset()) { | ||||||
|  |     ESP_LOGE(TAG, "Failed to reset BMP3XX..."); | ||||||
|  |     this->error_code_ = ERROR_SENSOR_RESET; | ||||||
|  |     this->mark_failed(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (!read_byte(BMP388_CHIP_ID, &this->chip_id_.reg)) { | ||||||
|  |     ESP_LOGE(TAG, "Can't read chip id"); | ||||||
|  |     this->error_code_ = ERROR_COMMUNICATION_FAILED; | ||||||
|  |     this->mark_failed(); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   ESP_LOGCONFIG(TAG, "Chip %s Id 0x%X", LOG_STR_ARG(chip_type_to_str(this->chip_id_.reg)), this->chip_id_.reg); | ||||||
|  |  | ||||||
|  |   if (chip_id_.reg != BMP388_ID && chip_id_.reg != BMP390_ID) { | ||||||
|  |     ESP_LOGE(TAG, "Unknown chip id - is this really a BMP388 or BMP390?"); | ||||||
|  |     this->error_code_ = ERROR_WRONG_CHIP_ID; | ||||||
|  |     this->mark_failed(); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   // set sensor in sleep mode | ||||||
|  |   stop_conversion(); | ||||||
|  |   // Read the calibration parameters into the params structure | ||||||
|  |   if (!read_bytes(BMP388_TRIM_PARAMS, (uint8_t *) &compensation_params_, sizeof(compensation_params_))) { | ||||||
|  |     ESP_LOGE(TAG, "Can't read calibration data"); | ||||||
|  |     this->error_code_ = ERROR_COMMUNICATION_FAILED; | ||||||
|  |     this->mark_failed(); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   compensation_float_params_.param_T1 = | ||||||
|  |       (float) compensation_params_.param_T1 / powf(2.0f, -8.0f);  // Calculate the floating point trim parameters | ||||||
|  |   compensation_float_params_.param_T2 = (float) compensation_params_.param_T2 / powf(2.0f, 30.0f); | ||||||
|  |   compensation_float_params_.param_T3 = (float) compensation_params_.param_T3 / powf(2.0f, 48.0f); | ||||||
|  |   compensation_float_params_.param_P1 = ((float) compensation_params_.param_P1 - powf(2.0f, 14.0f)) / powf(2.0f, 20.0f); | ||||||
|  |   compensation_float_params_.param_P2 = ((float) compensation_params_.param_P2 - powf(2.0f, 14.0f)) / powf(2.0f, 29.0f); | ||||||
|  |   compensation_float_params_.param_P3 = (float) compensation_params_.param_P3 / powf(2.0f, 32.0f); | ||||||
|  |   compensation_float_params_.param_P4 = (float) compensation_params_.param_P4 / powf(2.0f, 37.0f); | ||||||
|  |   compensation_float_params_.param_P5 = (float) compensation_params_.param_P5 / powf(2.0f, -3.0f); | ||||||
|  |   compensation_float_params_.param_P6 = (float) compensation_params_.param_P6 / powf(2.0f, 6.0f); | ||||||
|  |   compensation_float_params_.param_P7 = (float) compensation_params_.param_P7 / powf(2.0f, 8.0f); | ||||||
|  |   compensation_float_params_.param_P8 = (float) compensation_params_.param_P8 / powf(2.0f, 15.0f); | ||||||
|  |   compensation_float_params_.param_P9 = (float) compensation_params_.param_P9 / powf(2.0f, 48.0f); | ||||||
|  |   compensation_float_params_.param_P10 = (float) compensation_params_.param_P10 / powf(2.0f, 48.0f); | ||||||
|  |   compensation_float_params_.param_P11 = (float) compensation_params_.param_P11 / powf(2.0f, 65.0f); | ||||||
|  |  | ||||||
|  |   // Initialise the BMP388 IIR filter register | ||||||
|  |   if (!set_iir_filter(this->iir_filter_)) { | ||||||
|  |     ESP_LOGE(TAG, "Failed to set IIR filter"); | ||||||
|  |     this->error_code_ = ERROR_COMMUNICATION_FAILED; | ||||||
|  |     this->mark_failed(); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // Set power control registers | ||||||
|  |   pwr_ctrl_.bit.press_en = 1; | ||||||
|  |   pwr_ctrl_.bit.temp_en = 1; | ||||||
|  |   // Disable pressure if no sensor defined | ||||||
|  |   // keep temperature enabled since it's needed for compensation | ||||||
|  |   if (this->pressure_sensor_ == nullptr) { | ||||||
|  |     pwr_ctrl_.bit.press_en = 0; | ||||||
|  |     this->pressure_oversampling_ = OVERSAMPLING_NONE; | ||||||
|  |   } | ||||||
|  |   // just disable oeversampling for temp if not used | ||||||
|  |   if (this->temperature_sensor_ == nullptr) { | ||||||
|  |     this->temperature_oversampling_ = OVERSAMPLING_NONE; | ||||||
|  |   } | ||||||
|  |   // Initialise the BMP388 oversampling register | ||||||
|  |   if (!set_oversampling_register(this->pressure_oversampling_, this->temperature_oversampling_)) { | ||||||
|  |     ESP_LOGE(TAG, "Failed to set oversampling register"); | ||||||
|  |     this->error_code_ = ERROR_COMMUNICATION_FAILED; | ||||||
|  |     this->mark_failed(); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void BMP3XXComponent::dump_config() { | ||||||
|  |   ESP_LOGCONFIG(TAG, "BMP3XX:"); | ||||||
|  |   ESP_LOGCONFIG(TAG, "  Type: %s (0x%X)", LOG_STR_ARG(chip_type_to_str(this->chip_id_.reg)), this->chip_id_.reg); | ||||||
|  |   LOG_I2C_DEVICE(this); | ||||||
|  |   switch (this->error_code_) { | ||||||
|  |     case NONE: | ||||||
|  |       break; | ||||||
|  |     case ERROR_COMMUNICATION_FAILED: | ||||||
|  |       ESP_LOGE(TAG, "Communication with BMP3XX failed!"); | ||||||
|  |       break; | ||||||
|  |     case ERROR_WRONG_CHIP_ID: | ||||||
|  |       ESP_LOGE( | ||||||
|  |           TAG, | ||||||
|  |           "BMP3XX has wrong chip ID (reported id: 0x%X) - please check if you are really using a BMP 388 or BMP 390", | ||||||
|  |           this->chip_id_.reg); | ||||||
|  |       break; | ||||||
|  |     case ERROR_SENSOR_RESET: | ||||||
|  |       ESP_LOGE(TAG, "BMP3XX failed to reset"); | ||||||
|  |       break; | ||||||
|  |     default: | ||||||
|  |       ESP_LOGE(TAG, "BMP3XX error code %d", (int) this->error_code_); | ||||||
|  |       break; | ||||||
|  |   } | ||||||
|  |   ESP_LOGCONFIG(TAG, "  IIR Filter: %s", LOG_STR_ARG(iir_filter_to_str(this->iir_filter_))); | ||||||
|  |   LOG_UPDATE_INTERVAL(this); | ||||||
|  |   if (this->temperature_sensor_) { | ||||||
|  |     LOG_SENSOR("  ", "Temperature", this->temperature_sensor_); | ||||||
|  |     ESP_LOGCONFIG(TAG, "    Oversampling: %s", LOG_STR_ARG(oversampling_to_str(this->temperature_oversampling_))); | ||||||
|  |   } | ||||||
|  |   if (this->pressure_sensor_) { | ||||||
|  |     LOG_SENSOR("  ", "Pressure", this->pressure_sensor_); | ||||||
|  |     ESP_LOGCONFIG(TAG, "    Oversampling: %s", LOG_STR_ARG(oversampling_to_str(this->pressure_oversampling_))); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | float BMP3XXComponent::get_setup_priority() const { return setup_priority::DATA; } | ||||||
|  |  | ||||||
|  | inline uint8_t oversampling_to_time(Oversampling over_sampling) { return (1 << uint8_t(over_sampling)); } | ||||||
|  |  | ||||||
|  | void BMP3XXComponent::update() { | ||||||
|  |   // Enable sensor | ||||||
|  |   ESP_LOGV(TAG, "Sending conversion request..."); | ||||||
|  |   float meas_time = 1.0f; | ||||||
|  |   // Ref: https://www.bosch-sensortec.com/media/boschsensortec/downloads/datasheets/bst-bmp390-ds002.pdf 3.9.2 | ||||||
|  |   meas_time += 2.02f * oversampling_to_time(this->temperature_oversampling_) + 0.163f; | ||||||
|  |   meas_time += 2.02f * oversampling_to_time(this->pressure_oversampling_) + 0.392f; | ||||||
|  |   meas_time += 0.234f; | ||||||
|  |   if (!set_mode(FORCED_MODE)) { | ||||||
|  |     ESP_LOGE(TAG, "Failed start forced mode"); | ||||||
|  |     this->mark_failed(); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   ESP_LOGVV(TAG, "measurement time %d", uint32_t(ceilf(meas_time))); | ||||||
|  |   this->set_timeout("data", uint32_t(ceilf(meas_time)), [this]() { | ||||||
|  |     float temperature = 0.0f; | ||||||
|  |     float pressure = 0.0f; | ||||||
|  |     if (this->pressure_sensor_ != nullptr) { | ||||||
|  |       if (!get_measurements(temperature, pressure)) { | ||||||
|  |         ESP_LOGW(TAG, "Failed to read pressure and temperature - skipping update"); | ||||||
|  |         this->status_set_warning(); | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  |       ESP_LOGD(TAG, "Got temperature=%.1f°C pressure=%.1fhPa", temperature, pressure); | ||||||
|  |     } else { | ||||||
|  |       if (!get_temperature(temperature)) { | ||||||
|  |         ESP_LOGW(TAG, "Failed to read temperature - skipping update"); | ||||||
|  |         this->status_set_warning(); | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  |       ESP_LOGD(TAG, "Got temperature=%.1f°C", temperature); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if (this->temperature_sensor_ != nullptr) | ||||||
|  |       this->temperature_sensor_->publish_state(temperature); | ||||||
|  |     if (this->pressure_sensor_ != nullptr) | ||||||
|  |       this->pressure_sensor_->publish_state(pressure); | ||||||
|  |     this->status_clear_warning(); | ||||||
|  |     set_mode(SLEEP_MODE); | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Reset the BMP3XX | ||||||
|  | uint8_t BMP3XXComponent::reset() { | ||||||
|  |   write_byte(BMP388_CMD, RESET_CODE);  // Write the reset code to the command register | ||||||
|  |   // Wait for 10ms | ||||||
|  |   delay(10); | ||||||
|  |   this->read_byte(BMP388_EVENT, &event_.reg);  // Read the BMP388's event register | ||||||
|  |   return event_.bit.por_detected;              // Return if device reset is complete | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Start a one shot measurement in FORCED_MODE | ||||||
|  | bool BMP3XXComponent::start_forced_conversion() { | ||||||
|  |   // Only set FORCED_MODE if we're already in SLEEP_MODE | ||||||
|  |   if (pwr_ctrl_.bit.mode == SLEEP_MODE) { | ||||||
|  |     return set_mode(FORCED_MODE); | ||||||
|  |   } | ||||||
|  |   return true; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Stop the conversion and return to SLEEP_MODE | ||||||
|  | bool BMP3XXComponent::stop_conversion() { return set_mode(SLEEP_MODE); } | ||||||
|  |  | ||||||
|  | // Set the pressure oversampling rate | ||||||
|  | bool BMP3XXComponent::set_pressure_oversampling(Oversampling oversampling) { | ||||||
|  |   osr_.bit.osr_p = oversampling; | ||||||
|  |   return this->write_byte(BMP388_OSR, osr_.reg); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Set the temperature oversampling rate | ||||||
|  | bool BMP3XXComponent::set_temperature_oversampling(Oversampling oversampling) { | ||||||
|  |   osr_.bit.osr_t = oversampling; | ||||||
|  |   return this->write_byte(BMP388_OSR, osr_.reg); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Set the IIR filter setting | ||||||
|  | bool BMP3XXComponent::set_iir_filter(IIRFilter iir_filter) { | ||||||
|  |   config_.bit.iir_filter = iir_filter; | ||||||
|  |   return this->write_byte(BMP388_CONFIG, config_.reg); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Get temperature | ||||||
|  | bool BMP3XXComponent::get_temperature(float &temperature) { | ||||||
|  |   // Check if a measurement is ready | ||||||
|  |   if (!data_ready()) { | ||||||
|  |     return false; | ||||||
|  |   } | ||||||
|  |   uint8_t data[3]; | ||||||
|  |   // Read the temperature | ||||||
|  |   if (!this->read_bytes(BMP388_DATA_3, &data[0], 3)) { | ||||||
|  |     ESP_LOGE(TAG, "Failed to read temperature"); | ||||||
|  |     return false; | ||||||
|  |   } | ||||||
|  |   // Copy the temperature data into the adc variables | ||||||
|  |   int32_t adc_temp = (int32_t) data[2] << 16 | (int32_t) data[1] << 8 | (int32_t) data[0]; | ||||||
|  |   // Temperature compensation (function from BMP388 datasheet) | ||||||
|  |   temperature = bmp388_compensate_temperature_((float) adc_temp); | ||||||
|  |   return true; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Get the pressure | ||||||
|  | bool BMP3XXComponent::get_pressure(float &pressure) { | ||||||
|  |   float temperature; | ||||||
|  |   return get_measurements(temperature, pressure); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Get temperature and pressure | ||||||
|  | bool BMP3XXComponent::get_measurements(float &temperature, float &pressure) { | ||||||
|  |   // Check if a measurement is ready | ||||||
|  |   if (!data_ready()) { | ||||||
|  |     ESP_LOGD(TAG, "BMP3XX Get measurement - data not ready skipping update"); | ||||||
|  |     return false; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   uint8_t data[6]; | ||||||
|  |   // Read the temperature and pressure data | ||||||
|  |   if (!this->read_bytes(BMP388_DATA_0, &data[0], 6)) { | ||||||
|  |     ESP_LOGE(TAG, "Failed to read measurements"); | ||||||
|  |     return false; | ||||||
|  |   } | ||||||
|  |   // Copy the temperature and pressure data into the adc variables | ||||||
|  |   int32_t adc_pres = (int32_t) data[2] << 16 | (int32_t) data[1] << 8 | (int32_t) data[0]; | ||||||
|  |   int32_t adc_temp = (int32_t) data[5] << 16 | (int32_t) data[4] << 8 | (int32_t) data[3]; | ||||||
|  |  | ||||||
|  |   // Temperature compensation (function from BMP388 datasheet) | ||||||
|  |   temperature = bmp388_compensate_temperature_((float) adc_temp); | ||||||
|  |   // Pressure compensation (function from BMP388 datasheet) | ||||||
|  |   pressure = bmp388_compensate_pressure_((float) adc_pres, temperature); | ||||||
|  |   // Calculate the pressure in millibar/hPa | ||||||
|  |   pressure /= 100.0f; | ||||||
|  |   return true; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Set the BMP388's mode in the power control register | ||||||
|  | bool BMP3XXComponent::set_mode(OperationMode mode) { | ||||||
|  |   pwr_ctrl_.bit.mode = mode; | ||||||
|  |   return this->write_byte(BMP388_PWR_CTRL, pwr_ctrl_.reg); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Set the BMP388 oversampling register | ||||||
|  | bool BMP3XXComponent::set_oversampling_register(Oversampling pressure_oversampling, | ||||||
|  |                                                 Oversampling temperature_oversampling) { | ||||||
|  |   osr_.reg = temperature_oversampling << 3 | pressure_oversampling; | ||||||
|  |   return this->write_byte(BMP388_OSR, osr_.reg); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Check if measurement data is ready | ||||||
|  | bool BMP3XXComponent::data_ready() { | ||||||
|  |   // If we're in SLEEP_MODE return immediately | ||||||
|  |   if (pwr_ctrl_.bit.mode == SLEEP_MODE) { | ||||||
|  |     ESP_LOGD(TAG, "Not ready - sensor is in sleep mode"); | ||||||
|  |     return false; | ||||||
|  |   } | ||||||
|  |   // Read the interrupt status register | ||||||
|  |   uint8_t status; | ||||||
|  |   if (!this->read_byte(BMP388_INT_STATUS, &status)) { | ||||||
|  |     ESP_LOGE(TAG, "Failed to read status register"); | ||||||
|  |     return false; | ||||||
|  |   } | ||||||
|  |   int_status_.reg = status; | ||||||
|  |   ESP_LOGVV(TAG, "data ready status %d", status); | ||||||
|  |   // If we're in FORCED_MODE switch back to SLEEP_MODE | ||||||
|  |   if (int_status_.bit.drdy) { | ||||||
|  |     if (pwr_ctrl_.bit.mode == FORCED_MODE) { | ||||||
|  |       pwr_ctrl_.bit.mode = SLEEP_MODE; | ||||||
|  |     } | ||||||
|  |     return true;  // The measurement is ready | ||||||
|  |   } | ||||||
|  |   return false;  // The measurement is still pending | ||||||
|  | } | ||||||
|  |  | ||||||
|  | //////////////////////////////////////////////////////////////////////////////// | ||||||
|  | // Bosch BMP3XXComponent (Private) Member Functions | ||||||
|  | //////////////////////////////////////////////////////////////////////////////// | ||||||
|  |  | ||||||
|  | float BMP3XXComponent::bmp388_compensate_temperature_(float uncomp_temp) { | ||||||
|  |   float partial_data1 = uncomp_temp - compensation_float_params_.param_T1; | ||||||
|  |   float partial_data2 = partial_data1 * compensation_float_params_.param_T2; | ||||||
|  |   return partial_data2 + partial_data1 * partial_data1 * compensation_float_params_.param_T3; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | float BMP3XXComponent::bmp388_compensate_pressure_(float uncomp_press, float t_lin) { | ||||||
|  |   float partial_data1 = compensation_float_params_.param_P6 * t_lin; | ||||||
|  |   float partial_data2 = compensation_float_params_.param_P7 * t_lin * t_lin; | ||||||
|  |   float partial_data3 = compensation_float_params_.param_P8 * t_lin * t_lin * t_lin; | ||||||
|  |   float partial_out1 = compensation_float_params_.param_P5 + partial_data1 + partial_data2 + partial_data3; | ||||||
|  |   partial_data1 = compensation_float_params_.param_P2 * t_lin; | ||||||
|  |   partial_data2 = compensation_float_params_.param_P3 * t_lin * t_lin; | ||||||
|  |   partial_data3 = compensation_float_params_.param_P4 * t_lin * t_lin * t_lin; | ||||||
|  |   float partial_out2 = | ||||||
|  |       uncomp_press * (compensation_float_params_.param_P1 + partial_data1 + partial_data2 + partial_data3); | ||||||
|  |   partial_data1 = uncomp_press * uncomp_press; | ||||||
|  |   partial_data2 = compensation_float_params_.param_P9 + compensation_float_params_.param_P10 * t_lin; | ||||||
|  |   partial_data3 = partial_data1 * partial_data2; | ||||||
|  |   float partial_data4 = | ||||||
|  |       partial_data3 + uncomp_press * uncomp_press * uncomp_press * compensation_float_params_.param_P11; | ||||||
|  |   return partial_out1 + partial_out2 + partial_data4; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | }  // namespace bmp3xx | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										237
									
								
								esphome/components/bmp3xx/bmp3xx.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										237
									
								
								esphome/components/bmp3xx/bmp3xx.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,237 @@ | |||||||
|  | /* | ||||||
|  |   based on BMP388_DEV by Martin Lindupp | ||||||
|  |   under MIT License (MIT) | ||||||
|  |   Copyright (C) Martin Lindupp 2020 | ||||||
|  |   http://github.com/MartinL1/BMP388_DEV | ||||||
|  | */ | ||||||
|  |  | ||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include "esphome/core/component.h" | ||||||
|  | #include "esphome/components/sensor/sensor.h" | ||||||
|  | #include "esphome/components/i2c/i2c.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace bmp3xx { | ||||||
|  |  | ||||||
|  | static const uint8_t BMP388_ID = 0x50;   // The BMP388 device ID | ||||||
|  | static const uint8_t BMP390_ID = 0x60;   // The BMP390 device ID | ||||||
|  | static const uint8_t RESET_CODE = 0xB6;  // The BMP388 reset code | ||||||
|  |  | ||||||
|  | /// BMP388_DEV Registers | ||||||
|  | enum { | ||||||
|  |   BMP388_CHIP_ID = 0x00,       // Chip ID register sub-address | ||||||
|  |   BMP388_ERR_REG = 0x02,       // Error register sub-address | ||||||
|  |   BMP388_STATUS = 0x03,        // Status register sub-address | ||||||
|  |   BMP388_DATA_0 = 0x04,        // Pressure eXtended Least Significant Byte (XLSB) register sub-address | ||||||
|  |   BMP388_DATA_1 = 0x05,        // Pressure Least Significant Byte (LSB) register sub-address | ||||||
|  |   BMP388_DATA_2 = 0x06,        // Pressure Most Significant Byte (MSB) register sub-address | ||||||
|  |   BMP388_DATA_3 = 0x07,        // Temperature eXtended Least Significant Byte (XLSB) register sub-address | ||||||
|  |   BMP388_DATA_4 = 0x08,        // Temperature Least Significant Byte (LSB) register sub-address | ||||||
|  |   BMP388_DATA_5 = 0x09,        // Temperature Most Significant Byte (MSB) register sub-address | ||||||
|  |   BMP388_SENSORTIME_0 = 0x0C,  // Sensor time register 0 sub-address | ||||||
|  |   BMP388_SENSORTIME_1 = 0x0D,  // Sensor time register 1 sub-address | ||||||
|  |   BMP388_SENSORTIME_2 = 0x0E,  // Sensor time register 2 sub-address | ||||||
|  |   BMP388_EVENT = 0x10,         // Event register sub-address | ||||||
|  |   BMP388_INT_STATUS = 0x11,    // Interrupt Status register sub-address | ||||||
|  |   BMP388_INT_CTRL = 0x19,      // Interrupt Control register sub-address | ||||||
|  |   BMP388_IF_CONFIG = 0x1A,     // Interface Configuration register sub-address | ||||||
|  |   BMP388_PWR_CTRL = 0x1B,      // Power Control register sub-address | ||||||
|  |   BMP388_OSR = 0x1C,           // Oversampling register sub-address | ||||||
|  |   BMP388_ODR = 0x1D,           // Output Data Rate register sub-address | ||||||
|  |   BMP388_CONFIG = 0x1F,        // Configuration register sub-address | ||||||
|  |   BMP388_TRIM_PARAMS = 0x31,   // Trim parameter registers' base sub-address | ||||||
|  |   BMP388_CMD = 0x7E            // Command register sub-address | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /// Device mode bitfield in the control and measurement register | ||||||
|  | enum OperationMode { SLEEP_MODE = 0x00, FORCED_MODE = 0x01, NORMAL_MODE = 0x03 }; | ||||||
|  |  | ||||||
|  | /// Oversampling bit fields in the control and measurement register | ||||||
|  | enum Oversampling { | ||||||
|  |   OVERSAMPLING_NONE = 0x00, | ||||||
|  |   OVERSAMPLING_X2 = 0x01, | ||||||
|  |   OVERSAMPLING_X4 = 0x02, | ||||||
|  |   OVERSAMPLING_X8 = 0x03, | ||||||
|  |   OVERSAMPLING_X16 = 0x04, | ||||||
|  |   OVERSAMPLING_X32 = 0x05 | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /// Infinite Impulse Response (IIR) filter bit field in the configuration register | ||||||
|  | enum IIRFilter { | ||||||
|  |   IIR_FILTER_OFF = 0x00, | ||||||
|  |   IIR_FILTER_2 = 0x01, | ||||||
|  |   IIR_FILTER_4 = 0x02, | ||||||
|  |   IIR_FILTER_8 = 0x03, | ||||||
|  |   IIR_FILTER_16 = 0x04, | ||||||
|  |   IIR_FILTER_32 = 0x05, | ||||||
|  |   IIR_FILTER_64 = 0x06, | ||||||
|  |   IIR_FILTER_128 = 0x07 | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /// This class implements support for the BMP3XX Temperature+Pressure i2c sensor. | ||||||
|  | class BMP3XXComponent : public PollingComponent, public i2c::I2CDevice { | ||||||
|  |  public: | ||||||
|  |   void setup() override; | ||||||
|  |   void dump_config() override; | ||||||
|  |   float get_setup_priority() const override; | ||||||
|  |   void update() override; | ||||||
|  |  | ||||||
|  |   void set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_sensor; } | ||||||
|  |   void set_pressure_sensor(sensor::Sensor *pressure_sensor) { pressure_sensor_ = pressure_sensor; } | ||||||
|  |  | ||||||
|  |   /// Set the oversampling value for the temperature sensor. Default is 16x. | ||||||
|  |   void set_temperature_oversampling_config(Oversampling temperature_oversampling) { | ||||||
|  |     this->temperature_oversampling_ = temperature_oversampling; | ||||||
|  |   } | ||||||
|  |   /// Set the oversampling value for the pressure sensor. Default is 16x. | ||||||
|  |   void set_pressure_oversampling_config(Oversampling pressure_oversampling) { | ||||||
|  |     this->pressure_oversampling_ = pressure_oversampling; | ||||||
|  |   } | ||||||
|  |   /// Set the IIR Filter used to increase accuracy, defaults to no IIR Filter. | ||||||
|  |   void set_iir_filter_config(IIRFilter iir_filter) { this->iir_filter_ = iir_filter; } | ||||||
|  |  | ||||||
|  |   /// Soft reset the sensor | ||||||
|  |   uint8_t reset(); | ||||||
|  |   /// Start continuous measurement in NORMAL_MODE | ||||||
|  |   bool start_normal_conversion(); | ||||||
|  |   /// Start a one shot measurement in FORCED_MODE | ||||||
|  |   bool start_forced_conversion(); | ||||||
|  |   /// Stop the conversion and return to SLEEP_MODE | ||||||
|  |   bool stop_conversion(); | ||||||
|  |   /// Set the pressure oversampling: OFF, X1, X2, X4, X8, X16, X32 | ||||||
|  |   bool set_pressure_oversampling(Oversampling pressure_oversampling); | ||||||
|  |   /// Set the temperature oversampling: OFF, X1, X2, X4, X8, X16, X32 | ||||||
|  |   bool set_temperature_oversampling(Oversampling temperature_oversampling); | ||||||
|  |   /// Set the IIR filter setting: OFF, 2, 3, 8, 16, 32 | ||||||
|  |   bool set_iir_filter(IIRFilter iir_filter); | ||||||
|  |   /// Get a temperature measurement | ||||||
|  |   bool get_temperature(float &temperature); | ||||||
|  |   /// Get a pressure measurement | ||||||
|  |   bool get_pressure(float &pressure); | ||||||
|  |   /// Get a temperature and pressure measurement | ||||||
|  |   bool get_measurements(float &temperature, float &pressure); | ||||||
|  |   /// Get a temperature and pressure measurement | ||||||
|  |   bool get_measurement(); | ||||||
|  |   /// Set the barometer mode | ||||||
|  |   bool set_mode(OperationMode mode); | ||||||
|  |   /// Set the BMP388 oversampling register | ||||||
|  |   bool set_oversampling_register(Oversampling pressure_oversampling, Oversampling temperature_oversampling); | ||||||
|  |   /// Checks if a measurement is ready | ||||||
|  |   bool data_ready(); | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   Oversampling temperature_oversampling_{OVERSAMPLING_X16}; | ||||||
|  |   Oversampling pressure_oversampling_{OVERSAMPLING_X16}; | ||||||
|  |   IIRFilter iir_filter_{IIR_FILTER_OFF}; | ||||||
|  |   OperationMode operation_mode_{FORCED_MODE}; | ||||||
|  |   sensor::Sensor *temperature_sensor_; | ||||||
|  |   sensor::Sensor *pressure_sensor_; | ||||||
|  |   enum ErrorCode { | ||||||
|  |     NONE = 0, | ||||||
|  |     ERROR_COMMUNICATION_FAILED, | ||||||
|  |     ERROR_WRONG_CHIP_ID, | ||||||
|  |     ERROR_SENSOR_STATUS, | ||||||
|  |     ERROR_SENSOR_RESET, | ||||||
|  |   } error_code_{NONE}; | ||||||
|  |  | ||||||
|  |   struct {  // The BMP388 compensation trim parameters (coefficients) | ||||||
|  |     uint16_t param_T1; | ||||||
|  |     uint16_t param_T2; | ||||||
|  |     int8_t param_T3; | ||||||
|  |     int16_t param_P1; | ||||||
|  |     int16_t param_P2; | ||||||
|  |     int8_t param_P3; | ||||||
|  |     int8_t param_P4; | ||||||
|  |     uint16_t param_P5; | ||||||
|  |     uint16_t param_P6; | ||||||
|  |     int8_t param_P7; | ||||||
|  |     int8_t param_P8; | ||||||
|  |     int16_t param_P9; | ||||||
|  |     int8_t param_P10; | ||||||
|  |     int8_t param_P11; | ||||||
|  |   } __attribute__((packed)) compensation_params_; | ||||||
|  |  | ||||||
|  |   struct FloatParams {  // The BMP388 float point compensation trim parameters | ||||||
|  |     float param_T1; | ||||||
|  |     float param_T2; | ||||||
|  |     float param_T3; | ||||||
|  |     float param_P1; | ||||||
|  |     float param_P2; | ||||||
|  |     float param_P3; | ||||||
|  |     float param_P4; | ||||||
|  |     float param_P5; | ||||||
|  |     float param_P6; | ||||||
|  |     float param_P7; | ||||||
|  |     float param_P8; | ||||||
|  |     float param_P9; | ||||||
|  |     float param_P10; | ||||||
|  |     float param_P11; | ||||||
|  |   } compensation_float_params_; | ||||||
|  |  | ||||||
|  |   union {  // Copy of the BMP388's chip id register | ||||||
|  |     struct { | ||||||
|  |       uint8_t chip_id_nvm : 4; | ||||||
|  |       uint8_t chip_id_fixed : 4; | ||||||
|  |     } bit; | ||||||
|  |     uint8_t reg; | ||||||
|  |   } chip_id_ = {.reg = 0}; | ||||||
|  |  | ||||||
|  |   union {  // Copy of the BMP388's event register | ||||||
|  |     struct { | ||||||
|  |       uint8_t por_detected : 1; | ||||||
|  |     } bit; | ||||||
|  |     uint8_t reg; | ||||||
|  |   } event_ = {.reg = 0}; | ||||||
|  |  | ||||||
|  |   union {  // Copy of the BMP388's interrupt status register | ||||||
|  |     struct { | ||||||
|  |       uint8_t fwm_int : 1; | ||||||
|  |       uint8_t ffull_int : 1; | ||||||
|  |       uint8_t : 1; | ||||||
|  |       uint8_t drdy : 1; | ||||||
|  |     } bit; | ||||||
|  |     uint8_t reg; | ||||||
|  |   } int_status_ = {.reg = 0}; | ||||||
|  |  | ||||||
|  |   union {  // Copy of the BMP388's power control register | ||||||
|  |     struct { | ||||||
|  |       uint8_t press_en : 1; | ||||||
|  |       uint8_t temp_en : 1; | ||||||
|  |       uint8_t : 2; | ||||||
|  |       uint8_t mode : 2; | ||||||
|  |     } bit; | ||||||
|  |     uint8_t reg; | ||||||
|  |   } pwr_ctrl_ = {.reg = 0}; | ||||||
|  |  | ||||||
|  |   union {  // Copy of the BMP388's oversampling register | ||||||
|  |     struct { | ||||||
|  |       uint8_t osr_p : 3; | ||||||
|  |       uint8_t osr_t : 3; | ||||||
|  |     } bit; | ||||||
|  |     uint8_t reg; | ||||||
|  |   } osr_ = {.reg = 0}; | ||||||
|  |  | ||||||
|  |   union {  // Copy of the BMP388's output data rate register | ||||||
|  |     struct { | ||||||
|  |       uint8_t odr_sel : 5; | ||||||
|  |     } bit; | ||||||
|  |     uint8_t reg; | ||||||
|  |   } odr_ = {.reg = 0}; | ||||||
|  |  | ||||||
|  |   union {  // Copy of the BMP388's configuration register | ||||||
|  |     struct { | ||||||
|  |       uint8_t : 1; | ||||||
|  |       uint8_t iir_filter : 3; | ||||||
|  |     } bit; | ||||||
|  |     uint8_t reg; | ||||||
|  |   } config_ = {.reg = 0}; | ||||||
|  |  | ||||||
|  |   // Bosch temperature compensation function | ||||||
|  |   float bmp388_compensate_temperature_(float uncomp_temp); | ||||||
|  |   // Bosch pressure compensation function | ||||||
|  |   float bmp388_compensate_pressure_(float uncomp_press, float t_lin); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | }  // namespace bmp3xx | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										100
									
								
								esphome/components/bmp3xx/sensor.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										100
									
								
								esphome/components/bmp3xx/sensor.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,100 @@ | |||||||
|  | import esphome.codegen as cg | ||||||
|  | import esphome.config_validation as cv | ||||||
|  | from esphome.components import i2c, sensor | ||||||
|  | from esphome.const import ( | ||||||
|  |     CONF_ID, | ||||||
|  |     CONF_IIR_FILTER, | ||||||
|  |     CONF_OVERSAMPLING, | ||||||
|  |     CONF_PRESSURE, | ||||||
|  |     CONF_TEMPERATURE, | ||||||
|  |     DEVICE_CLASS_PRESSURE, | ||||||
|  |     DEVICE_CLASS_TEMPERATURE, | ||||||
|  |     STATE_CLASS_MEASUREMENT, | ||||||
|  |     UNIT_CELSIUS, | ||||||
|  |     UNIT_HECTOPASCAL, | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | CODEOWNERS = ["@martgras"] | ||||||
|  | DEPENDENCIES = ["i2c"] | ||||||
|  |  | ||||||
|  | bmp3xx_ns = cg.esphome_ns.namespace("bmp3xx") | ||||||
|  | Oversampling = bmp3xx_ns.enum("Oversampling") | ||||||
|  | OVERSAMPLING_OPTIONS = { | ||||||
|  |     "NONE": Oversampling.OVERSAMPLING_NONE, | ||||||
|  |     "2X": Oversampling.OVERSAMPLING_X2, | ||||||
|  |     "4X": Oversampling.OVERSAMPLING_X4, | ||||||
|  |     "8X": Oversampling.OVERSAMPLING_X8, | ||||||
|  |     "16X": Oversampling.OVERSAMPLING_X16, | ||||||
|  |     "32x": Oversampling.OVERSAMPLING_X32, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | IIRFilter = bmp3xx_ns.enum("IIRFilter") | ||||||
|  | IIR_FILTER_OPTIONS = { | ||||||
|  |     "OFF": IIRFilter.IIR_FILTER_OFF, | ||||||
|  |     "2X": IIRFilter.IIR_FILTER_2, | ||||||
|  |     "4X": IIRFilter.IIR_FILTER_4, | ||||||
|  |     "8X": IIRFilter.IIR_FILTER_8, | ||||||
|  |     "16X": IIRFilter.IIR_FILTER_16, | ||||||
|  |     "32X": IIRFilter.IIR_FILTER_32, | ||||||
|  |     "64X": IIRFilter.IIR_FILTER_64, | ||||||
|  |     "128X": IIRFilter.IIR_FILTER_128, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | BMP3XXComponent = bmp3xx_ns.class_( | ||||||
|  |     "BMP3XXComponent", cg.PollingComponent, i2c.I2CDevice | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | CONFIG_SCHEMA = ( | ||||||
|  |     cv.Schema( | ||||||
|  |         { | ||||||
|  |             cv.GenerateID(): cv.declare_id(BMP3XXComponent), | ||||||
|  |             cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( | ||||||
|  |                 unit_of_measurement=UNIT_CELSIUS, | ||||||
|  |                 accuracy_decimals=1, | ||||||
|  |                 device_class=DEVICE_CLASS_TEMPERATURE, | ||||||
|  |                 state_class=STATE_CLASS_MEASUREMENT, | ||||||
|  |             ).extend( | ||||||
|  |                 { | ||||||
|  |                     cv.Optional(CONF_OVERSAMPLING, default="2X"): cv.enum( | ||||||
|  |                         OVERSAMPLING_OPTIONS, upper=True | ||||||
|  |                     ), | ||||||
|  |                 } | ||||||
|  |             ), | ||||||
|  |             cv.Optional(CONF_PRESSURE): sensor.sensor_schema( | ||||||
|  |                 unit_of_measurement=UNIT_HECTOPASCAL, | ||||||
|  |                 accuracy_decimals=1, | ||||||
|  |                 device_class=DEVICE_CLASS_PRESSURE, | ||||||
|  |                 state_class=STATE_CLASS_MEASUREMENT, | ||||||
|  |             ).extend( | ||||||
|  |                 { | ||||||
|  |                     cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum( | ||||||
|  |                         OVERSAMPLING_OPTIONS, upper=True | ||||||
|  |                     ), | ||||||
|  |                 } | ||||||
|  |             ), | ||||||
|  |             cv.Optional(CONF_IIR_FILTER, default="OFF"): cv.enum( | ||||||
|  |                 IIR_FILTER_OPTIONS, upper=True | ||||||
|  |             ), | ||||||
|  |         } | ||||||
|  |     ) | ||||||
|  |     .extend(cv.polling_component_schema("60s")) | ||||||
|  |     .extend(i2c.i2c_device_schema(0x77)) | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | async def to_code(config): | ||||||
|  |     var = cg.new_Pvariable(config[CONF_ID]) | ||||||
|  |     await cg.register_component(var, config) | ||||||
|  |     await i2c.register_i2c_device(var, config) | ||||||
|  |     cg.add(var.set_iir_filter_config(config[CONF_IIR_FILTER])) | ||||||
|  |     if CONF_TEMPERATURE in config: | ||||||
|  |         conf = config[CONF_TEMPERATURE] | ||||||
|  |         sens = await sensor.new_sensor(conf) | ||||||
|  |         cg.add(var.set_temperature_sensor(sens)) | ||||||
|  |         cg.add(var.set_temperature_oversampling_config(conf[CONF_OVERSAMPLING])) | ||||||
|  |  | ||||||
|  |     if CONF_PRESSURE in config: | ||||||
|  |         conf = config[CONF_PRESSURE] | ||||||
|  |         sens = await sensor.new_sensor(conf) | ||||||
|  |         cg.add(var.set_pressure_sensor(sens)) | ||||||
|  |         cg.add(var.set_pressure_oversampling_config(conf[CONF_OVERSAMPLING])) | ||||||
							
								
								
									
										127
									
								
								esphome/components/button/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										127
									
								
								esphome/components/button/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,127 @@ | |||||||
|  | import esphome.codegen as cg | ||||||
|  | import esphome.config_validation as cv | ||||||
|  | from esphome import automation | ||||||
|  | from esphome.automation import maybe_simple_id | ||||||
|  | from esphome.components import mqtt | ||||||
|  | from esphome.const import ( | ||||||
|  |     CONF_DEVICE_CLASS, | ||||||
|  |     CONF_ENTITY_CATEGORY, | ||||||
|  |     CONF_ICON, | ||||||
|  |     CONF_ID, | ||||||
|  |     CONF_ON_PRESS, | ||||||
|  |     CONF_TRIGGER_ID, | ||||||
|  |     CONF_MQTT_ID, | ||||||
|  |     DEVICE_CLASS_RESTART, | ||||||
|  |     DEVICE_CLASS_UPDATE, | ||||||
|  | ) | ||||||
|  | from esphome.core import CORE, coroutine_with_priority | ||||||
|  | from esphome.cpp_helpers import setup_entity | ||||||
|  |  | ||||||
|  | CODEOWNERS = ["@esphome/core"] | ||||||
|  | IS_PLATFORM_COMPONENT = True | ||||||
|  |  | ||||||
|  | DEVICE_CLASSES = [ | ||||||
|  |     DEVICE_CLASS_RESTART, | ||||||
|  |     DEVICE_CLASS_UPDATE, | ||||||
|  | ] | ||||||
|  |  | ||||||
|  | button_ns = cg.esphome_ns.namespace("button") | ||||||
|  | Button = button_ns.class_("Button", cg.EntityBase) | ||||||
|  | ButtonPtr = Button.operator("ptr") | ||||||
|  |  | ||||||
|  | PressAction = button_ns.class_("PressAction", automation.Action) | ||||||
|  |  | ||||||
|  | ButtonPressTrigger = button_ns.class_( | ||||||
|  |     "ButtonPressTrigger", automation.Trigger.template() | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | validate_device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space="_") | ||||||
|  |  | ||||||
|  |  | ||||||
|  | BUTTON_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend( | ||||||
|  |     { | ||||||
|  |         cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTButtonComponent), | ||||||
|  |         cv.Optional(CONF_DEVICE_CLASS): validate_device_class, | ||||||
|  |         cv.Optional(CONF_ON_PRESS): automation.validate_automation( | ||||||
|  |             { | ||||||
|  |                 cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ButtonPressTrigger), | ||||||
|  |             } | ||||||
|  |         ), | ||||||
|  |     } | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | _UNDEF = object() | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def button_schema( | ||||||
|  |     icon: str = _UNDEF, | ||||||
|  |     entity_category: str = _UNDEF, | ||||||
|  |     device_class: str = _UNDEF, | ||||||
|  | ) -> cv.Schema: | ||||||
|  |     schema = BUTTON_SCHEMA | ||||||
|  |     if icon is not _UNDEF: | ||||||
|  |         schema = schema.extend({cv.Optional(CONF_ICON, default=icon): cv.icon}) | ||||||
|  |     if entity_category is not _UNDEF: | ||||||
|  |         schema = schema.extend( | ||||||
|  |             { | ||||||
|  |                 cv.Optional( | ||||||
|  |                     CONF_ENTITY_CATEGORY, default=entity_category | ||||||
|  |                 ): cv.entity_category | ||||||
|  |             } | ||||||
|  |         ) | ||||||
|  |     if device_class is not _UNDEF: | ||||||
|  |         schema = schema.extend( | ||||||
|  |             { | ||||||
|  |                 cv.Optional( | ||||||
|  |                     CONF_DEVICE_CLASS, default=device_class | ||||||
|  |                 ): validate_device_class | ||||||
|  |             } | ||||||
|  |         ) | ||||||
|  |     return schema | ||||||
|  |  | ||||||
|  |  | ||||||
|  | async def setup_button_core_(var, config): | ||||||
|  |     await setup_entity(var, config) | ||||||
|  |  | ||||||
|  |     for conf in config.get(CONF_ON_PRESS, []): | ||||||
|  |         trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) | ||||||
|  |         await automation.build_automation(trigger, [], conf) | ||||||
|  |  | ||||||
|  |     if CONF_DEVICE_CLASS in config: | ||||||
|  |         cg.add(var.set_device_class(config[CONF_DEVICE_CLASS])) | ||||||
|  |  | ||||||
|  |     if CONF_MQTT_ID in config: | ||||||
|  |         mqtt_ = cg.new_Pvariable(config[CONF_MQTT_ID], var) | ||||||
|  |         await mqtt.register_mqtt_component(mqtt_, config) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | async def register_button(var, config): | ||||||
|  |     if not CORE.has_id(config[CONF_ID]): | ||||||
|  |         var = cg.Pvariable(config[CONF_ID], var) | ||||||
|  |     cg.add(cg.App.register_button(var)) | ||||||
|  |     await setup_button_core_(var, config) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | async def new_button(config): | ||||||
|  |     var = cg.new_Pvariable(config[CONF_ID]) | ||||||
|  |     await register_button(var, config) | ||||||
|  |     return var | ||||||
|  |  | ||||||
|  |  | ||||||
|  | BUTTON_PRESS_SCHEMA = maybe_simple_id( | ||||||
|  |     { | ||||||
|  |         cv.Required(CONF_ID): cv.use_id(Button), | ||||||
|  |     } | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @automation.register_action("button.press", PressAction, BUTTON_PRESS_SCHEMA) | ||||||
|  | async def button_press_to_code(config, action_id, template_arg, args): | ||||||
|  |     paren = await cg.get_variable(config[CONF_ID]) | ||||||
|  |     return cg.new_Pvariable(action_id, template_arg, paren) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @coroutine_with_priority(100.0) | ||||||
|  | async def to_code(config): | ||||||
|  |     cg.add_global(button_ns.using) | ||||||
|  |     cg.add_define("USE_BUTTON") | ||||||
							
								
								
									
										28
									
								
								esphome/components/button/automation.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								esphome/components/button/automation.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,28 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include "esphome/components/button/button.h" | ||||||
|  | #include "esphome/core/automation.h" | ||||||
|  | #include "esphome/core/component.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace button { | ||||||
|  |  | ||||||
|  | template<typename... Ts> class PressAction : public Action<Ts...> { | ||||||
|  |  public: | ||||||
|  |   explicit PressAction(Button *button) : button_(button) {} | ||||||
|  |  | ||||||
|  |   void play(Ts... x) override { this->button_->press(); } | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   Button *button_; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | class ButtonPressTrigger : public Trigger<> { | ||||||
|  |  public: | ||||||
|  |   ButtonPressTrigger(Button *button) { | ||||||
|  |     button->add_on_press_callback([this]() { this->trigger(); }); | ||||||
|  |   } | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | }  // namespace button | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										28
									
								
								esphome/components/button/button.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								esphome/components/button/button.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,28 @@ | |||||||
|  | #include "button.h" | ||||||
|  | #include "esphome/core/log.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace button { | ||||||
|  |  | ||||||
|  | static const char *const TAG = "button"; | ||||||
|  |  | ||||||
|  | Button::Button(const std::string &name) : EntityBase(name) {} | ||||||
|  | Button::Button() : Button("") {} | ||||||
|  |  | ||||||
|  | void Button::press() { | ||||||
|  |   ESP_LOGD(TAG, "'%s' Pressed.", this->get_name().c_str()); | ||||||
|  |   this->press_action(); | ||||||
|  |   this->press_callback_.call(); | ||||||
|  | } | ||||||
|  | void Button::add_on_press_callback(std::function<void()> &&callback) { this->press_callback_.add(std::move(callback)); } | ||||||
|  | uint32_t Button::hash_base() { return 1495763804UL; } | ||||||
|  |  | ||||||
|  | void Button::set_device_class(const std::string &device_class) { this->device_class_ = device_class; } | ||||||
|  | std::string Button::get_device_class() { | ||||||
|  |   if (this->device_class_.has_value()) | ||||||
|  |     return *this->device_class_; | ||||||
|  |   return ""; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | }  // namespace button | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										57
									
								
								esphome/components/button/button.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								esphome/components/button/button.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,57 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include "esphome/core/component.h" | ||||||
|  | #include "esphome/core/entity_base.h" | ||||||
|  | #include "esphome/core/helpers.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace button { | ||||||
|  |  | ||||||
|  | #define LOG_BUTTON(prefix, type, obj) \ | ||||||
|  |   if ((obj) != nullptr) { \ | ||||||
|  |     ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, LOG_STR_LITERAL(type), (obj)->get_name().c_str()); \ | ||||||
|  |     if (!(obj)->get_icon().empty()) { \ | ||||||
|  |       ESP_LOGCONFIG(TAG, "%s  Icon: '%s'", prefix, (obj)->get_icon().c_str()); \ | ||||||
|  |     } \ | ||||||
|  |   } | ||||||
|  |  | ||||||
|  | /** Base class for all buttons. | ||||||
|  |  * | ||||||
|  |  * A button is just a momentary switch that does not have a state, only a trigger. | ||||||
|  |  */ | ||||||
|  | class Button : public EntityBase { | ||||||
|  |  public: | ||||||
|  |   explicit Button(); | ||||||
|  |   explicit Button(const std::string &name); | ||||||
|  |  | ||||||
|  |   /** Press this button. This is called by the front-end. | ||||||
|  |    * | ||||||
|  |    * For implementing buttons, please override press_action. | ||||||
|  |    */ | ||||||
|  |   void press(); | ||||||
|  |  | ||||||
|  |   /** Set callback for state changes. | ||||||
|  |    * | ||||||
|  |    * @param callback The void() callback. | ||||||
|  |    */ | ||||||
|  |   void add_on_press_callback(std::function<void()> &&callback); | ||||||
|  |  | ||||||
|  |   /// Set the Home Assistant device class (see button::device_class). | ||||||
|  |   void set_device_class(const std::string &device_class); | ||||||
|  |  | ||||||
|  |   /// Get the device class for this button. | ||||||
|  |   std::string get_device_class(); | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   /** You should implement this virtual method if you want to create your own button. | ||||||
|  |    */ | ||||||
|  |   virtual void press_action(){}; | ||||||
|  |  | ||||||
|  |   uint32_t hash_base() override; | ||||||
|  |  | ||||||
|  |   CallbackManager<void()> press_callback_{}; | ||||||
|  |   optional<std::string> device_class_{}; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | }  // namespace button | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										45
									
								
								esphome/components/cap1188/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								esphome/components/cap1188/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,45 @@ | |||||||
|  | import esphome.codegen as cg | ||||||
|  | import esphome.config_validation as cv | ||||||
|  | from esphome.components import i2c | ||||||
|  | from esphome.const import CONF_ID, CONF_RESET_PIN | ||||||
|  | from esphome import pins | ||||||
|  |  | ||||||
|  | CONF_TOUCH_THRESHOLD = "touch_threshold" | ||||||
|  | CONF_ALLOW_MULTIPLE_TOUCHES = "allow_multiple_touches" | ||||||
|  |  | ||||||
|  | DEPENDENCIES = ["i2c"] | ||||||
|  | AUTO_LOAD = ["binary_sensor", "output"] | ||||||
|  | CODEOWNERS = ["@MrEditor97"] | ||||||
|  |  | ||||||
|  | cap1188_ns = cg.esphome_ns.namespace("cap1188") | ||||||
|  | CONF_CAP1188_ID = "cap1188_id" | ||||||
|  | CAP1188Component = cap1188_ns.class_("CAP1188Component", cg.Component, i2c.I2CDevice) | ||||||
|  |  | ||||||
|  | MULTI_CONF = True | ||||||
|  | CONFIG_SCHEMA = ( | ||||||
|  |     cv.Schema( | ||||||
|  |         { | ||||||
|  |             cv.GenerateID(): cv.declare_id(CAP1188Component), | ||||||
|  |             cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema, | ||||||
|  |             cv.Optional(CONF_TOUCH_THRESHOLD, default=0x20): cv.int_range( | ||||||
|  |                 min=0x01, max=0x80 | ||||||
|  |             ), | ||||||
|  |             cv.Optional(CONF_ALLOW_MULTIPLE_TOUCHES, default=False): cv.boolean, | ||||||
|  |         } | ||||||
|  |     ) | ||||||
|  |     .extend(cv.COMPONENT_SCHEMA) | ||||||
|  |     .extend(i2c.i2c_device_schema(0x29)) | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | async def to_code(config): | ||||||
|  |     var = cg.new_Pvariable(config[CONF_ID]) | ||||||
|  |     cg.add(var.set_touch_threshold(config[CONF_TOUCH_THRESHOLD])) | ||||||
|  |     cg.add(var.set_allow_multiple_touches(config[CONF_ALLOW_MULTIPLE_TOUCHES])) | ||||||
|  |  | ||||||
|  |     if CONF_RESET_PIN in config: | ||||||
|  |         pin = await cg.gpio_pin_expression(config[CONF_RESET_PIN]) | ||||||
|  |         cg.add(var.set_reset_pin(pin)) | ||||||
|  |  | ||||||
|  |     await cg.register_component(var, config) | ||||||
|  |     await i2c.register_i2c_device(var, config) | ||||||
							
								
								
									
										25
									
								
								esphome/components/cap1188/binary_sensor.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								esphome/components/cap1188/binary_sensor.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,25 @@ | |||||||
|  | import esphome.codegen as cg | ||||||
|  | import esphome.config_validation as cv | ||||||
|  | from esphome.components import binary_sensor | ||||||
|  | from esphome.const import CONF_CHANNEL, CONF_ID | ||||||
|  | from . import cap1188_ns, CAP1188Component, CONF_CAP1188_ID | ||||||
|  |  | ||||||
|  | DEPENDENCIES = ["cap1188"] | ||||||
|  | CAP1188Channel = cap1188_ns.class_("CAP1188Channel", binary_sensor.BinarySensor) | ||||||
|  |  | ||||||
|  | CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend( | ||||||
|  |     { | ||||||
|  |         cv.GenerateID(): cv.declare_id(CAP1188Channel), | ||||||
|  |         cv.GenerateID(CONF_CAP1188_ID): cv.use_id(CAP1188Component), | ||||||
|  |         cv.Required(CONF_CHANNEL): cv.int_range(min=0, max=7), | ||||||
|  |     } | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | async def to_code(config): | ||||||
|  |     var = cg.new_Pvariable(config[CONF_ID]) | ||||||
|  |     await binary_sensor.register_binary_sensor(var, config) | ||||||
|  |     hub = await cg.get_variable(config[CONF_CAP1188_ID]) | ||||||
|  |     cg.add(var.set_channel(config[CONF_CHANNEL])) | ||||||
|  |  | ||||||
|  |     cg.add(hub.register_channel(var)) | ||||||
							
								
								
									
										88
									
								
								esphome/components/cap1188/cap1188.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								esphome/components/cap1188/cap1188.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,88 @@ | |||||||
|  | #include "cap1188.h" | ||||||
|  | #include "esphome/core/log.h" | ||||||
|  | #include "esphome/core/hal.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace cap1188 { | ||||||
|  |  | ||||||
|  | static const char *const TAG = "cap1188"; | ||||||
|  |  | ||||||
|  | void CAP1188Component::setup() { | ||||||
|  |   ESP_LOGCONFIG(TAG, "Setting up CAP1188..."); | ||||||
|  |  | ||||||
|  |   // Reset device using the reset pin | ||||||
|  |   if (this->reset_pin_ != nullptr) { | ||||||
|  |     this->reset_pin_->setup(); | ||||||
|  |     this->reset_pin_->digital_write(false); | ||||||
|  |     delay(100);  // NOLINT | ||||||
|  |     this->reset_pin_->digital_write(true); | ||||||
|  |     delay(100);  // NOLINT | ||||||
|  |     this->reset_pin_->digital_write(false); | ||||||
|  |     delay(100);  // NOLINT | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // Check if CAP1188 is actually connected | ||||||
|  |   this->read_byte(CAP1188_PRODUCT_ID, &this->cap1188_product_id_); | ||||||
|  |   this->read_byte(CAP1188_MANUFACTURE_ID, &this->cap1188_manufacture_id_); | ||||||
|  |   this->read_byte(CAP1188_REVISION, &this->cap1188_revision_); | ||||||
|  |  | ||||||
|  |   if ((this->cap1188_product_id_ != 0x50) || (this->cap1188_manufacture_id_ != 0x5D)) { | ||||||
|  |     this->error_code_ = COMMUNICATION_FAILED; | ||||||
|  |     this->mark_failed(); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // Set sensitivity | ||||||
|  |   uint8_t sensitivity = 0; | ||||||
|  |   this->read_byte(CAP1188_SENSITVITY, &sensitivity); | ||||||
|  |   sensitivity = sensitivity & 0x0f; | ||||||
|  |   this->write_byte(CAP1188_SENSITVITY, sensitivity | this->touch_threshold_); | ||||||
|  |  | ||||||
|  |   // Allow multiple touches | ||||||
|  |   this->write_byte(CAP1188_MULTI_TOUCH, this->allow_multiple_touches_); | ||||||
|  |  | ||||||
|  |   // Have LEDs follow touches | ||||||
|  |   this->write_byte(CAP1188_LED_LINK, 0xFF); | ||||||
|  |  | ||||||
|  |   // Speed up a bit | ||||||
|  |   this->write_byte(CAP1188_STAND_BY_CONFIGURATION, 0x30); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void CAP1188Component::dump_config() { | ||||||
|  |   ESP_LOGCONFIG(TAG, "CAP1188:"); | ||||||
|  |   LOG_I2C_DEVICE(this); | ||||||
|  |   LOG_PIN("  Reset Pin: ", this->reset_pin_); | ||||||
|  |   ESP_LOGCONFIG(TAG, "  Product ID: 0x%x", this->cap1188_product_id_); | ||||||
|  |   ESP_LOGCONFIG(TAG, "  Manufacture ID: 0x%x", this->cap1188_manufacture_id_); | ||||||
|  |   ESP_LOGCONFIG(TAG, "  Revision ID: 0x%x", this->cap1188_revision_); | ||||||
|  |  | ||||||
|  |   switch (this->error_code_) { | ||||||
|  |     case COMMUNICATION_FAILED: | ||||||
|  |       ESP_LOGE(TAG, "Product ID or Manufacture ID of the connected device does not match a known CAP1188."); | ||||||
|  |       break; | ||||||
|  |     case NONE: | ||||||
|  |     default: | ||||||
|  |       break; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void CAP1188Component::loop() { | ||||||
|  |   uint8_t touched = 0; | ||||||
|  |  | ||||||
|  |   this->read_register(CAP1188_SENSOR_INPUT_STATUS, &touched, 1); | ||||||
|  |  | ||||||
|  |   if (touched) { | ||||||
|  |     uint8_t data = 0; | ||||||
|  |     this->read_register(CAP1188_MAIN, &data, 1); | ||||||
|  |     data = data & ~CAP1188_MAIN_INT; | ||||||
|  |  | ||||||
|  |     this->write_register(CAP1188_MAIN, &data, 2); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   for (auto *channel : this->channels_) { | ||||||
|  |     channel->process(touched); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | }  // namespace cap1188 | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										68
									
								
								esphome/components/cap1188/cap1188.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								esphome/components/cap1188/cap1188.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,68 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include "esphome/core/component.h" | ||||||
|  | #include "esphome/core/hal.h" | ||||||
|  | #include "esphome/components/i2c/i2c.h" | ||||||
|  | #include "esphome/components/output/binary_output.h" | ||||||
|  | #include "esphome/components/binary_sensor/binary_sensor.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace cap1188 { | ||||||
|  |  | ||||||
|  | enum { | ||||||
|  |   CAP1188_I2CADDR = 0x29, | ||||||
|  |   CAP1188_SENSOR_INPUT_STATUS = 0x3, | ||||||
|  |   CAP1188_MULTI_TOUCH = 0x2A, | ||||||
|  |   CAP1188_LED_LINK = 0x72, | ||||||
|  |   CAP1188_PRODUCT_ID = 0xFD, | ||||||
|  |   CAP1188_MANUFACTURE_ID = 0xFE, | ||||||
|  |   CAP1188_STAND_BY_CONFIGURATION = 0x41, | ||||||
|  |   CAP1188_REVISION = 0xFF, | ||||||
|  |   CAP1188_MAIN = 0x00, | ||||||
|  |   CAP1188_MAIN_INT = 0x01, | ||||||
|  |   CAP1188_LEDPOL = 0x73, | ||||||
|  |   CAP1188_INTERUPT_REPEAT = 0x28, | ||||||
|  |   CAP1188_SENSITVITY = 0x1f, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | class CAP1188Channel : public binary_sensor::BinarySensor { | ||||||
|  |  public: | ||||||
|  |   void set_channel(uint8_t channel) { channel_ = channel; } | ||||||
|  |   void process(uint8_t data) { this->publish_state(static_cast<bool>(data & (1 << this->channel_))); } | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   uint8_t channel_{0}; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | class CAP1188Component : public Component, public i2c::I2CDevice { | ||||||
|  |  public: | ||||||
|  |   void register_channel(CAP1188Channel *channel) { this->channels_.push_back(channel); } | ||||||
|  |   void set_touch_threshold(uint8_t touch_threshold) { this->touch_threshold_ = touch_threshold; }; | ||||||
|  |   void set_allow_multiple_touches(bool allow_multiple_touches) { | ||||||
|  |     this->allow_multiple_touches_ = allow_multiple_touches ? 0x41 : 0x80; | ||||||
|  |   }; | ||||||
|  |   void set_reset_pin(GPIOPin *reset_pin) { this->reset_pin_ = reset_pin; } | ||||||
|  |   void setup() override; | ||||||
|  |   void dump_config() override; | ||||||
|  |   float get_setup_priority() const override { return setup_priority::DATA; } | ||||||
|  |   void loop() override; | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   std::vector<CAP1188Channel *> channels_{}; | ||||||
|  |   uint8_t touch_threshold_{0x20}; | ||||||
|  |   uint8_t allow_multiple_touches_{0x80}; | ||||||
|  |  | ||||||
|  |   GPIOPin *reset_pin_{nullptr}; | ||||||
|  |  | ||||||
|  |   uint8_t cap1188_product_id_{0}; | ||||||
|  |   uint8_t cap1188_manufacture_id_{0}; | ||||||
|  |   uint8_t cap1188_revision_{0}; | ||||||
|  |  | ||||||
|  |   enum ErrorCode { | ||||||
|  |     NONE = 0, | ||||||
|  |     COMMUNICATION_FAILED, | ||||||
|  |   } error_code_{NONE}; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | }  // namespace cap1188 | ||||||
|  | }  // namespace esphome | ||||||
| @@ -36,3 +36,5 @@ async def to_code(config): | |||||||
|     if CORE.is_esp32: |     if CORE.is_esp32: | ||||||
|         cg.add_library("DNSServer", None) |         cg.add_library("DNSServer", None) | ||||||
|         cg.add_library("WiFi", None) |         cg.add_library("WiFi", None) | ||||||
|  |     if CORE.is_esp8266: | ||||||
|  |         cg.add_library("DNSServer", None) | ||||||
|   | |||||||
| @@ -67,6 +67,7 @@ void CaptivePortal::handle_wifisave(AsyncWebServerRequest *request) { | |||||||
|   ESP_LOGI(TAG, "  SSID='%s'", ssid.c_str()); |   ESP_LOGI(TAG, "  SSID='%s'", ssid.c_str()); | ||||||
|   ESP_LOGI(TAG, "  Password=" LOG_SECRET("'%s'"), psk.c_str()); |   ESP_LOGI(TAG, "  Password=" LOG_SECRET("'%s'"), psk.c_str()); | ||||||
|   wifi::global_wifi_component->save_wifi_sta(ssid, psk); |   wifi::global_wifi_component->save_wifi_sta(ssid, psk); | ||||||
|  |   wifi::global_wifi_component->start_scanning(); | ||||||
|   request->redirect("/?save=true"); |   request->redirect("/?save=true"); | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -84,14 +85,7 @@ void CaptivePortal::start() { | |||||||
|   this->dns_server_->start(53, "*", (uint32_t) ip); |   this->dns_server_->start(53, "*", (uint32_t) ip); | ||||||
|  |  | ||||||
|   this->base_->get_server()->onNotFound([this](AsyncWebServerRequest *req) { |   this->base_->get_server()->onNotFound([this](AsyncWebServerRequest *req) { | ||||||
|     bool not_found = false; |     if (!this->active_ || req->host().c_str() == wifi::global_wifi_component->wifi_soft_ap_ip().str()) { | ||||||
|     if (!this->active_) { |  | ||||||
|       not_found = true; |  | ||||||
|     } else if (req->host().c_str() == wifi::global_wifi_component->wifi_soft_ap_ip().str()) { |  | ||||||
|       not_found = true; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     if (not_found) { |  | ||||||
|       req->send(404, "text/html", "File not found"); |       req->send(404, "text/html", "File not found"); | ||||||
|       return; |       return; | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -52,7 +52,7 @@ void CCS811Component::setup() { | |||||||
|  |  | ||||||
|   if (this->baseline_.has_value()) { |   if (this->baseline_.has_value()) { | ||||||
|     // baseline available, write to sensor |     // baseline available, write to sensor | ||||||
|     this->write_bytes(0x11, decode_uint16(*this->baseline_)); |     this->write_bytes(0x11, decode_value(*this->baseline_)); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   auto hardware_version_data = this->read_bytes<1>(0x21); |   auto hardware_version_data = this->read_bytes<1>(0x21); | ||||||
|   | |||||||
							
								
								
									
										53
									
								
								esphome/components/cd74hc4067/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								esphome/components/cd74hc4067/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,53 @@ | |||||||
|  | import esphome.codegen as cg | ||||||
|  | from esphome import pins | ||||||
|  | import esphome.config_validation as cv | ||||||
|  | from esphome.const import ( | ||||||
|  |     CONF_DELAY, | ||||||
|  |     CONF_ID, | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | CODEOWNERS = ["@asoehlke"] | ||||||
|  | AUTO_LOAD = ["sensor", "voltage_sampler"] | ||||||
|  |  | ||||||
|  | cd74hc4067_ns = cg.esphome_ns.namespace("cd74hc4067") | ||||||
|  |  | ||||||
|  | CD74HC4067Component = cd74hc4067_ns.class_( | ||||||
|  |     "CD74HC4067Component", cg.Component, cg.PollingComponent | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | CONF_PIN_S0 = "pin_s0" | ||||||
|  | CONF_PIN_S1 = "pin_s1" | ||||||
|  | CONF_PIN_S2 = "pin_s2" | ||||||
|  | CONF_PIN_S3 = "pin_s3" | ||||||
|  |  | ||||||
|  | DEFAULT_DELAY = "2ms" | ||||||
|  |  | ||||||
|  |  | ||||||
|  | CONFIG_SCHEMA = cv.Schema( | ||||||
|  |     { | ||||||
|  |         cv.GenerateID(): cv.declare_id(CD74HC4067Component), | ||||||
|  |         cv.Required(CONF_PIN_S0): pins.internal_gpio_output_pin_schema, | ||||||
|  |         cv.Required(CONF_PIN_S1): pins.internal_gpio_output_pin_schema, | ||||||
|  |         cv.Required(CONF_PIN_S2): pins.internal_gpio_output_pin_schema, | ||||||
|  |         cv.Required(CONF_PIN_S3): pins.internal_gpio_output_pin_schema, | ||||||
|  |         cv.Optional( | ||||||
|  |             CONF_DELAY, default=DEFAULT_DELAY | ||||||
|  |         ): cv.positive_time_period_milliseconds, | ||||||
|  |     } | ||||||
|  | ).extend(cv.COMPONENT_SCHEMA) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | async def to_code(config): | ||||||
|  |     var = cg.new_Pvariable(config[CONF_ID]) | ||||||
|  |     await cg.register_component(var, config) | ||||||
|  |  | ||||||
|  |     pin_s0 = await cg.gpio_pin_expression(config[CONF_PIN_S0]) | ||||||
|  |     cg.add(var.set_pin_s0(pin_s0)) | ||||||
|  |     pin_s1 = await cg.gpio_pin_expression(config[CONF_PIN_S1]) | ||||||
|  |     cg.add(var.set_pin_s1(pin_s1)) | ||||||
|  |     pin_s2 = await cg.gpio_pin_expression(config[CONF_PIN_S2]) | ||||||
|  |     cg.add(var.set_pin_s2(pin_s2)) | ||||||
|  |     pin_s3 = await cg.gpio_pin_expression(config[CONF_PIN_S3]) | ||||||
|  |     cg.add(var.set_pin_s3(pin_s3)) | ||||||
|  |  | ||||||
|  |     cg.add(var.set_switch_delay(config[CONF_DELAY])) | ||||||
							
								
								
									
										86
									
								
								esphome/components/cd74hc4067/cd74hc4067.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								esphome/components/cd74hc4067/cd74hc4067.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,86 @@ | |||||||
|  | #include "cd74hc4067.h" | ||||||
|  | #include "esphome/core/log.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace cd74hc4067 { | ||||||
|  |  | ||||||
|  | static const char *const TAG = "cd74hc4067"; | ||||||
|  |  | ||||||
|  | float CD74HC4067Component::get_setup_priority() const { return setup_priority::DATA; } | ||||||
|  |  | ||||||
|  | void CD74HC4067Component::setup() { | ||||||
|  |   ESP_LOGCONFIG(TAG, "Setting up CD74HC4067..."); | ||||||
|  |  | ||||||
|  |   this->pin_s0_->setup(); | ||||||
|  |   this->pin_s1_->setup(); | ||||||
|  |   this->pin_s2_->setup(); | ||||||
|  |   this->pin_s3_->setup(); | ||||||
|  |  | ||||||
|  |   // set other pin, so that activate_pin will really switch | ||||||
|  |   this->active_pin_ = 1; | ||||||
|  |   this->activate_pin(0); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void CD74HC4067Component::dump_config() { | ||||||
|  |   ESP_LOGCONFIG(TAG, "CD74HC4067 Multiplexer:"); | ||||||
|  |   LOG_PIN("  S0 Pin: ", this->pin_s0_); | ||||||
|  |   LOG_PIN("  S1 Pin: ", this->pin_s1_); | ||||||
|  |   LOG_PIN("  S2 Pin: ", this->pin_s2_); | ||||||
|  |   LOG_PIN("  S3 Pin: ", this->pin_s3_); | ||||||
|  |   ESP_LOGCONFIG(TAG, "switch delay: %d", this->switch_delay_); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void CD74HC4067Component::activate_pin(uint8_t pin) { | ||||||
|  |   if (this->active_pin_ != pin) { | ||||||
|  |     ESP_LOGD(TAG, "switch to input %d", pin); | ||||||
|  |  | ||||||
|  |     static int mux_channel[16][4] = { | ||||||
|  |         {0, 0, 0, 0},  // channel 0 | ||||||
|  |         {1, 0, 0, 0},  // channel 1 | ||||||
|  |         {0, 1, 0, 0},  // channel 2 | ||||||
|  |         {1, 1, 0, 0},  // channel 3 | ||||||
|  |         {0, 0, 1, 0},  // channel 4 | ||||||
|  |         {1, 0, 1, 0},  // channel 5 | ||||||
|  |         {0, 1, 1, 0},  // channel 6 | ||||||
|  |         {1, 1, 1, 0},  // channel 7 | ||||||
|  |         {0, 0, 0, 1},  // channel 8 | ||||||
|  |         {1, 0, 0, 1},  // channel 9 | ||||||
|  |         {0, 1, 0, 1},  // channel 10 | ||||||
|  |         {1, 1, 0, 1},  // channel 11 | ||||||
|  |         {0, 0, 1, 1},  // channel 12 | ||||||
|  |         {1, 0, 1, 1},  // channel 13 | ||||||
|  |         {0, 1, 1, 1},  // channel 14 | ||||||
|  |         {1, 1, 1, 1}   // channel 15 | ||||||
|  |     }; | ||||||
|  |     this->pin_s0_->digital_write(mux_channel[pin][0]); | ||||||
|  |     this->pin_s1_->digital_write(mux_channel[pin][1]); | ||||||
|  |     this->pin_s2_->digital_write(mux_channel[pin][2]); | ||||||
|  |     this->pin_s3_->digital_write(mux_channel[pin][3]); | ||||||
|  |     // small delay is needed to let the multiplexer switch | ||||||
|  |     delay(this->switch_delay_); | ||||||
|  |     this->active_pin_ = pin; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | CD74HC4067Sensor::CD74HC4067Sensor(CD74HC4067Component *parent) : parent_(parent) {} | ||||||
|  |  | ||||||
|  | void CD74HC4067Sensor::update() { | ||||||
|  |   float value_v = this->sample(); | ||||||
|  |   this->publish_state(value_v); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | float CD74HC4067Sensor::get_setup_priority() const { return this->parent_->get_setup_priority() - 1.0f; } | ||||||
|  |  | ||||||
|  | float CD74HC4067Sensor::sample() { | ||||||
|  |   this->parent_->activate_pin(this->pin_); | ||||||
|  |   return this->source_->sample(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void CD74HC4067Sensor::dump_config() { | ||||||
|  |   LOG_SENSOR(TAG, "CD74HC4067 Sensor", this); | ||||||
|  |   ESP_LOGCONFIG(TAG, "  Pin: %u", this->pin_); | ||||||
|  |   LOG_UPDATE_INTERVAL(this); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | }  // namespace cd74hc4067 | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										65
									
								
								esphome/components/cd74hc4067/cd74hc4067.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								esphome/components/cd74hc4067/cd74hc4067.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,65 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include "esphome/core/component.h" | ||||||
|  | #include "esphome/core/hal.h" | ||||||
|  | #include "esphome/components/sensor/sensor.h" | ||||||
|  | #include "esphome/components/voltage_sampler/voltage_sampler.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace cd74hc4067 { | ||||||
|  |  | ||||||
|  | class CD74HC4067Component : public Component { | ||||||
|  |  public: | ||||||
|  |   /// Set up the internal sensor array. | ||||||
|  |   void setup() override; | ||||||
|  |   void dump_config() override; | ||||||
|  |   float get_setup_priority() const override; | ||||||
|  |  | ||||||
|  |   /// setting pin active by setting the right combination of the four multiplexer input pins | ||||||
|  |   void activate_pin(uint8_t pin); | ||||||
|  |  | ||||||
|  |   /// set the pin connected to multiplexer control pin 0 | ||||||
|  |   void set_pin_s0(InternalGPIOPin *pin) { this->pin_s0_ = pin; } | ||||||
|  |   /// set the pin connected to multiplexer control pin 1 | ||||||
|  |   void set_pin_s1(InternalGPIOPin *pin) { this->pin_s1_ = pin; } | ||||||
|  |   /// set the pin connected to multiplexer control pin 2 | ||||||
|  |   void set_pin_s2(InternalGPIOPin *pin) { this->pin_s2_ = pin; } | ||||||
|  |   /// set the pin connected to multiplexer control pin 3 | ||||||
|  |   void set_pin_s3(InternalGPIOPin *pin) { this->pin_s3_ = pin; } | ||||||
|  |  | ||||||
|  |   /// set the delay needed after an input switch | ||||||
|  |   void set_switch_delay(uint32_t switch_delay) { this->switch_delay_ = switch_delay; } | ||||||
|  |  | ||||||
|  |  private: | ||||||
|  |   InternalGPIOPin *pin_s0_; | ||||||
|  |   InternalGPIOPin *pin_s1_; | ||||||
|  |   InternalGPIOPin *pin_s2_; | ||||||
|  |   InternalGPIOPin *pin_s3_; | ||||||
|  |   /// the currently active pin | ||||||
|  |   uint8_t active_pin_; | ||||||
|  |   uint32_t switch_delay_; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | class CD74HC4067Sensor : public sensor::Sensor, public PollingComponent, public voltage_sampler::VoltageSampler { | ||||||
|  |  public: | ||||||
|  |   CD74HC4067Sensor(CD74HC4067Component *parent); | ||||||
|  |  | ||||||
|  |   void update() override; | ||||||
|  |  | ||||||
|  |   void dump_config() override; | ||||||
|  |   /// `HARDWARE_LATE` setup priority. | ||||||
|  |   float get_setup_priority() const override; | ||||||
|  |   void set_pin(uint8_t pin) { this->pin_ = pin; } | ||||||
|  |   void set_source(voltage_sampler::VoltageSampler *source) { this->source_ = source; } | ||||||
|  |  | ||||||
|  |   float sample() override; | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   CD74HC4067Component *parent_; | ||||||
|  |   /// The sampling source to read values from. | ||||||
|  |   voltage_sampler::VoltageSampler *source_; | ||||||
|  |  | ||||||
|  |   uint8_t pin_; | ||||||
|  | }; | ||||||
|  | }  // namespace cd74hc4067 | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										55
									
								
								esphome/components/cd74hc4067/sensor.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								esphome/components/cd74hc4067/sensor.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,55 @@ | |||||||
|  | import esphome.codegen as cg | ||||||
|  | import esphome.config_validation as cv | ||||||
|  | from esphome.components import sensor, voltage_sampler | ||||||
|  | from esphome.const import ( | ||||||
|  |     CONF_ID, | ||||||
|  |     CONF_SENSOR, | ||||||
|  |     CONF_NUMBER, | ||||||
|  |     ICON_FLASH, | ||||||
|  |     UNIT_VOLT, | ||||||
|  |     STATE_CLASS_MEASUREMENT, | ||||||
|  |     DEVICE_CLASS_VOLTAGE, | ||||||
|  | ) | ||||||
|  | from . import cd74hc4067_ns, CD74HC4067Component | ||||||
|  |  | ||||||
|  | DEPENDENCIES = ["cd74hc4067"] | ||||||
|  |  | ||||||
|  | CD74HC4067Sensor = cd74hc4067_ns.class_( | ||||||
|  |     "CD74HC4067Sensor", | ||||||
|  |     sensor.Sensor, | ||||||
|  |     cg.PollingComponent, | ||||||
|  |     voltage_sampler.VoltageSampler, | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | CONF_CD74HC4067_ID = "cd74hc4067_id" | ||||||
|  |  | ||||||
|  | CONFIG_SCHEMA = ( | ||||||
|  |     sensor.sensor_schema( | ||||||
|  |         unit_of_measurement=UNIT_VOLT, | ||||||
|  |         accuracy_decimals=3, | ||||||
|  |         device_class=DEVICE_CLASS_VOLTAGE, | ||||||
|  |         state_class=STATE_CLASS_MEASUREMENT, | ||||||
|  |         icon=ICON_FLASH, | ||||||
|  |     ) | ||||||
|  |     .extend( | ||||||
|  |         { | ||||||
|  |             cv.GenerateID(): cv.declare_id(CD74HC4067Sensor), | ||||||
|  |             cv.GenerateID(CONF_CD74HC4067_ID): cv.use_id(CD74HC4067Component), | ||||||
|  |             cv.Required(CONF_NUMBER): cv.int_range(0, 15), | ||||||
|  |             cv.Required(CONF_SENSOR): cv.use_id(voltage_sampler.VoltageSampler), | ||||||
|  |         } | ||||||
|  |     ) | ||||||
|  |     .extend(cv.polling_component_schema("60s")) | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | async def to_code(config): | ||||||
|  |     parent = await cg.get_variable(config[CONF_CD74HC4067_ID]) | ||||||
|  |  | ||||||
|  |     var = cg.new_Pvariable(config[CONF_ID], parent) | ||||||
|  |     await cg.register_component(var, config) | ||||||
|  |     await sensor.register_sensor(var, config) | ||||||
|  |     cg.add(var.set_pin(config[CONF_NUMBER])) | ||||||
|  |  | ||||||
|  |     sens = await cg.get_variable(config[CONF_SENSOR]) | ||||||
|  |     cg.add(var.set_source(sens)) | ||||||
| @@ -20,6 +20,7 @@ from esphome.const import ( | |||||||
|     CONF_MODE, |     CONF_MODE, | ||||||
|     CONF_MODE_COMMAND_TOPIC, |     CONF_MODE_COMMAND_TOPIC, | ||||||
|     CONF_MODE_STATE_TOPIC, |     CONF_MODE_STATE_TOPIC, | ||||||
|  |     CONF_ON_STATE, | ||||||
|     CONF_PRESET, |     CONF_PRESET, | ||||||
|     CONF_SWING_MODE, |     CONF_SWING_MODE, | ||||||
|     CONF_SWING_MODE_COMMAND_TOPIC, |     CONF_SWING_MODE_COMMAND_TOPIC, | ||||||
| @@ -34,6 +35,7 @@ from esphome.const import ( | |||||||
|     CONF_TARGET_TEMPERATURE_LOW_COMMAND_TOPIC, |     CONF_TARGET_TEMPERATURE_LOW_COMMAND_TOPIC, | ||||||
|     CONF_TARGET_TEMPERATURE_LOW_STATE_TOPIC, |     CONF_TARGET_TEMPERATURE_LOW_STATE_TOPIC, | ||||||
|     CONF_TEMPERATURE_STEP, |     CONF_TEMPERATURE_STEP, | ||||||
|  |     CONF_TRIGGER_ID, | ||||||
|     CONF_VISUAL, |     CONF_VISUAL, | ||||||
|     CONF_MQTT_ID, |     CONF_MQTT_ID, | ||||||
| ) | ) | ||||||
| @@ -101,6 +103,7 @@ 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) | ||||||
|  | StateTrigger = climate_ns.class_("StateTrigger", automation.Trigger.template()) | ||||||
|  |  | ||||||
| CLIMATE_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend( | CLIMATE_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend( | ||||||
|     { |     { | ||||||
| @@ -161,6 +164,11 @@ CLIMATE_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA). | |||||||
|         cv.Optional(CONF_TARGET_TEMPERATURE_LOW_STATE_TOPIC): cv.All( |         cv.Optional(CONF_TARGET_TEMPERATURE_LOW_STATE_TOPIC): cv.All( | ||||||
|             cv.requires_component("mqtt"), cv.publish_topic |             cv.requires_component("mqtt"), cv.publish_topic | ||||||
|         ), |         ), | ||||||
|  |         cv.Optional(CONF_ON_STATE): automation.validate_automation( | ||||||
|  |             { | ||||||
|  |                 cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(StateTrigger), | ||||||
|  |             } | ||||||
|  |         ), | ||||||
|     } |     } | ||||||
| ) | ) | ||||||
|  |  | ||||||
| @@ -205,7 +213,7 @@ async def setup_climate_core_(var, config): | |||||||
|         if CONF_MODE_COMMAND_TOPIC in config: |         if CONF_MODE_COMMAND_TOPIC in config: | ||||||
|             cg.add(mqtt_.set_custom_mode_command_topic(config[CONF_MODE_COMMAND_TOPIC])) |             cg.add(mqtt_.set_custom_mode_command_topic(config[CONF_MODE_COMMAND_TOPIC])) | ||||||
|         if CONF_MODE_STATE_TOPIC in config: |         if CONF_MODE_STATE_TOPIC in config: | ||||||
|             cg.add(mqtt_.set_custom_state_topic(config[CONF_MODE_STATE_TOPIC])) |             cg.add(mqtt_.set_custom_mode_state_topic(config[CONF_MODE_STATE_TOPIC])) | ||||||
|  |  | ||||||
|         if CONF_SWING_MODE_COMMAND_TOPIC in config: |         if CONF_SWING_MODE_COMMAND_TOPIC in config: | ||||||
|             cg.add( |             cg.add( | ||||||
| @@ -256,6 +264,10 @@ async def setup_climate_core_(var, config): | |||||||
|                 ) |                 ) | ||||||
|             ) |             ) | ||||||
|  |  | ||||||
|  |     for conf in config.get(CONF_ON_STATE, []): | ||||||
|  |         trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) | ||||||
|  |         await automation.build_automation(trigger, [], conf) | ||||||
|  |  | ||||||
|  |  | ||||||
| async def register_climate(var, config): | async def register_climate(var, config): | ||||||
|     if not CORE.has_id(config[CONF_ID]): |     if not CORE.has_id(config[CONF_ID]): | ||||||
|   | |||||||
| @@ -42,5 +42,12 @@ template<typename... Ts> class ControlAction : public Action<Ts...> { | |||||||
|   Climate *climate_; |   Climate *climate_; | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | class StateTrigger : public Trigger<> { | ||||||
|  |  public: | ||||||
|  |   StateTrigger(Climate *climate) { | ||||||
|  |     climate->add_on_state_callback([this]() { this->trigger(); }); | ||||||
|  |   } | ||||||
|  | }; | ||||||
|  |  | ||||||
| }  // namespace climate | }  // namespace climate | ||||||
| }  // namespace esphome | }  // namespace esphome | ||||||
|   | |||||||
| @@ -440,7 +440,11 @@ void Climate::set_visual_max_temperature_override(float visual_max_temperature_o | |||||||
| void Climate::set_visual_temperature_step_override(float visual_temperature_step_override) { | void Climate::set_visual_temperature_step_override(float visual_temperature_step_override) { | ||||||
|   this->visual_temperature_step_override_ = visual_temperature_step_override; |   this->visual_temperature_step_override_ = visual_temperature_step_override; | ||||||
| } | } | ||||||
|  | #pragma GCC diagnostic push | ||||||
|  | #pragma GCC diagnostic ignored "-Wdeprecated-declarations" | ||||||
| Climate::Climate(const std::string &name) : EntityBase(name) {} | Climate::Climate(const std::string &name) : EntityBase(name) {} | ||||||
|  | #pragma GCC diagnostic pop | ||||||
|  |  | ||||||
| Climate::Climate() : Climate("") {} | Climate::Climate() : Climate("") {} | ||||||
| ClimateCall Climate::make_call() { return ClimateCall(this); } | ClimateCall Climate::make_call() { return ClimateCall(this); } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -10,21 +10,22 @@ climate::ClimateTraits ClimateIR::traits() { | |||||||
|   auto traits = climate::ClimateTraits(); |   auto traits = climate::ClimateTraits(); | ||||||
|   traits.set_supports_current_temperature(this->sensor_ != nullptr); |   traits.set_supports_current_temperature(this->sensor_ != nullptr); | ||||||
|   traits.set_supported_modes({climate::CLIMATE_MODE_OFF, climate::CLIMATE_MODE_HEAT_COOL}); |   traits.set_supported_modes({climate::CLIMATE_MODE_OFF, climate::CLIMATE_MODE_HEAT_COOL}); | ||||||
|   if (supports_cool_) |   if (this->supports_cool_) | ||||||
|     traits.add_supported_mode(climate::CLIMATE_MODE_COOL); |     traits.add_supported_mode(climate::CLIMATE_MODE_COOL); | ||||||
|   if (supports_heat_) |   if (this->supports_heat_) | ||||||
|     traits.add_supported_mode(climate::CLIMATE_MODE_HEAT); |     traits.add_supported_mode(climate::CLIMATE_MODE_HEAT); | ||||||
|   if (supports_dry_) |   if (this->supports_dry_) | ||||||
|     traits.add_supported_mode(climate::CLIMATE_MODE_DRY); |     traits.add_supported_mode(climate::CLIMATE_MODE_DRY); | ||||||
|   if (supports_fan_only_) |   if (this->supports_fan_only_) | ||||||
|     traits.add_supported_mode(climate::CLIMATE_MODE_FAN_ONLY); |     traits.add_supported_mode(climate::CLIMATE_MODE_FAN_ONLY); | ||||||
|  |  | ||||||
|   traits.set_supports_two_point_target_temperature(false); |   traits.set_supports_two_point_target_temperature(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_); | ||||||
|   traits.set_supported_fan_modes(fan_modes_); |   traits.set_supported_fan_modes(this->fan_modes_); | ||||||
|   traits.set_supported_swing_modes(swing_modes_); |   traits.set_supported_swing_modes(this->swing_modes_); | ||||||
|  |   traits.set_supported_presets(this->presets_); | ||||||
|   return traits; |   return traits; | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -50,6 +51,7 @@ void ClimateIR::setup() { | |||||||
|         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->fan_mode = climate::CLIMATE_FAN_AUTO; | ||||||
|     this->swing_mode = climate::CLIMATE_SWING_OFF; |     this->swing_mode = climate::CLIMATE_SWING_OFF; | ||||||
|  |     this->preset = climate::CLIMATE_PRESET_NONE; | ||||||
|   } |   } | ||||||
|   // Never send nan to HA |   // Never send nan to HA | ||||||
|   if (std::isnan(this->target_temperature)) |   if (std::isnan(this->target_temperature)) | ||||||
| @@ -65,6 +67,8 @@ void ClimateIR::control(const climate::ClimateCall &call) { | |||||||
|     this->fan_mode = *call.get_fan_mode(); |     this->fan_mode = *call.get_fan_mode(); | ||||||
|   if (call.get_swing_mode().has_value()) |   if (call.get_swing_mode().has_value()) | ||||||
|     this->swing_mode = *call.get_swing_mode(); |     this->swing_mode = *call.get_swing_mode(); | ||||||
|  |   if (call.get_preset().has_value()) | ||||||
|  |     this->preset = *call.get_preset(); | ||||||
|   this->transmit_state(); |   this->transmit_state(); | ||||||
|   this->publish_state(); |   this->publish_state(); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -22,7 +22,7 @@ class ClimateIR : public climate::Climate, public Component, public remote_base: | |||||||
|  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::set<climate::ClimateFanMode> fan_modes = {}, |             bool supports_dry = false, bool supports_fan_only = false, std::set<climate::ClimateFanMode> fan_modes = {}, | ||||||
|             std::set<climate::ClimateSwingMode> swing_modes = {}) { |             std::set<climate::ClimateSwingMode> swing_modes = {}, std::set<climate::ClimatePreset> presets = {}) { | ||||||
|     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; | ||||||
| @@ -30,6 +30,7 @@ class ClimateIR : public climate::Climate, public Component, public remote_base: | |||||||
|     this->supports_fan_only_ = supports_fan_only; |     this->supports_fan_only_ = supports_fan_only; | ||||||
|     this->fan_modes_ = std::move(fan_modes); |     this->fan_modes_ = std::move(fan_modes); | ||||||
|     this->swing_modes_ = std::move(swing_modes); |     this->swing_modes_ = std::move(swing_modes); | ||||||
|  |     this->presets_ = std::move(presets); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   void setup() override; |   void setup() override; | ||||||
| @@ -61,6 +62,7 @@ class ClimateIR : public climate::Climate, public Component, public remote_base: | |||||||
|   bool supports_fan_only_{false}; |   bool supports_fan_only_{false}; | ||||||
|   std::set<climate::ClimateFanMode> fan_modes_ = {}; |   std::set<climate::ClimateFanMode> fan_modes_ = {}; | ||||||
|   std::set<climate::ClimateSwingMode> swing_modes_ = {}; |   std::set<climate::ClimateSwingMode> swing_modes_ = {}; | ||||||
|  |   std::set<climate::ClimatePreset> presets_ = {}; | ||||||
|  |  | ||||||
|   remote_transmitter::RemoteTransmitterComponent *transmitter_; |   remote_transmitter::RemoteTransmitterComponent *transmitter_; | ||||||
|   sensor::Sensor *sensor_{nullptr}; |   sensor::Sensor *sensor_{nullptr}; | ||||||
|   | |||||||
| @@ -1,4 +1,5 @@ | |||||||
| #include "coolix.h" | #include "coolix.h" | ||||||
|  | #include "esphome/components/remote_base/coolix_protocol.h" | ||||||
| #include "esphome/core/log.h" | #include "esphome/core/log.h" | ||||||
|  |  | ||||||
| namespace esphome { | namespace esphome { | ||||||
| @@ -6,29 +7,29 @@ namespace coolix { | |||||||
|  |  | ||||||
| static const char *const TAG = "coolix.climate"; | static const char *const TAG = "coolix.climate"; | ||||||
|  |  | ||||||
| const uint32_t COOLIX_OFF = 0xB27BE0; | static const uint32_t COOLIX_OFF = 0xB27BE0; | ||||||
| const uint32_t COOLIX_SWING = 0xB26BE0; | static const uint32_t COOLIX_SWING = 0xB26BE0; | ||||||
| const uint32_t COOLIX_LED = 0xB5F5A5; | static const uint32_t COOLIX_LED = 0xB5F5A5; | ||||||
| const uint32_t COOLIX_SILENCE_FP = 0xB5F5B6; | static const uint32_t COOLIX_SILENCE_FP = 0xB5F5B6; | ||||||
|  |  | ||||||
| // On, 25C, Mode: Auto, Fan: Auto, Zone Follow: Off, Sensor Temp: Ignore. | // On, 25C, Mode: Auto, Fan: Auto, Zone Follow: Off, Sensor Temp: Ignore. | ||||||
| const uint8_t COOLIX_COOL = 0b0000; | static const uint8_t COOLIX_COOL = 0b0000; | ||||||
| const uint8_t COOLIX_DRY_FAN = 0b0100; | static const uint8_t COOLIX_DRY_FAN = 0b0100; | ||||||
| const uint8_t COOLIX_AUTO = 0b1000; | static const uint8_t COOLIX_AUTO = 0b1000; | ||||||
| const uint8_t COOLIX_HEAT = 0b1100; | static const uint8_t COOLIX_HEAT = 0b1100; | ||||||
| const uint32_t COOLIX_MODE_MASK = 0b1100; | static const uint32_t COOLIX_MODE_MASK = 0b1100; | ||||||
| const uint32_t COOLIX_FAN_MASK = 0xF000; | static const uint32_t COOLIX_FAN_MASK = 0xF000; | ||||||
| const uint32_t COOLIX_FAN_MODE_AUTO_DRY = 0x1000; | static const uint32_t COOLIX_FAN_MODE_AUTO_DRY = 0x1000; | ||||||
| const uint32_t COOLIX_FAN_AUTO = 0xB000; | static const uint32_t COOLIX_FAN_AUTO = 0xB000; | ||||||
| const uint32_t COOLIX_FAN_MIN = 0x9000; | static const uint32_t COOLIX_FAN_MIN = 0x9000; | ||||||
| const uint32_t COOLIX_FAN_MED = 0x5000; | static const uint32_t COOLIX_FAN_MED = 0x5000; | ||||||
| const uint32_t COOLIX_FAN_MAX = 0x3000; | static const uint32_t COOLIX_FAN_MAX = 0x3000; | ||||||
|  |  | ||||||
| // Temperature | // Temperature | ||||||
| const uint8_t COOLIX_TEMP_RANGE = COOLIX_TEMP_MAX - COOLIX_TEMP_MIN + 1; | static const uint8_t COOLIX_TEMP_RANGE = COOLIX_TEMP_MAX - COOLIX_TEMP_MIN + 1; | ||||||
| const uint8_t COOLIX_FAN_TEMP_CODE = 0b11100000;  // Part of Fan Mode. | static const uint8_t COOLIX_FAN_TEMP_CODE = 0b11100000;  // Part of Fan Mode. | ||||||
| const uint32_t COOLIX_TEMP_MASK = 0b11110000; | static const uint32_t COOLIX_TEMP_MASK = 0b11110000; | ||||||
| const uint8_t COOLIX_TEMP_MAP[COOLIX_TEMP_RANGE] = { | static const uint8_t COOLIX_TEMP_MAP[COOLIX_TEMP_RANGE] = { | ||||||
|     0b00000000,  // 17C |     0b00000000,  // 17C | ||||||
|     0b00010000,  // 18c |     0b00010000,  // 18c | ||||||
|     0b00110000,  // 19C |     0b00110000,  // 19C | ||||||
| @@ -45,17 +46,6 @@ const uint8_t COOLIX_TEMP_MAP[COOLIX_TEMP_RANGE] = { | |||||||
|     0b10110000   // 30C |     0b10110000   // 30C | ||||||
| }; | }; | ||||||
|  |  | ||||||
| // Constants |  | ||||||
| static const uint32_t BIT_MARK_US = 660; |  | ||||||
| static const uint32_t HEADER_MARK_US = 560 * 8; |  | ||||||
| static const uint32_t HEADER_SPACE_US = 560 * 8; |  | ||||||
| static const uint32_t BIT_ONE_SPACE_US = 1500; |  | ||||||
| static const uint32_t BIT_ZERO_SPACE_US = 450; |  | ||||||
| static const uint32_t FOOTER_MARK_US = BIT_MARK_US; |  | ||||||
| static const uint32_t FOOTER_SPACE_US = HEADER_SPACE_US; |  | ||||||
|  |  | ||||||
| const uint16_t COOLIX_BITS = 24; |  | ||||||
|  |  | ||||||
| void CoolixClimate::transmit_state() { | void CoolixClimate::transmit_state() { | ||||||
|   uint32_t remote_state = 0xB20F00; |   uint32_t remote_state = 0xB20F00; | ||||||
|  |  | ||||||
| @@ -111,119 +101,60 @@ void CoolixClimate::transmit_state() { | |||||||
|       } |       } | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|   ESP_LOGV(TAG, "Sending coolix code: 0x%02X", remote_state); |   ESP_LOGV(TAG, "Sending coolix code: 0x%06X", remote_state); | ||||||
|  |  | ||||||
|   auto transmit = this->transmitter_->transmit(); |   auto transmit = this->transmitter_->transmit(); | ||||||
|   auto data = transmit.get_data(); |   auto data = transmit.get_data(); | ||||||
|  |   remote_base::CoolixProtocol().encode(data, remote_state); | ||||||
|   data->set_carrier_frequency(38000); |  | ||||||
|   uint16_t repeat = 1; |  | ||||||
|   for (uint16_t r = 0; r <= repeat; r++) { |  | ||||||
|     // Header |  | ||||||
|     data->mark(HEADER_MARK_US); |  | ||||||
|     data->space(HEADER_SPACE_US); |  | ||||||
|     // Data |  | ||||||
|     //   Break data into bytes, starting at the Most Significant |  | ||||||
|     //   Byte. Each byte then being sent normal, then followed inverted. |  | ||||||
|     for (uint16_t i = 8; i <= COOLIX_BITS; i += 8) { |  | ||||||
|       // Grab a bytes worth of data. |  | ||||||
|       uint8_t byte = (remote_state >> (COOLIX_BITS - i)) & 0xFF; |  | ||||||
|       // Normal |  | ||||||
|       for (uint64_t mask = 1ULL << 7; mask; mask >>= 1) { |  | ||||||
|         data->mark(BIT_MARK_US); |  | ||||||
|         data->space((byte & mask) ? BIT_ONE_SPACE_US : BIT_ZERO_SPACE_US); |  | ||||||
|       } |  | ||||||
|       // Inverted |  | ||||||
|       for (uint64_t mask = 1ULL << 7; mask; mask >>= 1) { |  | ||||||
|         data->mark(BIT_MARK_US); |  | ||||||
|         data->space(!(byte & mask) ? BIT_ONE_SPACE_US : BIT_ZERO_SPACE_US); |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|     // Footer |  | ||||||
|     data->mark(BIT_MARK_US); |  | ||||||
|     data->space(FOOTER_SPACE_US);  // Pause before repeating |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   transmit.perform(); |   transmit.perform(); | ||||||
| } | } | ||||||
|  |  | ||||||
| bool CoolixClimate::on_receive(remote_base::RemoteReceiveData data) { | bool CoolixClimate::on_coolix(climate::Climate *parent, remote_base::RemoteReceiveData data) { | ||||||
|  |   auto decoded = remote_base::CoolixProtocol().decode(data); | ||||||
|  |   if (!decoded.has_value()) | ||||||
|  |     return false; | ||||||
|   // Decoded remote state y 3 bytes long code. |   // Decoded remote state y 3 bytes long code. | ||||||
|   uint32_t remote_state = 0; |   uint32_t remote_state = *decoded; | ||||||
|   // The protocol sends the data twice, read here |   ESP_LOGV(TAG, "Decoded 0x%06X", remote_state); | ||||||
|   uint32_t loop_read; |   if ((remote_state & 0xFF0000) != 0xB20000) | ||||||
|   for (uint16_t loop = 1; loop <= 2; loop++) { |  | ||||||
|     if (!data.expect_item(HEADER_MARK_US, HEADER_SPACE_US)) |  | ||||||
|       return false; |  | ||||||
|     loop_read = 0; |  | ||||||
|     for (uint8_t a_byte = 0; a_byte < 3; a_byte++) { |  | ||||||
|       uint8_t byte = 0; |  | ||||||
|       for (int8_t a_bit = 7; a_bit >= 0; a_bit--) { |  | ||||||
|         if (data.expect_item(BIT_MARK_US, BIT_ONE_SPACE_US)) |  | ||||||
|           byte |= 1 << a_bit; |  | ||||||
|         else if (!data.expect_item(BIT_MARK_US, BIT_ZERO_SPACE_US)) |  | ||||||
|           return false; |  | ||||||
|       } |  | ||||||
|       // Need to see this segment inverted |  | ||||||
|       for (int8_t a_bit = 7; a_bit >= 0; a_bit--) { |  | ||||||
|         bool bit = byte & (1 << a_bit); |  | ||||||
|         if (!data.expect_item(BIT_MARK_US, bit ? BIT_ZERO_SPACE_US : BIT_ONE_SPACE_US)) |  | ||||||
|           return false; |  | ||||||
|       } |  | ||||||
|       // Receiving MSB first: reorder bytes |  | ||||||
|       loop_read |= byte << ((2 - a_byte) * 8); |  | ||||||
|     } |  | ||||||
|     // Footer Mark |  | ||||||
|     if (!data.expect_mark(BIT_MARK_US)) |  | ||||||
|       return false; |  | ||||||
|     if (loop == 1) { |  | ||||||
|       // Back up state on first loop |  | ||||||
|       remote_state = loop_read; |  | ||||||
|       if (!data.expect_space(FOOTER_SPACE_US)) |  | ||||||
|         return false; |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   ESP_LOGV(TAG, "Decoded 0x%02X", remote_state); |  | ||||||
|   if (remote_state != loop_read || (remote_state & 0xFF0000) != 0xB20000) |  | ||||||
|     return false; |     return false; | ||||||
|  |  | ||||||
|   if (remote_state == COOLIX_OFF) { |   if (remote_state == COOLIX_OFF) { | ||||||
|     this->mode = climate::CLIMATE_MODE_OFF; |     parent->mode = climate::CLIMATE_MODE_OFF; | ||||||
|   } else if (remote_state == COOLIX_SWING) { |   } else if (remote_state == COOLIX_SWING) { | ||||||
|     this->swing_mode = |     parent->swing_mode = | ||||||
|         this->swing_mode == climate::CLIMATE_SWING_OFF ? climate::CLIMATE_SWING_VERTICAL : climate::CLIMATE_SWING_OFF; |         parent->swing_mode == climate::CLIMATE_SWING_OFF ? climate::CLIMATE_SWING_VERTICAL : climate::CLIMATE_SWING_OFF; | ||||||
|   } else { |   } else { | ||||||
|     if ((remote_state & COOLIX_MODE_MASK) == COOLIX_HEAT) |     if ((remote_state & COOLIX_MODE_MASK) == COOLIX_HEAT) | ||||||
|       this->mode = climate::CLIMATE_MODE_HEAT; |       parent->mode = climate::CLIMATE_MODE_HEAT; | ||||||
|     else if ((remote_state & COOLIX_MODE_MASK) == COOLIX_AUTO) |     else if ((remote_state & COOLIX_MODE_MASK) == COOLIX_AUTO) | ||||||
|       this->mode = climate::CLIMATE_MODE_HEAT_COOL; |       parent->mode = climate::CLIMATE_MODE_HEAT_COOL; | ||||||
|     else if ((remote_state & COOLIX_MODE_MASK) == COOLIX_DRY_FAN) { |     else if ((remote_state & COOLIX_MODE_MASK) == COOLIX_DRY_FAN) { | ||||||
|       if ((remote_state & COOLIX_FAN_MASK) == COOLIX_FAN_MODE_AUTO_DRY) |       if ((remote_state & COOLIX_FAN_MASK) == COOLIX_FAN_MODE_AUTO_DRY) | ||||||
|         this->mode = climate::CLIMATE_MODE_DRY; |         parent->mode = climate::CLIMATE_MODE_DRY; | ||||||
|       else |       else | ||||||
|         this->mode = climate::CLIMATE_MODE_FAN_ONLY; |         parent->mode = climate::CLIMATE_MODE_FAN_ONLY; | ||||||
|     } else |     } else | ||||||
|       this->mode = climate::CLIMATE_MODE_COOL; |       parent->mode = climate::CLIMATE_MODE_COOL; | ||||||
|  |  | ||||||
|     // Fan Speed |     // Fan Speed | ||||||
|     if ((remote_state & COOLIX_FAN_AUTO) == COOLIX_FAN_AUTO || this->mode == climate::CLIMATE_MODE_HEAT_COOL || |     if ((remote_state & COOLIX_FAN_AUTO) == COOLIX_FAN_AUTO || parent->mode == climate::CLIMATE_MODE_HEAT_COOL || | ||||||
|         this->mode == climate::CLIMATE_MODE_DRY) |         parent->mode == climate::CLIMATE_MODE_DRY) | ||||||
|       this->fan_mode = climate::CLIMATE_FAN_AUTO; |       parent->fan_mode = climate::CLIMATE_FAN_AUTO; | ||||||
|     else if ((remote_state & COOLIX_FAN_MIN) == COOLIX_FAN_MIN) |     else if ((remote_state & COOLIX_FAN_MIN) == COOLIX_FAN_MIN) | ||||||
|       this->fan_mode = climate::CLIMATE_FAN_LOW; |       parent->fan_mode = climate::CLIMATE_FAN_LOW; | ||||||
|     else if ((remote_state & COOLIX_FAN_MED) == COOLIX_FAN_MED) |     else if ((remote_state & COOLIX_FAN_MED) == COOLIX_FAN_MED) | ||||||
|       this->fan_mode = climate::CLIMATE_FAN_MEDIUM; |       parent->fan_mode = climate::CLIMATE_FAN_MEDIUM; | ||||||
|     else if ((remote_state & COOLIX_FAN_MAX) == COOLIX_FAN_MAX) |     else if ((remote_state & COOLIX_FAN_MAX) == COOLIX_FAN_MAX) | ||||||
|       this->fan_mode = climate::CLIMATE_FAN_HIGH; |       parent->fan_mode = climate::CLIMATE_FAN_HIGH; | ||||||
|  |  | ||||||
|     // Temperature |     // Temperature | ||||||
|     uint8_t temperature_code = remote_state & COOLIX_TEMP_MASK; |     uint8_t temperature_code = remote_state & COOLIX_TEMP_MASK; | ||||||
|     for (uint8_t i = 0; i < COOLIX_TEMP_RANGE; i++) |     for (uint8_t i = 0; i < COOLIX_TEMP_RANGE; i++) | ||||||
|       if (COOLIX_TEMP_MAP[i] == temperature_code) |       if (COOLIX_TEMP_MAP[i] == temperature_code) | ||||||
|         this->target_temperature = i + COOLIX_TEMP_MIN; |         parent->target_temperature = i + COOLIX_TEMP_MIN; | ||||||
|   } |   } | ||||||
|   this->publish_state(); |   parent->publish_state(); | ||||||
|  |  | ||||||
|   return true; |   return true; | ||||||
| } | } | ||||||
|   | |||||||
| @@ -26,11 +26,15 @@ class CoolixClimate : public climate_ir::ClimateIR { | |||||||
|     climate_ir::ClimateIR::control(call); |     climate_ir::ClimateIR::control(call); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   /// This static method can be used in other climate components that accept the Coolix protocol. See midea_ir for | ||||||
|  |   /// example. | ||||||
|  |   static bool on_coolix(climate::Climate *parent, remote_base::RemoteReceiveData data); | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   /// Transmit via IR the state of this climate controller. |   /// Transmit via IR the state of this climate controller. | ||||||
|   void transmit_state() override; |   void transmit_state() override; | ||||||
|   /// Handle received IR Buffer |   /// Handle received IR Buffer | ||||||
|   bool on_receive(remote_base::RemoteReceiveData data) override; |   bool on_receive(remote_base::RemoteReceiveData data) override { return CoolixClimate::on_coolix(this, data); } | ||||||
|  |  | ||||||
|   bool send_swing_cmd_{false}; |   bool send_swing_cmd_{false}; | ||||||
| }; | }; | ||||||
|   | |||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user