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-*, | ||||
|   -android-*, | ||||
|   -boost-*, | ||||
|   -bugprone-branch-clone, | ||||
|   -bugprone-easily-swappable-parameters, | ||||
|   -bugprone-narrowing-conversions, | ||||
|   -bugprone-signed-char-misuse, | ||||
|   -bugprone-too-small-loop-variable, | ||||
|   -cert-dcl50-cpp, | ||||
|   -cert-err58-cpp, | ||||
|   -cert-oop57-cpp, | ||||
| @@ -19,12 +16,10 @@ Checks: >- | ||||
|   -clang-diagnostic-delete-abstract-non-virtual-dtor, | ||||
|   -clang-diagnostic-delete-non-abstract-non-virtual-dtor, | ||||
|   -clang-diagnostic-shadow-field, | ||||
|   -clang-diagnostic-sign-compare, | ||||
|   -clang-diagnostic-unused-variable, | ||||
|   -clang-diagnostic-unused-const-variable, | ||||
|   -clang-diagnostic-unused-parameter, | ||||
|   -concurrency-*, | ||||
|   -cppcoreguidelines-avoid-c-arrays, | ||||
|   -cppcoreguidelines-avoid-goto, | ||||
|   -cppcoreguidelines-avoid-magic-numbers, | ||||
|   -cppcoreguidelines-init-variables, | ||||
|   -cppcoreguidelines-macro-usage, | ||||
| @@ -41,7 +36,6 @@ Checks: >- | ||||
|   -cppcoreguidelines-pro-type-union-access, | ||||
|   -cppcoreguidelines-pro-type-vararg, | ||||
|   -cppcoreguidelines-special-member-functions, | ||||
|   -fuchsia-default-arguments, | ||||
|   -fuchsia-multiple-inheritance, | ||||
|   -fuchsia-overloaded-operator, | ||||
|   -fuchsia-statically-constructed-objects, | ||||
| @@ -51,6 +45,7 @@ Checks: >- | ||||
|   -google-explicit-constructor, | ||||
|   -google-readability-braces-around-statements, | ||||
|   -google-readability-casting, | ||||
|   -google-readability-namespace-comments, | ||||
|   -google-readability-todo, | ||||
|   -google-runtime-references, | ||||
|   -hicpp-*, | ||||
| @@ -97,9 +92,11 @@ CheckOptions: | ||||
|     value:           '1' | ||||
|   - key:             google-readability-function-size.StatementThreshold | ||||
|     value:           '800' | ||||
|   - key:             google-readability-namespace-comments.ShortNamespaceLines | ||||
|   - key:             google-runtime-int.TypeSuffix | ||||
|     value:           '_t' | ||||
|   - key:             llvm-namespace-comment.ShortNamespaceLines | ||||
|     value:           '10' | ||||
|   - key:             google-readability-namespace-comments.SpacesBeforeComments | ||||
|   - key:             llvm-namespace-comment.SpacesBeforeComments | ||||
|     value:           '2' | ||||
|   - key:             modernize-loop-convert.MaxCopySize | ||||
|     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 | ||||
|  | ||||
| - [ ] ESP32 | ||||
| - [ ] ESP32 IDF | ||||
| - [ ] ESP8266 | ||||
|  | ||||
| ## 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' | ||||
|       - 'platformio.ini' | ||||
|  | ||||
| permissions: | ||||
|   contents: read | ||||
|   packages: read | ||||
|  | ||||
| jobs: | ||||
|   check-docker: | ||||
|     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 | ||||
|  | ||||
| on: | ||||
| @@ -8,6 +6,13 @@ on: | ||||
|  | ||||
|   pull_request: | ||||
|  | ||||
| permissions: | ||||
|   contents: read | ||||
|  | ||||
| concurrency: | ||||
|   group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} | ||||
|   cancel-in-progress: true | ||||
|  | ||||
| jobs: | ||||
|   ci: | ||||
|     name: ${{ matrix.name }} | ||||
| @@ -31,7 +36,7 @@ jobs: | ||||
|           - id: test | ||||
|             file: tests/test3.yaml | ||||
|             name: Test tests/test3.yaml | ||||
|             pio_cache_key: test1 | ||||
|             pio_cache_key: test3 | ||||
|           - id: test | ||||
|             file: tests/test4.yaml | ||||
|             name: Test tests/test4.yaml | ||||
| @@ -46,26 +51,26 @@ jobs: | ||||
|             name: Run script/clang-format | ||||
|           - id: clang-tidy | ||||
|             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 | ||||
|           - id: clang-tidy | ||||
|             name: Run script/clang-tidy for ESP32 1/4 | ||||
|             options: --environment esp32-tidy --split-num 4 --split-at 1 | ||||
|             name: Run script/clang-tidy for ESP32 Arduino 1/4 | ||||
|             options: --environment esp32-arduino-tidy --split-num 4 --split-at 1 | ||||
|             pio_cache_key: tidyesp32 | ||||
|           - id: clang-tidy | ||||
|             name: Run script/clang-tidy for ESP32 2/4 | ||||
|             options: --environment esp32-tidy --split-num 4 --split-at 2 | ||||
|             name: Run script/clang-tidy for ESP32 Arduino 2/4 | ||||
|             options: --environment esp32-arduino-tidy --split-num 4 --split-at 2 | ||||
|             pio_cache_key: tidyesp32 | ||||
|           - id: clang-tidy | ||||
|             name: Run script/clang-tidy for ESP32 3/4 | ||||
|             options: --environment esp32-tidy --split-num 4 --split-at 3 | ||||
|             name: Run script/clang-tidy for ESP32 Arduino 3/4 | ||||
|             options: --environment esp32-arduino-tidy --split-num 4 --split-at 3 | ||||
|             pio_cache_key: tidyesp32 | ||||
|           - id: clang-tidy | ||||
|             name: Run script/clang-tidy for ESP32 4/4 | ||||
|             options: --environment esp32-tidy --split-num 4 --split-at 4 | ||||
|             name: Run script/clang-tidy for ESP32 Arduino 4/4 | ||||
|             options: --environment esp32-arduino-tidy --split-num 4 --split-at 4 | ||||
|             pio_cache_key: tidyesp32 | ||||
|           - 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 | ||||
|             pio_cache_key: tidyesp32-idf | ||||
|  | ||||
| @@ -77,18 +82,23 @@ jobs: | ||||
|         with: | ||||
|           python-version: '3.7' | ||||
|  | ||||
|       - name: Cache pip modules | ||||
|       - name: Cache virtualenv | ||||
|         uses: actions/cache@v2 | ||||
|         with: | ||||
|           path: ~/.cache/pip | ||||
|           key: pip-${{ steps.python.outputs.python-version }}-${{ hashFiles('requirements*.txt') }} | ||||
|           path: .venv | ||||
|           key: venv-${{ steps.python.outputs.python-version }}-${{ hashFiles('requirements*.txt') }} | ||||
|           restore-keys: | | ||||
|             pip-${{ steps.python.outputs.python-version }}- | ||||
|             venv-${{ steps.python.outputs.python-version }}- | ||||
|  | ||||
|       - name: Set up python environment | ||||
|       - name: Set up virtualenv | ||||
|         run: | | ||||
|           pip3 install -r requirements.txt -r requirements_optional.txt -r requirements_test.txt | ||||
|           pip3 install -e . | ||||
|           python -m venv .venv | ||||
|           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 | ||||
|       - name: Cache platformio | ||||
|   | ||||
							
								
								
									
										14
									
								
								.github/workflows/lock.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										14
									
								
								.github/workflows/lock.yml
									
									
									
									
										vendored
									
									
								
							| @@ -9,13 +9,19 @@ permissions: | ||||
|   issues: write | ||||
|   pull-requests: write | ||||
|  | ||||
| concurrency: | ||||
|   group: lock | ||||
|  | ||||
| jobs: | ||||
|   lock: | ||||
|     runs-on: ubuntu-latest | ||||
|     steps: | ||||
|       - uses: dessant/lock-threads@v2 | ||||
|       - uses: dessant/lock-threads@v3 | ||||
|         with: | ||||
|           github-token: ${{ github.token }} | ||||
|           pr-lock-inactive-days: "1" | ||||
|           pr-inactive-days: "1" | ||||
|           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", | ||||
|             "pattern": [ | ||||
|                 { | ||||
|                     "regexp": "^ERROR (.*):(\\d+):(\\d+) - (.*)$", | ||||
|                     "regexp": "^(.*):(\\d+):(\\d+):\\s+lint:\\s+(.*)$", | ||||
|                     "file": 1, | ||||
|                     "line": 2, | ||||
|                     "column": 3, | ||||
|   | ||||
							
								
								
									
										2
									
								
								.github/workflows/matchers/gcc.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/matchers/gcc.json
									
									
									
									
										vendored
									
									
								
							| @@ -5,7 +5,7 @@ | ||||
|       "severity": "error", | ||||
|       "pattern": [ | ||||
|         { | ||||
|           "regexp": "^(.*):(\\d+):(\\d+):\\s+(?:fatal\\s+)?(warning|error):\\s+(.*)$", | ||||
|           "regexp": "^src/(.*):(\\d+):(\\d+):\\s+(?:fatal\\s+)?(warning|error):\\s+(.*)$", | ||||
|           "file": 1, | ||||
|           "line": 2, | ||||
|           "column": 3, | ||||
|   | ||||
							
								
								
									
										15
									
								
								.github/workflows/matchers/lint-python.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										15
									
								
								.github/workflows/matchers/lint-python.json
									
									
									
									
										vendored
									
									
								
							| @@ -1,11 +1,22 @@ | ||||
| { | ||||
|   "problemMatcher": [ | ||||
|     { | ||||
|       "owner": "black", | ||||
|       "severity": "error", | ||||
|       "pattern": [ | ||||
|         { | ||||
|           "regexp": "^(.*): (Please format this file with the black formatter)", | ||||
|           "file": 1, | ||||
|           "message": 2 | ||||
|         } | ||||
|       ] | ||||
|     }, | ||||
|     { | ||||
|       "owner": "flake8", | ||||
|       "severity": "error", | ||||
|       "pattern": [ | ||||
|           { | ||||
|           "regexp": "^(.*):(\\d+) - ([EFCDNW]\\d{3}.*)$", | ||||
|           "regexp": "^(.*):(\\d+): ([EFCDNW]\\d{3}.*)$", | ||||
|           "file": 1, | ||||
|           "line": 2, | ||||
|           "message": 3 | ||||
| @@ -17,7 +28,7 @@ | ||||
|       "severity": "error", | ||||
|       "pattern": [ | ||||
|         { | ||||
|           "regexp": "^(.*):(\\d+) - (\\[[EFCRW]\\d{4}\\(.*\\),.*\\].*)$", | ||||
|           "regexp": "^(.*):(\\d+): (\\[[EFCRW]\\d{4}\\(.*\\),.*\\].*)$", | ||||
|           "file": 1, | ||||
|           "line": 2, | ||||
|           "message": 3 | ||||
|   | ||||
							
								
								
									
										9
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										9
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							| @@ -7,6 +7,9 @@ on: | ||||
|   schedule: | ||||
|     - cron: "0 2 * * *" | ||||
|  | ||||
| permissions: | ||||
|   contents: read | ||||
|  | ||||
| jobs: | ||||
|   init: | ||||
|     name: Initialize build | ||||
| @@ -52,6 +55,9 @@ jobs: | ||||
|   deploy-docker: | ||||
|     name: Build and publish docker containers | ||||
|     if: github.repository == 'esphome/esphome' | ||||
|     permissions: | ||||
|       contents: read | ||||
|       packages: write | ||||
|     runs-on: ubuntu-latest | ||||
|     needs: [init] | ||||
|     strategy: | ||||
| @@ -93,6 +99,9 @@ jobs: | ||||
|  | ||||
|   deploy-docker-manifest: | ||||
|     if: github.repository == 'esphome/esphome' | ||||
|     permissions: | ||||
|       contents: read | ||||
|       packages: write | ||||
|     runs-on: ubuntu-latest | ||||
|     needs: [init, deploy-docker] | ||||
|     strategy: | ||||
|   | ||||
							
								
								
									
										20
									
								
								.github/workflows/stale.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										20
									
								
								.github/workflows/stale.yml
									
									
									
									
										vendored
									
									
								
							| @@ -9,13 +9,15 @@ permissions: | ||||
|   issues: write | ||||
|   pull-requests: write | ||||
|  | ||||
| concurrency: | ||||
|   group: lock | ||||
|  | ||||
| jobs: | ||||
|   stale: | ||||
|     runs-on: ubuntu-latest | ||||
|     steps: | ||||
|       - uses: actions/stale@v4 | ||||
|         with: | ||||
|           repo-token: ${{ github.token }} | ||||
|           days-before-pr-stale: 90 | ||||
|           days-before-pr-close: 7 | ||||
|           days-before-issue-stale: -1 | ||||
| @@ -28,3 +30,19 @@ jobs: | ||||
|             pull request has been automatically marked as stale because of that | ||||
|             and will be closed if no further activity occurs within 7 days. | ||||
|             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 | ||||
| tasks: | ||||
| - 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/bang_bang/* @OttoWinter | ||||
| esphome/components/binary_sensor/* @esphome/core | ||||
| esphome/components/bl0940/* @tobias- | ||||
| esphome/components/ble_client/* @buxtronix | ||||
| esphome/components/bme680_bsec/* @trvrnrth | ||||
| esphome/components/bmp3xx/* @martgras | ||||
| esphome/components/button/* @esphome/core | ||||
| esphome/components/canbus/* @danielschramm @mvturnho | ||||
| esphome/components/cap1188/* @MrEditor97 | ||||
| esphome/components/captive_portal/* @OttoWinter | ||||
| esphome/components/ccs811/* @habbie | ||||
| esphome/components/cd74hc4067/* @asoehlke | ||||
| esphome/components/climate/* @esphome/core | ||||
| esphome/components/climate_ir/* @glmnet | ||||
| esphome/components/color_temperature/* @jesserockz | ||||
| esphome/components/coolix/* @glmnet | ||||
| esphome/components/cover/* @esphome/core | ||||
| esphome/components/cs5460a/* @balrog-kun | ||||
| esphome/components/cse7761/* @berfenger | ||||
| esphome/components/ct_clamp/* @jesserockz | ||||
| esphome/components/current_based/* @djwmarcx | ||||
| esphome/components/daly_bms/* @s1lvi0 | ||||
| @@ -52,6 +58,8 @@ esphome/components/esp32/* @esphome/core | ||||
| esphome/components/esp32_ble/* @jesserockz | ||||
| esphome/components/esp32_ble_controller/* @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/esp8266/* @esphome/core | ||||
| esphome/components/exposure_notifications/* @OttoWinter | ||||
| @@ -62,6 +70,7 @@ esphome/components/globals/* @esphome/core | ||||
| esphome/components/gpio/* @esphome/core | ||||
| esphome/components/gps/* @coogle | ||||
| esphome/components/graph/* @synco | ||||
| esphome/components/growatt_solar/* @leeuwte | ||||
| esphome/components/havells_solar/* @sourabhjaiswal | ||||
| esphome/components/hbridge/fan/* @WeekendWarrior | ||||
| esphome/components/hbridge/light/* @DotNetDann | ||||
| @@ -70,12 +79,14 @@ esphome/components/hitachi_ac424/* @sourabhjaiswal | ||||
| esphome/components/homeassistant/* @OttoWinter | ||||
| esphome/components/hrxl_maxsonar_wr/* @netmikey | ||||
| 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/inkplate6/* @jesserockz | ||||
| esphome/components/integration/* @OttoWinter | ||||
| esphome/components/interval/* @esphome/core | ||||
| esphome/components/json/* @OttoWinter | ||||
| esphome/components/kalman_combinator/* @Cat-Ion | ||||
| esphome/components/ledc/* @OttoWinter | ||||
| esphome/components/light/* @esphome/core | ||||
| esphome/components/logger/* @esphome/core | ||||
| @@ -89,9 +100,13 @@ esphome/components/mcp23x08_base/* @jesserockz | ||||
| esphome/components/mcp23x17_base/* @jesserockz | ||||
| esphome/components/mcp23xxx_base/* @jesserockz | ||||
| esphome/components/mcp2515/* @danielschramm @mvturnho | ||||
| esphome/components/mcp3204/* @rsumner | ||||
| esphome/components/mcp47a1/* @jesserockz | ||||
| esphome/components/mcp9808/* @k7hpn | ||||
| esphome/components/md5/* @esphome/core | ||||
| esphome/components/mdns/* @esphome/core | ||||
| esphome/components/midea/* @dudanov | ||||
| esphome/components/midea_ir/* @dudanov | ||||
| esphome/components/mitsubishi/* @RubyBailey | ||||
| esphome/components/modbus_controller/* @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/power_supply/* @esphome/core | ||||
| esphome/components/preferences/* @esphome/core | ||||
| esphome/components/psram/* @esphome/core | ||||
| esphome/components/pulse_meter/* @stevebaxter | ||||
| esphome/components/pvvx_mithermometer/* @pasiz | ||||
| esphome/components/rc522/* @glmnet | ||||
| @@ -128,7 +144,7 @@ esphome/components/restart/* @esphome/core | ||||
| esphome/components/rf_bridge/* @jesserockz | ||||
| esphome/components/rgbct/* @jesserockz | ||||
| esphome/components/rtttl/* @glmnet | ||||
| esphome/components/safe_mode/* @paulmonigatti | ||||
| esphome/components/safe_mode/* @jsuanet @paulmonigatti | ||||
| esphome/components/scd4x/* @sjtrny | ||||
| esphome/components/script/* @esphome/core | ||||
| esphome/components/sdm_meter/* @jesserockz @polyfaces | ||||
| @@ -138,7 +154,7 @@ esphome/components/select/* @esphome/core | ||||
| esphome/components/sensor/* @esphome/core | ||||
| esphome/components/sgp40/* @SenexCrenshaw | ||||
| esphome/components/sht4x/* @sjtrny | ||||
| esphome/components/shutdown/* @esphome/core | ||||
| esphome/components/shutdown/* @esphome/core @jsuanet | ||||
| esphome/components/sim800l/* @glmnet | ||||
| esphome/components/sm2135/* @BoukeHaarsma23 | ||||
| esphome/components/socket/* @esphome/core | ||||
| @@ -175,8 +191,10 @@ esphome/components/toshiba/* @kbx81 | ||||
| esphome/components/tsl2591/* @wjcarpenter | ||||
| esphome/components/tuya/binary_sensor/* @jesserockz | ||||
| esphome/components/tuya/climate/* @jesserockz | ||||
| esphome/components/tuya/number/* @frankiboy1 | ||||
| esphome/components/tuya/sensor/* @jesserockz | ||||
| esphome/components/tuya/switch/* @jesserockz | ||||
| esphome/components/tuya/text_sensor/* @dentra | ||||
| esphome/components/uart/* @esphome/core | ||||
| esphome/components/ultrasonic/* @OttoWinter | ||||
| esphome/components/version/* @esphome/core | ||||
|   | ||||
| @@ -5,12 +5,12 @@ | ||||
| # One of "docker", "hassio" | ||||
| 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/aarch64:5.1.0 AS base-hassio-arm64 | ||||
| FROM ghcr.io/hassio-addons/debian-base/armv7:5.1.0 AS base-hassio-armv7 | ||||
| FROM debian:bullseye-20210902-slim AS base-docker-amd64 | ||||
| FROM debian:bullseye-20210902-slim AS base-docker-arm64 | ||||
| FROM debian:bullseye-20210902-slim AS base-docker-armv7 | ||||
| FROM ghcr.io/hassio-addons/debian-base/amd64:5.2.3 AS base-hassio-amd64 | ||||
| FROM ghcr.io/hassio-addons/debian-base/aarch64:5.2.3 AS base-hassio-arm64 | ||||
| FROM ghcr.io/hassio-addons/debian-base/armv7:5.2.3 AS base-hassio-armv7 | ||||
| FROM debian:bullseye-20211220-slim AS base-docker-amd64 | ||||
| FROM debian:bullseye-20211220-slim AS base-docker-arm64 | ||||
| FROM debian:bullseye-20211220-slim AS base-docker-armv7 | ||||
|  | ||||
| # Use TARGETARCH/TARGETVARIANT defined by docker | ||||
| # 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 \ | ||||
|         iputils-ping=3:20210202-1 \ | ||||
|         git=1:2.30.2-1 \ | ||||
|         curl=7.74.0-1.3+b1 \ | ||||
|         curl=7.74.0-1.3+deb11u1 \ | ||||
|     && rm -rf \ | ||||
|         /tmp/* \ | ||||
|         /var/{cache,log}/* \ | ||||
| @@ -42,8 +42,8 @@ ENV \ | ||||
| RUN \ | ||||
|     # Ubuntu python3-pip is missing wheel | ||||
|     pip3 install --no-cache-dir \ | ||||
|         wheel==0.36.2 \ | ||||
|         platformio==5.2.0 \ | ||||
|         wheel==0.37.1 \ | ||||
|         platformio==5.2.4 \ | ||||
|     # Change some platformio settings | ||||
|     && platformio settings set enable_telemetry No \ | ||||
|     && platformio settings set check_libraries_interval 1000000 \ | ||||
| @@ -64,7 +64,7 @@ RUN \ | ||||
|  | ||||
| # Copy esphome and install | ||||
| COPY . /esphome | ||||
| RUN pip3 install --no-cache-dir -e /esphome | ||||
| RUN pip3 install --no-cache-dir --no-use-pep517 -e /esphome | ||||
|  | ||||
| # Settings for dashboard | ||||
| ENV USERNAME="" PASSWORD="" | ||||
| @@ -112,7 +112,7 @@ RUN \ | ||||
|  | ||||
| # Copy esphome and install | ||||
| COPY . /esphome | ||||
| RUN pip3 install --no-cache-dir -e /esphome | ||||
| RUN pip3 install --no-cache-dir --no-use-pep517 -e /esphome | ||||
|  | ||||
| # Labels | ||||
| LABEL \ | ||||
| @@ -147,9 +147,9 @@ RUN \ | ||||
|         /var/{cache,log}/* \ | ||||
|         /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 \ | ||||
|     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 | ||||
|  | ||||
| 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) | ||||
| 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("--load", help="Load the docker image locally", action="store_true") | ||||
| manifest_parser = subparsers.add_parser("manifest", help="Create a manifest from already pushed images") | ||||
|  | ||||
|  | ||||
| @@ -132,6 +133,8 @@ def main(): | ||||
|             cmd += ["--tag", img] | ||||
|         if args.push: | ||||
|             cmd += ["--push", "--cache-to", f"type=registry,ref={cache_img},mode=max"] | ||||
|         if args.load: | ||||
|             cmd += ["--load"] | ||||
|  | ||||
|         run_command(*cmd, ".") | ||||
|     elif args.command == "manifest": | ||||
|   | ||||
| @@ -8,6 +8,23 @@ import sys | ||||
|  | ||||
| config = configparser.ConfigParser(inline_comment_prefixes=(';', )) | ||||
| 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]) | ||||
|   | ||||
| @@ -18,6 +18,7 @@ from esphome.const import ( | ||||
|     CONF_PORT, | ||||
|     CONF_ESPHOME, | ||||
|     CONF_PLATFORMIO_OPTIONS, | ||||
|     SECRETS_FILES, | ||||
| ) | ||||
| from esphome.core import CORE, EsphomeError, coroutine | ||||
| from esphome.helpers import indent | ||||
| @@ -144,6 +145,8 @@ def wrap_to_code(name, comp): | ||||
|         if comp.config_schema is not None: | ||||
|             conf_str = yaml_util.dump(conf) | ||||
|             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))) | ||||
|         await coro(conf) | ||||
|  | ||||
| @@ -180,7 +183,11 @@ def compile_program(args, config): | ||||
|     from esphome import platformio_api | ||||
|  | ||||
|     _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): | ||||
| @@ -196,8 +203,7 @@ def upload_using_esptool(config, port): | ||||
|         firmware_offset = "0x10000" if CORE.is_esp32 else "0x0" | ||||
|         flash_images = [ | ||||
|             platformio_api.FlashImage( | ||||
|                 path=idedata.firmware_bin_path, | ||||
|                 offset=firmware_offset, | ||||
|                 path=idedata.firmware_bin_path, offset=firmware_offset | ||||
|             ), | ||||
|             *idedata.extra_flash_images, | ||||
|         ] | ||||
| @@ -222,6 +228,8 @@ def upload_using_esptool(config, port): | ||||
|             mcu, | ||||
|             "write_flash", | ||||
|             "-z", | ||||
|             "--flash_size", | ||||
|             "detect", | ||||
|         ] | ||||
|         for img in flash_images: | ||||
|             cmd += [img.offset, img.path] | ||||
| @@ -458,6 +466,21 @@ def command_update_all(args): | ||||
|     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 = { | ||||
|     "wizard": command_wizard, | ||||
|     "version": command_version, | ||||
| @@ -475,6 +498,7 @@ POST_CONFIG_ACTIONS = { | ||||
|     "clean-mqtt": command_clean_mqtt, | ||||
|     "mqtt-fingerprint": command_mqtt_fingerprint, | ||||
|     "clean": command_clean, | ||||
|     "idedata": command_idedata, | ||||
| } | ||||
|  | ||||
|  | ||||
| @@ -585,10 +609,7 @@ def parse_args(argv): | ||||
|         "wizard", | ||||
|         help="A helpful setup wizard that will guide you through setting up ESPHome.", | ||||
|     ) | ||||
|     parser_wizard.add_argument( | ||||
|         "configuration", | ||||
|         help="Your YAML configuration file.", | ||||
|     ) | ||||
|     parser_wizard.add_argument("configuration", help="Your YAML configuration file.") | ||||
|  | ||||
|     parser_fingerprint = subparsers.add_parser( | ||||
|         "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." | ||||
|     ) | ||||
|     parser_dashboard.add_argument( | ||||
|         "configuration", | ||||
|         help="Your YAML configuration file directory.", | ||||
|         "configuration", help="Your YAML configuration file directory." | ||||
|     ) | ||||
|     parser_dashboard.add_argument( | ||||
|         "--port", | ||||
| @@ -619,6 +639,12 @@ def parse_args(argv): | ||||
|         type=int, | ||||
|         default=6052, | ||||
|     ) | ||||
|     parser_dashboard.add_argument( | ||||
|         "--address", | ||||
|         help="The address to bind to.", | ||||
|         type=str, | ||||
|         default="0.0.0.0", | ||||
|     ) | ||||
|     parser_dashboard.add_argument( | ||||
|         "--username", | ||||
|         help="The optional username to require for authentication.", | ||||
| @@ -650,6 +676,11 @@ def parse_args(argv): | ||||
|         "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 | ||||
|     # esphome <config> <command>. | ||||
|     # | ||||
| @@ -733,7 +764,12 @@ def run_esphome(argv): | ||||
|     args = parse_args(argv) | ||||
|     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": | ||||
|         _LOGGER.warning( | ||||
|             "Calling ESPHome with the configuration before the command is deprecated " | ||||
| @@ -757,12 +793,16 @@ def run_esphome(argv): | ||||
|             return 1 | ||||
|  | ||||
|     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.dashboard = args.dashboard | ||||
|  | ||||
|         config = read_config(dict(args.substitution) if args.substitution else {}) | ||||
|         if config is None: | ||||
|             return 1 | ||||
|             return 2 | ||||
|         CORE.config = config | ||||
|  | ||||
|         if args.command not in POST_CONFIG_ACTIONS: | ||||
|   | ||||
| @@ -3,6 +3,7 @@ import esphome.config_validation as cv | ||||
| from esphome.const import ( | ||||
|     CONF_AUTOMATION_ID, | ||||
|     CONF_CONDITION, | ||||
|     CONF_COUNT, | ||||
|     CONF_ELSE, | ||||
|     CONF_ID, | ||||
|     CONF_THEN, | ||||
| @@ -66,6 +67,7 @@ DelayAction = cg.esphome_ns.class_("DelayAction", Action, cg.Component) | ||||
| LambdaAction = cg.esphome_ns.class_("LambdaAction", Action) | ||||
| IfAction = cg.esphome_ns.class_("IfAction", Action) | ||||
| WhileAction = cg.esphome_ns.class_("WhileAction", Action) | ||||
| RepeatAction = cg.esphome_ns.class_("RepeatAction", Action) | ||||
| WaitUntilAction = cg.esphome_ns.class_("WaitUntilAction", Action, cg.Component) | ||||
| UpdateComponentAction = cg.esphome_ns.class_("UpdateComponentAction", Action) | ||||
| Automation = cg.esphome_ns.class_("Automation") | ||||
| @@ -241,6 +243,25 @@ async def while_action_to_code(config, action_id, template_arg, args): | ||||
|     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): | ||||
|     schema = cv.Schema( | ||||
|         { | ||||
|   | ||||
| @@ -75,10 +75,10 @@ from esphome.cpp_types import (  # noqa | ||||
|     optional, | ||||
|     arduino_json_ns, | ||||
|     JsonObject, | ||||
|     JsonObjectRef, | ||||
|     JsonObjectConstRef, | ||||
|     JsonObjectConst, | ||||
|     Controller, | ||||
|     GPIOPin, | ||||
|     InternalGPIOPin, | ||||
|     gpio_Flags, | ||||
|     EntityCategory, | ||||
| ) | ||||
|   | ||||
| @@ -25,7 +25,7 @@ void AdalightLightEffect::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 | ||||
|   // 2 bytes: LED count | ||||
|   // 1 byte: checksum | ||||
|   | ||||
| @@ -25,7 +25,7 @@ class AdalightLightEffect : public light::AddressableLightEffect, public uart::U | ||||
|     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 blank_all_leds_(light::AddressableLight &it); | ||||
|   Frame parse_frame_(light::AddressableLight &it); | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| #include "adc_sensor.h" | ||||
| #include "esphome/core/log.h" | ||||
| #include "esphome/core/helpers.h" | ||||
|  | ||||
| #ifdef USE_ESP8266 | ||||
| #ifdef USE_ADC_SENSOR_VCC | ||||
| @@ -15,50 +16,6 @@ namespace 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() { | ||||
|   ESP_LOGCONFIG(TAG, "Setting up ADC '%s'...", this->get_name().c_str()); | ||||
| #ifndef USE_ADC_SENSOR_VCC | ||||
| @@ -66,13 +23,36 @@ void ADCSensor::setup() { | ||||
| #endif | ||||
|  | ||||
| #ifdef USE_ESP32 | ||||
|   adc1_config_channel_atten(gpio_to_adc1(pin_->get_pin()), attenuation_); | ||||
|   adc1_config_width(ADC_WIDTH_BIT_12); | ||||
| #if !CONFIG_IDF_TARGET_ESP32C3 && !CONFIG_IDF_TARGET_ESP32H2 | ||||
|   adc_gpio_init(ADC_UNIT_1, (adc_channel_t) gpio_to_adc1(pin_->get_pin())); | ||||
| #endif | ||||
|   if (!autorange_) { | ||||
|     adc1_config_channel_atten(channel_, attenuation_); | ||||
|   } | ||||
|  | ||||
|   // 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  // USE_ESP32 | ||||
| } | ||||
|  | ||||
| void ADCSensor::dump_config() { | ||||
|   LOG_SENSOR("", "ADC Sensor", this); | ||||
| #ifdef USE_ESP8266 | ||||
| @@ -81,84 +61,107 @@ void ADCSensor::dump_config() { | ||||
| #else | ||||
|   LOG_PIN("  Pin: ", pin_); | ||||
| #endif | ||||
| #endif | ||||
| #endif  // USE_ESP8266 | ||||
|  | ||||
| #ifdef USE_ESP32 | ||||
|   LOG_PIN("  Pin: ", pin_); | ||||
|   switch (this->attenuation_) { | ||||
|     case ADC_ATTEN_DB_0: | ||||
|       ESP_LOGCONFIG(TAG, " Attenuation: 0db (max 1.1V)"); | ||||
|       break; | ||||
|     case ADC_ATTEN_DB_2_5: | ||||
|       ESP_LOGCONFIG(TAG, " Attenuation: 2.5db (max 1.5V)"); | ||||
|       break; | ||||
|     case ADC_ATTEN_DB_6: | ||||
|       ESP_LOGCONFIG(TAG, " Attenuation: 6db (max 2.2V)"); | ||||
|       break; | ||||
|     case ADC_ATTEN_DB_11: | ||||
|       ESP_LOGCONFIG(TAG, " Attenuation: 11db (max 3.9V)"); | ||||
|       break; | ||||
|     default:  // This is to satisfy the unused ADC_ATTEN_MAX | ||||
|       break; | ||||
|   } | ||||
| #endif | ||||
|   if (autorange_) | ||||
|     ESP_LOGCONFIG(TAG, " Attenuation: auto"); | ||||
|   else | ||||
|     switch (this->attenuation_) { | ||||
|       case ADC_ATTEN_DB_0: | ||||
|         ESP_LOGCONFIG(TAG, " Attenuation: 0db (max 1.1V)"); | ||||
|         break; | ||||
|       case ADC_ATTEN_DB_2_5: | ||||
|         ESP_LOGCONFIG(TAG, " Attenuation: 2.5db (max 1.5V)"); | ||||
|         break; | ||||
|       case ADC_ATTEN_DB_6: | ||||
|         ESP_LOGCONFIG(TAG, " Attenuation: 6db (max 2.2V)"); | ||||
|         break; | ||||
|       case ADC_ATTEN_DB_11: | ||||
|         ESP_LOGCONFIG(TAG, " Attenuation: 11db (max 3.9V)"); | ||||
|         break; | ||||
|       default:  // This is to satisfy the unused ADC_ATTEN_MAX | ||||
|         break; | ||||
|     } | ||||
| #endif  // USE_ESP32 | ||||
|   LOG_UPDATE_INTERVAL(this); | ||||
| } | ||||
|  | ||||
| float ADCSensor::get_setup_priority() const { return setup_priority::DATA; } | ||||
| void ADCSensor::update() { | ||||
|   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); | ||||
| } | ||||
| 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 | ||||
| float ADCSensor::sample() { | ||||
| #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 | ||||
|   return analogRead(this->pin_->get_pin()) / 1024.0f;  // NOLINT | ||||
| #endif | ||||
|   int raw = analogRead(this->pin_->get_pin());  // NOLINT | ||||
| #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 | ||||
| std::string ADCSensor::unique_id() { return get_mac_address() + "-adc"; } | ||||
| #endif | ||||
|   | ||||
| @@ -8,6 +8,7 @@ | ||||
|  | ||||
| #ifdef USE_ESP32 | ||||
| #include "driver/adc.h" | ||||
| #include <esp_adc_cal.h> | ||||
| #endif | ||||
|  | ||||
| namespace esphome { | ||||
| @@ -17,7 +18,9 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage | ||||
|  public: | ||||
| #ifdef USE_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 | ||||
|  | ||||
|   /// Update adc values. | ||||
| @@ -28,6 +31,7 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage | ||||
|   /// `HARDWARE_LATE` setup priority. | ||||
|   float get_setup_priority() const override; | ||||
|   void set_pin(InternalGPIOPin *pin) { this->pin_ = pin; } | ||||
|   void set_output_raw(bool output_raw) { output_raw_ = output_raw; } | ||||
|   float sample() override; | ||||
|  | ||||
| #ifdef USE_ESP8266 | ||||
| @@ -36,9 +40,13 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage | ||||
|  | ||||
|  protected: | ||||
|   InternalGPIOPin *pin_; | ||||
|   bool output_raw_{false}; | ||||
|  | ||||
| #ifdef USE_ESP32 | ||||
|   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 | ||||
| }; | ||||
|  | ||||
|   | ||||
| @@ -4,14 +4,24 @@ from esphome import pins | ||||
| from esphome.components import sensor, voltage_sampler | ||||
| from esphome.const import ( | ||||
|     CONF_ATTENUATION, | ||||
|     CONF_RAW, | ||||
|     CONF_ID, | ||||
|     CONF_INPUT, | ||||
|     CONF_NUMBER, | ||||
|     CONF_PIN, | ||||
|     DEVICE_CLASS_VOLTAGE, | ||||
|     STATE_CLASS_MEASUREMENT, | ||||
|     UNIT_VOLT, | ||||
| ) | ||||
| 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"] | ||||
| @@ -21,6 +31,62 @@ ATTENUATION_MODES = { | ||||
|     "2.5db": cg.global_ns.ADC_ATTEN_DB_2_5, | ||||
|     "6db": cg.global_ns.ADC_ATTEN_DB_6, | ||||
|     "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") | ||||
|  | ||||
|     if CORE.is_esp32: | ||||
|         from esphome.components.esp32 import is_esp32c3 | ||||
|  | ||||
|         value = pins.internal_gpio_input_pin_number(value) | ||||
|         if is_esp32c3(): | ||||
|             if not (0 <= value <= 4):  # ADC1 | ||||
|                 raise cv.Invalid("ESP32-C3: Only pins 0 though 4 support ADC.") | ||||
|         if not (32 <= value <= 39):  # ADC1 | ||||
|             raise cv.Invalid("ESP32: Only pins 32 though 39 support ADC.") | ||||
|     elif CORE.is_esp8266: | ||||
|         variant = get_esp32_variant() | ||||
|         if variant not in ESP32_VARIANT_ADC1_PIN_TO_CHANNEL: | ||||
|             raise cv.Invalid(f"This ESP32 variant ({variant}) is not supported") | ||||
|  | ||||
|         if value not in ESP32_VARIANT_ADC1_PIN_TO_CHANNEL[variant]: | ||||
|             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 | ||||
|  | ||||
|         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( | ||||
|             {CONF_ANALOG: True, CONF_INPUT: True}, internal=True | ||||
|         )(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") | ||||
| @@ -60,7 +131,7 @@ ADCSensor = adc_ns.class_( | ||||
|     "ADCSensor", sensor.Sensor, cg.PollingComponent, voltage_sampler.VoltageSampler | ||||
| ) | ||||
|  | ||||
| CONFIG_SCHEMA = ( | ||||
| CONFIG_SCHEMA = cv.All( | ||||
|     sensor.sensor_schema( | ||||
|         unit_of_measurement=UNIT_VOLT, | ||||
|         accuracy_decimals=2, | ||||
| @@ -71,12 +142,14 @@ CONFIG_SCHEMA = ( | ||||
|         { | ||||
|             cv.GenerateID(): cv.declare_id(ADCSensor), | ||||
|             cv.Required(CONF_PIN): validate_adc_pin, | ||||
|             cv.Optional(CONF_RAW, default=False): cv.boolean, | ||||
|             cv.SplitDefault(CONF_ATTENUATION, esp32="0db"): cv.All( | ||||
|                 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]) | ||||
|         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: | ||||
|         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; | ||||
|     *value = 0; | ||||
|     *value |= ((uint32_t) recv[0]) << 24; | ||||
|     *value |= ((uint32_t) recv[1]) << 24; | ||||
|     *value |= ((uint32_t) recv[2]) << 24; | ||||
|     *value |= ((uint32_t) recv[3]) << 24; | ||||
|     *value |= ((uint32_t) recv[1]) << 16; | ||||
|     *value |= ((uint32_t) recv[2]) << 8; | ||||
|     *value |= ((uint32_t) recv[3]); | ||||
|     return i2c::ERROR_OK; | ||||
|   } | ||||
|  | ||||
|   | ||||
| @@ -73,13 +73,6 @@ void AHT10Component::update() { | ||||
|   bool success = false; | ||||
|   for (int i = 0; i < AHT10_ATTEMPTS; ++i) { | ||||
|     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); | ||||
|     if (this->read(data, 6) != i2c::ERROR_OK) { | ||||
|       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_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; | ||||
|   if (raw_humidity == 0) {  // unrealistic value | ||||
|     humidity = NAN; | ||||
|   } else { | ||||
|     humidity = (float) raw_humidity * 100.0 / 1048576.0; | ||||
|     humidity = (float) raw_humidity * 100.0f / 1048576.0f; | ||||
|   } | ||||
|  | ||||
|   if (this->temperature_sensor_ != nullptr) { | ||||
|   | ||||
| @@ -38,9 +38,9 @@ void AM2320Component::update() { | ||||
|     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; | ||||
|   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); | ||||
|   if (this->temperature_sensor_ != nullptr) | ||||
|   | ||||
| @@ -4,7 +4,8 @@ from esphome.components import sensor, ble_client | ||||
| from esphome.const import ( | ||||
|     CONF_ID, | ||||
|     CONF_BATTERY_LEVEL, | ||||
|     ICON_BATTERY, | ||||
|     DEVICE_CLASS_BATTERY, | ||||
|     ENTITY_CATEGORY_DIAGNOSTIC, | ||||
|     CONF_ILLUMINANCE, | ||||
|     ICON_BRIGHTNESS_5, | ||||
|     UNIT_PERCENT, | ||||
| @@ -20,10 +21,15 @@ CONFIG_SCHEMA = ( | ||||
|         { | ||||
|             cv.GenerateID(): cv.declare_id(Am43), | ||||
|             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( | ||||
|                 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 | ||||
|     frames = image.n_frames | ||||
|     if CONF_RESIZE in config: | ||||
|         image.thumbnail(config[CONF_RESIZE]) | ||||
|         width, height = image.size | ||||
|         new_width_max, new_height_max = config[CONF_RESIZE] | ||||
|         ratio = min(new_width_max / width, new_height_max / height) | ||||
|         width, height = int(width * ratio), int(height * ratio) | ||||
|     else: | ||||
|         if width > 500 or height > 500: | ||||
|             _LOGGER.warning( | ||||
| @@ -59,7 +60,13 @@ async def to_code(config): | ||||
|         for frameIndex in range(frames): | ||||
|             image.seek(frameIndex) | ||||
|             frame = image.convert("L", dither=Image.NONE) | ||||
|             if CONF_RESIZE in config: | ||||
|                 frame = frame.resize([width, height]) | ||||
|             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: | ||||
|                 data[pos] = pix | ||||
|                 pos += 1 | ||||
| @@ -70,7 +77,13 @@ async def to_code(config): | ||||
|         for frameIndex in range(frames): | ||||
|             image.seek(frameIndex) | ||||
|             frame = image.convert("RGB") | ||||
|             if CONF_RESIZE in config: | ||||
|                 frame = frame.resize([width, height]) | ||||
|             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: | ||||
|                 data[pos] = pix[0] | ||||
|                 pos += 1 | ||||
| @@ -85,6 +98,8 @@ async def to_code(config): | ||||
|         for frameIndex in range(frames): | ||||
|             image.seek(frameIndex) | ||||
|             frame = image.convert("1", dither=Image.NONE) | ||||
|             if CONF_RESIZE in config: | ||||
|                 frame = frame.resize([width, height]) | ||||
|             for y in range(height): | ||||
|                 for x in range(width): | ||||
|                     if frame.getpixel((x, y)): | ||||
|   | ||||
| @@ -30,7 +30,7 @@ class Anova : public climate::Climate, public esphome::ble_client::BLEClientNode | ||||
|   climate::ClimateTraits traits() override { | ||||
|     auto traits = climate::ClimateTraits(); | ||||
|     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_max_temperature(100.0); | ||||
|     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) { | ||||
|   memset(this->buf_, 0, 32); | ||||
|   strncpy(this->buf_, (char *) data, length); | ||||
|   char buf[32]; | ||||
|   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; | ||||
|   switch (this->current_query_) { | ||||
|     case READ_DEVICE_STATUS: { | ||||
|       if (!strncmp(this->buf_, "stopped", 7)) { | ||||
|       if (!strncmp(buf, "stopped", 7)) { | ||||
|         this->has_running_ = true; | ||||
|         this->running_ = false; | ||||
|       } | ||||
|       if (!strncmp(this->buf_, "running", 7)) { | ||||
|       if (!strncmp(buf, "running", 7)) { | ||||
|         this->has_running_ = true; | ||||
|         this->running_ = true; | ||||
|       } | ||||
|       break; | ||||
|     } | ||||
|     case START: { | ||||
|       if (!strncmp(this->buf_, "start", 5)) { | ||||
|       if (!strncmp(buf, "start", 5)) { | ||||
|         this->has_running_ = true; | ||||
|         this->running_ = true; | ||||
|       } | ||||
|       break; | ||||
|     } | ||||
|     case STOP: { | ||||
|       if (!strncmp(this->buf_, "stop", 4)) { | ||||
|       if (!strncmp(buf, "stop", 4)) { | ||||
|         this->has_running_ = true; | ||||
|         this->running_ = false; | ||||
|       } | ||||
|       break; | ||||
|     } | ||||
|     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 READ_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_) | ||||
|         this->target_temp_ = ftoc(this->target_temp_); | ||||
|       this->has_target_temp_ = true; | ||||
|       break; | ||||
|     } | ||||
|     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_) | ||||
|         this->current_temp_ = ftoc(this->current_temp_); | ||||
|       this->has_current_temp_ = true; | ||||
| @@ -125,8 +120,8 @@ void AnovaCodec::decode(const uint8_t *data, uint16_t length) { | ||||
|     } | ||||
|     case SET_UNIT: | ||||
|     case READ_UNIT: { | ||||
|       this->unit_ = this->buf_[0]; | ||||
|       this->fahrenheit_ = this->buf_[0] == 'f'; | ||||
|       this->unit_ = buf[0]; | ||||
|       this->fahrenheit_ = buf[0] == 'f'; | ||||
|       this->has_unit_ = true; | ||||
|       break; | ||||
|     } | ||||
|   | ||||
| @@ -70,7 +70,6 @@ class AnovaCodec { | ||||
|   bool has_current_temp_; | ||||
|   bool has_unit_; | ||||
|   bool has_running_; | ||||
|   char buf_[32]; | ||||
|   bool fahrenheit_; | ||||
|  | ||||
|   CurrentQuery current_query_; | ||||
|   | ||||
| @@ -121,7 +121,7 @@ async def to_code(config): | ||||
|         decoded = base64.b64decode(conf[CONF_KEY]) | ||||
|         cg.add(var.set_noise_psk(list(decoded))) | ||||
|         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: | ||||
|         cg.add_define("USE_API_PLAINTEXT") | ||||
|  | ||||
|   | ||||
| @@ -40,6 +40,7 @@ service APIConnection { | ||||
|   rpc climate_command (ClimateCommandRequest) returns (void) {} | ||||
|   rpc number_command (NumberCommandRequest) 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 | ||||
|   string project_name = 8; | ||||
|   string project_version = 9; | ||||
|  | ||||
|   uint32 webserver_port = 10; | ||||
| } | ||||
|  | ||||
| message ListEntitiesRequest { | ||||
| @@ -201,6 +204,14 @@ message SubscribeStatesRequest { | ||||
|   // Empty | ||||
| } | ||||
|  | ||||
| // ==================== COMMON ===================== | ||||
|  | ||||
| enum EntityCategory { | ||||
|   ENTITY_CATEGORY_NONE = 0; | ||||
|   ENTITY_CATEGORY_CONFIG = 1; | ||||
|   ENTITY_CATEGORY_DIAGNOSTIC = 2; | ||||
| } | ||||
|  | ||||
| // ==================== BINARY SENSOR ==================== | ||||
| message ListEntitiesBinarySensorResponse { | ||||
|   option (id) = 12; | ||||
| @@ -216,6 +227,7 @@ message ListEntitiesBinarySensorResponse { | ||||
|   bool is_status_binary_sensor = 6; | ||||
|   bool disabled_by_default = 7; | ||||
|   string icon = 8; | ||||
|   EntityCategory entity_category = 9; | ||||
| } | ||||
| message BinarySensorStateResponse { | ||||
|   option (id) = 21; | ||||
| @@ -247,6 +259,7 @@ message ListEntitiesCoverResponse { | ||||
|   string device_class = 8; | ||||
|   bool disabled_by_default = 9; | ||||
|   string icon = 10; | ||||
|   EntityCategory entity_category = 11; | ||||
| } | ||||
|  | ||||
| enum LegacyCoverState { | ||||
| @@ -316,6 +329,7 @@ message ListEntitiesFanResponse { | ||||
|   int32 supported_speed_count = 8; | ||||
|   bool disabled_by_default = 9; | ||||
|   string icon = 10; | ||||
|   EntityCategory entity_category = 11; | ||||
| } | ||||
| enum FanSpeed { | ||||
|   FAN_SPEED_LOW = 0; | ||||
| @@ -392,6 +406,7 @@ message ListEntitiesLightResponse { | ||||
|   repeated string effects = 11; | ||||
|   bool disabled_by_default = 13; | ||||
|   string icon = 14; | ||||
|   EntityCategory entity_category = 15; | ||||
| } | ||||
| message LightStateResponse { | ||||
|   option (id) = 24; | ||||
| @@ -480,6 +495,7 @@ message ListEntitiesSensorResponse { | ||||
|   // Last reset type removed in 2021.9.0 | ||||
|   SensorLastResetType legacy_last_reset_type = 11; | ||||
|   bool disabled_by_default = 12; | ||||
|   EntityCategory entity_category = 13; | ||||
| } | ||||
| message SensorStateResponse { | ||||
|   option (id) = 25; | ||||
| @@ -508,6 +524,7 @@ message ListEntitiesSwitchResponse { | ||||
|   string icon = 5; | ||||
|   bool assumed_state = 6; | ||||
|   bool disabled_by_default = 7; | ||||
|   EntityCategory entity_category = 8; | ||||
| } | ||||
| message SwitchStateResponse { | ||||
|   option (id) = 26; | ||||
| @@ -541,6 +558,7 @@ message ListEntitiesTextSensorResponse { | ||||
|  | ||||
|   string icon = 5; | ||||
|   bool disabled_by_default = 6; | ||||
|   EntityCategory entity_category = 7; | ||||
| } | ||||
| message TextSensorStateResponse { | ||||
|   option (id) = 27; | ||||
| @@ -701,6 +719,8 @@ message ListEntitiesCameraResponse { | ||||
|   string name = 3; | ||||
|   string unique_id = 4; | ||||
|   bool disabled_by_default = 5; | ||||
|   string icon = 6; | ||||
|   EntityCategory entity_category = 7; | ||||
| } | ||||
|  | ||||
| message CameraImageResponse { | ||||
| @@ -795,6 +815,7 @@ message ListEntitiesClimateResponse { | ||||
|   repeated string supported_custom_presets = 17; | ||||
|   bool disabled_by_default = 18; | ||||
|   string icon = 19; | ||||
|   EntityCategory entity_category = 20; | ||||
| } | ||||
| message ClimateStateResponse { | ||||
|   option (id) = 47; | ||||
| @@ -848,6 +869,11 @@ message ClimateCommandRequest { | ||||
| } | ||||
|  | ||||
| // ==================== NUMBER ==================== | ||||
| enum NumberMode { | ||||
|   NUMBER_MODE_AUTO = 0; | ||||
|   NUMBER_MODE_BOX = 1; | ||||
|   NUMBER_MODE_SLIDER = 2; | ||||
| } | ||||
| message ListEntitiesNumberResponse { | ||||
|   option (id) = 49; | ||||
|   option (source) = SOURCE_SERVER; | ||||
| @@ -863,6 +889,9 @@ message ListEntitiesNumberResponse { | ||||
|   float max_value = 7; | ||||
|   float step = 8; | ||||
|   bool disabled_by_default = 9; | ||||
|   EntityCategory entity_category = 10; | ||||
|   string unit_of_measurement = 11; | ||||
|   NumberMode mode = 12; | ||||
| } | ||||
| message NumberStateResponse { | ||||
|   option (id) = 50; | ||||
| @@ -900,6 +929,7 @@ message ListEntitiesSelectResponse { | ||||
|   string icon = 5; | ||||
|   repeated string options = 6; | ||||
|   bool disabled_by_default = 7; | ||||
|   EntityCategory entity_category = 8; | ||||
| } | ||||
| message SelectStateResponse { | ||||
|   option (id) = 53; | ||||
| @@ -922,3 +952,28 @@ message SelectCommandRequest { | ||||
|   fixed32 key = 1; | ||||
|   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 { | ||||
|  | ||||
| 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) | ||||
|     : parent_(parent), initial_state_iterator_(parent, this), list_entities_iterator_(parent, this) { | ||||
| @@ -78,6 +79,8 @@ void APIConnection::loop() { | ||||
|     on_fatal_error(); | ||||
|     if (err == APIError::SOCKET_READ_FAILED && errno == ECONNRESET) { | ||||
|       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 { | ||||
|       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) { | ||||
|     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; | ||||
|     } else { | ||||
|       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.disabled_by_default = binary_sensor->is_disabled_by_default(); | ||||
|   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); | ||||
| } | ||||
| #endif | ||||
| @@ -215,6 +219,7 @@ bool APIConnection::send_cover_info(cover::Cover *cover) { | ||||
|   msg.device_class = cover->get_device_class(); | ||||
|   msg.disabled_by_default = cover->is_disabled_by_default(); | ||||
|   msg.icon = cover->get_icon(); | ||||
|   msg.entity_category = static_cast<enums::EntityCategory>(cover->get_entity_category()); | ||||
|   return this->send_list_entities_cover_response(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.disabled_by_default = fan->is_disabled_by_default(); | ||||
|   msg.icon = fan->get_icon(); | ||||
|   msg.entity_category = static_cast<enums::EntityCategory>(fan->get_entity_category()); | ||||
|   return this->send_list_entities_fan_response(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.icon = light->get_icon(); | ||||
|   msg.entity_category = static_cast<enums::EntityCategory>(light->get_entity_category()); | ||||
|  | ||||
|   for (auto mode : traits.get_supported_color_modes()) | ||||
|     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.state_class = static_cast<enums::SensorStateClass>(sensor->get_state_class()); | ||||
|   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); | ||||
| } | ||||
| #endif | ||||
| @@ -454,6 +461,7 @@ bool APIConnection::send_switch_info(switch_::Switch *a_switch) { | ||||
|   msg.icon = a_switch->get_icon(); | ||||
|   msg.assumed_state = a_switch->assumed_state(); | ||||
|   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); | ||||
| } | ||||
| 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.icon = text_sensor->get_icon(); | ||||
|   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); | ||||
| } | ||||
| #endif | ||||
| @@ -535,6 +544,7 @@ bool APIConnection::send_climate_info(climate::Climate *climate) { | ||||
|  | ||||
|   msg.disabled_by_default = climate->is_disabled_by_default(); | ||||
|   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_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.icon = number->get_icon(); | ||||
|   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.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.icon = select->get_icon(); | ||||
|   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()) | ||||
|     msg.options.push_back(option); | ||||
| @@ -663,13 +677,37 @@ void APIConnection::select_command(const SelectCommandRequest &msg) { | ||||
| } | ||||
| #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 | ||||
| void APIConnection::send_camera_state(std::shared_ptr<esp32_camera::CameraImage> image) { | ||||
|   if (!this->state_subscription_) | ||||
|     return; | ||||
|   if (this->image_reader_.available()) | ||||
|     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) { | ||||
|   ListEntitiesCameraResponse msg; | ||||
| @@ -678,6 +716,8 @@ bool APIConnection::send_camera_info(esp32_camera::ESP32Camera *camera) { | ||||
|   msg.name = camera->get_name(); | ||||
|   msg.unique_id = get_default_unique_id("camera", camera); | ||||
|   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); | ||||
| } | ||||
| void APIConnection::camera_image(const CameraImageRequest &msg) { | ||||
| @@ -685,9 +725,14 @@ void APIConnection::camera_image(const CameraImageRequest &msg) { | ||||
|     return; | ||||
|  | ||||
|   if (msg.single) | ||||
|     esp32_camera::global_esp32_camera->request_image(); | ||||
|   if (msg.stream) | ||||
|     esp32_camera::global_esp32_camera->request_stream(); | ||||
|     esp32_camera::global_esp32_camera->request_image(esphome::esp32_camera::API_REQUESTER); | ||||
|   if (msg.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 | ||||
|  | ||||
| @@ -756,6 +801,9 @@ DeviceInfoResponse APIConnection::device_info(const DeviceInfoRequest &msg) { | ||||
| #ifdef ESPHOME_PROJECT_NAME | ||||
|   resp.project_name = ESPHOME_PROJECT_NAME; | ||||
|   resp.project_version = ESPHOME_PROJECT_VERSION; | ||||
| #endif | ||||
| #ifdef USE_WEBSERVER | ||||
|   resp.webserver_port = WEBSERVER_PORT; | ||||
| #endif | ||||
|   return resp; | ||||
| } | ||||
|   | ||||
| @@ -73,6 +73,10 @@ class APIConnection : public APIServerConnection { | ||||
|   bool send_select_state(select::Select *select, std::string state); | ||||
|   bool send_select_info(select::Select *select); | ||||
|   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 | ||||
|   bool send_log_message(int level, const char *tag, const char *line); | ||||
|   void send_homeassistant_service_call(const HomeassistantServiceResponse &call) { | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| #include "api_frame_helper.h" | ||||
|  | ||||
| #include "esphome/core/log.h" | ||||
| #include "esphome/core/hal.h" | ||||
| #include "esphome/core/helpers.h" | ||||
| #include "proto.h" | ||||
| #include <cstring> | ||||
| @@ -10,7 +11,7 @@ namespace api { | ||||
|  | ||||
| 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) { | ||||
|   if (ret == -1) { | ||||
|     return errno == EWOULDBLOCK || errno == EAGAIN; | ||||
| @@ -64,6 +65,8 @@ const char *api_error_to_str(APIError err) { | ||||
|     return "HANDSHAKESTATE_SPLIT_FAILED"; | ||||
|   } else if (err == APIError::BAD_HANDSHAKE_ERROR_BYTE) { | ||||
|     return "BAD_HANDSHAKE_ERROR_BYTE"; | ||||
|   } else if (err == APIError::CONNECTION_CLOSED) { | ||||
|     return "CONNECTION_CLOSED"; | ||||
|   } | ||||
|   return "UNKNOWN"; | ||||
| } | ||||
| @@ -172,9 +175,6 @@ APIError APINoiseFrameHelper::loop() { | ||||
|  * errno API_ERROR_HANDSHAKE_PACKET_LEN: Packet too big for this phase. | ||||
|  */ | ||||
| APIError APINoiseFrameHelper::try_read_frame_(ParsedFrame *frame) { | ||||
|   int err; | ||||
|   APIError aerr; | ||||
|  | ||||
|   if (frame == nullptr) { | ||||
|     HELPER_LOG("Bad argument for try_read_frame_"); | ||||
|     return APIError::BAD_ARG; | ||||
| @@ -185,15 +185,20 @@ APIError APINoiseFrameHelper::try_read_frame_(ParsedFrame *frame) { | ||||
|     // no header information yet | ||||
|     size_t to_read = 3 - rx_header_buf_len_; | ||||
|     ssize_t received = socket_->read(&rx_header_buf_[rx_header_buf_len_], to_read); | ||||
|     if (is_would_block(received)) { | ||||
|       return APIError::WOULD_BLOCK; | ||||
|     } else if (received == -1) { | ||||
|     if (received == -1) { | ||||
|       if (errno == EWOULDBLOCK || errno == EAGAIN) { | ||||
|         return APIError::WOULD_BLOCK; | ||||
|       } | ||||
|       state_ = State::FAILED; | ||||
|       HELPER_LOG("Socket read failed with errno %d", errno); | ||||
|       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; | ||||
|     if (received != to_read) { | ||||
|     if ((size_t) received != to_read) { | ||||
|       // not a full read | ||||
|       return APIError::WOULD_BLOCK; | ||||
|     } | ||||
| @@ -227,15 +232,20 @@ APIError APINoiseFrameHelper::try_read_frame_(ParsedFrame *frame) { | ||||
|     // more data to read | ||||
|     size_t to_read = msg_size - rx_buf_len_; | ||||
|     ssize_t received = socket_->read(&rx_buf_[rx_buf_len_], to_read); | ||||
|     if (is_would_block(received)) { | ||||
|       return APIError::WOULD_BLOCK; | ||||
|     } else if (received == -1) { | ||||
|     if (received == -1) { | ||||
|       if (errno == EWOULDBLOCK || errno == EAGAIN) { | ||||
|         return APIError::WOULD_BLOCK; | ||||
|       } | ||||
|       state_ = State::FAILED; | ||||
|       HELPER_LOG("Socket read failed with errno %d", errno); | ||||
|       return APIError::SOCKET_READ_FAILED; | ||||
|     } else if (received == 0) { | ||||
|       state_ = State::FAILED; | ||||
|       HELPER_LOG("Connection closed"); | ||||
|       return APIError::CONNECTION_CLOSED; | ||||
|     } | ||||
|     rx_buf_len_ += received; | ||||
|     if (received != to_read) { | ||||
|     if ((size_t) received != to_read) { | ||||
|       // not all read | ||||
|       return APIError::WOULD_BLOCK; | ||||
|     } | ||||
| @@ -243,7 +253,7 @@ APIError APINoiseFrameHelper::try_read_frame_(ParsedFrame *frame) { | ||||
|  | ||||
|   // uncomment for even more debugging | ||||
| #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 | ||||
|   frame->msg = std::move(rx_buf_); | ||||
|   // consume msg | ||||
| @@ -532,13 +542,13 @@ APIError APINoiseFrameHelper::try_send_tx_buf_() { | ||||
| APIError APINoiseFrameHelper::write_raw_(const struct iovec *iov, int iovcnt) { | ||||
|   if (iovcnt == 0) | ||||
|     return APIError::OK; | ||||
|   int err; | ||||
|   APIError aerr; | ||||
|  | ||||
|   size_t total_write_len = 0; | ||||
|   for (int i = 0; i < iovcnt; i++) { | ||||
| #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 | ||||
|     total_write_len += iov[i].iov_len; | ||||
|   } | ||||
| @@ -572,7 +582,7 @@ APIError APINoiseFrameHelper::write_raw_(const struct iovec *iov, int iovcnt) { | ||||
|     state_ = State::FAILED; | ||||
|     HELPER_LOG("Socket write failed with errno %d", errno); | ||||
|     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 | ||||
|     size_t to_consume = sent; | ||||
|     for (int i = 0; i < iovcnt; i++) { | ||||
| @@ -712,7 +722,12 @@ APIError APINoiseFrameHelper::shutdown(int how) { | ||||
| } | ||||
| extern "C" { | ||||
| // 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 | ||||
|  | ||||
| @@ -766,9 +781,6 @@ APIError APIPlaintextFrameHelper::loop() { | ||||
|  * error API_ERROR_BAD_INDICATOR: Bad indicator byte at start of frame. | ||||
|  */ | ||||
| APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) { | ||||
|   int err; | ||||
|   APIError aerr; | ||||
|  | ||||
|   if (frame == nullptr) { | ||||
|     HELPER_LOG("Bad argument for try_read_frame_"); | ||||
|     return APIError::BAD_ARG; | ||||
| @@ -778,12 +790,17 @@ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) { | ||||
|   while (!rx_header_parsed_) { | ||||
|     uint8_t data; | ||||
|     ssize_t received = socket_->read(&data, 1); | ||||
|     if (is_would_block(received)) { | ||||
|       return APIError::WOULD_BLOCK; | ||||
|     } else if (received == -1) { | ||||
|     if (received == -1) { | ||||
|       if (errno == EWOULDBLOCK || errno == EAGAIN) { | ||||
|         return APIError::WOULD_BLOCK; | ||||
|       } | ||||
|       state_ = State::FAILED; | ||||
|       HELPER_LOG("Socket read failed with errno %d", errno); | ||||
|       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); | ||||
|  | ||||
| @@ -824,15 +841,20 @@ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) { | ||||
|     // more data to read | ||||
|     size_t to_read = rx_header_parsed_len_ - rx_buf_len_; | ||||
|     ssize_t received = socket_->read(&rx_buf_[rx_buf_len_], to_read); | ||||
|     if (is_would_block(received)) { | ||||
|       return APIError::WOULD_BLOCK; | ||||
|     } else if (received == -1) { | ||||
|     if (received == -1) { | ||||
|       if (errno == EWOULDBLOCK || errno == EAGAIN) { | ||||
|         return APIError::WOULD_BLOCK; | ||||
|       } | ||||
|       state_ = State::FAILED; | ||||
|       HELPER_LOG("Socket read failed with errno %d", errno); | ||||
|       return APIError::SOCKET_READ_FAILED; | ||||
|     } else if (received == 0) { | ||||
|       state_ = State::FAILED; | ||||
|       HELPER_LOG("Connection closed"); | ||||
|       return APIError::CONNECTION_CLOSED; | ||||
|     } | ||||
|     rx_buf_len_ += received; | ||||
|     if (received != to_read) { | ||||
|     if ((size_t) received != to_read) { | ||||
|       // not all read | ||||
|       return APIError::WOULD_BLOCK; | ||||
|     } | ||||
| @@ -840,7 +862,7 @@ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) { | ||||
|  | ||||
|   // uncomment for even more debugging | ||||
| #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 | ||||
|   frame->msg = std::move(rx_buf_); | ||||
|   // consume msg | ||||
| @@ -852,7 +874,6 @@ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) { | ||||
| } | ||||
|  | ||||
| APIError APIPlaintextFrameHelper::read_packet(ReadPacketBuffer *buffer) { | ||||
|   int err; | ||||
|   APIError aerr; | ||||
|  | ||||
|   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(); } | ||||
| APIError APIPlaintextFrameHelper::write_packet(uint16_t type, const uint8_t *payload, size_t payload_len) { | ||||
|   int err; | ||||
|   APIError aerr; | ||||
|  | ||||
|   if (state_ != State::DATA) { | ||||
|     return APIError::BAD_STATE; | ||||
|   } | ||||
| @@ -918,13 +936,13 @@ APIError APIPlaintextFrameHelper::try_send_tx_buf_() { | ||||
| APIError APIPlaintextFrameHelper::write_raw_(const struct iovec *iov, int iovcnt) { | ||||
|   if (iovcnt == 0) | ||||
|     return APIError::OK; | ||||
|   int err; | ||||
|   APIError aerr; | ||||
|  | ||||
|   size_t total_write_len = 0; | ||||
|   for (int i = 0; i < iovcnt; i++) { | ||||
| #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 | ||||
|     total_write_len += iov[i].iov_len; | ||||
|   } | ||||
| @@ -958,7 +976,7 @@ APIError APIPlaintextFrameHelper::write_raw_(const struct iovec *iov, int iovcnt | ||||
|     state_ = State::FAILED; | ||||
|     HELPER_LOG("Socket write failed with errno %d", errno); | ||||
|     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 | ||||
|     size_t to_consume = sent; | ||||
|     for (int i = 0; i < iovcnt; i++) { | ||||
|   | ||||
| @@ -53,6 +53,7 @@ enum class APIError : int { | ||||
|   HANDSHAKESTATE_SETUP_FAILED = 1019, | ||||
|   HANDSHAKESTATE_SPLIT_FAILED = 1020, | ||||
|   BAD_HANDSHAKE_ERROR_BYTE = 1021, | ||||
|   CONNECTION_CLOSED = 1022, | ||||
| }; | ||||
|  | ||||
| const char *api_error_to_str(APIError err); | ||||
|   | ||||
| @@ -6,6 +6,18 @@ | ||||
| namespace esphome { | ||||
| 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) { | ||||
|   switch (value) { | ||||
|     case enums::LEGACY_COVER_STATE_OPEN: | ||||
| @@ -254,6 +266,18 @@ template<> const char *proto_enum_to_string<enums::ClimatePreset>(enums::Climate | ||||
|       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) { | ||||
|   switch (field_id) { | ||||
|     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); } | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
| void HelloRequest::dump_to(std::string &out) const { | ||||
|   char buffer[64]; | ||||
|   __attribute__((unused)) char buffer[64]; | ||||
|   out.append("HelloRequest {\n"); | ||||
|   out.append("  client_info: "); | ||||
|   out.append("'").append(this->client_info).append("'"); | ||||
| @@ -306,7 +330,7 @@ void HelloResponse::encode(ProtoWriteBuffer buffer) const { | ||||
| } | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
| void HelloResponse::dump_to(std::string &out) const { | ||||
|   char buffer[64]; | ||||
|   __attribute__((unused)) char buffer[64]; | ||||
|   out.append("HelloResponse {\n"); | ||||
|   out.append("  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); } | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
| void ConnectRequest::dump_to(std::string &out) const { | ||||
|   char buffer[64]; | ||||
|   __attribute__((unused)) char buffer[64]; | ||||
|   out.append("ConnectRequest {\n"); | ||||
|   out.append("  password: "); | ||||
|   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); } | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
| void ConnectResponse::dump_to(std::string &out) const { | ||||
|   char buffer[64]; | ||||
|   __attribute__((unused)) char buffer[64]; | ||||
|   out.append("ConnectResponse {\n"); | ||||
|   out.append("  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(); | ||||
|       return true; | ||||
|     } | ||||
|     case 10: { | ||||
|       this->webserver_port = value.as_uint32(); | ||||
|       return true; | ||||
|     } | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| @@ -444,10 +472,11 @@ void DeviceInfoResponse::encode(ProtoWriteBuffer buffer) const { | ||||
|   buffer.encode_bool(7, this->has_deep_sleep); | ||||
|   buffer.encode_string(8, this->project_name); | ||||
|   buffer.encode_string(9, this->project_version); | ||||
|   buffer.encode_uint32(10, this->webserver_port); | ||||
| } | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
| void DeviceInfoResponse::dump_to(std::string &out) const { | ||||
|   char buffer[64]; | ||||
|   __attribute__((unused)) char buffer[64]; | ||||
|   out.append("DeviceInfoResponse {\n"); | ||||
|   out.append("  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("'").append(this->project_version).append("'"); | ||||
|   out.append("\n"); | ||||
|  | ||||
|   out.append("  webserver_port: "); | ||||
|   sprintf(buffer, "%u", this->webserver_port); | ||||
|   out.append(buffer); | ||||
|   out.append("\n"); | ||||
|   out.append("}"); | ||||
| } | ||||
| #endif | ||||
| @@ -509,6 +543,10 @@ bool ListEntitiesBinarySensorResponse::decode_varint(uint32_t field_id, ProtoVar | ||||
|       this->disabled_by_default = value.as_bool(); | ||||
|       return true; | ||||
|     } | ||||
|     case 9: { | ||||
|       this->entity_category = value.as_enum<enums::EntityCategory>(); | ||||
|       return true; | ||||
|     } | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| @@ -558,10 +596,11 @@ void ListEntitiesBinarySensorResponse::encode(ProtoWriteBuffer buffer) const { | ||||
|   buffer.encode_bool(6, this->is_status_binary_sensor); | ||||
|   buffer.encode_bool(7, this->disabled_by_default); | ||||
|   buffer.encode_string(8, this->icon); | ||||
|   buffer.encode_enum<enums::EntityCategory>(9, this->entity_category); | ||||
| } | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
| void ListEntitiesBinarySensorResponse::dump_to(std::string &out) const { | ||||
|   char buffer[64]; | ||||
|   __attribute__((unused)) char buffer[64]; | ||||
|   out.append("ListEntitiesBinarySensorResponse {\n"); | ||||
|   out.append("  object_id: "); | ||||
|   out.append("'").append(this->object_id).append("'"); | ||||
| @@ -595,6 +634,10 @@ void ListEntitiesBinarySensorResponse::dump_to(std::string &out) const { | ||||
|   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("}"); | ||||
| } | ||||
| #endif | ||||
| @@ -629,7 +672,7 @@ void BinarySensorStateResponse::encode(ProtoWriteBuffer buffer) const { | ||||
| } | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
| void BinarySensorStateResponse::dump_to(std::string &out) const { | ||||
|   char buffer[64]; | ||||
|   __attribute__((unused)) char buffer[64]; | ||||
|   out.append("BinarySensorStateResponse {\n"); | ||||
|   out.append("  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(); | ||||
|       return true; | ||||
|     } | ||||
|     case 11: { | ||||
|       this->entity_category = value.as_enum<enums::EntityCategory>(); | ||||
|       return true; | ||||
|     } | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| @@ -715,10 +762,11 @@ void ListEntitiesCoverResponse::encode(ProtoWriteBuffer buffer) const { | ||||
|   buffer.encode_string(8, this->device_class); | ||||
|   buffer.encode_bool(9, this->disabled_by_default); | ||||
|   buffer.encode_string(10, this->icon); | ||||
|   buffer.encode_enum<enums::EntityCategory>(11, this->entity_category); | ||||
| } | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
| void ListEntitiesCoverResponse::dump_to(std::string &out) const { | ||||
|   char buffer[64]; | ||||
|   __attribute__((unused)) char buffer[64]; | ||||
|   out.append("ListEntitiesCoverResponse {\n"); | ||||
|   out.append("  object_id: "); | ||||
|   out.append("'").append(this->object_id).append("'"); | ||||
| @@ -760,6 +808,10 @@ void ListEntitiesCoverResponse::dump_to(std::string &out) const { | ||||
|   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("}"); | ||||
| } | ||||
| #endif | ||||
| @@ -804,7 +856,7 @@ void CoverStateResponse::encode(ProtoWriteBuffer buffer) const { | ||||
| } | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
| void CoverStateResponse::dump_to(std::string &out) const { | ||||
|   char buffer[64]; | ||||
|   __attribute__((unused)) char buffer[64]; | ||||
|   out.append("CoverStateResponse {\n"); | ||||
|   out.append("  key: "); | ||||
|   sprintf(buffer, "%u", this->key); | ||||
| @@ -887,7 +939,7 @@ void CoverCommandRequest::encode(ProtoWriteBuffer buffer) const { | ||||
| } | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
| void CoverCommandRequest::dump_to(std::string &out) const { | ||||
|   char buffer[64]; | ||||
|   __attribute__((unused)) char buffer[64]; | ||||
|   out.append("CoverCommandRequest {\n"); | ||||
|   out.append("  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(); | ||||
|       return true; | ||||
|     } | ||||
|     case 11: { | ||||
|       this->entity_category = value.as_enum<enums::EntityCategory>(); | ||||
|       return true; | ||||
|     } | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| @@ -995,10 +1051,11 @@ void ListEntitiesFanResponse::encode(ProtoWriteBuffer buffer) const { | ||||
|   buffer.encode_int32(8, this->supported_speed_count); | ||||
|   buffer.encode_bool(9, this->disabled_by_default); | ||||
|   buffer.encode_string(10, this->icon); | ||||
|   buffer.encode_enum<enums::EntityCategory>(11, this->entity_category); | ||||
| } | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
| void ListEntitiesFanResponse::dump_to(std::string &out) const { | ||||
|   char buffer[64]; | ||||
|   __attribute__((unused)) char buffer[64]; | ||||
|   out.append("ListEntitiesFanResponse {\n"); | ||||
|   out.append("  object_id: "); | ||||
|   out.append("'").append(this->object_id).append("'"); | ||||
| @@ -1041,6 +1098,10 @@ void ListEntitiesFanResponse::dump_to(std::string &out) const { | ||||
|   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("}"); | ||||
| } | ||||
| #endif | ||||
| @@ -1090,7 +1151,7 @@ void FanStateResponse::encode(ProtoWriteBuffer buffer) const { | ||||
| } | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
| void FanStateResponse::dump_to(std::string &out) const { | ||||
|   char buffer[64]; | ||||
|   __attribute__((unused)) char buffer[64]; | ||||
|   out.append("FanStateResponse {\n"); | ||||
|   out.append("  key: "); | ||||
|   sprintf(buffer, "%u", this->key); | ||||
| @@ -1191,7 +1252,7 @@ void FanCommandRequest::encode(ProtoWriteBuffer buffer) const { | ||||
| } | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
| void FanCommandRequest::dump_to(std::string &out) const { | ||||
|   char buffer[64]; | ||||
|   __attribute__((unused)) char buffer[64]; | ||||
|   out.append("FanCommandRequest {\n"); | ||||
|   out.append("  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(); | ||||
|       return true; | ||||
|     } | ||||
|     case 15: { | ||||
|       this->entity_category = value.as_enum<enums::EntityCategory>(); | ||||
|       return true; | ||||
|     } | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| @@ -1334,10 +1399,11 @@ void ListEntitiesLightResponse::encode(ProtoWriteBuffer buffer) const { | ||||
|   } | ||||
|   buffer.encode_bool(13, this->disabled_by_default); | ||||
|   buffer.encode_string(14, this->icon); | ||||
|   buffer.encode_enum<enums::EntityCategory>(15, this->entity_category); | ||||
| } | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
| void ListEntitiesLightResponse::dump_to(std::string &out) const { | ||||
|   char buffer[64]; | ||||
|   __attribute__((unused)) char buffer[64]; | ||||
|   out.append("ListEntitiesLightResponse {\n"); | ||||
|   out.append("  object_id: "); | ||||
|   out.append("'").append(this->object_id).append("'"); | ||||
| @@ -1401,6 +1467,10 @@ void ListEntitiesLightResponse::dump_to(std::string &out) const { | ||||
|   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("}"); | ||||
| } | ||||
| #endif | ||||
| @@ -1491,7 +1561,7 @@ void LightStateResponse::encode(ProtoWriteBuffer buffer) const { | ||||
| } | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
| void LightStateResponse::dump_to(std::string &out) const { | ||||
|   char buffer[64]; | ||||
|   __attribute__((unused)) char buffer[64]; | ||||
|   out.append("LightStateResponse {\n"); | ||||
|   out.append("  key: "); | ||||
|   sprintf(buffer, "%u", this->key); | ||||
| @@ -1714,7 +1784,7 @@ void LightCommandRequest::encode(ProtoWriteBuffer buffer) const { | ||||
| } | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
| void LightCommandRequest::dump_to(std::string &out) const { | ||||
|   char buffer[64]; | ||||
|   __attribute__((unused)) char buffer[64]; | ||||
|   out.append("LightCommandRequest {\n"); | ||||
|   out.append("  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(); | ||||
|       return true; | ||||
|     } | ||||
|     case 13: { | ||||
|       this->entity_category = value.as_enum<enums::EntityCategory>(); | ||||
|       return true; | ||||
|     } | ||||
|     default: | ||||
|       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::SensorLastResetType>(11, this->legacy_last_reset_type); | ||||
|   buffer.encode_bool(12, this->disabled_by_default); | ||||
|   buffer.encode_enum<enums::EntityCategory>(13, this->entity_category); | ||||
| } | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
| void ListEntitiesSensorResponse::dump_to(std::string &out) const { | ||||
|   char buffer[64]; | ||||
|   __attribute__((unused)) char buffer[64]; | ||||
|   out.append("ListEntitiesSensorResponse {\n"); | ||||
|   out.append("  object_id: "); | ||||
|   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(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("}"); | ||||
| } | ||||
| #endif | ||||
| @@ -2005,7 +2084,7 @@ void SensorStateResponse::encode(ProtoWriteBuffer buffer) const { | ||||
| } | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
| void SensorStateResponse::dump_to(std::string &out) const { | ||||
|   char buffer[64]; | ||||
|   __attribute__((unused)) char buffer[64]; | ||||
|   out.append("SensorStateResponse {\n"); | ||||
|   out.append("  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(); | ||||
|       return true; | ||||
|     } | ||||
|     case 8: { | ||||
|       this->entity_category = value.as_enum<enums::EntityCategory>(); | ||||
|       return true; | ||||
|     } | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| @@ -2077,10 +2160,11 @@ void ListEntitiesSwitchResponse::encode(ProtoWriteBuffer buffer) const { | ||||
|   buffer.encode_string(5, this->icon); | ||||
|   buffer.encode_bool(6, this->assumed_state); | ||||
|   buffer.encode_bool(7, this->disabled_by_default); | ||||
|   buffer.encode_enum<enums::EntityCategory>(8, this->entity_category); | ||||
| } | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
| void ListEntitiesSwitchResponse::dump_to(std::string &out) const { | ||||
|   char buffer[64]; | ||||
|   __attribute__((unused)) char buffer[64]; | ||||
|   out.append("ListEntitiesSwitchResponse {\n"); | ||||
|   out.append("  object_id: "); | ||||
|   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(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("}"); | ||||
| } | ||||
| #endif | ||||
| @@ -2139,7 +2227,7 @@ void SwitchStateResponse::encode(ProtoWriteBuffer buffer) const { | ||||
| } | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
| void SwitchStateResponse::dump_to(std::string &out) const { | ||||
|   char buffer[64]; | ||||
|   __attribute__((unused)) char buffer[64]; | ||||
|   out.append("SwitchStateResponse {\n"); | ||||
|   out.append("  key: "); | ||||
|   sprintf(buffer, "%u", this->key); | ||||
| @@ -2178,7 +2266,7 @@ void SwitchCommandRequest::encode(ProtoWriteBuffer buffer) const { | ||||
| } | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
| void SwitchCommandRequest::dump_to(std::string &out) const { | ||||
|   char buffer[64]; | ||||
|   __attribute__((unused)) char buffer[64]; | ||||
|   out.append("SwitchCommandRequest {\n"); | ||||
|   out.append("  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(); | ||||
|       return true; | ||||
|     } | ||||
|     case 7: { | ||||
|       this->entity_category = value.as_enum<enums::EntityCategory>(); | ||||
|       return true; | ||||
|     } | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| @@ -2240,10 +2332,11 @@ void ListEntitiesTextSensorResponse::encode(ProtoWriteBuffer buffer) const { | ||||
|   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); | ||||
| } | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
| void ListEntitiesTextSensorResponse::dump_to(std::string &out) const { | ||||
|   char buffer[64]; | ||||
|   __attribute__((unused)) char buffer[64]; | ||||
|   out.append("ListEntitiesTextSensorResponse {\n"); | ||||
|   out.append("  object_id: "); | ||||
|   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(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("}"); | ||||
| } | ||||
| #endif | ||||
| @@ -2309,7 +2406,7 @@ void TextSensorStateResponse::encode(ProtoWriteBuffer buffer) const { | ||||
| } | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
| void TextSensorStateResponse::dump_to(std::string &out) const { | ||||
|   char buffer[64]; | ||||
|   __attribute__((unused)) char buffer[64]; | ||||
|   out.append("TextSensorStateResponse {\n"); | ||||
|   out.append("  key: "); | ||||
|   sprintf(buffer, "%u", this->key); | ||||
| @@ -2346,7 +2443,7 @@ void SubscribeLogsRequest::encode(ProtoWriteBuffer buffer) const { | ||||
| } | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
| void SubscribeLogsRequest::dump_to(std::string &out) const { | ||||
|   char buffer[64]; | ||||
|   __attribute__((unused)) char buffer[64]; | ||||
|   out.append("SubscribeLogsRequest {\n"); | ||||
|   out.append("  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 | ||||
| void SubscribeLogsResponse::dump_to(std::string &out) const { | ||||
|   char buffer[64]; | ||||
|   __attribute__((unused)) char buffer[64]; | ||||
|   out.append("SubscribeLogsResponse {\n"); | ||||
|   out.append("  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 | ||||
| void HomeassistantServiceMap::dump_to(std::string &out) const { | ||||
|   char buffer[64]; | ||||
|   __attribute__((unused)) char buffer[64]; | ||||
|   out.append("HomeassistantServiceMap {\n"); | ||||
|   out.append("  key: "); | ||||
|   out.append("'").append(this->key).append("'"); | ||||
| @@ -2490,7 +2587,7 @@ void HomeassistantServiceResponse::encode(ProtoWriteBuffer buffer) const { | ||||
| } | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
| void HomeassistantServiceResponse::dump_to(std::string &out) const { | ||||
|   char buffer[64]; | ||||
|   __attribute__((unused)) char buffer[64]; | ||||
|   out.append("HomeassistantServiceResponse {\n"); | ||||
|   out.append("  service: "); | ||||
|   out.append("'").append(this->service).append("'"); | ||||
| @@ -2546,7 +2643,7 @@ void SubscribeHomeAssistantStateResponse::encode(ProtoWriteBuffer buffer) const | ||||
| } | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
| void SubscribeHomeAssistantStateResponse::dump_to(std::string &out) const { | ||||
|   char buffer[64]; | ||||
|   __attribute__((unused)) char buffer[64]; | ||||
|   out.append("SubscribeHomeAssistantStateResponse {\n"); | ||||
|   out.append("  entity_id: "); | ||||
|   out.append("'").append(this->entity_id).append("'"); | ||||
| @@ -2583,7 +2680,7 @@ void HomeAssistantStateResponse::encode(ProtoWriteBuffer buffer) const { | ||||
| } | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
| void HomeAssistantStateResponse::dump_to(std::string &out) const { | ||||
|   char buffer[64]; | ||||
|   __attribute__((unused)) char buffer[64]; | ||||
|   out.append("HomeAssistantStateResponse {\n"); | ||||
|   out.append("  entity_id: "); | ||||
|   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); } | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
| void GetTimeResponse::dump_to(std::string &out) const { | ||||
|   char buffer[64]; | ||||
|   __attribute__((unused)) char buffer[64]; | ||||
|   out.append("GetTimeResponse {\n"); | ||||
|   out.append("  epoch_seconds: "); | ||||
|   sprintf(buffer, "%u", this->epoch_seconds); | ||||
| @@ -2651,7 +2748,7 @@ void ListEntitiesServicesArgument::encode(ProtoWriteBuffer buffer) const { | ||||
| } | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
| void ListEntitiesServicesArgument::dump_to(std::string &out) const { | ||||
|   char buffer[64]; | ||||
|   __attribute__((unused)) char buffer[64]; | ||||
|   out.append("ListEntitiesServicesArgument {\n"); | ||||
|   out.append("  name: "); | ||||
|   out.append("'").append(this->name).append("'"); | ||||
| @@ -2696,7 +2793,7 @@ void ListEntitiesServicesResponse::encode(ProtoWriteBuffer buffer) const { | ||||
| } | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
| void ListEntitiesServicesResponse::dump_to(std::string &out) const { | ||||
|   char buffer[64]; | ||||
|   __attribute__((unused)) char buffer[64]; | ||||
|   out.append("ListEntitiesServicesResponse {\n"); | ||||
|   out.append("  name: "); | ||||
|   out.append("'").append(this->name).append("'"); | ||||
| @@ -2790,7 +2887,7 @@ void ExecuteServiceArgument::encode(ProtoWriteBuffer buffer) const { | ||||
| } | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
| void ExecuteServiceArgument::dump_to(std::string &out) const { | ||||
|   char buffer[64]; | ||||
|   __attribute__((unused)) char buffer[64]; | ||||
|   out.append("ExecuteServiceArgument {\n"); | ||||
|   out.append("  bool_: "); | ||||
|   out.append(YESNO(this->bool_)); | ||||
| @@ -2871,7 +2968,7 @@ void ExecuteServiceRequest::encode(ProtoWriteBuffer buffer) const { | ||||
| } | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
| void ExecuteServiceRequest::dump_to(std::string &out) const { | ||||
|   char buffer[64]; | ||||
|   __attribute__((unused)) char buffer[64]; | ||||
|   out.append("ExecuteServiceRequest {\n"); | ||||
|   out.append("  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(); | ||||
|       return true; | ||||
|     } | ||||
|     case 7: { | ||||
|       this->entity_category = value.as_enum<enums::EntityCategory>(); | ||||
|       return true; | ||||
|     } | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| @@ -2910,6 +3011,10 @@ bool ListEntitiesCameraResponse::decode_length(uint32_t field_id, ProtoLengthDel | ||||
|       this->unique_id = value.as_string(); | ||||
|       return true; | ||||
|     } | ||||
|     case 6: { | ||||
|       this->icon = value.as_string(); | ||||
|       return true; | ||||
|     } | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| @@ -2930,10 +3035,12 @@ void ListEntitiesCameraResponse::encode(ProtoWriteBuffer buffer) const { | ||||
|   buffer.encode_string(3, this->name); | ||||
|   buffer.encode_string(4, this->unique_id); | ||||
|   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 | ||||
| void ListEntitiesCameraResponse::dump_to(std::string &out) const { | ||||
|   char buffer[64]; | ||||
|   __attribute__((unused)) char buffer[64]; | ||||
|   out.append("ListEntitiesCameraResponse {\n"); | ||||
|   out.append("  object_id: "); | ||||
|   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(YESNO(this->disabled_by_default)); | ||||
|   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("}"); | ||||
| } | ||||
| #endif | ||||
| @@ -2995,7 +3110,7 @@ void CameraImageResponse::encode(ProtoWriteBuffer buffer) const { | ||||
| } | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
| void CameraImageResponse::dump_to(std::string &out) const { | ||||
|   char buffer[64]; | ||||
|   __attribute__((unused)) char buffer[64]; | ||||
|   out.append("CameraImageResponse {\n"); | ||||
|   out.append("  key: "); | ||||
|   sprintf(buffer, "%u", this->key); | ||||
| @@ -3032,7 +3147,7 @@ void CameraImageRequest::encode(ProtoWriteBuffer buffer) const { | ||||
| } | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
| void CameraImageRequest::dump_to(std::string &out) const { | ||||
|   char buffer[64]; | ||||
|   __attribute__((unused)) char buffer[64]; | ||||
|   out.append("CameraImageRequest {\n"); | ||||
|   out.append("  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(); | ||||
|       return true; | ||||
|     } | ||||
|     case 20: { | ||||
|       this->entity_category = value.as_enum<enums::EntityCategory>(); | ||||
|       return true; | ||||
|     } | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| @@ -3170,10 +3289,11 @@ void ListEntitiesClimateResponse::encode(ProtoWriteBuffer buffer) const { | ||||
|   } | ||||
|   buffer.encode_bool(18, this->disabled_by_default); | ||||
|   buffer.encode_string(19, this->icon); | ||||
|   buffer.encode_enum<enums::EntityCategory>(20, this->entity_category); | ||||
| } | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
| void ListEntitiesClimateResponse::dump_to(std::string &out) const { | ||||
|   char buffer[64]; | ||||
|   __attribute__((unused)) char buffer[64]; | ||||
|   out.append("ListEntitiesClimateResponse {\n"); | ||||
|   out.append("  object_id: "); | ||||
|   out.append("'").append(this->object_id).append("'"); | ||||
| @@ -3266,6 +3386,10 @@ void ListEntitiesClimateResponse::dump_to(std::string &out) const { | ||||
|   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("}"); | ||||
| } | ||||
| #endif | ||||
| @@ -3356,7 +3480,7 @@ void ClimateStateResponse::encode(ProtoWriteBuffer buffer) const { | ||||
| } | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
| void ClimateStateResponse::dump_to(std::string &out) const { | ||||
|   char buffer[64]; | ||||
|   __attribute__((unused)) char buffer[64]; | ||||
|   out.append("ClimateStateResponse {\n"); | ||||
|   out.append("  key: "); | ||||
|   sprintf(buffer, "%u", this->key); | ||||
| @@ -3544,7 +3668,7 @@ void ClimateCommandRequest::encode(ProtoWriteBuffer buffer) const { | ||||
| } | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
| void ClimateCommandRequest::dump_to(std::string &out) const { | ||||
|   char buffer[64]; | ||||
|   __attribute__((unused)) char buffer[64]; | ||||
|   out.append("ClimateCommandRequest {\n"); | ||||
|   out.append("  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(); | ||||
|       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: | ||||
|       return false; | ||||
|   } | ||||
| @@ -3664,6 +3796,10 @@ bool ListEntitiesNumberResponse::decode_length(uint32_t field_id, ProtoLengthDel | ||||
|       this->icon = value.as_string(); | ||||
|       return true; | ||||
|     } | ||||
|     case 11: { | ||||
|       this->unit_of_measurement = value.as_string(); | ||||
|       return true; | ||||
|     } | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| @@ -3700,10 +3836,13 @@ void ListEntitiesNumberResponse::encode(ProtoWriteBuffer buffer) const { | ||||
|   buffer.encode_float(7, this->max_value); | ||||
|   buffer.encode_float(8, this->step); | ||||
|   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 | ||||
| void ListEntitiesNumberResponse::dump_to(std::string &out) const { | ||||
|   char buffer[64]; | ||||
|   __attribute__((unused)) char buffer[64]; | ||||
|   out.append("ListEntitiesNumberResponse {\n"); | ||||
|   out.append("  object_id: "); | ||||
|   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(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("  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("}"); | ||||
| } | ||||
| #endif | ||||
| @@ -3778,7 +3929,7 @@ void NumberStateResponse::encode(ProtoWriteBuffer buffer) const { | ||||
| } | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
| void NumberStateResponse::dump_to(std::string &out) const { | ||||
|   char buffer[64]; | ||||
|   __attribute__((unused)) char buffer[64]; | ||||
|   out.append("NumberStateResponse {\n"); | ||||
|   out.append("  key: "); | ||||
|   sprintf(buffer, "%u", this->key); | ||||
| @@ -3816,7 +3967,7 @@ void NumberCommandRequest::encode(ProtoWriteBuffer buffer) const { | ||||
| } | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
| void NumberCommandRequest::dump_to(std::string &out) const { | ||||
|   char buffer[64]; | ||||
|   __attribute__((unused)) char buffer[64]; | ||||
|   out.append("NumberCommandRequest {\n"); | ||||
|   out.append("  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(); | ||||
|       return true; | ||||
|     } | ||||
|     case 8: { | ||||
|       this->entity_category = value.as_enum<enums::EntityCategory>(); | ||||
|       return true; | ||||
|     } | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| @@ -3886,10 +4041,11 @@ void ListEntitiesSelectResponse::encode(ProtoWriteBuffer buffer) const { | ||||
|     buffer.encode_string(6, it, true); | ||||
|   } | ||||
|   buffer.encode_bool(7, this->disabled_by_default); | ||||
|   buffer.encode_enum<enums::EntityCategory>(8, this->entity_category); | ||||
| } | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
| void ListEntitiesSelectResponse::dump_to(std::string &out) const { | ||||
|   char buffer[64]; | ||||
|   __attribute__((unused)) char buffer[64]; | ||||
|   out.append("ListEntitiesSelectResponse {\n"); | ||||
|   out.append("  object_id: "); | ||||
|   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(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("}"); | ||||
| } | ||||
| #endif | ||||
| @@ -3961,7 +4121,7 @@ void SelectStateResponse::encode(ProtoWriteBuffer buffer) const { | ||||
| } | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
| void SelectStateResponse::dump_to(std::string &out) const { | ||||
|   char buffer[64]; | ||||
|   __attribute__((unused)) char buffer[64]; | ||||
|   out.append("SelectStateResponse {\n"); | ||||
|   out.append("  key: "); | ||||
|   sprintf(buffer, "%u", this->key); | ||||
| @@ -4004,7 +4164,7 @@ void SelectCommandRequest::encode(ProtoWriteBuffer buffer) const { | ||||
| } | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
| void SelectCommandRequest::dump_to(std::string &out) const { | ||||
|   char buffer[64]; | ||||
|   __attribute__((unused)) char buffer[64]; | ||||
|   out.append("SelectCommandRequest {\n"); | ||||
|   out.append("  key: "); | ||||
|   sprintf(buffer, "%u", this->key); | ||||
| @@ -4017,6 +4177,127 @@ void SelectCommandRequest::dump_to(std::string &out) const { | ||||
|   out.append("}"); | ||||
| } | ||||
| #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 esphome | ||||
|   | ||||
| @@ -9,6 +9,11 @@ namespace api { | ||||
|  | ||||
| namespace enums { | ||||
|  | ||||
| enum EntityCategory : uint32_t { | ||||
|   ENTITY_CATEGORY_NONE = 0, | ||||
|   ENTITY_CATEGORY_CONFIG = 1, | ||||
|   ENTITY_CATEGORY_DIAGNOSTIC = 2, | ||||
| }; | ||||
| enum LegacyCoverState : uint32_t { | ||||
|   LEGACY_COVER_STATE_OPEN = 0, | ||||
|   LEGACY_COVER_STATE_CLOSED = 1, | ||||
| @@ -118,6 +123,11 @@ enum ClimatePreset : uint32_t { | ||||
|   CLIMATE_PRESET_SLEEP = 6, | ||||
|   CLIMATE_PRESET_ACTIVITY = 7, | ||||
| }; | ||||
| enum NumberMode : uint32_t { | ||||
|   NUMBER_MODE_AUTO = 0, | ||||
|   NUMBER_MODE_BOX = 1, | ||||
|   NUMBER_MODE_SLIDER = 2, | ||||
| }; | ||||
|  | ||||
| }  // namespace enums | ||||
|  | ||||
| @@ -224,6 +234,7 @@ class DeviceInfoResponse : public ProtoMessage { | ||||
|   bool has_deep_sleep{false}; | ||||
|   std::string project_name{}; | ||||
|   std::string project_version{}; | ||||
|   uint32_t webserver_port{0}; | ||||
|   void encode(ProtoWriteBuffer buffer) const override; | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|   void dump_to(std::string &out) const override; | ||||
| @@ -270,6 +281,7 @@ class ListEntitiesBinarySensorResponse : public ProtoMessage { | ||||
|   bool is_status_binary_sensor{false}; | ||||
|   bool disabled_by_default{false}; | ||||
|   std::string icon{}; | ||||
|   enums::EntityCategory entity_category{}; | ||||
|   void encode(ProtoWriteBuffer buffer) const override; | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|   void dump_to(std::string &out) const override; | ||||
| @@ -306,6 +318,7 @@ class ListEntitiesCoverResponse : public ProtoMessage { | ||||
|   std::string device_class{}; | ||||
|   bool disabled_by_default{false}; | ||||
|   std::string icon{}; | ||||
|   enums::EntityCategory entity_category{}; | ||||
|   void encode(ProtoWriteBuffer buffer) const override; | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|   void dump_to(std::string &out) const override; | ||||
| @@ -363,6 +376,7 @@ class ListEntitiesFanResponse : public ProtoMessage { | ||||
|   int32_t supported_speed_count{0}; | ||||
|   bool disabled_by_default{false}; | ||||
|   std::string icon{}; | ||||
|   enums::EntityCategory entity_category{}; | ||||
|   void encode(ProtoWriteBuffer buffer) const override; | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|   void dump_to(std::string &out) const override; | ||||
| @@ -428,6 +442,7 @@ class ListEntitiesLightResponse : public ProtoMessage { | ||||
|   std::vector<std::string> effects{}; | ||||
|   bool disabled_by_default{false}; | ||||
|   std::string icon{}; | ||||
|   enums::EntityCategory entity_category{}; | ||||
|   void encode(ProtoWriteBuffer buffer) const override; | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|   void dump_to(std::string &out) const override; | ||||
| @@ -516,6 +531,7 @@ class ListEntitiesSensorResponse : public ProtoMessage { | ||||
|   enums::SensorStateClass state_class{}; | ||||
|   enums::SensorLastResetType legacy_last_reset_type{}; | ||||
|   bool disabled_by_default{false}; | ||||
|   enums::EntityCategory entity_category{}; | ||||
|   void encode(ProtoWriteBuffer buffer) const override; | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|   void dump_to(std::string &out) const override; | ||||
| @@ -549,6 +565,7 @@ class ListEntitiesSwitchResponse : public ProtoMessage { | ||||
|   std::string icon{}; | ||||
|   bool assumed_state{false}; | ||||
|   bool disabled_by_default{false}; | ||||
|   enums::EntityCategory entity_category{}; | ||||
|   void encode(ProtoWriteBuffer buffer) const override; | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|   void dump_to(std::string &out) const override; | ||||
| @@ -593,6 +610,7 @@ class ListEntitiesTextSensorResponse : public ProtoMessage { | ||||
|   std::string unique_id{}; | ||||
|   std::string icon{}; | ||||
|   bool disabled_by_default{false}; | ||||
|   enums::EntityCategory entity_category{}; | ||||
|   void encode(ProtoWriteBuffer buffer) const override; | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|   void dump_to(std::string &out) const override; | ||||
| @@ -803,6 +821,8 @@ class ListEntitiesCameraResponse : public ProtoMessage { | ||||
|   std::string name{}; | ||||
|   std::string unique_id{}; | ||||
|   bool disabled_by_default{false}; | ||||
|   std::string icon{}; | ||||
|   enums::EntityCategory entity_category{}; | ||||
|   void encode(ProtoWriteBuffer buffer) const override; | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|   void dump_to(std::string &out) const override; | ||||
| @@ -861,6 +881,7 @@ class ListEntitiesClimateResponse : public ProtoMessage { | ||||
|   std::vector<std::string> supported_custom_presets{}; | ||||
|   bool disabled_by_default{false}; | ||||
|   std::string icon{}; | ||||
|   enums::EntityCategory entity_category{}; | ||||
|   void encode(ProtoWriteBuffer buffer) const override; | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|   void dump_to(std::string &out) const override; | ||||
| @@ -940,6 +961,9 @@ class ListEntitiesNumberResponse : public ProtoMessage { | ||||
|   float max_value{0.0f}; | ||||
|   float step{0.0f}; | ||||
|   bool disabled_by_default{false}; | ||||
|   enums::EntityCategory entity_category{}; | ||||
|   std::string unit_of_measurement{}; | ||||
|   enums::NumberMode mode{}; | ||||
|   void encode(ProtoWriteBuffer buffer) const override; | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|   void dump_to(std::string &out) const override; | ||||
| @@ -985,6 +1009,7 @@ class ListEntitiesSelectResponse : public ProtoMessage { | ||||
|   std::string icon{}; | ||||
|   std::vector<std::string> options{}; | ||||
|   bool disabled_by_default{false}; | ||||
|   enums::EntityCategory entity_category{}; | ||||
|   void encode(ProtoWriteBuffer buffer) const override; | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|   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_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 esphome | ||||
|   | ||||
| @@ -282,6 +282,16 @@ bool APIServerConnectionBase::send_select_state_response(const SelectStateRespon | ||||
| #endif | ||||
| #ifdef USE_SELECT | ||||
| #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) { | ||||
|   switch (msg_type) { | ||||
|     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()); | ||||
| #endif | ||||
|       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 | ||||
|       break; | ||||
|     } | ||||
| @@ -737,6 +758,19 @@ void APIServerConnection::on_select_command_request(const SelectCommandRequest & | ||||
|   this->select_command(msg); | ||||
| } | ||||
| #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 esphome | ||||
|   | ||||
| @@ -129,6 +129,12 @@ class APIServerConnectionBase : public ProtoService { | ||||
| #endif | ||||
| #ifdef USE_SELECT | ||||
|   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 | ||||
|  protected: | ||||
|   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 | ||||
| #ifdef USE_SELECT | ||||
|   virtual void select_command(const SelectCommandRequest &msg) = 0; | ||||
| #endif | ||||
| #ifdef USE_BUTTON | ||||
|   virtual void button_command(const ButtonCommandRequest &msg) = 0; | ||||
| #endif | ||||
|  protected: | ||||
|   void on_hello_request(const HelloRequest &msg) override; | ||||
| @@ -209,6 +218,9 @@ class APIServerConnection : public APIServerConnectionBase { | ||||
| #ifdef USE_SELECT | ||||
|   void on_select_command_request(const SelectCommandRequest &msg) override; | ||||
| #endif | ||||
| #ifdef USE_BUTTON | ||||
|   void on_button_command_request(const ButtonCommandRequest &msg) override; | ||||
| #endif | ||||
| }; | ||||
|  | ||||
| }  // namespace api | ||||
|   | ||||
| @@ -77,7 +77,7 @@ void APIServer::setup() { | ||||
|   this->last_connected_ = millis(); | ||||
|  | ||||
| #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( | ||||
|         [this](const std::shared_ptr<esp32_camera::CameraImage> &image) { | ||||
|           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) | ||||
|     zc = zeroconf.Zeroconf() | ||||
|     cli = APIClient( | ||||
|         asyncio.get_event_loop(), | ||||
|         address, | ||||
|         port, | ||||
|         password, | ||||
|   | ||||
| @@ -8,6 +8,18 @@ | ||||
| namespace esphome { | ||||
| 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 { | ||||
|  public: | ||||
|   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: | ||||
|   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) { | ||||
|     this->data_.push_back(TemplatableKeyValuePair<Ts...>(key, value)); | ||||
|   } | ||||
| @@ -58,6 +71,7 @@ template<typename... Ts> class HomeAssistantServiceCallAction : public Action<Ts | ||||
|  protected: | ||||
|   APIServer *parent_; | ||||
|   bool is_event_; | ||||
|   TemplatableStringValue<Ts...> service_{}; | ||||
|   std::vector<TemplatableKeyValuePair<Ts...>> data_; | ||||
|   std::vector<TemplatableKeyValuePair<Ts...>> data_template_; | ||||
|   std::vector<TemplatableKeyValuePair<Ts...>> variables_; | ||||
|   | ||||
| @@ -27,6 +27,9 @@ bool ListEntitiesIterator::on_sensor(sensor::Sensor *sensor) { return this->clie | ||||
| #ifdef USE_SWITCH | ||||
| bool ListEntitiesIterator::on_switch(switch_::Switch *a_switch) { return this->client_->send_switch_info(a_switch); } | ||||
| #endif | ||||
| #ifdef USE_BUTTON | ||||
| bool ListEntitiesIterator::on_button(button::Button *button) { return this->client_->send_button_info(button); } | ||||
| #endif | ||||
| #ifdef USE_TEXT_SENSOR | ||||
| bool ListEntitiesIterator::on_text_sensor(text_sensor::TextSensor *text_sensor) { | ||||
|   return this->client_->send_text_sensor_info(text_sensor); | ||||
|   | ||||
| @@ -30,6 +30,9 @@ class ListEntitiesIterator : public ComponentIterator { | ||||
| #ifdef USE_SWITCH | ||||
|   bool on_switch(switch_::Switch *a_switch) override; | ||||
| #endif | ||||
| #ifdef USE_BUTTON | ||||
|   bool on_button(button::Button *button) override; | ||||
| #endif | ||||
| #ifdef USE_TEXT_SENSOR | ||||
|   bool on_text_sensor(text_sensor::TextSensor *text_sensor) override; | ||||
| #endif | ||||
|   | ||||
| @@ -31,6 +31,9 @@ class InitialStateIterator : public ComponentIterator { | ||||
| #ifdef USE_SWITCH | ||||
|   bool on_switch(switch_::Switch *a_switch) override; | ||||
| #endif | ||||
| #ifdef USE_BUTTON | ||||
|   bool on_button(button::Button *button) override { return true; }; | ||||
| #endif | ||||
| #ifdef USE_TEXT_SENSOR | ||||
|   bool on_text_sensor(text_sensor::TextSensor *text_sensor) override; | ||||
| #endif | ||||
|   | ||||
| @@ -116,6 +116,21 @@ void ComponentIterator::advance() { | ||||
|       } | ||||
|       break; | ||||
| #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 | ||||
|     case IteratorState::TEXT_SENSOR: | ||||
|       if (this->at_ >= App.get_text_sensors().size()) { | ||||
|   | ||||
| @@ -38,6 +38,9 @@ class ComponentIterator { | ||||
| #ifdef USE_SWITCH | ||||
|   virtual bool on_switch(switch_::Switch *a_switch) = 0; | ||||
| #endif | ||||
| #ifdef USE_BUTTON | ||||
|   virtual bool on_button(button::Button *button) = 0; | ||||
| #endif | ||||
| #ifdef USE_TEXT_SENSOR | ||||
|   virtual bool on_text_sensor(text_sensor::TextSensor *text_sensor) = 0; | ||||
| #endif | ||||
| @@ -78,6 +81,9 @@ class ComponentIterator { | ||||
| #ifdef USE_SWITCH | ||||
|     SWITCH, | ||||
| #endif | ||||
| #ifdef USE_BUTTON | ||||
|     BUTTON, | ||||
| #endif | ||||
| #ifdef USE_TEXT_SENSOR | ||||
|     TEXT_SENSOR, | ||||
| #endif | ||||
|   | ||||
| @@ -45,6 +45,8 @@ bool ATCMiThermometer::parse_device(const esp32_ble_tracker::ESPBTDevice &device | ||||
|       this->battery_voltage_->publish_state(*res->battery_voltage); | ||||
|     success = true; | ||||
|   } | ||||
|   if (this->signal_strength_ != nullptr) | ||||
|     this->signal_strength_->publish_state(device.get_rssi()); | ||||
|  | ||||
|   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_battery_level(sensor::Sensor *battery_level) { battery_level_ = battery_level; } | ||||
|   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: | ||||
|   uint64_t address_; | ||||
| @@ -35,6 +36,7 @@ class ATCMiThermometer : public Component, public esp32_ble_tracker::ESPBTDevice | ||||
|   sensor::Sensor *humidity_{nullptr}; | ||||
|   sensor::Sensor *battery_level_{nullptr}; | ||||
|   sensor::Sensor *battery_voltage_{nullptr}; | ||||
|   sensor::Sensor *signal_strength_{nullptr}; | ||||
|  | ||||
|   optional<ParseResult> parse_header_(const esp32_ble_tracker::ServiceData &service_data); | ||||
|   bool parse_message_(const std::vector<uint8_t> &message, ParseResult &result); | ||||
|   | ||||
| @@ -6,14 +6,18 @@ from esphome.const import ( | ||||
|     CONF_BATTERY_VOLTAGE, | ||||
|     CONF_MAC_ADDRESS, | ||||
|     CONF_HUMIDITY, | ||||
|     CONF_SIGNAL_STRENGTH, | ||||
|     CONF_TEMPERATURE, | ||||
|     CONF_ID, | ||||
|     DEVICE_CLASS_BATTERY, | ||||
|     DEVICE_CLASS_HUMIDITY, | ||||
|     DEVICE_CLASS_SIGNAL_STRENGTH, | ||||
|     DEVICE_CLASS_TEMPERATURE, | ||||
|     DEVICE_CLASS_VOLTAGE, | ||||
|     ENTITY_CATEGORY_DIAGNOSTIC, | ||||
|     STATE_CLASS_MEASUREMENT, | ||||
|     UNIT_CELSIUS, | ||||
|     UNIT_DECIBEL_MILLIWATT, | ||||
|     UNIT_PERCENT, | ||||
|     UNIT_VOLT, | ||||
| ) | ||||
| @@ -49,12 +53,21 @@ CONFIG_SCHEMA = ( | ||||
|                 accuracy_decimals=0, | ||||
|                 device_class=DEVICE_CLASS_BATTERY, | ||||
|                 state_class=STATE_CLASS_MEASUREMENT, | ||||
|                 entity_category=ENTITY_CATEGORY_DIAGNOSTIC, | ||||
|             ), | ||||
|             cv.Optional(CONF_BATTERY_VOLTAGE): sensor.sensor_schema( | ||||
|                 unit_of_measurement=UNIT_VOLT, | ||||
|                 accuracy_decimals=3, | ||||
|                 device_class=DEVICE_CLASS_VOLTAGE, | ||||
|                 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: | ||||
|         sens = await sensor.new_sensor(config[CONF_BATTERY_VOLTAGE]) | ||||
|         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_TEMPERATURE, | ||||
|     DEVICE_CLASS_VOLTAGE, | ||||
|     ENTITY_CATEGORY_DIAGNOSTIC, | ||||
|     ICON_LIGHTBULB, | ||||
|     ICON_CURRENT_AC, | ||||
|     STATE_CLASS_MEASUREMENT, | ||||
| @@ -125,6 +126,7 @@ CONFIG_SCHEMA = ( | ||||
|                 accuracy_decimals=1, | ||||
|                 device_class=DEVICE_CLASS_TEMPERATURE, | ||||
|                 state_class=STATE_CLASS_MEASUREMENT, | ||||
|                 entity_category=ENTITY_CATEGORY_DIAGNOSTIC, | ||||
|             ), | ||||
|             cv.Required(CONF_LINE_FREQUENCY): cv.enum(LINE_FREQS, upper=True), | ||||
|             cv.Optional(CONF_CURRENT_PHASES, default="3"): cv.enum( | ||||
|   | ||||
| @@ -13,6 +13,7 @@ from esphome.const import ( | ||||
|     DEVICE_CLASS_ILLUMINANCE, | ||||
|     DEVICE_CLASS_TEMPERATURE, | ||||
|     DEVICE_CLASS_VOLTAGE, | ||||
|     ENTITY_CATEGORY_DIAGNOSTIC, | ||||
|     STATE_CLASS_MEASUREMENT, | ||||
|     UNIT_CELSIUS, | ||||
|     UNIT_LUX, | ||||
| @@ -51,6 +52,7 @@ CONFIG_SCHEMA = ( | ||||
|                 accuracy_decimals=3, | ||||
|                 device_class=DEVICE_CLASS_VOLTAGE, | ||||
|                 state_class=STATE_CLASS_MEASUREMENT, | ||||
|                 entity_category=ENTITY_CATEGORY_DIAGNOSTIC, | ||||
|             ), | ||||
|             cv.Optional(CONF_MOISTURE): sensor.sensor_schema( | ||||
|                 unit_of_measurement=UNIT_PERCENT, | ||||
|   | ||||
| @@ -80,21 +80,23 @@ void BangBangClimate::compute_state_() { | ||||
|  | ||||
|   climate::ClimateAction target_action; | ||||
|   if (too_cold) { | ||||
|     // too cold -> enable heating if possible, else idle | ||||
|     if (this->supports_heat_) | ||||
|     // too cold -> enable heating if possible and enabled, else idle | ||||
|     if (this->supports_heat_ && | ||||
|         (this->mode == climate::CLIMATE_MODE_HEAT_COOL || this->mode == climate::CLIMATE_MODE_HEAT)) | ||||
|       target_action = climate::CLIMATE_ACTION_HEATING; | ||||
|     else | ||||
|       target_action = climate::CLIMATE_ACTION_IDLE; | ||||
|   } else if (too_hot) { | ||||
|     // too hot -> enable cooling if possible, else idle | ||||
|     if (this->supports_cool_) | ||||
|     // too hot -> enable cooling if possible and enabled, else idle | ||||
|     if (this->supports_cool_ && | ||||
|         (this->mode == climate::CLIMATE_MODE_HEAT_COOL || this->mode == climate::CLIMATE_MODE_COOL)) | ||||
|       target_action = climate::CLIMATE_ACTION_COOLING; | ||||
|     else | ||||
|       target_action = climate::CLIMATE_ACTION_IDLE; | ||||
|   } else { | ||||
|     // neither too hot nor too cold -> in range | ||||
|     if (this->supports_cool_ && this->supports_heat_) { | ||||
|       // if supports both ends, go to idle action | ||||
|     if (this->supports_cool_ && this->supports_heat_ && this->mode == climate::CLIMATE_MODE_HEAT_COOL) { | ||||
|       // if supports both ends and both cooling and heating enabled, go to idle action | ||||
|       target_action = climate::CLIMATE_ACTION_IDLE; | ||||
|     } else { | ||||
|       // else use current mode and don't change (hysteresis) | ||||
|   | ||||
| @@ -79,6 +79,9 @@ void BH1750Sensor::read_data_() { | ||||
|  | ||||
|   float lx = float(raw_value) / 1.2f; | ||||
|   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); | ||||
|   this->publish_state(lx); | ||||
|   this->status_clear_warning(); | ||||
|   | ||||
| @@ -44,9 +44,11 @@ from esphome.const import ( | ||||
|     DEVICE_CLASS_POWER, | ||||
|     DEVICE_CLASS_PRESENCE, | ||||
|     DEVICE_CLASS_PROBLEM, | ||||
|     DEVICE_CLASS_RUNNING, | ||||
|     DEVICE_CLASS_SAFETY, | ||||
|     DEVICE_CLASS_SMOKE, | ||||
|     DEVICE_CLASS_SOUND, | ||||
|     DEVICE_CLASS_TAMPER, | ||||
|     DEVICE_CLASS_UPDATE, | ||||
|     DEVICE_CLASS_VIBRATION, | ||||
|     DEVICE_CLASS_WINDOW, | ||||
| @@ -76,9 +78,11 @@ DEVICE_CLASSES = [ | ||||
|     DEVICE_CLASS_POWER, | ||||
|     DEVICE_CLASS_PRESENCE, | ||||
|     DEVICE_CLASS_PROBLEM, | ||||
|     DEVICE_CLASS_RUNNING, | ||||
|     DEVICE_CLASS_SAFETY, | ||||
|     DEVICE_CLASS_SMOKE, | ||||
|     DEVICE_CLASS_SOUND, | ||||
|     DEVICE_CLASS_TAMPER, | ||||
|     DEVICE_CLASS_UPDATE, | ||||
|     DEVICE_CLASS_VIBRATION, | ||||
|     DEVICE_CLASS_WINDOW, | ||||
| @@ -313,6 +317,7 @@ def validate_multi_click_timing(value): | ||||
|  | ||||
| device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space="_") | ||||
|  | ||||
|  | ||||
| BINARY_SENSOR_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend( | ||||
|     { | ||||
|         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() { | ||||
|   if (this->device_class_.has_value()) | ||||
|     return *this->device_class_; | ||||
| #pragma GCC diagnostic push | ||||
| #pragma GCC diagnostic ignored "-Wdeprecated-declarations" | ||||
|   return this->device_class(); | ||||
| #pragma GCC diagnostic pop | ||||
| } | ||||
| void BinarySensor::add_filter(Filter *filter) { | ||||
|   filter->parent_ = this; | ||||
|   | ||||
| @@ -74,7 +74,10 @@ class BinarySensor : public EntityBase { | ||||
|  | ||||
|   // ========== OVERRIDE METHODS ========== | ||||
|   // (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(); | ||||
|  | ||||
|  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"; | ||||
|  | ||||
| float BLEClient::get_setup_priority() const { return setup_priority::AFTER_BLUETOOTH; } | ||||
|  | ||||
| void BLEClient::setup() { | ||||
|   auto ret = esp_ble_gattc_app_register(this->app_id); | ||||
|   if (ret) { | ||||
| @@ -386,6 +388,15 @@ BLEDescriptor *BLECharacteristic::get_descriptor(uint16_t 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 esphome | ||||
|  | ||||
|   | ||||
| @@ -59,7 +59,7 @@ class BLECharacteristic { | ||||
|   void parse_descriptors(); | ||||
|   BLEDescriptor *get_descriptor(espbt::ESPBTUUID uuid); | ||||
|   BLEDescriptor *get_descriptor(uint16_t uuid); | ||||
|  | ||||
|   void write_value(uint8_t *new_val, int16_t new_val_size); | ||||
|   BLEService *service; | ||||
| }; | ||||
|  | ||||
| @@ -81,6 +81,7 @@ class BLEClient : public espbt::ESPBTClient, public Component { | ||||
|   void setup() override; | ||||
|   void dump_config() 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, | ||||
|                            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])) | ||||
|         ) | ||||
|     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)) | ||||
|  | ||||
|     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( | ||||
|         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)) | ||||
|  | ||||
|     if CONF_DESCRIPTOR_UUID in config: | ||||
| @@ -108,7 +110,9 @@ async def to_code(config): | ||||
|         elif len(config[CONF_DESCRIPTOR_UUID]) == len( | ||||
|             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)) | ||||
|  | ||||
|     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_CONTROL = 0xF4; | ||||
| 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_TEMPDATA = 0xFA; | ||||
| static const uint8_t BME280_REGISTER_HUMIDDATA = 0xFD; | ||||
| @@ -178,23 +179,29 @@ void BME280Component::update() { | ||||
|     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->pressure_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]() { | ||||
|     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; | ||||
|     float temperature = this->read_temperature_(&t_fine); | ||||
|     float temperature = this->read_temperature_(data, &t_fine); | ||||
|     if (std::isnan(temperature)) { | ||||
|       ESP_LOGW(TAG, "Invalid temperature, cannot read pressure & humidity values."); | ||||
|       this->status_set_warning(); | ||||
|       return; | ||||
|     } | ||||
|     float pressure = this->read_pressure_(t_fine); | ||||
|     float humidity = this->read_humidity_(t_fine); | ||||
|     float pressure = this->read_pressure_(data, 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) | ||||
|       this->temperature_sensor_->publish_state(temperature); | ||||
|     if (this->pressure_sensor_ != nullptr) | ||||
| @@ -204,11 +211,8 @@ void BME280Component::update() { | ||||
|     this->status_clear_warning(); | ||||
|   }); | ||||
| } | ||||
| float BME280Component::read_temperature_(int32_t *t_fine) { | ||||
|   uint8_t data[3]; | ||||
|   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); | ||||
| float BME280Component::read_temperature_(const uint8_t *data, int32_t *t_fine) { | ||||
|   int32_t adc = ((data[3] & 0xFF) << 16) | ((data[4] & 0xFF) << 8) | (data[5] & 0xFF); | ||||
|   adc >>= 4; | ||||
|   if (adc == 0x80000) | ||||
|     // temperature was disabled | ||||
| @@ -226,10 +230,7 @@ float BME280Component::read_temperature_(int32_t *t_fine) { | ||||
|   return temperature / 100.0f; | ||||
| } | ||||
|  | ||||
| float BME280Component::read_pressure_(int32_t t_fine) { | ||||
|   uint8_t data[3]; | ||||
|   if (!this->read_bytes(BME280_REGISTER_PRESSUREDATA, data, 3)) | ||||
|     return NAN; | ||||
| float BME280Component::read_pressure_(const uint8_t *data, int32_t t_fine) { | ||||
|   int32_t adc = ((data[0] & 0xFF) << 16) | ((data[1] & 0xFF) << 8) | (data[2] & 0xFF); | ||||
|   adc >>= 4; | ||||
|   if (adc == 0x80000) | ||||
| @@ -265,9 +266,9 @@ float BME280Component::read_pressure_(int32_t t_fine) { | ||||
|   return (p / 256.0f) / 100.0f; | ||||
| } | ||||
|  | ||||
| float BME280Component::read_humidity_(int32_t t_fine) { | ||||
|   uint16_t raw_adc; | ||||
|   if (!this->read_byte_16(BME280_REGISTER_HUMIDDATA, &raw_adc) || raw_adc == 0x8000) | ||||
| float BME280Component::read_humidity_(const uint8_t *data, int32_t t_fine) { | ||||
|   uint16_t raw_adc = ((data[6] & 0xFF) << 8) | (data[7] & 0xFF); | ||||
|   if (raw_adc == 0x8000) | ||||
|     return NAN; | ||||
|  | ||||
|   int32_t adc = raw_adc; | ||||
|   | ||||
| @@ -82,11 +82,11 @@ class BME280Component : public PollingComponent, public i2c::I2CDevice { | ||||
|  | ||||
|  protected: | ||||
|   /// 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. | ||||
|   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. | ||||
|   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); | ||||
|   uint16_t read_u16_le_(uint8_t a_register); | ||||
|   int16_t read_s16_le_(uint8_t a_register); | ||||
|   | ||||
| @@ -44,7 +44,8 @@ CONFIG_SCHEMA = cv.Schema( | ||||
|         cv.Optional( | ||||
|             CONF_STATE_SAVE_INTERVAL, default="6hours" | ||||
|         ): cv.positive_time_period_minutes, | ||||
|     } | ||||
|     }, | ||||
|     cv.only_with_arduino, | ||||
| ).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) | ||||
|     ) | ||||
|  | ||||
|     # 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_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: | ||||
|         cg.add_library("DNSServer", 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, "  Password=" LOG_SECRET("'%s'"), psk.c_str()); | ||||
|   wifi::global_wifi_component->save_wifi_sta(ssid, psk); | ||||
|   wifi::global_wifi_component->start_scanning(); | ||||
|   request->redirect("/?save=true"); | ||||
| } | ||||
|  | ||||
| @@ -84,14 +85,7 @@ void CaptivePortal::start() { | ||||
|   this->dns_server_->start(53, "*", (uint32_t) ip); | ||||
|  | ||||
|   this->base_->get_server()->onNotFound([this](AsyncWebServerRequest *req) { | ||||
|     bool not_found = false; | ||||
|     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) { | ||||
|     if (!this->active_ || req->host().c_str() == wifi::global_wifi_component->wifi_soft_ap_ip().str()) { | ||||
|       req->send(404, "text/html", "File not found"); | ||||
|       return; | ||||
|     } | ||||
|   | ||||
| @@ -52,7 +52,7 @@ void CCS811Component::setup() { | ||||
|  | ||||
|   if (this->baseline_.has_value()) { | ||||
|     // 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); | ||||
|   | ||||
							
								
								
									
										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_COMMAND_TOPIC, | ||||
|     CONF_MODE_STATE_TOPIC, | ||||
|     CONF_ON_STATE, | ||||
|     CONF_PRESET, | ||||
|     CONF_SWING_MODE, | ||||
|     CONF_SWING_MODE_COMMAND_TOPIC, | ||||
| @@ -34,6 +35,7 @@ from esphome.const import ( | ||||
|     CONF_TARGET_TEMPERATURE_LOW_COMMAND_TOPIC, | ||||
|     CONF_TARGET_TEMPERATURE_LOW_STATE_TOPIC, | ||||
|     CONF_TEMPERATURE_STEP, | ||||
|     CONF_TRIGGER_ID, | ||||
|     CONF_VISUAL, | ||||
|     CONF_MQTT_ID, | ||||
| ) | ||||
| @@ -101,6 +103,7 @@ validate_climate_swing_mode = cv.enum(CLIMATE_SWING_MODES, upper=True) | ||||
|  | ||||
| # Actions | ||||
| 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( | ||||
|     { | ||||
| @@ -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.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: | ||||
|             cg.add(mqtt_.set_custom_mode_command_topic(config[CONF_MODE_COMMAND_TOPIC])) | ||||
|         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: | ||||
|             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): | ||||
|     if not CORE.has_id(config[CONF_ID]): | ||||
|   | ||||
| @@ -42,5 +42,12 @@ template<typename... Ts> class ControlAction : public Action<Ts...> { | ||||
|   Climate *climate_; | ||||
| }; | ||||
|  | ||||
| class StateTrigger : public Trigger<> { | ||||
|  public: | ||||
|   StateTrigger(Climate *climate) { | ||||
|     climate->add_on_state_callback([this]() { this->trigger(); }); | ||||
|   } | ||||
| }; | ||||
|  | ||||
| }  // namespace climate | ||||
| }  // 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) { | ||||
|   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) {} | ||||
| #pragma GCC diagnostic pop | ||||
|  | ||||
| Climate::Climate() : Climate("") {} | ||||
| ClimateCall Climate::make_call() { return ClimateCall(this); } | ||||
|  | ||||
|   | ||||
| @@ -10,21 +10,22 @@ climate::ClimateTraits ClimateIR::traits() { | ||||
|   auto traits = climate::ClimateTraits(); | ||||
|   traits.set_supports_current_temperature(this->sensor_ != nullptr); | ||||
|   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); | ||||
|   if (supports_heat_) | ||||
|   if (this->supports_heat_) | ||||
|     traits.add_supported_mode(climate::CLIMATE_MODE_HEAT); | ||||
|   if (supports_dry_) | ||||
|   if (this->supports_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.set_supports_two_point_target_temperature(false); | ||||
|   traits.set_visual_min_temperature(this->minimum_temperature_); | ||||
|   traits.set_visual_max_temperature(this->maximum_temperature_); | ||||
|   traits.set_visual_temperature_step(this->temperature_step_); | ||||
|   traits.set_supported_fan_modes(fan_modes_); | ||||
|   traits.set_supported_swing_modes(swing_modes_); | ||||
|   traits.set_supported_fan_modes(this->fan_modes_); | ||||
|   traits.set_supported_swing_modes(this->swing_modes_); | ||||
|   traits.set_supported_presets(this->presets_); | ||||
|   return traits; | ||||
| } | ||||
|  | ||||
| @@ -50,6 +51,7 @@ void ClimateIR::setup() { | ||||
|         roundf(clamp(this->current_temperature, this->minimum_temperature_, this->maximum_temperature_)); | ||||
|     this->fan_mode = climate::CLIMATE_FAN_AUTO; | ||||
|     this->swing_mode = climate::CLIMATE_SWING_OFF; | ||||
|     this->preset = climate::CLIMATE_PRESET_NONE; | ||||
|   } | ||||
|   // Never send nan to HA | ||||
|   if (std::isnan(this->target_temperature)) | ||||
| @@ -65,6 +67,8 @@ void ClimateIR::control(const climate::ClimateCall &call) { | ||||
|     this->fan_mode = *call.get_fan_mode(); | ||||
|   if (call.get_swing_mode().has_value()) | ||||
|     this->swing_mode = *call.get_swing_mode(); | ||||
|   if (call.get_preset().has_value()) | ||||
|     this->preset = *call.get_preset(); | ||||
|   this->transmit_state(); | ||||
|   this->publish_state(); | ||||
| } | ||||
|   | ||||
| @@ -22,7 +22,7 @@ class ClimateIR : public climate::Climate, public Component, public remote_base: | ||||
|  public: | ||||
|   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 = {}, | ||||
|             std::set<climate::ClimateSwingMode> swing_modes = {}) { | ||||
|             std::set<climate::ClimateSwingMode> swing_modes = {}, std::set<climate::ClimatePreset> presets = {}) { | ||||
|     this->minimum_temperature_ = minimum_temperature; | ||||
|     this->maximum_temperature_ = maximum_temperature; | ||||
|     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->fan_modes_ = std::move(fan_modes); | ||||
|     this->swing_modes_ = std::move(swing_modes); | ||||
|     this->presets_ = std::move(presets); | ||||
|   } | ||||
|  | ||||
|   void setup() override; | ||||
| @@ -61,6 +62,7 @@ class ClimateIR : public climate::Climate, public Component, public remote_base: | ||||
|   bool supports_fan_only_{false}; | ||||
|   std::set<climate::ClimateFanMode> fan_modes_ = {}; | ||||
|   std::set<climate::ClimateSwingMode> swing_modes_ = {}; | ||||
|   std::set<climate::ClimatePreset> presets_ = {}; | ||||
|  | ||||
|   remote_transmitter::RemoteTransmitterComponent *transmitter_; | ||||
|   sensor::Sensor *sensor_{nullptr}; | ||||
|   | ||||
| @@ -1,4 +1,5 @@ | ||||
| #include "coolix.h" | ||||
| #include "esphome/components/remote_base/coolix_protocol.h" | ||||
| #include "esphome/core/log.h" | ||||
|  | ||||
| namespace esphome { | ||||
| @@ -6,29 +7,29 @@ namespace coolix { | ||||
|  | ||||
| static const char *const TAG = "coolix.climate"; | ||||
|  | ||||
| const uint32_t COOLIX_OFF = 0xB27BE0; | ||||
| const uint32_t COOLIX_SWING = 0xB26BE0; | ||||
| const uint32_t COOLIX_LED = 0xB5F5A5; | ||||
| const uint32_t COOLIX_SILENCE_FP = 0xB5F5B6; | ||||
| static const uint32_t COOLIX_OFF = 0xB27BE0; | ||||
| static const uint32_t COOLIX_SWING = 0xB26BE0; | ||||
| static const uint32_t COOLIX_LED = 0xB5F5A5; | ||||
| static const uint32_t COOLIX_SILENCE_FP = 0xB5F5B6; | ||||
|  | ||||
| // On, 25C, Mode: Auto, Fan: Auto, Zone Follow: Off, Sensor Temp: Ignore. | ||||
| const uint8_t COOLIX_COOL = 0b0000; | ||||
| const uint8_t COOLIX_DRY_FAN = 0b0100; | ||||
| const uint8_t COOLIX_AUTO = 0b1000; | ||||
| const uint8_t COOLIX_HEAT = 0b1100; | ||||
| const uint32_t COOLIX_MODE_MASK = 0b1100; | ||||
| const uint32_t COOLIX_FAN_MASK = 0xF000; | ||||
| const uint32_t COOLIX_FAN_MODE_AUTO_DRY = 0x1000; | ||||
| const uint32_t COOLIX_FAN_AUTO = 0xB000; | ||||
| const uint32_t COOLIX_FAN_MIN = 0x9000; | ||||
| const uint32_t COOLIX_FAN_MED = 0x5000; | ||||
| const uint32_t COOLIX_FAN_MAX = 0x3000; | ||||
| static const uint8_t COOLIX_COOL = 0b0000; | ||||
| static const uint8_t COOLIX_DRY_FAN = 0b0100; | ||||
| static const uint8_t COOLIX_AUTO = 0b1000; | ||||
| static const uint8_t COOLIX_HEAT = 0b1100; | ||||
| static const uint32_t COOLIX_MODE_MASK = 0b1100; | ||||
| static const uint32_t COOLIX_FAN_MASK = 0xF000; | ||||
| static const uint32_t COOLIX_FAN_MODE_AUTO_DRY = 0x1000; | ||||
| static const uint32_t COOLIX_FAN_AUTO = 0xB000; | ||||
| static const uint32_t COOLIX_FAN_MIN = 0x9000; | ||||
| static const uint32_t COOLIX_FAN_MED = 0x5000; | ||||
| static const uint32_t COOLIX_FAN_MAX = 0x3000; | ||||
|  | ||||
| // Temperature | ||||
| 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. | ||||
| const uint32_t COOLIX_TEMP_MASK = 0b11110000; | ||||
| const uint8_t COOLIX_TEMP_MAP[COOLIX_TEMP_RANGE] = { | ||||
| static const uint8_t COOLIX_TEMP_RANGE = COOLIX_TEMP_MAX - COOLIX_TEMP_MIN + 1; | ||||
| static const uint8_t COOLIX_FAN_TEMP_CODE = 0b11100000;  // Part of Fan Mode. | ||||
| static const uint32_t COOLIX_TEMP_MASK = 0b11110000; | ||||
| static const uint8_t COOLIX_TEMP_MAP[COOLIX_TEMP_RANGE] = { | ||||
|     0b00000000,  // 17C | ||||
|     0b00010000,  // 18c | ||||
|     0b00110000,  // 19C | ||||
| @@ -45,17 +46,6 @@ const uint8_t COOLIX_TEMP_MAP[COOLIX_TEMP_RANGE] = { | ||||
|     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() { | ||||
|   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 data = transmit.get_data(); | ||||
|  | ||||
|   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 | ||||
|   } | ||||
|  | ||||
|   remote_base::CoolixProtocol().encode(data, remote_state); | ||||
|   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. | ||||
|   uint32_t remote_state = 0; | ||||
|   // The protocol sends the data twice, read here | ||||
|   uint32_t loop_read; | ||||
|   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) | ||||
|   uint32_t remote_state = *decoded; | ||||
|   ESP_LOGV(TAG, "Decoded 0x%06X", remote_state); | ||||
|   if ((remote_state & 0xFF0000) != 0xB20000) | ||||
|     return false; | ||||
|  | ||||
|   if (remote_state == COOLIX_OFF) { | ||||
|     this->mode = climate::CLIMATE_MODE_OFF; | ||||
|     parent->mode = climate::CLIMATE_MODE_OFF; | ||||
|   } else if (remote_state == COOLIX_SWING) { | ||||
|     this->swing_mode = | ||||
|         this->swing_mode == climate::CLIMATE_SWING_OFF ? climate::CLIMATE_SWING_VERTICAL : climate::CLIMATE_SWING_OFF; | ||||
|     parent->swing_mode = | ||||
|         parent->swing_mode == climate::CLIMATE_SWING_OFF ? climate::CLIMATE_SWING_VERTICAL : climate::CLIMATE_SWING_OFF; | ||||
|   } else { | ||||
|     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) | ||||
|       this->mode = climate::CLIMATE_MODE_HEAT_COOL; | ||||
|       parent->mode = climate::CLIMATE_MODE_HEAT_COOL; | ||||
|     else if ((remote_state & COOLIX_MODE_MASK) == COOLIX_DRY_FAN) { | ||||
|       if ((remote_state & COOLIX_FAN_MASK) == COOLIX_FAN_MODE_AUTO_DRY) | ||||
|         this->mode = climate::CLIMATE_MODE_DRY; | ||||
|         parent->mode = climate::CLIMATE_MODE_DRY; | ||||
|       else | ||||
|         this->mode = climate::CLIMATE_MODE_FAN_ONLY; | ||||
|         parent->mode = climate::CLIMATE_MODE_FAN_ONLY; | ||||
|     } else | ||||
|       this->mode = climate::CLIMATE_MODE_COOL; | ||||
|       parent->mode = climate::CLIMATE_MODE_COOL; | ||||
|  | ||||
|     // Fan Speed | ||||
|     if ((remote_state & COOLIX_FAN_AUTO) == COOLIX_FAN_AUTO || this->mode == climate::CLIMATE_MODE_HEAT_COOL || | ||||
|         this->mode == climate::CLIMATE_MODE_DRY) | ||||
|       this->fan_mode = climate::CLIMATE_FAN_AUTO; | ||||
|     if ((remote_state & COOLIX_FAN_AUTO) == COOLIX_FAN_AUTO || parent->mode == climate::CLIMATE_MODE_HEAT_COOL || | ||||
|         parent->mode == climate::CLIMATE_MODE_DRY) | ||||
|       parent->fan_mode = climate::CLIMATE_FAN_AUTO; | ||||
|     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) | ||||
|       this->fan_mode = climate::CLIMATE_FAN_MEDIUM; | ||||
|       parent->fan_mode = climate::CLIMATE_FAN_MEDIUM; | ||||
|     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 | ||||
|     uint8_t temperature_code = remote_state & COOLIX_TEMP_MASK; | ||||
|     for (uint8_t i = 0; i < COOLIX_TEMP_RANGE; i++) | ||||
|       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; | ||||
| } | ||||
|   | ||||
| @@ -26,11 +26,15 @@ class CoolixClimate : public climate_ir::ClimateIR { | ||||
|     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: | ||||
|   /// Transmit via IR the state of this climate controller. | ||||
|   void transmit_state() override; | ||||
|   /// 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}; | ||||
| }; | ||||
|   | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user