mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-30 22:53:59 +00:00 
			
		
		
		
	| @@ -25,10 +25,9 @@ indent_size = 2 | |||||||
| [*.{yaml,yml}] | [*.{yaml,yml}] | ||||||
| indent_style = space | indent_style = space | ||||||
| indent_size = 2 | indent_size = 2 | ||||||
| quote_type = single | quote_type = double | ||||||
|  |  | ||||||
| # JSON | # JSON | ||||||
| [*.json] | [*.json] | ||||||
| indent_style = space | indent_style = space | ||||||
| indent_size = 2 | indent_size = 2 | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										1
									
								
								.github/FUNDING.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.github/FUNDING.yml
									
									
									
									
										vendored
									
									
								
							| @@ -1,3 +1,4 @@ | |||||||
|  | --- | ||||||
| # These are supported funding model platforms | # These are supported funding model platforms | ||||||
|  |  | ||||||
| custom: https://www.nabucasa.com | custom: https://www.nabucasa.com | ||||||
|   | |||||||
							
								
								
									
										8
									
								
								.github/ISSUE_TEMPLATE/config.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										8
									
								
								.github/ISSUE_TEMPLATE/config.yml
									
									
									
									
										vendored
									
									
								
							| @@ -1,3 +1,4 @@ | |||||||
|  | --- | ||||||
| blank_issues_enabled: false | blank_issues_enabled: false | ||||||
| contact_links: | contact_links: | ||||||
|   - name: Issue Tracker |   - name: Issue Tracker | ||||||
| @@ -5,7 +6,10 @@ contact_links: | |||||||
|     about: Please create bug reports in the dedicated issue tracker. |     about: Please create bug reports in the dedicated issue tracker. | ||||||
|   - name: Feature Request Tracker |   - name: Feature Request Tracker | ||||||
|     url: https://github.com/esphome/feature-requests |     url: https://github.com/esphome/feature-requests | ||||||
|     about: Please create feature requests in the dedicated feature request tracker. |     about: | | ||||||
|  |       Please create feature requests in the dedicated feature request tracker. | ||||||
|   - name: Frequently Asked Question |   - name: Frequently Asked Question | ||||||
|     url: https://esphome.io/guides/faq.html |     url: https://esphome.io/guides/faq.html | ||||||
|     about: Please view the FAQ for common questions and what to include in a bug report. |     about: | | ||||||
|  |       Please view the FAQ for common questions and what | ||||||
|  |       to include in a bug report. | ||||||
|   | |||||||
							
								
								
									
										7
									
								
								.github/dependabot.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										7
									
								
								.github/dependabot.yml
									
									
									
									
										vendored
									
									
								
							| @@ -1,13 +1,14 @@ | |||||||
|  | --- | ||||||
| version: 2 | version: 2 | ||||||
| updates: | updates: | ||||||
|   - package-ecosystem: "pip" |   - package-ecosystem: pip | ||||||
|     directory: "/" |     directory: "/" | ||||||
|     schedule: |     schedule: | ||||||
|       interval: "daily" |       interval: daily | ||||||
|     ignore: |     ignore: | ||||||
|       # Hypotehsis is only used for testing and is updated quite often |       # Hypotehsis is only used for testing and is updated quite often | ||||||
|       - dependency-name: hypothesis |       - dependency-name: hypothesis | ||||||
|   - package-ecosystem: "github-actions" |   - package-ecosystem: github-actions | ||||||
|     directory: "/" |     directory: "/" | ||||||
|     schedule: |     schedule: | ||||||
|       interval: daily |       interval: daily | ||||||
|   | |||||||
							
								
								
									
										56
									
								
								.github/workflows/ci-docker.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										56
									
								
								.github/workflows/ci-docker.yml
									
									
									
									
										vendored
									
									
								
							| @@ -1,21 +1,23 @@ | |||||||
|  | --- | ||||||
| name: CI for docker images | name: CI for docker images | ||||||
|  |  | ||||||
| # Only run when docker paths change | # Only run when docker paths change | ||||||
|  | # yamllint disable-line rule:truthy | ||||||
| on: | on: | ||||||
|   push: |   push: | ||||||
|     branches: [dev, beta, release] |     branches: [dev, beta, release] | ||||||
|     paths: |     paths: | ||||||
|       - 'docker/**' |       - "docker/**" | ||||||
|       - '.github/workflows/**' |       - ".github/workflows/**" | ||||||
|       - 'requirements*.txt' |       - "requirements*.txt" | ||||||
|       - 'platformio.ini' |       - "platformio.ini" | ||||||
|  |  | ||||||
|   pull_request: |   pull_request: | ||||||
|     paths: |     paths: | ||||||
|       - 'docker/**' |       - "docker/**" | ||||||
|       - '.github/workflows/**' |       - ".github/workflows/**" | ||||||
|       - 'requirements*.txt' |       - "requirements*.txt" | ||||||
|       - 'platformio.ini' |       - "platformio.ini" | ||||||
|  |  | ||||||
| permissions: | permissions: | ||||||
|   contents: read |   contents: read | ||||||
| @@ -30,24 +32,24 @@ jobs: | |||||||
|         arch: [amd64, armv7, aarch64] |         arch: [amd64, armv7, aarch64] | ||||||
|         build_type: ["ha-addon", "docker", "lint"] |         build_type: ["ha-addon", "docker", "lint"] | ||||||
|     steps: |     steps: | ||||||
|     - uses: actions/checkout@v3 |       - uses: actions/checkout@v3 | ||||||
|     - name: Set up Python |       - name: Set up Python | ||||||
|       uses: actions/setup-python@v4 |         uses: actions/setup-python@v4 | ||||||
|       with: |         with: | ||||||
|         python-version: '3.9' |           python-version: "3.9" | ||||||
|     - name: Set up Docker Buildx |       - name: Set up Docker Buildx | ||||||
|       uses: docker/setup-buildx-action@v2 |         uses: docker/setup-buildx-action@v2 | ||||||
|     - name: Set up QEMU |       - name: Set up QEMU | ||||||
|       uses: docker/setup-qemu-action@v2 |         uses: docker/setup-qemu-action@v2 | ||||||
|  |  | ||||||
|     - name: Set TAG |       - name: Set TAG | ||||||
|       run: | |         run: | | ||||||
|         echo "TAG=check" >> $GITHUB_ENV |           echo "TAG=check" >> $GITHUB_ENV | ||||||
|  |  | ||||||
|     - name: Run build |       - name: Run build | ||||||
|       run: | |         run: | | ||||||
|         docker/build.py \ |           docker/build.py \ | ||||||
|           --tag "${TAG}" \ |             --tag "${TAG}" \ | ||||||
|           --arch "${{ matrix.arch }}" \ |             --arch "${{ matrix.arch }}" \ | ||||||
|           --build-type "${{ matrix.build_type }}" \ |             --build-type "${{ matrix.build_type }}" \ | ||||||
|           build |             build | ||||||
|   | |||||||
							
								
								
									
										21
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										21
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							| @@ -1,5 +1,7 @@ | |||||||
|  | --- | ||||||
| name: CI | name: CI | ||||||
|  |  | ||||||
|  | # yamllint disable-line rule:truthy | ||||||
| on: | on: | ||||||
|   push: |   push: | ||||||
|     branches: [dev, beta, release] |     branches: [dev, beta, release] | ||||||
| @@ -10,6 +12,7 @@ permissions: | |||||||
|   contents: read |   contents: read | ||||||
|  |  | ||||||
| concurrency: | concurrency: | ||||||
|  |   # yamllint disable-line rule:line-length | ||||||
|   group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} |   group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} | ||||||
|   cancel-in-progress: true |   cancel-in-progress: true | ||||||
|  |  | ||||||
| @@ -73,6 +76,8 @@ jobs: | |||||||
|             name: Run script/clang-tidy for ESP32 IDF |             name: Run script/clang-tidy for ESP32 IDF | ||||||
|             options: --environment esp32-idf-tidy --grep USE_ESP_IDF |             options: --environment esp32-idf-tidy --grep USE_ESP_IDF | ||||||
|             pio_cache_key: tidyesp32-idf |             pio_cache_key: tidyesp32-idf | ||||||
|  |           - id: yamllint | ||||||
|  |             name: Run yamllint | ||||||
|  |  | ||||||
|     steps: |     steps: | ||||||
|       - uses: actions/checkout@v3 |       - uses: actions/checkout@v3 | ||||||
| @@ -80,17 +85,19 @@ jobs: | |||||||
|         uses: actions/setup-python@v4 |         uses: actions/setup-python@v4 | ||||||
|         id: python |         id: python | ||||||
|         with: |         with: | ||||||
|           python-version: '3.8' |           python-version: "3.8" | ||||||
|  |  | ||||||
|       - name: Cache virtualenv |       - name: Cache virtualenv | ||||||
|         uses: actions/cache@v3 |         uses: actions/cache@v3 | ||||||
|         with: |         with: | ||||||
|           path: .venv |           path: .venv | ||||||
|  |           # yamllint disable-line rule:line-length | ||||||
|           key: venv-${{ steps.python.outputs.python-version }}-${{ hashFiles('requirements*.txt') }} |           key: venv-${{ steps.python.outputs.python-version }}-${{ hashFiles('requirements*.txt') }} | ||||||
|           restore-keys: | |           restore-keys: | | ||||||
|             venv-${{ steps.python.outputs.python-version }}- |             venv-${{ steps.python.outputs.python-version }}- | ||||||
|  |  | ||||||
|       - name: Set up virtualenv |       - name: Set up virtualenv | ||||||
|  |         # yamllint disable rule:line-length | ||||||
|         run: | |         run: | | ||||||
|           python -m venv .venv |           python -m venv .venv | ||||||
|           source .venv/bin/activate |           source .venv/bin/activate | ||||||
| @@ -99,12 +106,14 @@ jobs: | |||||||
|           pip install -e . |           pip install -e . | ||||||
|           echo "$GITHUB_WORKSPACE/.venv/bin" >> $GITHUB_PATH |           echo "$GITHUB_WORKSPACE/.venv/bin" >> $GITHUB_PATH | ||||||
|           echo "VIRTUAL_ENV=$GITHUB_WORKSPACE/.venv" >> $GITHUB_ENV |           echo "VIRTUAL_ENV=$GITHUB_WORKSPACE/.venv" >> $GITHUB_ENV | ||||||
|  |         # yamllint enable rule:line-length | ||||||
|  |  | ||||||
|       # Use per check platformio cache because checks use different parts |       # Use per check platformio cache because checks use different parts | ||||||
|       - name: Cache platformio |       - name: Cache platformio | ||||||
|         uses: actions/cache@v3 |         uses: actions/cache@v3 | ||||||
|         with: |         with: | ||||||
|           path: ~/.platformio |           path: ~/.platformio | ||||||
|  |           # yamllint disable-line rule:line-length | ||||||
|           key: platformio-${{ matrix.pio_cache_key }}-${{ hashFiles('platformio.ini') }} |           key: platformio-${{ matrix.pio_cache_key }}-${{ hashFiles('platformio.ini') }} | ||||||
|         if: matrix.id == 'test' || matrix.id == 'clang-tidy' |         if: matrix.id == 'test' || matrix.id == 'clang-tidy' | ||||||
|  |  | ||||||
| @@ -145,8 +154,9 @@ jobs: | |||||||
|           pytest -vv --tb=native tests |           pytest -vv --tb=native tests | ||||||
|         if: matrix.id == 'pytest' |         if: matrix.id == 'pytest' | ||||||
|  |  | ||||||
|       # Also run git-diff-index so that the step is marked as failed on formatting errors, |       # Also run git-diff-index so that the step is marked as failed on | ||||||
|       # since clang-format doesn't do anything but change files if -i is passed. |       # formatting errors, since clang-format doesn't do anything but | ||||||
|  |       # change files if -i is passed. | ||||||
|       - name: Run clang-format |       - name: Run clang-format | ||||||
|         run: | |         run: | | ||||||
|           script/clang-format -i |           script/clang-format -i | ||||||
| @@ -161,6 +171,11 @@ jobs: | |||||||
|           # Also cache libdeps, store them in a ~/.platformio subfolder |           # Also cache libdeps, store them in a ~/.platformio subfolder | ||||||
|           PLATFORMIO_LIBDEPS_DIR: ~/.platformio/libdeps |           PLATFORMIO_LIBDEPS_DIR: ~/.platformio/libdeps | ||||||
|  |  | ||||||
|  |       - name: Run yamllint | ||||||
|  |         if: matrix.id == 'yamllint' | ||||||
|  |         uses: frenck/action-yamllint@v1.3.0 | ||||||
|  |  | ||||||
|       - name: Suggested changes |       - name: Suggested changes | ||||||
|         run: script/ci-suggest-changes |         run: script/ci-suggest-changes | ||||||
|  |         # yamllint disable-line rule:line-length | ||||||
|         if: always() && (matrix.id == 'clang-tidy' || matrix.id == 'clang-format' || matrix.id == 'lint-python') |         if: always() && (matrix.id == 'clang-tidy' || matrix.id == 'clang-format' || matrix.id == 'lint-python') | ||||||
|   | |||||||
							
								
								
									
										4
									
								
								.github/workflows/lock.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/workflows/lock.yml
									
									
									
									
										vendored
									
									
								
							| @@ -1,8 +1,10 @@ | |||||||
|  | --- | ||||||
| name: Lock | name: Lock | ||||||
|  |  | ||||||
|  | # yamllint disable-line rule:truthy | ||||||
| on: | on: | ||||||
|   schedule: |   schedule: | ||||||
|     - cron: '30 0 * * *' |     - cron: "30 0 * * *" | ||||||
|   workflow_dispatch: |   workflow_dispatch: | ||||||
|  |  | ||||||
| permissions: | permissions: | ||||||
|   | |||||||
							
								
								
									
										104
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										104
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							| @@ -1,5 +1,7 @@ | |||||||
|  | --- | ||||||
| name: Publish Release | name: Publish Release | ||||||
|  |  | ||||||
|  | # yamllint disable-line rule:truthy | ||||||
| on: | on: | ||||||
|   workflow_dispatch: |   workflow_dispatch: | ||||||
|   release: |   release: | ||||||
| @@ -20,6 +22,7 @@ jobs: | |||||||
|       - uses: actions/checkout@v3 |       - uses: actions/checkout@v3 | ||||||
|       - name: Get tag |       - name: Get tag | ||||||
|         id: tag |         id: tag | ||||||
|  |         # yamllint disable rule:line-length | ||||||
|         run: | |         run: | | ||||||
|           if [[ "$GITHUB_EVENT_NAME" = "release" ]]; then |           if [[ "$GITHUB_EVENT_NAME" = "release" ]]; then | ||||||
|             TAG="${GITHUB_REF#refs/tags/}" |             TAG="${GITHUB_REF#refs/tags/}" | ||||||
| @@ -29,6 +32,7 @@ jobs: | |||||||
|             TAG="${TAG}${today}" |             TAG="${TAG}${today}" | ||||||
|           fi |           fi | ||||||
|           echo "::set-output name=tag::${TAG}" |           echo "::set-output name=tag::${TAG}" | ||||||
|  |         # yamllint enable rule:line-length | ||||||
|  |  | ||||||
|   deploy-pypi: |   deploy-pypi: | ||||||
|     name: Build and publish to PyPi |     name: Build and publish to PyPi | ||||||
| @@ -39,7 +43,7 @@ jobs: | |||||||
|       - name: Set up Python |       - name: Set up Python | ||||||
|         uses: actions/setup-python@v4 |         uses: actions/setup-python@v4 | ||||||
|         with: |         with: | ||||||
|           python-version: '3.x' |           python-version: "3.x" | ||||||
|       - name: Set up python environment |       - name: Set up python environment | ||||||
|         run: | |         run: | | ||||||
|           script/setup |           script/setup | ||||||
| @@ -65,37 +69,37 @@ jobs: | |||||||
|         arch: [amd64, armv7, aarch64] |         arch: [amd64, armv7, aarch64] | ||||||
|         build_type: ["ha-addon", "docker", "lint"] |         build_type: ["ha-addon", "docker", "lint"] | ||||||
|     steps: |     steps: | ||||||
|     - uses: actions/checkout@v3 |       - uses: actions/checkout@v3 | ||||||
|     - name: Set up Python |       - name: Set up Python | ||||||
|       uses: actions/setup-python@v4 |         uses: actions/setup-python@v4 | ||||||
|       with: |         with: | ||||||
|         python-version: '3.9' |           python-version: "3.9" | ||||||
|  |  | ||||||
|     - name: Set up Docker Buildx |       - name: Set up Docker Buildx | ||||||
|       uses: docker/setup-buildx-action@v2 |         uses: docker/setup-buildx-action@v2 | ||||||
|     - name: Set up QEMU |       - name: Set up QEMU | ||||||
|       uses: docker/setup-qemu-action@v2 |         uses: docker/setup-qemu-action@v2 | ||||||
|  |  | ||||||
|     - name: Log in to docker hub |       - name: Log in to docker hub | ||||||
|       uses: docker/login-action@v2 |         uses: docker/login-action@v2 | ||||||
|       with: |         with: | ||||||
|         username: ${{ secrets.DOCKER_USER }} |           username: ${{ secrets.DOCKER_USER }} | ||||||
|         password: ${{ secrets.DOCKER_PASSWORD }} |           password: ${{ secrets.DOCKER_PASSWORD }} | ||||||
|     - name: Log in to the GitHub container registry |       - name: Log in to the GitHub container registry | ||||||
|       uses: docker/login-action@v2 |         uses: docker/login-action@v2 | ||||||
|       with: |         with: | ||||||
|           registry: ghcr.io |           registry: ghcr.io | ||||||
|           username: ${{ github.actor }} |           username: ${{ github.actor }} | ||||||
|           password: ${{ secrets.GITHUB_TOKEN }} |           password: ${{ secrets.GITHUB_TOKEN }} | ||||||
|  |  | ||||||
|     - name: Build and push |       - name: Build and push | ||||||
|       run: | |         run: | | ||||||
|         docker/build.py \ |           docker/build.py \ | ||||||
|           --tag "${{ needs.init.outputs.tag }}" \ |             --tag "${{ needs.init.outputs.tag }}" \ | ||||||
|           --arch "${{ matrix.arch }}" \ |             --arch "${{ matrix.arch }}" \ | ||||||
|           --build-type "${{ matrix.build_type }}" \ |             --build-type "${{ matrix.build_type }}" \ | ||||||
|           build \ |             build \ | ||||||
|           --push |             --push | ||||||
|  |  | ||||||
|   deploy-docker-manifest: |   deploy-docker-manifest: | ||||||
|     if: github.repository == 'esphome/esphome' |     if: github.repository == 'esphome/esphome' | ||||||
| @@ -108,34 +112,34 @@ jobs: | |||||||
|       matrix: |       matrix: | ||||||
|         build_type: ["ha-addon", "docker", "lint"] |         build_type: ["ha-addon", "docker", "lint"] | ||||||
|     steps: |     steps: | ||||||
|     - uses: actions/checkout@v3 |       - uses: actions/checkout@v3 | ||||||
|     - name: Set up Python |       - name: Set up Python | ||||||
|       uses: actions/setup-python@v4 |         uses: actions/setup-python@v4 | ||||||
|       with: |         with: | ||||||
|         python-version: '3.9' |           python-version: "3.9" | ||||||
|     - name: Enable experimental manifest support |       - name: Enable experimental manifest support | ||||||
|       run: | |         run: | | ||||||
|         mkdir -p ~/.docker |           mkdir -p ~/.docker | ||||||
|         echo "{\"experimental\": \"enabled\"}" > ~/.docker/config.json |           echo "{\"experimental\": \"enabled\"}" > ~/.docker/config.json | ||||||
|  |  | ||||||
|     - name: Log in to docker hub |       - name: Log in to docker hub | ||||||
|       uses: docker/login-action@v2 |         uses: docker/login-action@v2 | ||||||
|       with: |         with: | ||||||
|         username: ${{ secrets.DOCKER_USER }} |           username: ${{ secrets.DOCKER_USER }} | ||||||
|         password: ${{ secrets.DOCKER_PASSWORD }} |           password: ${{ secrets.DOCKER_PASSWORD }} | ||||||
|     - name: Log in to the GitHub container registry |       - name: Log in to the GitHub container registry | ||||||
|       uses: docker/login-action@v2 |         uses: docker/login-action@v2 | ||||||
|       with: |         with: | ||||||
|           registry: ghcr.io |           registry: ghcr.io | ||||||
|           username: ${{ github.actor }} |           username: ${{ github.actor }} | ||||||
|           password: ${{ secrets.GITHUB_TOKEN }} |           password: ${{ secrets.GITHUB_TOKEN }} | ||||||
|  |  | ||||||
|     - name: Run manifest |       - name: Run manifest | ||||||
|       run: | |         run: | | ||||||
|         docker/build.py \ |           docker/build.py \ | ||||||
|           --tag "${{ needs.init.outputs.tag }}" \ |             --tag "${{ needs.init.outputs.tag }}" \ | ||||||
|           --build-type "${{ matrix.build_type }}" \ |             --build-type "${{ matrix.build_type }}" \ | ||||||
|           manifest |             manifest | ||||||
|  |  | ||||||
|   deploy-ha-addon-repo: |   deploy-ha-addon-repo: | ||||||
|     if: github.repository == 'esphome/esphome' && github.event_name == 'release' |     if: github.repository == 'esphome/esphome' && github.event_name == 'release' | ||||||
| @@ -144,6 +148,7 @@ jobs: | |||||||
|     steps: |     steps: | ||||||
|       - env: |       - env: | ||||||
|           TOKEN: ${{ secrets.DEPLOY_HA_ADDON_REPO_TOKEN }} |           TOKEN: ${{ secrets.DEPLOY_HA_ADDON_REPO_TOKEN }} | ||||||
|  |         # yamllint disable rule:line-length | ||||||
|         run: | |         run: | | ||||||
|           TAG="${GITHUB_REF#refs/tags/}" |           TAG="${GITHUB_REF#refs/tags/}" | ||||||
|           curl \ |           curl \ | ||||||
| @@ -152,3 +157,4 @@ jobs: | |||||||
|             -H "Accept: application/vnd.github.v3+json" \ |             -H "Accept: application/vnd.github.v3+json" \ | ||||||
|             https://api.github.com/repos/esphome/home-assistant-addon/actions/workflows/bump-version.yml/dispatches \ |             https://api.github.com/repos/esphome/home-assistant-addon/actions/workflows/bump-version.yml/dispatches \ | ||||||
|             -d "{\"ref\":\"main\",\"inputs\":{\"version\":\"$TAG\"}}" |             -d "{\"ref\":\"main\",\"inputs\":{\"version\":\"$TAG\"}}" | ||||||
|  |         # yamllint enable rule:line-length | ||||||
|   | |||||||
							
								
								
									
										7
									
								
								.github/workflows/stale.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										7
									
								
								.github/workflows/stale.yml
									
									
									
									
										vendored
									
									
								
							| @@ -1,8 +1,10 @@ | |||||||
|  | --- | ||||||
| name: Stale | name: Stale | ||||||
|  |  | ||||||
|  | # yamllint disable-line rule:truthy | ||||||
| on: | on: | ||||||
|   schedule: |   schedule: | ||||||
|     - cron: '30 0 * * *' |     - cron: "30 0 * * *" | ||||||
|   workflow_dispatch: |   workflow_dispatch: | ||||||
|  |  | ||||||
| permissions: | permissions: | ||||||
| @@ -31,7 +33,8 @@ jobs: | |||||||
|             and will be closed if no further activity occurs within 7 days. |             and will be closed if no further activity occurs within 7 days. | ||||||
|             Thank you for your contributions. |             Thank you for your contributions. | ||||||
|  |  | ||||||
|   # Use stale to automatically close issues with a reference to the issue tracker |   # Use stale to automatically close issues with a | ||||||
|  |   # reference to the issue tracker | ||||||
|   close-issues: |   close-issues: | ||||||
|     runs-on: ubuntu-latest |     runs-on: ubuntu-latest | ||||||
|     steps: |     steps: | ||||||
|   | |||||||
							
								
								
									
										10
									
								
								.gitpod.yml
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								.gitpod.yml
									
									
									
									
									
								
							| @@ -1,6 +1,8 @@ | |||||||
|  | --- | ||||||
| ports: | ports: | ||||||
| - port: 6052 |   - port: 6052 | ||||||
|   onOpen: open-preview |     onOpen: open-preview | ||||||
| tasks: | tasks: | ||||||
| - before: pyenv local $(pyenv version | grep '^3\.' | cut -d ' ' -f 1) && script/setup |   # yamllint disable-line rule:line-length | ||||||
|   command: python -m esphome dashboard config |   - before: pyenv local $(pyenv version | grep '^3\.' | cut -d ' ' -f 1) && script/setup | ||||||
|  |     command: python -m esphome dashboard config | ||||||
|   | |||||||
| @@ -1,14 +1,15 @@ | |||||||
|  | --- | ||||||
| # See https://pre-commit.com for more information | # See https://pre-commit.com for more information | ||||||
| # See https://pre-commit.com/hooks.html for more hooks | # See https://pre-commit.com/hooks.html for more hooks | ||||||
| repos: | repos: | ||||||
|   - repo: https://github.com/ambv/black |   - repo: https://github.com/ambv/black | ||||||
|     rev: 22.6.0 |     rev: 22.6.0 | ||||||
|     hooks: |     hooks: | ||||||
|     - id: black |       - id: black | ||||||
|       args: |         args: | ||||||
|         - --safe |           - --safe | ||||||
|         - --quiet |           - --quiet | ||||||
|       files: ^((esphome|script|tests)/.+)?[^/]+\.py$ |         files: ^((esphome|script|tests)/.+)?[^/]+\.py$ | ||||||
|   - repo: https://gitlab.com/pycqa/flake8 |   - repo: https://gitlab.com/pycqa/flake8 | ||||||
|     rev: 4.0.1 |     rev: 4.0.1 | ||||||
|     hooks: |     hooks: | ||||||
|   | |||||||
							
								
								
									
										12
									
								
								CODEOWNERS
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								CODEOWNERS
									
									
									
									
									
								
							| @@ -29,11 +29,15 @@ esphome/components/b_parasite/* @rbaron | |||||||
| esphome/components/ballu/* @bazuchan | esphome/components/ballu/* @bazuchan | ||||||
| esphome/components/bang_bang/* @OttoWinter | esphome/components/bang_bang/* @OttoWinter | ||||||
| esphome/components/bedjet/* @jhansche | esphome/components/bedjet/* @jhansche | ||||||
|  | esphome/components/bedjet/climate/* @jhansche | ||||||
|  | esphome/components/bedjet/fan/* @jhansche | ||||||
| esphome/components/bh1750/* @OttoWinter | esphome/components/bh1750/* @OttoWinter | ||||||
| esphome/components/binary_sensor/* @esphome/core | esphome/components/binary_sensor/* @esphome/core | ||||||
| esphome/components/bl0939/* @ziceva | esphome/components/bl0939/* @ziceva | ||||||
| esphome/components/bl0940/* @tobias- | esphome/components/bl0940/* @tobias- | ||||||
|  | esphome/components/bl0942/* @dbuezas | ||||||
| esphome/components/ble_client/* @buxtronix | esphome/components/ble_client/* @buxtronix | ||||||
|  | esphome/components/bluetooth_proxy/* @jesserockz | ||||||
| esphome/components/bme680_bsec/* @trvrnrth | esphome/components/bme680_bsec/* @trvrnrth | ||||||
| esphome/components/bmp3xx/* @martgras | esphome/components/bmp3xx/* @martgras | ||||||
| esphome/components/button/* @esphome/core | esphome/components/button/* @esphome/core | ||||||
| @@ -59,6 +63,7 @@ esphome/components/debug/* @OttoWinter | |||||||
| esphome/components/delonghi/* @grob6000 | esphome/components/delonghi/* @grob6000 | ||||||
| esphome/components/dfplayer/* @glmnet | esphome/components/dfplayer/* @glmnet | ||||||
| esphome/components/dht/* @OttoWinter | esphome/components/dht/* @OttoWinter | ||||||
|  | esphome/components/dps310/* @kbx81 | ||||||
| esphome/components/ds1307/* @badbadc0ffee | esphome/components/ds1307/* @badbadc0ffee | ||||||
| esphome/components/dsmr/* @glmnet @zuidwijk | esphome/components/dsmr/* @glmnet @zuidwijk | ||||||
| esphome/components/ektf2232/* @jesserockz | esphome/components/ektf2232/* @jesserockz | ||||||
| @@ -72,6 +77,7 @@ esphome/components/esp32_improv/* @jesserockz | |||||||
| esphome/components/esp8266/* @esphome/core | esphome/components/esp8266/* @esphome/core | ||||||
| esphome/components/exposure_notifications/* @OttoWinter | esphome/components/exposure_notifications/* @OttoWinter | ||||||
| esphome/components/ezo/* @ssieb | esphome/components/ezo/* @ssieb | ||||||
|  | esphome/components/factory_reset/* @anatoly-savchenkov | ||||||
| esphome/components/fastled_base/* @OttoWinter | esphome/components/fastled_base/* @OttoWinter | ||||||
| esphome/components/feedback/* @ianchi | esphome/components/feedback/* @ianchi | ||||||
| esphome/components/fingerprint_grow/* @OnFreund @loongyh | esphome/components/fingerprint_grow/* @OnFreund @loongyh | ||||||
| @@ -120,6 +126,7 @@ esphome/components/mcp2515/* @danielschramm @mvturnho | |||||||
| esphome/components/mcp3204/* @rsumner | esphome/components/mcp3204/* @rsumner | ||||||
| esphome/components/mcp4728/* @berfenger | esphome/components/mcp4728/* @berfenger | ||||||
| esphome/components/mcp47a1/* @jesserockz | esphome/components/mcp47a1/* @jesserockz | ||||||
|  | esphome/components/mcp9600/* @MrEditor97 | ||||||
| esphome/components/mcp9808/* @k7hpn | esphome/components/mcp9808/* @k7hpn | ||||||
| esphome/components/md5/* @esphome/core | esphome/components/md5/* @esphome/core | ||||||
| esphome/components/mdns/* @esphome/core | esphome/components/mdns/* @esphome/core | ||||||
| @@ -138,6 +145,7 @@ esphome/components/modbus_controller/switch/* @martgras | |||||||
| esphome/components/modbus_controller/text_sensor/* @martgras | esphome/components/modbus_controller/text_sensor/* @martgras | ||||||
| esphome/components/mopeka_ble/* @spbrogan | esphome/components/mopeka_ble/* @spbrogan | ||||||
| esphome/components/mopeka_pro_check/* @spbrogan | esphome/components/mopeka_pro_check/* @spbrogan | ||||||
|  | esphome/components/mpl3115a2/* @kbickar | ||||||
| esphome/components/mpu6886/* @fabaff | esphome/components/mpu6886/* @fabaff | ||||||
| esphome/components/network/* @esphome/core | esphome/components/network/* @esphome/core | ||||||
| esphome/components/nextion/* @senexcrenshaw | esphome/components/nextion/* @senexcrenshaw | ||||||
| @@ -220,7 +228,9 @@ esphome/components/teleinfo/* @0hax | |||||||
| esphome/components/thermostat/* @kbx81 | esphome/components/thermostat/* @kbx81 | ||||||
| esphome/components/time/* @OttoWinter | esphome/components/time/* @OttoWinter | ||||||
| esphome/components/tlc5947/* @rnauber | esphome/components/tlc5947/* @rnauber | ||||||
|  | esphome/components/tm1621/* @Philippe12 | ||||||
| esphome/components/tm1637/* @glmnet | esphome/components/tm1637/* @glmnet | ||||||
|  | esphome/components/tm1638/* @skykingjwc | ||||||
| esphome/components/tmp102/* @timsavage | esphome/components/tmp102/* @timsavage | ||||||
| esphome/components/tmp117/* @Azimath | esphome/components/tmp117/* @Azimath | ||||||
| esphome/components/tof10120/* @wstrzalka | esphome/components/tof10120/* @wstrzalka | ||||||
| @@ -235,6 +245,8 @@ esphome/components/tuya/sensor/* @jesserockz | |||||||
| esphome/components/tuya/switch/* @jesserockz | esphome/components/tuya/switch/* @jesserockz | ||||||
| esphome/components/tuya/text_sensor/* @dentra | esphome/components/tuya/text_sensor/* @dentra | ||||||
| esphome/components/uart/* @esphome/core | esphome/components/uart/* @esphome/core | ||||||
|  | esphome/components/ufire_ec/* @pvizeli | ||||||
|  | esphome/components/ufire_ise/* @pvizeli | ||||||
| esphome/components/ultrasonic/* @OttoWinter | esphome/components/ultrasonic/* @OttoWinter | ||||||
| esphome/components/version/* @esphome/core | esphome/components/version/* @esphome/core | ||||||
| esphome/components/wake_on_lan/* @willwill2will54 | esphome/components/wake_on_lan/* @willwill2will54 | ||||||
|   | |||||||
| @@ -88,10 +88,12 @@ def main(): | |||||||
|                 sys.exit(1) |                 sys.exit(1) | ||||||
|  |  | ||||||
|     # detect channel from tag |     # detect channel from tag | ||||||
|     match = re.match(r'^\d+\.\d+(?:\.\d+)?(b\d+)?$', args.tag) |     match = re.match(r"^(\d+\.\d+)(?:\.\d+)?(b\d+)?$", args.tag) | ||||||
|  |     major_minor_version = None | ||||||
|     if match is None: |     if match is None: | ||||||
|         channel = CHANNEL_DEV |         channel = CHANNEL_DEV | ||||||
|     elif match.group(1) is None: |     elif match.group(2) is None: | ||||||
|  |         major_minor_version = match.group(1) | ||||||
|         channel = CHANNEL_RELEASE |         channel = CHANNEL_RELEASE | ||||||
|     else: |     else: | ||||||
|         channel = CHANNEL_BETA |         channel = CHANNEL_BETA | ||||||
| @@ -106,6 +108,11 @@ def main(): | |||||||
|         tags_to_push.append("beta") |         tags_to_push.append("beta") | ||||||
|         tags_to_push.append("latest") |         tags_to_push.append("latest") | ||||||
|  |  | ||||||
|  |         # Compatibility with HA tags | ||||||
|  |         if major_minor_version: | ||||||
|  |             tags_to_push.append("stable") | ||||||
|  |             tags_to_push.append(major_minor_version) | ||||||
|  |  | ||||||
|     if args.command == "build": |     if args.command == "build": | ||||||
|         # 1. pull cache image |         # 1. pull cache image | ||||||
|         params = DockerParams.for_type_arch(args.build_type, args.arch) |         params = DockerParams.for_type_arch(args.build_type, args.arch) | ||||||
|   | |||||||
| @@ -4,33 +4,15 @@ | |||||||
| //  - Arduino - AM2320: https://github.com/EngDial/AM2320/blob/master/src/AM2320.cpp | //  - Arduino - AM2320: https://github.com/EngDial/AM2320/blob/master/src/AM2320.cpp | ||||||
|  |  | ||||||
| #include "am2320.h" | #include "am2320.h" | ||||||
| #include "esphome/core/log.h" |  | ||||||
| #include "esphome/core/hal.h" | #include "esphome/core/hal.h" | ||||||
|  | #include "esphome/core/helpers.h" | ||||||
|  | #include "esphome/core/log.h" | ||||||
|  |  | ||||||
| namespace esphome { | namespace esphome { | ||||||
| namespace am2320 { | namespace am2320 { | ||||||
|  |  | ||||||
| static const char *const TAG = "am2320"; | static const char *const TAG = "am2320"; | ||||||
|  |  | ||||||
| // ---=== Calc CRC16 ===--- |  | ||||||
| uint16_t crc_16(uint8_t *ptr, uint8_t length) { |  | ||||||
|   uint16_t crc = 0xFFFF; |  | ||||||
|   uint8_t i; |  | ||||||
|   //------------------------------ |  | ||||||
|   while (length--) { |  | ||||||
|     crc ^= *ptr++; |  | ||||||
|     for (i = 0; i < 8; i++) { |  | ||||||
|       if ((crc & 0x01) != 0) { |  | ||||||
|         crc >>= 1; |  | ||||||
|         crc ^= 0xA001; |  | ||||||
|       } else { |  | ||||||
|         crc >>= 1; |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|   return crc; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void AM2320Component::update() { | void AM2320Component::update() { | ||||||
|   uint8_t data[8]; |   uint8_t data[8]; | ||||||
|   data[0] = 0; |   data[0] = 0; | ||||||
| @@ -98,7 +80,7 @@ bool AM2320Component::read_data_(uint8_t *data) { | |||||||
|   checksum = data[7] << 8; |   checksum = data[7] << 8; | ||||||
|   checksum += data[6]; |   checksum += data[6]; | ||||||
|  |  | ||||||
|   if (crc_16(data, 6) != checksum) { |   if (crc16(data, 6) != checksum) { | ||||||
|     ESP_LOGW(TAG, "AM2320 Checksum invalid!"); |     ESP_LOGW(TAG, "AM2320 Checksum invalid!"); | ||||||
|     return false; |     return false; | ||||||
|   } |   } | ||||||
|   | |||||||
| @@ -8,6 +8,27 @@ AUTO_LOAD = ["sensor", "binary_sensor"] | |||||||
| MULTI_CONF = True | MULTI_CONF = True | ||||||
|  |  | ||||||
| CONF_APDS9960_ID = "apds9960_id" | CONF_APDS9960_ID = "apds9960_id" | ||||||
|  | CONF_LED_DRIVE = "led_drive" | ||||||
|  | CONF_PROXIMITY_GAIN = "proximity_gain" | ||||||
|  | CONF_AMBIENT_LIGHT_GAIN = "ambient_light_gain" | ||||||
|  | CONF_GESTURE_LED_DRIVE = "gesture_led_drive" | ||||||
|  | CONF_GESTURE_GAIN = "gesture_gain" | ||||||
|  | CONF_GESTURE_WAIT_TIME = "gesture_wait_time" | ||||||
|  |  | ||||||
|  | DRIVE_LEVELS = {"100ma": 0, "50ma": 1, "25ma": 2, "12.5ma": 3} | ||||||
|  | PROXIMITY_LEVELS = {"1x": 0, "2x": 1, "4x": 2, "8x": 3} | ||||||
|  | AMBIENT_LEVELS = {"1x": 0, "4x": 1, "16x": 2, "64x": 3} | ||||||
|  | GESTURE_LEVELS = {"1x": 0, "2x": 1, "4x": 2, "8x": 3} | ||||||
|  | GESTURE_WAIT_TIMES = { | ||||||
|  |     "0ms": 0, | ||||||
|  |     "2.8ms": 1, | ||||||
|  |     "5.6ms": 2, | ||||||
|  |     "8.4ms": 3, | ||||||
|  |     "14ms": 4, | ||||||
|  |     "22.4ms": 5, | ||||||
|  |     "30.8ms": 6, | ||||||
|  |     "39.2ms": 7, | ||||||
|  | } | ||||||
|  |  | ||||||
| apds9960_nds = cg.esphome_ns.namespace("apds9960") | apds9960_nds = cg.esphome_ns.namespace("apds9960") | ||||||
| APDS9960 = apds9960_nds.class_("APDS9960", cg.PollingComponent, i2c.I2CDevice) | APDS9960 = apds9960_nds.class_("APDS9960", cg.PollingComponent, i2c.I2CDevice) | ||||||
| @@ -16,6 +37,20 @@ CONFIG_SCHEMA = ( | |||||||
|     cv.Schema( |     cv.Schema( | ||||||
|         { |         { | ||||||
|             cv.GenerateID(): cv.declare_id(APDS9960), |             cv.GenerateID(): cv.declare_id(APDS9960), | ||||||
|  |             cv.Optional(CONF_LED_DRIVE, "100mA"): cv.enum(DRIVE_LEVELS, lower=True), | ||||||
|  |             cv.Optional(CONF_PROXIMITY_GAIN, "4x"): cv.enum( | ||||||
|  |                 PROXIMITY_LEVELS, lower=True | ||||||
|  |             ), | ||||||
|  |             cv.Optional(CONF_AMBIENT_LIGHT_GAIN, "4x"): cv.enum( | ||||||
|  |                 AMBIENT_LEVELS, lower=True | ||||||
|  |             ), | ||||||
|  |             cv.Optional(CONF_GESTURE_LED_DRIVE, "100mA"): cv.enum( | ||||||
|  |                 DRIVE_LEVELS, lower=True | ||||||
|  |             ), | ||||||
|  |             cv.Optional(CONF_GESTURE_GAIN, "4x"): cv.enum(GESTURE_LEVELS, lower=True), | ||||||
|  |             cv.Optional(CONF_GESTURE_WAIT_TIME, "2.8ms"): cv.enum( | ||||||
|  |                 GESTURE_WAIT_TIMES, lower=True | ||||||
|  |             ), | ||||||
|         } |         } | ||||||
|     ) |     ) | ||||||
|     .extend(cv.polling_component_schema("60s")) |     .extend(cv.polling_component_schema("60s")) | ||||||
| @@ -27,3 +62,9 @@ async def to_code(config): | |||||||
|     var = cg.new_Pvariable(config[CONF_ID]) |     var = cg.new_Pvariable(config[CONF_ID]) | ||||||
|     await cg.register_component(var, config) |     await cg.register_component(var, config) | ||||||
|     await i2c.register_i2c_device(var, config) |     await i2c.register_i2c_device(var, config) | ||||||
|  |     cg.add(var.set_led_drive(config[CONF_LED_DRIVE])) | ||||||
|  |     cg.add(var.set_proximity_gain(config[CONF_PROXIMITY_GAIN])) | ||||||
|  |     cg.add(var.set_ambient_gain(config[CONF_AMBIENT_LIGHT_GAIN])) | ||||||
|  |     cg.add(var.set_gesture_led_drive(config[CONF_GESTURE_LED_DRIVE])) | ||||||
|  |     cg.add(var.set_gesture_gain(config[CONF_GESTURE_GAIN])) | ||||||
|  |     cg.add(var.set_gesture_wait_time(config[CONF_GESTURE_WAIT_TIME])) | ||||||
|   | |||||||
| @@ -46,16 +46,16 @@ void APDS9960::setup() { | |||||||
|   uint8_t val = 0; |   uint8_t val = 0; | ||||||
|   APDS9960_ERROR_CHECK(this->read_byte(0x8F, &val)); |   APDS9960_ERROR_CHECK(this->read_byte(0x8F, &val)); | ||||||
|   val &= 0b00111111; |   val &= 0b00111111; | ||||||
|   uint8_t led_drive = 0;  // led drive, 0 -> 100mA, 1 -> 50mA, 2 -> 25mA, 3 -> 12.5mA |   // led drive, 0 -> 100mA, 1 -> 50mA, 2 -> 25mA, 3 -> 12.5mA | ||||||
|   val |= (led_drive & 0b11) << 6; |   val |= (this->led_drive_ & 0b11) << 6; | ||||||
|  |  | ||||||
|   val &= 0b11110011; |   val &= 0b11110011; | ||||||
|   uint8_t proximity_gain = 2;  // proximity gain, 0 -> 1x, 1 -> 2X, 2 -> 4X, 4 -> 8X |   // proximity gain, 0 -> 1x, 1 -> 2X, 2 -> 4X, 3 -> 8X | ||||||
|   val |= (proximity_gain & 0b11) << 2; |   val |= (this->proximity_gain_ & 0b11) << 2; | ||||||
|  |  | ||||||
|   val &= 0b11111100; |   val &= 0b11111100; | ||||||
|   uint8_t ambient_gain = 1;  // ambient light gain, 0 -> 1x, 1 -> 4x, 2 -> 16x, 3 -> 64x |   // ambient light gain, 0 -> 1x, 1 -> 4x, 2 -> 16x, 3 -> 64x | ||||||
|   val |= (ambient_gain & 0b11) << 0; |   val |= (this->ambient_gain_ & 0b11) << 0; | ||||||
|   APDS9960_WRITE_BYTE(0x8F, val); |   APDS9960_WRITE_BYTE(0x8F, val); | ||||||
|  |  | ||||||
|   // Pers (0x8C) -> 0x11 (2 consecutive proximity or ALS for interrupt) |   // Pers (0x8C) -> 0x11 (2 consecutive proximity or ALS for interrupt) | ||||||
| @@ -75,19 +75,18 @@ void APDS9960::setup() { | |||||||
|   // GConf 2 (0xA3, gesture config 2) -> |   // GConf 2 (0xA3, gesture config 2) -> | ||||||
|   APDS9960_ERROR_CHECK(this->read_byte(0xA3, &val)); |   APDS9960_ERROR_CHECK(this->read_byte(0xA3, &val)); | ||||||
|   val &= 0b10011111; |   val &= 0b10011111; | ||||||
|   uint8_t gesture_gain = 2;  // gesture gain, 0 -> 1x, 1 -> 2x, 2 -> 4x, 3 -> 8x |   // gesture gain, 0 -> 1x, 1 -> 2x, 2 -> 4x, 3 -> 8x | ||||||
|   val |= (gesture_gain & 0b11) << 5; |   val |= (this->gesture_gain_ & 0b11) << 5; | ||||||
|  |  | ||||||
|   val &= 0b11100111; |   val &= 0b11100111; | ||||||
|   uint8_t gesture_led_drive = 0;  // gesture led drive, 0 -> 100mA, 1 -> 50mA, 2 -> 25mA, 3 -> 12.5mA |   // gesture led drive, 0 -> 100mA, 1 -> 50mA, 2 -> 25mA, 3 -> 12.5mA | ||||||
|   val |= (gesture_led_drive & 0b11) << 3; |   val |= (this->gesture_led_drive_ & 0b11) << 3; | ||||||
|  |  | ||||||
|   val &= 0b11111000; |   val &= 0b11111000; | ||||||
|   // gesture wait time |   // gesture wait time | ||||||
|   // 0 -> 0ms, 1 -> 2.8ms, 2 -> 5.6ms, 3 -> 8.4ms |   // 0 -> 0ms, 1 -> 2.8ms, 2 -> 5.6ms, 3 -> 8.4ms | ||||||
|   // 4 -> 14.0ms, 5 -> 22.4 ms, 6 -> 30.8ms, 7 -> 39.2 ms |   // 4 -> 14.0ms, 5 -> 22.4 ms, 6 -> 30.8ms, 7 -> 39.2 ms | ||||||
|   uint8_t gesture_wait_time = 1;  // gesture wait time |   val |= (this->gesture_wait_time_ & 0b111) << 0; | ||||||
|   val |= (gesture_wait_time & 0b111) << 0; |  | ||||||
|   APDS9960_WRITE_BYTE(0xA3, val); |   APDS9960_WRITE_BYTE(0xA3, val); | ||||||
|  |  | ||||||
|   // GOffsetU (0xA4) -> 0x00 (no offset) |   // GOffsetU (0xA4) -> 0x00 (no offset) | ||||||
|   | |||||||
| @@ -16,6 +16,13 @@ class APDS9960 : public PollingComponent, public i2c::I2CDevice { | |||||||
|   void update() override; |   void update() override; | ||||||
|   void loop() override; |   void loop() override; | ||||||
|  |  | ||||||
|  |   void set_led_drive(uint8_t level) { this->led_drive_ = level; } | ||||||
|  |   void set_proximity_gain(uint8_t gain) { this->proximity_gain_ = gain; } | ||||||
|  |   void set_ambient_gain(uint8_t gain) { this->ambient_gain_ = gain; } | ||||||
|  |   void set_gesture_led_drive(uint8_t level) { this->gesture_led_drive_ = level; } | ||||||
|  |   void set_gesture_gain(uint8_t gain) { this->gesture_gain_ = gain; } | ||||||
|  |   void set_gesture_wait_time(uint8_t wait_time) { this->gesture_wait_time_ = wait_time; } | ||||||
|  |  | ||||||
|   void set_red_channel(sensor::Sensor *red_channel) { red_channel_ = red_channel; } |   void set_red_channel(sensor::Sensor *red_channel) { red_channel_ = red_channel; } | ||||||
|   void set_green_channel(sensor::Sensor *green_channel) { green_channel_ = green_channel; } |   void set_green_channel(sensor::Sensor *green_channel) { green_channel_ = green_channel; } | ||||||
|   void set_blue_channel(sensor::Sensor *blue_channel) { blue_channel_ = blue_channel; } |   void set_blue_channel(sensor::Sensor *blue_channel) { blue_channel_ = blue_channel; } | ||||||
| @@ -36,6 +43,13 @@ class APDS9960 : public PollingComponent, public i2c::I2CDevice { | |||||||
|   void report_gesture_(int gesture); |   void report_gesture_(int gesture); | ||||||
|   void process_dataset_(int up, int down, int left, int right); |   void process_dataset_(int up, int down, int left, int right); | ||||||
|  |  | ||||||
|  |   uint8_t led_drive_; | ||||||
|  |   uint8_t proximity_gain_; | ||||||
|  |   uint8_t ambient_gain_; | ||||||
|  |   uint8_t gesture_led_drive_; | ||||||
|  |   uint8_t gesture_gain_; | ||||||
|  |   uint8_t gesture_wait_time_; | ||||||
|  |  | ||||||
|   sensor::Sensor *red_channel_{nullptr}; |   sensor::Sensor *red_channel_{nullptr}; | ||||||
|   sensor::Sensor *green_channel_{nullptr}; |   sensor::Sensor *green_channel_{nullptr}; | ||||||
|   sensor::Sensor *blue_channel_{nullptr}; |   sensor::Sensor *blue_channel_{nullptr}; | ||||||
|   | |||||||
| @@ -27,6 +27,7 @@ service APIConnection { | |||||||
|   rpc subscribe_logs (SubscribeLogsRequest) returns (void) {} |   rpc subscribe_logs (SubscribeLogsRequest) returns (void) {} | ||||||
|   rpc subscribe_homeassistant_services (SubscribeHomeassistantServicesRequest) returns (void) {} |   rpc subscribe_homeassistant_services (SubscribeHomeassistantServicesRequest) returns (void) {} | ||||||
|   rpc subscribe_home_assistant_states (SubscribeHomeAssistantStatesRequest) returns (void) {} |   rpc subscribe_home_assistant_states (SubscribeHomeAssistantStatesRequest) returns (void) {} | ||||||
|  |   rpc subscribe_bluetooth_le_advertisements (SubscribeBluetoothLEAdvertisementsRequest) returns (void) {} | ||||||
|   rpc get_time (GetTimeRequest) returns (GetTimeResponse) { |   rpc get_time (GetTimeRequest) returns (GetTimeResponse) { | ||||||
|     option (needs_authentication) = false; |     option (needs_authentication) = false; | ||||||
|   } |   } | ||||||
| @@ -190,6 +191,8 @@ message DeviceInfoResponse { | |||||||
|   string project_version = 9; |   string project_version = 9; | ||||||
|  |  | ||||||
|   uint32 webserver_port = 10; |   uint32 webserver_port = 10; | ||||||
|  |  | ||||||
|  |   bool has_bluetooth_proxy = 11; | ||||||
| } | } | ||||||
|  |  | ||||||
| message ListEntitiesRequest { | message ListEntitiesRequest { | ||||||
| @@ -1099,3 +1102,28 @@ message MediaPlayerCommandRequest { | |||||||
|   bool has_media_url = 6; |   bool has_media_url = 6; | ||||||
|   string media_url = 7; |   string media_url = 7; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // ==================== BLUETOOTH ==================== | ||||||
|  | message SubscribeBluetoothLEAdvertisementsRequest { | ||||||
|  |   option (id) = 66; | ||||||
|  |   option (source) = SOURCE_CLIENT; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | message BluetoothServiceData { | ||||||
|  |   string uuid = 1; | ||||||
|  |   repeated uint32 data = 2 [packed=false]; | ||||||
|  | } | ||||||
|  | message BluetoothLEAdvertisementResponse { | ||||||
|  |   option (id) = 67; | ||||||
|  |   option (source) = SOURCE_SERVER; | ||||||
|  |   option (ifdef) = "USE_BLUETOOTH_PROXY"; | ||||||
|  |   option (no_delay) = true; | ||||||
|  |  | ||||||
|  |   uint64 address = 1; | ||||||
|  |   string name = 2; | ||||||
|  |   sint32 rssi = 3; | ||||||
|  |  | ||||||
|  |   repeated string service_uuids = 4; | ||||||
|  |   repeated BluetoothServiceData service_data = 5; | ||||||
|  |   repeated BluetoothServiceData manufacturer_data = 6; | ||||||
|  | } | ||||||
|   | |||||||
| @@ -886,6 +886,9 @@ DeviceInfoResponse APIConnection::device_info(const DeviceInfoRequest &msg) { | |||||||
| #endif | #endif | ||||||
| #ifdef USE_WEBSERVER | #ifdef USE_WEBSERVER | ||||||
|   resp.webserver_port = USE_WEBSERVER_PORT; |   resp.webserver_port = USE_WEBSERVER_PORT; | ||||||
|  | #endif | ||||||
|  | #ifdef USE_BLUETOOTH_PROXY | ||||||
|  |   resp.has_bluetooth_proxy = true; | ||||||
| #endif | #endif | ||||||
|   return resp; |   return resp; | ||||||
| } | } | ||||||
|   | |||||||
| @@ -7,6 +7,10 @@ | |||||||
| #include "api_server.h" | #include "api_server.h" | ||||||
| #include "api_frame_helper.h" | #include "api_frame_helper.h" | ||||||
|  |  | ||||||
|  | #ifdef USE_BLUETOOTH_PROXY | ||||||
|  | #include "esphome/components/bluetooth_proxy/bluetooth_proxy.h" | ||||||
|  | #endif | ||||||
|  |  | ||||||
| namespace esphome { | namespace esphome { | ||||||
| namespace api { | namespace api { | ||||||
|  |  | ||||||
| @@ -94,6 +98,13 @@ class APIConnection : public APIServerConnection { | |||||||
|       return; |       return; | ||||||
|     this->send_homeassistant_service_response(call); |     this->send_homeassistant_service_response(call); | ||||||
|   } |   } | ||||||
|  | #ifdef USE_BLUETOOTH_PROXY | ||||||
|  |   bool send_bluetooth_le_advertisement(const BluetoothLEAdvertisementResponse &call) { | ||||||
|  |     if (!this->bluetooth_le_advertisement_subscription_) | ||||||
|  |       return false; | ||||||
|  |     return this->send_bluetooth_le_advertisement_response(call); | ||||||
|  |   } | ||||||
|  | #endif | ||||||
| #ifdef USE_HOMEASSISTANT_TIME | #ifdef USE_HOMEASSISTANT_TIME | ||||||
|   void send_time_request() { |   void send_time_request() { | ||||||
|     GetTimeRequest req; |     GetTimeRequest req; | ||||||
| @@ -134,6 +145,9 @@ class APIConnection : public APIServerConnection { | |||||||
|     return {}; |     return {}; | ||||||
|   } |   } | ||||||
|   void execute_service(const ExecuteServiceRequest &msg) override; |   void execute_service(const ExecuteServiceRequest &msg) override; | ||||||
|  |   void subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) override { | ||||||
|  |     this->bluetooth_le_advertisement_subscription_ = true; | ||||||
|  |   } | ||||||
|   bool is_authenticated() override { return this->connection_state_ == ConnectionState::AUTHENTICATED; } |   bool is_authenticated() override { return this->connection_state_ == ConnectionState::AUTHENTICATED; } | ||||||
|   bool is_connection_setup() override { |   bool is_connection_setup() override { | ||||||
|     return this->connection_state_ == ConnectionState ::CONNECTED || this->is_authenticated(); |     return this->connection_state_ == ConnectionState ::CONNECTED || this->is_authenticated(); | ||||||
| @@ -176,6 +190,7 @@ class APIConnection : public APIServerConnection { | |||||||
|   uint32_t last_traffic_; |   uint32_t last_traffic_; | ||||||
|   bool sent_ping_{false}; |   bool sent_ping_{false}; | ||||||
|   bool service_call_subscription_{false}; |   bool service_call_subscription_{false}; | ||||||
|  |   bool bluetooth_le_advertisement_subscription_{true}; | ||||||
|   bool next_close_ = false; |   bool next_close_ = false; | ||||||
|   APIServer *parent_; |   APIServer *parent_; | ||||||
|   InitialStateIterator initial_state_iterator_; |   InitialStateIterator initial_state_iterator_; | ||||||
|   | |||||||
| @@ -495,6 +495,10 @@ bool DeviceInfoResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { | |||||||
|       this->webserver_port = value.as_uint32(); |       this->webserver_port = value.as_uint32(); | ||||||
|       return true; |       return true; | ||||||
|     } |     } | ||||||
|  |     case 11: { | ||||||
|  |       this->has_bluetooth_proxy = value.as_bool(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|     default: |     default: | ||||||
|       return false; |       return false; | ||||||
|   } |   } | ||||||
| @@ -544,6 +548,7 @@ void DeviceInfoResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
|   buffer.encode_string(8, this->project_name); |   buffer.encode_string(8, this->project_name); | ||||||
|   buffer.encode_string(9, this->project_version); |   buffer.encode_string(9, this->project_version); | ||||||
|   buffer.encode_uint32(10, this->webserver_port); |   buffer.encode_uint32(10, this->webserver_port); | ||||||
|  |   buffer.encode_bool(11, this->has_bluetooth_proxy); | ||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void DeviceInfoResponse::dump_to(std::string &out) const { | void DeviceInfoResponse::dump_to(std::string &out) const { | ||||||
| @@ -589,6 +594,10 @@ void DeviceInfoResponse::dump_to(std::string &out) const { | |||||||
|   sprintf(buffer, "%u", this->webserver_port); |   sprintf(buffer, "%u", this->webserver_port); | ||||||
|   out.append(buffer); |   out.append(buffer); | ||||||
|   out.append("\n"); |   out.append("\n"); | ||||||
|  |  | ||||||
|  |   out.append("  has_bluetooth_proxy: "); | ||||||
|  |   out.append(YESNO(this->has_bluetooth_proxy)); | ||||||
|  |   out.append("\n"); | ||||||
|   out.append("}"); |   out.append("}"); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| @@ -4854,6 +4863,143 @@ void MediaPlayerCommandRequest::dump_to(std::string &out) const { | |||||||
|   out.append("}"); |   out.append("}"); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
|  | void SubscribeBluetoothLEAdvertisementsRequest::encode(ProtoWriteBuffer buffer) const {} | ||||||
|  | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|  | void SubscribeBluetoothLEAdvertisementsRequest::dump_to(std::string &out) const { | ||||||
|  |   out.append("SubscribeBluetoothLEAdvertisementsRequest {}"); | ||||||
|  | } | ||||||
|  | #endif | ||||||
|  | bool BluetoothServiceData::decode_varint(uint32_t field_id, ProtoVarInt value) { | ||||||
|  |   switch (field_id) { | ||||||
|  |     case 2: { | ||||||
|  |       this->data.push_back(value.as_uint32()); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|  |     default: | ||||||
|  |       return false; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | bool BluetoothServiceData::decode_length(uint32_t field_id, ProtoLengthDelimited value) { | ||||||
|  |   switch (field_id) { | ||||||
|  |     case 1: { | ||||||
|  |       this->uuid = value.as_string(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|  |     default: | ||||||
|  |       return false; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | void BluetoothServiceData::encode(ProtoWriteBuffer buffer) const { | ||||||
|  |   buffer.encode_string(1, this->uuid); | ||||||
|  |   for (auto &it : this->data) { | ||||||
|  |     buffer.encode_uint32(2, it, true); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|  | void BluetoothServiceData::dump_to(std::string &out) const { | ||||||
|  |   __attribute__((unused)) char buffer[64]; | ||||||
|  |   out.append("BluetoothServiceData {\n"); | ||||||
|  |   out.append("  uuid: "); | ||||||
|  |   out.append("'").append(this->uuid).append("'"); | ||||||
|  |   out.append("\n"); | ||||||
|  |  | ||||||
|  |   for (const auto &it : this->data) { | ||||||
|  |     out.append("  data: "); | ||||||
|  |     sprintf(buffer, "%u", it); | ||||||
|  |     out.append(buffer); | ||||||
|  |     out.append("\n"); | ||||||
|  |   } | ||||||
|  |   out.append("}"); | ||||||
|  | } | ||||||
|  | #endif | ||||||
|  | bool BluetoothLEAdvertisementResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { | ||||||
|  |   switch (field_id) { | ||||||
|  |     case 1: { | ||||||
|  |       this->address = value.as_uint64(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|  |     case 3: { | ||||||
|  |       this->rssi = value.as_sint32(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|  |     default: | ||||||
|  |       return false; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | bool BluetoothLEAdvertisementResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { | ||||||
|  |   switch (field_id) { | ||||||
|  |     case 2: { | ||||||
|  |       this->name = value.as_string(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|  |     case 4: { | ||||||
|  |       this->service_uuids.push_back(value.as_string()); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|  |     case 5: { | ||||||
|  |       this->service_data.push_back(value.as_message<BluetoothServiceData>()); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|  |     case 6: { | ||||||
|  |       this->manufacturer_data.push_back(value.as_message<BluetoothServiceData>()); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|  |     default: | ||||||
|  |       return false; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | void BluetoothLEAdvertisementResponse::encode(ProtoWriteBuffer buffer) const { | ||||||
|  |   buffer.encode_uint64(1, this->address); | ||||||
|  |   buffer.encode_string(2, this->name); | ||||||
|  |   buffer.encode_sint32(3, this->rssi); | ||||||
|  |   for (auto &it : this->service_uuids) { | ||||||
|  |     buffer.encode_string(4, it, true); | ||||||
|  |   } | ||||||
|  |   for (auto &it : this->service_data) { | ||||||
|  |     buffer.encode_message<BluetoothServiceData>(5, it, true); | ||||||
|  |   } | ||||||
|  |   for (auto &it : this->manufacturer_data) { | ||||||
|  |     buffer.encode_message<BluetoothServiceData>(6, it, true); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|  | void BluetoothLEAdvertisementResponse::dump_to(std::string &out) const { | ||||||
|  |   __attribute__((unused)) char buffer[64]; | ||||||
|  |   out.append("BluetoothLEAdvertisementResponse {\n"); | ||||||
|  |   out.append("  address: "); | ||||||
|  |   sprintf(buffer, "%llu", this->address); | ||||||
|  |   out.append(buffer); | ||||||
|  |   out.append("\n"); | ||||||
|  |  | ||||||
|  |   out.append("  name: "); | ||||||
|  |   out.append("'").append(this->name).append("'"); | ||||||
|  |   out.append("\n"); | ||||||
|  |  | ||||||
|  |   out.append("  rssi: "); | ||||||
|  |   sprintf(buffer, "%d", this->rssi); | ||||||
|  |   out.append(buffer); | ||||||
|  |   out.append("\n"); | ||||||
|  |  | ||||||
|  |   for (const auto &it : this->service_uuids) { | ||||||
|  |     out.append("  service_uuids: "); | ||||||
|  |     out.append("'").append(it).append("'"); | ||||||
|  |     out.append("\n"); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   for (const auto &it : this->service_data) { | ||||||
|  |     out.append("  service_data: "); | ||||||
|  |     it.dump_to(out); | ||||||
|  |     out.append("\n"); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   for (const auto &it : this->manufacturer_data) { | ||||||
|  |     out.append("  manufacturer_data: "); | ||||||
|  |     it.dump_to(out); | ||||||
|  |     out.append("\n"); | ||||||
|  |   } | ||||||
|  |   out.append("}"); | ||||||
|  | } | ||||||
|  | #endif | ||||||
|  |  | ||||||
| }  // namespace api | }  // namespace api | ||||||
| }  // namespace esphome | }  // namespace esphome | ||||||
|   | |||||||
| @@ -263,6 +263,7 @@ class DeviceInfoResponse : public ProtoMessage { | |||||||
|   std::string project_name{}; |   std::string project_name{}; | ||||||
|   std::string project_version{}; |   std::string project_version{}; | ||||||
|   uint32_t webserver_port{0}; |   uint32_t webserver_port{0}; | ||||||
|  |   bool has_bluetooth_proxy{false}; | ||||||
|   void encode(ProtoWriteBuffer buffer) const override; |   void encode(ProtoWriteBuffer buffer) const override; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   void dump_to(std::string &out) const override; |   void dump_to(std::string &out) const override; | ||||||
| @@ -1214,6 +1215,45 @@ class MediaPlayerCommandRequest : public ProtoMessage { | |||||||
|   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; |   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; | ||||||
|   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; |   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; | ||||||
| }; | }; | ||||||
|  | class SubscribeBluetoothLEAdvertisementsRequest : public ProtoMessage { | ||||||
|  |  public: | ||||||
|  |   void encode(ProtoWriteBuffer buffer) const override; | ||||||
|  | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|  |   void dump_to(std::string &out) const override; | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  | }; | ||||||
|  | class BluetoothServiceData : public ProtoMessage { | ||||||
|  |  public: | ||||||
|  |   std::string uuid{}; | ||||||
|  |   std::vector<uint32_t> data{}; | ||||||
|  |   void encode(ProtoWriteBuffer buffer) const override; | ||||||
|  | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|  |   void dump_to(std::string &out) const override; | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; | ||||||
|  |   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; | ||||||
|  | }; | ||||||
|  | class BluetoothLEAdvertisementResponse : public ProtoMessage { | ||||||
|  |  public: | ||||||
|  |   uint64_t address{0}; | ||||||
|  |   std::string name{}; | ||||||
|  |   int32_t rssi{0}; | ||||||
|  |   std::vector<std::string> service_uuids{}; | ||||||
|  |   std::vector<BluetoothServiceData> service_data{}; | ||||||
|  |   std::vector<BluetoothServiceData> manufacturer_data{}; | ||||||
|  |   void encode(ProtoWriteBuffer buffer) const override; | ||||||
|  | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|  |   void dump_to(std::string &out) const override; | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; | ||||||
|  |   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; | ||||||
|  | }; | ||||||
|  |  | ||||||
| }  // namespace api | }  // namespace api | ||||||
| }  // namespace esphome | }  // namespace esphome | ||||||
|   | |||||||
| @@ -328,6 +328,14 @@ bool APIServerConnectionBase::send_media_player_state_response(const MediaPlayer | |||||||
| #endif | #endif | ||||||
| #ifdef USE_MEDIA_PLAYER | #ifdef USE_MEDIA_PLAYER | ||||||
| #endif | #endif | ||||||
|  | #ifdef USE_BLUETOOTH_PROXY | ||||||
|  | bool APIServerConnectionBase::send_bluetooth_le_advertisement_response(const BluetoothLEAdvertisementResponse &msg) { | ||||||
|  | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|  |   ESP_LOGVV(TAG, "send_bluetooth_le_advertisement_response: %s", msg.dump().c_str()); | ||||||
|  | #endif | ||||||
|  |   return this->send_message_<BluetoothLEAdvertisementResponse>(msg, 67); | ||||||
|  | } | ||||||
|  | #endif | ||||||
| bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) { | bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) { | ||||||
|   switch (msg_type) { |   switch (msg_type) { | ||||||
|     case 1: { |     case 1: { | ||||||
| @@ -595,6 +603,15 @@ bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, | |||||||
| #endif | #endif | ||||||
|       break; |       break; | ||||||
|     } |     } | ||||||
|  |     case 66: { | ||||||
|  |       SubscribeBluetoothLEAdvertisementsRequest msg; | ||||||
|  |       msg.decode(msg_data, msg_size); | ||||||
|  | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|  |       ESP_LOGVV(TAG, "on_subscribe_bluetooth_le_advertisements_request: %s", msg.dump().c_str()); | ||||||
|  | #endif | ||||||
|  |       this->on_subscribe_bluetooth_le_advertisements_request(msg); | ||||||
|  |       break; | ||||||
|  |     } | ||||||
|     default: |     default: | ||||||
|       return false; |       return false; | ||||||
|   } |   } | ||||||
| @@ -691,6 +708,18 @@ void APIServerConnection::on_subscribe_home_assistant_states_request(const Subsc | |||||||
|   } |   } | ||||||
|   this->subscribe_home_assistant_states(msg); |   this->subscribe_home_assistant_states(msg); | ||||||
| } | } | ||||||
|  | void APIServerConnection::on_subscribe_bluetooth_le_advertisements_request( | ||||||
|  |     const SubscribeBluetoothLEAdvertisementsRequest &msg) { | ||||||
|  |   if (!this->is_connection_setup()) { | ||||||
|  |     this->on_no_setup_connection(); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   if (!this->is_authenticated()) { | ||||||
|  |     this->on_unauthenticated_access(); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   this->subscribe_bluetooth_le_advertisements(msg); | ||||||
|  | } | ||||||
| void APIServerConnection::on_get_time_request(const GetTimeRequest &msg) { | void APIServerConnection::on_get_time_request(const GetTimeRequest &msg) { | ||||||
|   if (!this->is_connection_setup()) { |   if (!this->is_connection_setup()) { | ||||||
|     this->on_no_setup_connection(); |     this->on_no_setup_connection(); | ||||||
|   | |||||||
| @@ -153,6 +153,11 @@ class APIServerConnectionBase : public ProtoService { | |||||||
| #endif | #endif | ||||||
| #ifdef USE_MEDIA_PLAYER | #ifdef USE_MEDIA_PLAYER | ||||||
|   virtual void on_media_player_command_request(const MediaPlayerCommandRequest &value){}; |   virtual void on_media_player_command_request(const MediaPlayerCommandRequest &value){}; | ||||||
|  | #endif | ||||||
|  |   virtual void on_subscribe_bluetooth_le_advertisements_request( | ||||||
|  |       const SubscribeBluetoothLEAdvertisementsRequest &value){}; | ||||||
|  | #ifdef USE_BLUETOOTH_PROXY | ||||||
|  |   bool send_bluetooth_le_advertisement_response(const BluetoothLEAdvertisementResponse &msg); | ||||||
| #endif | #endif | ||||||
|  protected: |  protected: | ||||||
|   bool read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override; |   bool read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override; | ||||||
| @@ -170,6 +175,7 @@ class APIServerConnection : public APIServerConnectionBase { | |||||||
|   virtual void subscribe_logs(const SubscribeLogsRequest &msg) = 0; |   virtual void subscribe_logs(const SubscribeLogsRequest &msg) = 0; | ||||||
|   virtual void subscribe_homeassistant_services(const SubscribeHomeassistantServicesRequest &msg) = 0; |   virtual void subscribe_homeassistant_services(const SubscribeHomeassistantServicesRequest &msg) = 0; | ||||||
|   virtual void subscribe_home_assistant_states(const SubscribeHomeAssistantStatesRequest &msg) = 0; |   virtual void subscribe_home_assistant_states(const SubscribeHomeAssistantStatesRequest &msg) = 0; | ||||||
|  |   virtual void subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) = 0; | ||||||
|   virtual GetTimeResponse get_time(const GetTimeRequest &msg) = 0; |   virtual GetTimeResponse get_time(const GetTimeRequest &msg) = 0; | ||||||
|   virtual void execute_service(const ExecuteServiceRequest &msg) = 0; |   virtual void execute_service(const ExecuteServiceRequest &msg) = 0; | ||||||
| #ifdef USE_COVER | #ifdef USE_COVER | ||||||
| @@ -216,6 +222,7 @@ class APIServerConnection : public APIServerConnectionBase { | |||||||
|   void on_subscribe_logs_request(const SubscribeLogsRequest &msg) override; |   void on_subscribe_logs_request(const SubscribeLogsRequest &msg) override; | ||||||
|   void on_subscribe_homeassistant_services_request(const SubscribeHomeassistantServicesRequest &msg) override; |   void on_subscribe_homeassistant_services_request(const SubscribeHomeassistantServicesRequest &msg) override; | ||||||
|   void on_subscribe_home_assistant_states_request(const SubscribeHomeAssistantStatesRequest &msg) override; |   void on_subscribe_home_assistant_states_request(const SubscribeHomeAssistantStatesRequest &msg) override; | ||||||
|  |   void on_subscribe_bluetooth_le_advertisements_request(const SubscribeBluetoothLEAdvertisementsRequest &msg) override; | ||||||
|   void on_get_time_request(const GetTimeRequest &msg) override; |   void on_get_time_request(const GetTimeRequest &msg) override; | ||||||
|   void on_execute_service_request(const ExecuteServiceRequest &msg) override; |   void on_execute_service_request(const ExecuteServiceRequest &msg) override; | ||||||
| #ifdef USE_COVER | #ifdef USE_COVER | ||||||
|   | |||||||
| @@ -291,6 +291,13 @@ void APIServer::send_homeassistant_service_call(const HomeassistantServiceRespon | |||||||
|     client->send_homeassistant_service_call(call); |     client->send_homeassistant_service_call(call); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  | #ifdef USE_BLUETOOTH_PROXY | ||||||
|  | void APIServer::send_bluetooth_le_advertisement(const BluetoothLEAdvertisementResponse &call) { | ||||||
|  |   for (auto &client : this->clients_) { | ||||||
|  |     client->send_bluetooth_le_advertisement(call); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | #endif | ||||||
| APIServer::APIServer() { global_api_server = this; } | APIServer::APIServer() { global_api_server = this; } | ||||||
| void APIServer::subscribe_home_assistant_state(std::string entity_id, optional<std::string> attribute, | void APIServer::subscribe_home_assistant_state(std::string entity_id, optional<std::string> attribute, | ||||||
|                                                std::function<void(std::string)> f) { |                                                std::function<void(std::string)> f) { | ||||||
|   | |||||||
| @@ -73,6 +73,9 @@ class APIServer : public Component, public Controller { | |||||||
|   void on_media_player_update(media_player::MediaPlayer *obj) override; |   void on_media_player_update(media_player::MediaPlayer *obj) override; | ||||||
| #endif | #endif | ||||||
|   void send_homeassistant_service_call(const HomeassistantServiceResponse &call); |   void send_homeassistant_service_call(const HomeassistantServiceResponse &call); | ||||||
|  | #ifdef USE_BLUETOOTH_PROXY | ||||||
|  |   void send_bluetooth_le_advertisement(const BluetoothLEAdvertisementResponse &call); | ||||||
|  | #endif | ||||||
|   void register_user_service(UserServiceDescriptor *descriptor) { this->user_services_.push_back(descriptor); } |   void register_user_service(UserServiceDescriptor *descriptor) { this->user_services_.push_back(descriptor); } | ||||||
| #ifdef USE_HOMEASSISTANT_TIME | #ifdef USE_HOMEASSISTANT_TIME | ||||||
|   void request_time(); |   void request_time(); | ||||||
|   | |||||||
| @@ -70,7 +70,7 @@ class ProtoVarInt { | |||||||
|     } |     } | ||||||
|   } |   } | ||||||
|   void encode(std::vector<uint8_t> &out) { |   void encode(std::vector<uint8_t> &out) { | ||||||
|     uint32_t val = this->value_; |     uint64_t val = this->value_; | ||||||
|     if (val <= 0x7F) { |     if (val <= 0x7F) { | ||||||
|       out.push_back(val); |       out.push_back(val); | ||||||
|       return; |       return; | ||||||
|   | |||||||
| @@ -89,8 +89,10 @@ enum BedjetCommand : uint8_t { | |||||||
|         "85%", "90%", "95%", "100%" \ |         "85%", "90%", "95%", "100%" \ | ||||||
|   } |   } | ||||||
|  |  | ||||||
| static const char *const BEDJET_FAN_STEP_NAMES[20] = BEDJET_FAN_STEP_NAMES_; | static const uint8_t BEDJET_FAN_SPEED_COUNT = 20; | ||||||
| static const std::string BEDJET_FAN_STEP_NAME_STRINGS[20] = BEDJET_FAN_STEP_NAMES_; |  | ||||||
|  | static const char *const BEDJET_FAN_STEP_NAMES[BEDJET_FAN_SPEED_COUNT] = BEDJET_FAN_STEP_NAMES_; | ||||||
|  | static const std::string BEDJET_FAN_STEP_NAME_STRINGS[BEDJET_FAN_SPEED_COUNT] = BEDJET_FAN_STEP_NAMES_; | ||||||
| static const std::set<std::string> BEDJET_FAN_STEP_NAMES_SET BEDJET_FAN_STEP_NAMES_; | static const std::set<std::string> BEDJET_FAN_STEP_NAMES_SET BEDJET_FAN_STEP_NAMES_; | ||||||
|  |  | ||||||
| }  // namespace bedjet | }  // namespace bedjet | ||||||
|   | |||||||
| @@ -9,19 +9,17 @@ from esphome.const import ( | |||||||
|     CONF_RECEIVE_TIMEOUT, |     CONF_RECEIVE_TIMEOUT, | ||||||
|     CONF_TIME_ID, |     CONF_TIME_ID, | ||||||
| ) | ) | ||||||
| from . import ( | from .. import ( | ||||||
|     BEDJET_CLIENT_SCHEMA, |     BEDJET_CLIENT_SCHEMA, | ||||||
|  |     bedjet_ns, | ||||||
|     register_bedjet_child, |     register_bedjet_child, | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| _LOGGER = logging.getLogger(__name__) | _LOGGER = logging.getLogger(__name__) | ||||||
| CODEOWNERS = ["@jhansche"] | CODEOWNERS = ["@jhansche"] | ||||||
| DEPENDENCIES = ["ble_client"] | DEPENDENCIES = ["bedjet"] | ||||||
| 
 | 
 | ||||||
| bedjet_ns = cg.esphome_ns.namespace("bedjet") | BedJetClimate = bedjet_ns.class_("BedJetClimate", climate.Climate, cg.PollingComponent) | ||||||
| BedJetClimate = bedjet_ns.class_( |  | ||||||
|     "BedJetClimate", climate.Climate, ble_client.BLEClientNode, cg.PollingComponent |  | ||||||
| ) |  | ||||||
| BedjetHeatMode = bedjet_ns.enum("BedjetHeatMode") | BedjetHeatMode = bedjet_ns.enum("BedjetHeatMode") | ||||||
| BEDJET_HEAT_MODES = { | BEDJET_HEAT_MODES = { | ||||||
|     "heat": BedjetHeatMode.HEAT_MODE_HEAT, |     "heat": BedjetHeatMode.HEAT_MODE_HEAT, | ||||||
| @@ -15,13 +15,13 @@ float bedjet_temp_to_c(const uint8_t temp) { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static const std::string *bedjet_fan_step_to_fan_mode(const uint8_t fan_step) { | static const std::string *bedjet_fan_step_to_fan_mode(const uint8_t fan_step) { | ||||||
|   if (fan_step <= 19) |   if (fan_step < BEDJET_FAN_SPEED_COUNT) | ||||||
|     return &BEDJET_FAN_STEP_NAME_STRINGS[fan_step]; |     return &BEDJET_FAN_STEP_NAME_STRINGS[fan_step]; | ||||||
|   return nullptr; |   return nullptr; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static uint8_t bedjet_fan_speed_to_step(const std::string &fan_step_percent) { | static uint8_t bedjet_fan_speed_to_step(const std::string &fan_step_percent) { | ||||||
|   for (int i = 0; i < sizeof(BEDJET_FAN_STEP_NAME_STRINGS); i++) { |   for (int i = 0; i < BEDJET_FAN_SPEED_COUNT; i++) { | ||||||
|     if (fan_step_percent == BEDJET_FAN_STEP_NAME_STRINGS[i]) { |     if (fan_step_percent == BEDJET_FAN_STEP_NAME_STRINGS[i]) { | ||||||
|       return i; |       return i; | ||||||
|     } |     } | ||||||
| @@ -1,12 +1,12 @@ | |||||||
| #pragma once | #pragma once | ||||||
| 
 | 
 | ||||||
| #include "esphome/components/climate/climate.h" |  | ||||||
| #include "esphome/core/component.h" | #include "esphome/core/component.h" | ||||||
| #include "esphome/core/defines.h" | #include "esphome/core/defines.h" | ||||||
| #include "esphome/core/hal.h" | #include "esphome/core/hal.h" | ||||||
| #include "bedjet_child.h" | #include "esphome/components/bedjet/bedjet_child.h" | ||||||
| #include "bedjet_codec.h" | #include "esphome/components/bedjet/bedjet_codec.h" | ||||||
| #include "bedjet_hub.h" | #include "esphome/components/bedjet/bedjet_hub.h" | ||||||
|  | #include "esphome/components/climate/climate.h" | ||||||
| 
 | 
 | ||||||
| #ifdef USE_ESP32 | #ifdef USE_ESP32 | ||||||
| 
 | 
 | ||||||
							
								
								
									
										36
									
								
								esphome/components/bedjet/fan/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								esphome/components/bedjet/fan/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,36 @@ | |||||||
|  | import logging | ||||||
|  |  | ||||||
|  | import esphome.codegen as cg | ||||||
|  | import esphome.config_validation as cv | ||||||
|  | from esphome.components import fan | ||||||
|  | from esphome.const import ( | ||||||
|  |     CONF_ID, | ||||||
|  | ) | ||||||
|  | from .. import ( | ||||||
|  |     BEDJET_CLIENT_SCHEMA, | ||||||
|  |     bedjet_ns, | ||||||
|  |     register_bedjet_child, | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | _LOGGER = logging.getLogger(__name__) | ||||||
|  | CODEOWNERS = ["@jhansche"] | ||||||
|  | DEPENDENCIES = ["bedjet"] | ||||||
|  |  | ||||||
|  | BedJetFan = bedjet_ns.class_("BedJetFan", fan.Fan, cg.PollingComponent) | ||||||
|  |  | ||||||
|  | CONFIG_SCHEMA = ( | ||||||
|  |     fan.FAN_SCHEMA.extend( | ||||||
|  |         { | ||||||
|  |             cv.GenerateID(): cv.declare_id(BedJetFan), | ||||||
|  |         } | ||||||
|  |     ) | ||||||
|  |     .extend(cv.polling_component_schema("60s")) | ||||||
|  |     .extend(BEDJET_CLIENT_SCHEMA) | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | async def to_code(config): | ||||||
|  |     var = cg.new_Pvariable(config[CONF_ID]) | ||||||
|  |     await cg.register_component(var, config) | ||||||
|  |     await fan.register_fan(var, config) | ||||||
|  |     await register_bedjet_child(var, config) | ||||||
							
								
								
									
										108
									
								
								esphome/components/bedjet/fan/bedjet_fan.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										108
									
								
								esphome/components/bedjet/fan/bedjet_fan.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,108 @@ | |||||||
|  | #include "bedjet_fan.h" | ||||||
|  | #include "esphome/core/log.h" | ||||||
|  |  | ||||||
|  | #ifdef USE_ESP32 | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace bedjet { | ||||||
|  |  | ||||||
|  | using namespace esphome::fan; | ||||||
|  |  | ||||||
|  | void BedJetFan::dump_config() { LOG_FAN("", "BedJet Fan", this); } | ||||||
|  | std::string BedJetFan::describe() { return "BedJet Fan"; } | ||||||
|  |  | ||||||
|  | void BedJetFan::control(const fan::FanCall &call) { | ||||||
|  |   ESP_LOGD(TAG, "Received BedJetFan::control"); | ||||||
|  |   if (!this->parent_->is_connected()) { | ||||||
|  |     ESP_LOGW(TAG, "Not connected, cannot handle control call yet."); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   bool did_change = false; | ||||||
|  |  | ||||||
|  |   if (call.get_state().has_value() && this->state != *call.get_state()) { | ||||||
|  |     // Turning off is easy: | ||||||
|  |     if (this->state && this->parent_->button_off()) { | ||||||
|  |       this->state = false; | ||||||
|  |       this->publish_state(); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // Turning on, we have to choose a specific mode; for now, use "COOL" mode | ||||||
|  |     // In the future we could configure the mode to use for fan.turn_on. | ||||||
|  |     if (this->parent_->button_cool()) { | ||||||
|  |       this->state = true; | ||||||
|  |       did_change = true; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // ignore speed changes if not on or turning on | ||||||
|  |   if (this->state && call.get_speed().has_value()) { | ||||||
|  |     this->speed = *call.get_speed(); | ||||||
|  |     this->parent_->set_fan_index(this->speed); | ||||||
|  |     did_change = true; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (did_change) { | ||||||
|  |     this->publish_state(); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void BedJetFan::on_status(const BedjetStatusPacket *data) { | ||||||
|  |   ESP_LOGVV(TAG, "[%s] Handling on_status with data=%p", this->get_name().c_str(), (void *) data); | ||||||
|  |   bool did_change = false; | ||||||
|  |   bool new_state = data->mode != MODE_STANDBY && data->mode != MODE_WAIT; | ||||||
|  |  | ||||||
|  |   if (new_state != this->state) { | ||||||
|  |     this->state = new_state; | ||||||
|  |     did_change = true; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (data->fan_step != this->speed) { | ||||||
|  |     this->speed = data->fan_step; | ||||||
|  |     did_change = true; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (did_change) { | ||||||
|  |     this->publish_state(); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** Attempts to update the fan device from the last received BedjetStatusPacket. | ||||||
|  |  * | ||||||
|  |  * This will be called from #on_status() when the parent dispatches new status packets, | ||||||
|  |  * and from #update() when the polling interval is triggered. | ||||||
|  |  * | ||||||
|  |  * @return `true` if the status has been applied; `false` if there is nothing to apply. | ||||||
|  |  */ | ||||||
|  | bool BedJetFan::update_status_() { | ||||||
|  |   if (!this->parent_->is_connected()) | ||||||
|  |     return false; | ||||||
|  |   if (!this->parent_->has_status()) | ||||||
|  |     return false; | ||||||
|  |  | ||||||
|  |   auto *status = this->parent_->get_status_packet(); | ||||||
|  |  | ||||||
|  |   if (status == nullptr) | ||||||
|  |     return false; | ||||||
|  |  | ||||||
|  |   this->on_status(status); | ||||||
|  |   return true; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void BedJetFan::update() { | ||||||
|  |   ESP_LOGD(TAG, "[%s] update()", this->get_name().c_str()); | ||||||
|  |   // TODO: if the hub component is already polling, do we also need to include polling? | ||||||
|  |   //  We're already going to get on_status() at the hub's polling interval. | ||||||
|  |   auto result = this->update_status_(); | ||||||
|  |   ESP_LOGD(TAG, "[%s] update_status result=%s", this->get_name().c_str(), result ? "true" : "false"); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** Resets states to defaults. */ | ||||||
|  | void BedJetFan::reset_state_() { | ||||||
|  |   this->state = false; | ||||||
|  |   this->publish_state(); | ||||||
|  | } | ||||||
|  | }  // namespace bedjet | ||||||
|  | }  // namespace esphome | ||||||
|  |  | ||||||
|  | #endif | ||||||
							
								
								
									
										40
									
								
								esphome/components/bedjet/fan/bedjet_fan.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								esphome/components/bedjet/fan/bedjet_fan.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,40 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include "esphome/core/component.h" | ||||||
|  | #include "esphome/core/defines.h" | ||||||
|  | #include "esphome/core/hal.h" | ||||||
|  | #include "esphome/components/bedjet/bedjet_child.h" | ||||||
|  | #include "esphome/components/bedjet/bedjet_codec.h" | ||||||
|  | #include "esphome/components/bedjet/bedjet_hub.h" | ||||||
|  | #include "esphome/components/fan/fan.h" | ||||||
|  |  | ||||||
|  | #ifdef USE_ESP32 | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace bedjet { | ||||||
|  |  | ||||||
|  | class BedJetFan : public fan::Fan, public BedJetClient, public PollingComponent { | ||||||
|  |  public: | ||||||
|  |   void update() override; | ||||||
|  |   void dump_config() override; | ||||||
|  |   float get_setup_priority() const override { return setup_priority::AFTER_WIFI; } | ||||||
|  |  | ||||||
|  |   /* BedJetClient status update */ | ||||||
|  |   void on_status(const BedjetStatusPacket *data) override; | ||||||
|  |   void on_bedjet_state(bool is_ready) override{}; | ||||||
|  |   std::string describe() override; | ||||||
|  |  | ||||||
|  |   fan::FanTraits get_traits() override { return fan::FanTraits(false, true, false, BEDJET_FAN_SPEED_COUNT); } | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   void control(const fan::FanCall &call) override; | ||||||
|  |  | ||||||
|  |  private: | ||||||
|  |   void reset_state_(); | ||||||
|  |   bool update_status_(); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | }  // namespace bedjet | ||||||
|  | }  // namespace esphome | ||||||
|  |  | ||||||
|  | #endif | ||||||
| @@ -13,6 +13,9 @@ void BinarySensorMap::loop() { | |||||||
|     case BINARY_SENSOR_MAP_TYPE_GROUP: |     case BINARY_SENSOR_MAP_TYPE_GROUP: | ||||||
|       this->process_group_(); |       this->process_group_(); | ||||||
|       break; |       break; | ||||||
|  |     case BINARY_SENSOR_MAP_TYPE_SUM: | ||||||
|  |       this->process_sum_(); | ||||||
|  |       break; | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -46,6 +49,34 @@ void BinarySensorMap::process_group_() { | |||||||
|   this->last_mask_ = mask; |   this->last_mask_ = mask; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | void BinarySensorMap::process_sum_() { | ||||||
|  |   float total_current_value = 0.0; | ||||||
|  |   uint64_t mask = 0x00; | ||||||
|  |   // check all binary_sensors for its state. when active add its value to total_current_value. | ||||||
|  |   // create a bitmask for the binary_sensor status on all channels | ||||||
|  |   for (size_t i = 0; i < this->channels_.size(); i++) { | ||||||
|  |     auto bs = this->channels_[i]; | ||||||
|  |     if (bs.binary_sensor->state) { | ||||||
|  |       total_current_value += bs.sensor_value; | ||||||
|  |       mask |= 1 << i; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   // check if the sensor map was touched | ||||||
|  |   if (mask != 0ULL) { | ||||||
|  |     // did the bit_mask change or is it a new sensor touch | ||||||
|  |     if (this->last_mask_ != mask) { | ||||||
|  |       float publish_value = total_current_value; | ||||||
|  |       ESP_LOGD(TAG, "'%s' - Publishing %.2f", this->name_.c_str(), publish_value); | ||||||
|  |       this->publish_state(publish_value); | ||||||
|  |     } | ||||||
|  |   } else if (this->last_mask_ != 0ULL) { | ||||||
|  |     // is this a new sensor release | ||||||
|  |     ESP_LOGD(TAG, "'%s' - No binary sensor active, publishing 0", this->name_.c_str()); | ||||||
|  |     this->publish_state(0.0); | ||||||
|  |   } | ||||||
|  |   this->last_mask_ = mask; | ||||||
|  | } | ||||||
|  |  | ||||||
| void BinarySensorMap::add_channel(binary_sensor::BinarySensor *sensor, float value) { | void BinarySensorMap::add_channel(binary_sensor::BinarySensor *sensor, float value) { | ||||||
|   BinarySensorMapChannel sensor_channel{ |   BinarySensorMapChannel sensor_channel{ | ||||||
|       .binary_sensor = sensor, |       .binary_sensor = sensor, | ||||||
|   | |||||||
| @@ -9,6 +9,7 @@ namespace binary_sensor_map { | |||||||
|  |  | ||||||
| enum BinarySensorMapType { | enum BinarySensorMapType { | ||||||
|   BINARY_SENSOR_MAP_TYPE_GROUP, |   BINARY_SENSOR_MAP_TYPE_GROUP, | ||||||
|  |   BINARY_SENSOR_MAP_TYPE_SUM, | ||||||
| }; | }; | ||||||
|  |  | ||||||
| struct BinarySensorMapChannel { | struct BinarySensorMapChannel { | ||||||
| @@ -50,8 +51,10 @@ class BinarySensorMap : public sensor::Sensor, public Component { | |||||||
|   /** |   /** | ||||||
|    * methods to process the types of binary_sensor_maps |    * methods to process the types of binary_sensor_maps | ||||||
|    * GROUP: process_group_() just map to a value |    * GROUP: process_group_() just map to a value | ||||||
|  |    * ADD: process_add_() adds all the values | ||||||
|    * */ |    * */ | ||||||
|   void process_group_(); |   void process_group_(); | ||||||
|  |   void process_sum_(); | ||||||
| }; | }; | ||||||
|  |  | ||||||
| }  // namespace binary_sensor_map | }  // namespace binary_sensor_map | ||||||
|   | |||||||
| @@ -9,6 +9,7 @@ from esphome.const import ( | |||||||
|     ICON_CHECK_CIRCLE_OUTLINE, |     ICON_CHECK_CIRCLE_OUTLINE, | ||||||
|     CONF_BINARY_SENSOR, |     CONF_BINARY_SENSOR, | ||||||
|     CONF_GROUP, |     CONF_GROUP, | ||||||
|  |     CONF_SUM, | ||||||
| ) | ) | ||||||
|  |  | ||||||
| DEPENDENCIES = ["binary_sensor"] | DEPENDENCIES = ["binary_sensor"] | ||||||
| @@ -21,6 +22,7 @@ SensorMapType = binary_sensor_map_ns.enum("SensorMapType") | |||||||
|  |  | ||||||
| SENSOR_MAP_TYPES = { | SENSOR_MAP_TYPES = { | ||||||
|     CONF_GROUP: SensorMapType.BINARY_SENSOR_MAP_TYPE_GROUP, |     CONF_GROUP: SensorMapType.BINARY_SENSOR_MAP_TYPE_GROUP, | ||||||
|  |     CONF_SUM: SensorMapType.BINARY_SENSOR_MAP_TYPE_SUM, | ||||||
| } | } | ||||||
|  |  | ||||||
| entry = { | entry = { | ||||||
| @@ -41,6 +43,17 @@ CONFIG_SCHEMA = cv.typed_schema( | |||||||
|                 ), |                 ), | ||||||
|             } |             } | ||||||
|         ), |         ), | ||||||
|  |         CONF_SUM: sensor.sensor_schema( | ||||||
|  |             BinarySensorMap, | ||||||
|  |             icon=ICON_CHECK_CIRCLE_OUTLINE, | ||||||
|  |             accuracy_decimals=0, | ||||||
|  |         ).extend( | ||||||
|  |             { | ||||||
|  |                 cv.Required(CONF_CHANNELS): cv.All( | ||||||
|  |                     cv.ensure_list(entry), cv.Length(min=1) | ||||||
|  |                 ), | ||||||
|  |             } | ||||||
|  |         ), | ||||||
|     }, |     }, | ||||||
|     lower=True, |     lower=True, | ||||||
| ) | ) | ||||||
|   | |||||||
							
								
								
									
										1
									
								
								esphome/components/bl0942/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								esphome/components/bl0942/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | |||||||
|  | CODEOWNERS = ["@dbuezas"] | ||||||
							
								
								
									
										121
									
								
								esphome/components/bl0942/bl0942.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										121
									
								
								esphome/components/bl0942/bl0942.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,121 @@ | |||||||
|  | #include "bl0942.h" | ||||||
|  | #include "esphome/core/log.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace bl0942 { | ||||||
|  |  | ||||||
|  | static const char *const TAG = "bl0942"; | ||||||
|  |  | ||||||
|  | static const uint8_t BL0942_READ_COMMAND = 0x58; | ||||||
|  | static const uint8_t BL0942_FULL_PACKET = 0xAA; | ||||||
|  | static const uint8_t BL0942_PACKET_HEADER = 0x55; | ||||||
|  |  | ||||||
|  | static const uint8_t BL0942_WRITE_COMMAND = 0xA8; | ||||||
|  | static const uint8_t BL0942_REG_I_FAST_RMS_CTRL = 0x10; | ||||||
|  | static const uint8_t BL0942_REG_MODE = 0x18; | ||||||
|  | static const uint8_t BL0942_REG_SOFT_RESET = 0x19; | ||||||
|  | static const uint8_t BL0942_REG_USR_WRPROT = 0x1A; | ||||||
|  | static const uint8_t BL0942_REG_TPS_CTRL = 0x1B; | ||||||
|  |  | ||||||
|  | // TODO: Confirm insialisation works as intended | ||||||
|  | const uint8_t BL0942_INIT[5][6] = { | ||||||
|  |     // Reset to default | ||||||
|  |     {BL0942_WRITE_COMMAND, BL0942_REG_SOFT_RESET, 0x5A, 0x5A, 0x5A, 0x38}, | ||||||
|  |     // Enable User Operation Write | ||||||
|  |     {BL0942_WRITE_COMMAND, BL0942_REG_USR_WRPROT, 0x55, 0x00, 0x00, 0xF0}, | ||||||
|  |     // 0x0100 = CF_UNABLE energy pulse, AC_FREQ_SEL 50Hz, RMS_UPDATE_SEL 800mS | ||||||
|  |     {BL0942_WRITE_COMMAND, BL0942_REG_MODE, 0x00, 0x10, 0x00, 0x37}, | ||||||
|  |     // 0x47FF = Over-current and leakage alarm on, Automatic temperature measurement, Interval 100mS | ||||||
|  |     {BL0942_WRITE_COMMAND, BL0942_REG_TPS_CTRL, 0xFF, 0x47, 0x00, 0xFE}, | ||||||
|  |     // 0x181C = Half cycle, Fast RMS threshold 6172 | ||||||
|  |     {BL0942_WRITE_COMMAND, BL0942_REG_I_FAST_RMS_CTRL, 0x1C, 0x18, 0x00, 0x1B}}; | ||||||
|  |  | ||||||
|  | void BL0942::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 BL0942::validate_checksum(DataPacket *data) { | ||||||
|  |   uint8_t checksum = BL0942_READ_COMMAND; | ||||||
|  |   // Whole package but checksum | ||||||
|  |   uint8_t *raw = (uint8_t *) data; | ||||||
|  |   for (uint32_t i = 0; i < sizeof(*data) - 1; i++) { | ||||||
|  |     checksum += raw[i]; | ||||||
|  |   } | ||||||
|  |   checksum ^= 0xFF; | ||||||
|  |   if (checksum != data->checksum) { | ||||||
|  |     ESP_LOGW(TAG, "BL0942 invalid checksum! 0x%02X != 0x%02X", checksum, data->checksum); | ||||||
|  |   } | ||||||
|  |   return checksum == data->checksum; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void BL0942::update() { | ||||||
|  |   this->flush(); | ||||||
|  |   this->write_byte(BL0942_READ_COMMAND); | ||||||
|  |   this->write_byte(BL0942_FULL_PACKET); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void BL0942::setup() { | ||||||
|  |   for (auto *i : BL0942_INIT) { | ||||||
|  |     this->write_array(i, 6); | ||||||
|  |     delay(1); | ||||||
|  |   } | ||||||
|  |   this->flush(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void BL0942::received_package_(DataPacket *data) { | ||||||
|  |   // Bad header | ||||||
|  |   if (data->frame_header != BL0942_PACKET_HEADER) { | ||||||
|  |     ESP_LOGI(TAG, "Invalid data. Header mismatch: %d", data->frame_header); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   float v_rms = (uint24_t) data->v_rms / voltage_reference_; | ||||||
|  |   float i_rms = (uint24_t) data->i_rms / current_reference_; | ||||||
|  |   float watt = (int24_t) data->watt / power_reference_; | ||||||
|  |   uint32_t cf_cnt = (uint24_t) data->cf_cnt; | ||||||
|  |   float total_energy_consumption = cf_cnt / energy_reference_; | ||||||
|  |   float frequency = 1000000.0f / data->frequency; | ||||||
|  |  | ||||||
|  |   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); | ||||||
|  |   } | ||||||
|  |   if (frequency_sensor_ != nullptr) { | ||||||
|  |     frequency_sensor_->publish_state(frequency); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   ESP_LOGV(TAG, "BL0942: U %fV, I %fA, P %fW, Cnt %d, ∫P %fkWh, frequency %f°Hz, status 0x%08X", v_rms, i_rms, watt, | ||||||
|  |            cf_cnt, total_energy_consumption, frequency, data->status); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void BL0942::dump_config() {  // NOLINT(readability-function-cognitive-complexity) | ||||||
|  |   ESP_LOGCONFIG(TAG, "BL0942:"); | ||||||
|  |   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("", "frequency", this->frequency_sensor_); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | }  // namespace bl0942 | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										68
									
								
								esphome/components/bl0942/bl0942.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								esphome/components/bl0942/bl0942.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,68 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include "esphome/core/component.h" | ||||||
|  | #include "esphome/core/datatypes.h" | ||||||
|  | #include "esphome/components/uart/uart.h" | ||||||
|  | #include "esphome/components/sensor/sensor.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace bl0942 { | ||||||
|  |  | ||||||
|  | static const float BL0942_PREF = 596;              // taken from tasmota | ||||||
|  | static const float BL0942_UREF = 15873.35944299;   // should be 73989/1.218 | ||||||
|  | static const float BL0942_IREF = 251213.46469622;  // 305978/1.218 | ||||||
|  | static const float BL0942_EREF = 3304.61127328;    // Measured | ||||||
|  |  | ||||||
|  | struct DataPacket { | ||||||
|  |   uint8_t frame_header; | ||||||
|  |   uint24_le_t i_rms; | ||||||
|  |   uint24_le_t v_rms; | ||||||
|  |   uint24_le_t i_fast_rms; | ||||||
|  |   int24_le_t watt; | ||||||
|  |   uint24_le_t cf_cnt; | ||||||
|  |   uint16_le_t frequency; | ||||||
|  |   uint8_t reserved1; | ||||||
|  |   uint8_t status; | ||||||
|  |   uint8_t reserved2; | ||||||
|  |   uint8_t reserved3; | ||||||
|  |   uint8_t checksum; | ||||||
|  | } __attribute__((packed)); | ||||||
|  |  | ||||||
|  | class BL0942 : 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_frequency_sensor(sensor::Sensor *frequency_sensor) { frequency_sensor_ = frequency_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 *frequency_sensor_; | ||||||
|  |  | ||||||
|  |   // Divide by this to turn into Watt | ||||||
|  |   float power_reference_ = BL0942_PREF; | ||||||
|  |   // Divide by this to turn into Volt | ||||||
|  |   float voltage_reference_ = BL0942_UREF; | ||||||
|  |   // Divide by this to turn into Ampere | ||||||
|  |   float current_reference_ = BL0942_IREF; | ||||||
|  |   // Divide by this to turn into kWh | ||||||
|  |   float energy_reference_ = BL0942_EREF; | ||||||
|  |  | ||||||
|  |   static bool validate_checksum(DataPacket *data); | ||||||
|  |  | ||||||
|  |   void received_package_(DataPacket *data); | ||||||
|  | }; | ||||||
|  | }  // namespace bl0942 | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										93
									
								
								esphome/components/bl0942/sensor.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										93
									
								
								esphome/components/bl0942/sensor.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,93 @@ | |||||||
|  | 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, | ||||||
|  |     CONF_FREQUENCY, | ||||||
|  |     DEVICE_CLASS_CURRENT, | ||||||
|  |     DEVICE_CLASS_ENERGY, | ||||||
|  |     DEVICE_CLASS_POWER, | ||||||
|  |     DEVICE_CLASS_VOLTAGE, | ||||||
|  |     DEVICE_CLASS_FREQUENCY, | ||||||
|  |     STATE_CLASS_MEASUREMENT, | ||||||
|  |     UNIT_AMPERE, | ||||||
|  |     UNIT_KILOWATT_HOURS, | ||||||
|  |     UNIT_VOLT, | ||||||
|  |     UNIT_WATT, | ||||||
|  |     UNIT_HERTZ, | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | DEPENDENCIES = ["uart"] | ||||||
|  |  | ||||||
|  | bl0942_ns = cg.esphome_ns.namespace("bl0942") | ||||||
|  | BL0942 = bl0942_ns.class_("BL0942", cg.PollingComponent, uart.UARTDevice) | ||||||
|  |  | ||||||
|  | CONFIG_SCHEMA = ( | ||||||
|  |     cv.Schema( | ||||||
|  |         { | ||||||
|  |             cv.GenerateID(): cv.declare_id(BL0942), | ||||||
|  |             cv.Optional(CONF_VOLTAGE): sensor.sensor_schema( | ||||||
|  |                 unit_of_measurement=UNIT_VOLT, | ||||||
|  |                 accuracy_decimals=1, | ||||||
|  |                 device_class=DEVICE_CLASS_VOLTAGE, | ||||||
|  |                 state_class=STATE_CLASS_MEASUREMENT, | ||||||
|  |             ), | ||||||
|  |             cv.Optional(CONF_CURRENT): sensor.sensor_schema( | ||||||
|  |                 unit_of_measurement=UNIT_AMPERE, | ||||||
|  |                 accuracy_decimals=2, | ||||||
|  |                 device_class=DEVICE_CLASS_CURRENT, | ||||||
|  |                 state_class=STATE_CLASS_MEASUREMENT, | ||||||
|  |             ), | ||||||
|  |             cv.Optional(CONF_POWER): sensor.sensor_schema( | ||||||
|  |                 unit_of_measurement=UNIT_WATT, | ||||||
|  |                 accuracy_decimals=0, | ||||||
|  |                 device_class=DEVICE_CLASS_POWER, | ||||||
|  |                 state_class=STATE_CLASS_MEASUREMENT, | ||||||
|  |             ), | ||||||
|  |             cv.Optional(CONF_ENERGY): sensor.sensor_schema( | ||||||
|  |                 unit_of_measurement=UNIT_KILOWATT_HOURS, | ||||||
|  |                 accuracy_decimals=0, | ||||||
|  |                 device_class=DEVICE_CLASS_ENERGY, | ||||||
|  |             ), | ||||||
|  |             cv.Optional(CONF_FREQUENCY): sensor.sensor_schema( | ||||||
|  |                 unit_of_measurement=UNIT_HERTZ, | ||||||
|  |                 accuracy_decimals=0, | ||||||
|  |                 device_class=DEVICE_CLASS_FREQUENCY, | ||||||
|  |                 state_class=STATE_CLASS_MEASUREMENT, | ||||||
|  |             ), | ||||||
|  |         } | ||||||
|  |     ) | ||||||
|  |     .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_FREQUENCY in config: | ||||||
|  |         conf = config[CONF_FREQUENCY] | ||||||
|  |         sens = await sensor.new_sensor(conf) | ||||||
|  |         cg.add(var.set_frequency_sensor(sens)) | ||||||
| @@ -1,7 +1,7 @@ | |||||||
| import esphome.codegen as cg | import esphome.codegen as cg | ||||||
| import esphome.config_validation as cv | import esphome.config_validation as cv | ||||||
| from esphome.components import switch, ble_client | from esphome.components import switch, ble_client | ||||||
| from esphome.const import CONF_ICON, CONF_ID, CONF_INVERTED, ICON_BLUETOOTH | from esphome.const import ICON_BLUETOOTH | ||||||
| from .. import ble_client_ns | from .. import ble_client_ns | ||||||
|  |  | ||||||
| BLEClientSwitch = ble_client_ns.class_( | BLEClientSwitch = ble_client_ns.class_( | ||||||
| @@ -9,22 +9,13 @@ BLEClientSwitch = ble_client_ns.class_( | |||||||
| ) | ) | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = ( | CONFIG_SCHEMA = ( | ||||||
|     switch.SWITCH_SCHEMA.extend( |     switch.switch_schema(BLEClientSwitch, icon=ICON_BLUETOOTH, block_inverted=True) | ||||||
|         { |  | ||||||
|             cv.GenerateID(): cv.declare_id(BLEClientSwitch), |  | ||||||
|             cv.Optional(CONF_INVERTED): cv.invalid( |  | ||||||
|                 "BLE client switches do not support inverted mode!" |  | ||||||
|             ), |  | ||||||
|             cv.Optional(CONF_ICON, default=ICON_BLUETOOTH): switch.icon, |  | ||||||
|         } |  | ||||||
|     ) |  | ||||||
|     .extend(ble_client.BLE_CLIENT_SCHEMA) |     .extend(ble_client.BLE_CLIENT_SCHEMA) | ||||||
|     .extend(cv.COMPONENT_SCHEMA) |     .extend(cv.COMPONENT_SCHEMA) | ||||||
| ) | ) | ||||||
|  |  | ||||||
|  |  | ||||||
| async def to_code(config): | async def to_code(config): | ||||||
|     var = cg.new_Pvariable(config[CONF_ID]) |     var = await switch.new_switch(config) | ||||||
|     await cg.register_component(var, config) |     await cg.register_component(var, config) | ||||||
|     await switch.register_switch(var, config) |  | ||||||
|     await ble_client.register_ble_node(var, config) |     await ble_client.register_ble_node(var, config) | ||||||
|   | |||||||
| @@ -12,41 +12,78 @@ namespace ble_rssi { | |||||||
| class BLERSSISensor : public sensor::Sensor, public esp32_ble_tracker::ESPBTDeviceListener, public Component { | class BLERSSISensor : public sensor::Sensor, public esp32_ble_tracker::ESPBTDeviceListener, public Component { | ||||||
|  public: |  public: | ||||||
|   void set_address(uint64_t address) { |   void set_address(uint64_t address) { | ||||||
|     this->by_address_ = true; |     this->match_by_ = MATCH_BY_MAC_ADDRESS; | ||||||
|     this->address_ = address; |     this->address_ = address; | ||||||
|   } |   } | ||||||
|   void set_service_uuid16(uint16_t uuid) { |   void set_service_uuid16(uint16_t uuid) { | ||||||
|     this->by_address_ = false; |     this->match_by_ = MATCH_BY_SERVICE_UUID; | ||||||
|     this->uuid_ = esp32_ble_tracker::ESPBTUUID::from_uint16(uuid); |     this->uuid_ = esp32_ble_tracker::ESPBTUUID::from_uint16(uuid); | ||||||
|   } |   } | ||||||
|   void set_service_uuid32(uint32_t uuid) { |   void set_service_uuid32(uint32_t uuid) { | ||||||
|     this->by_address_ = false; |     this->match_by_ = MATCH_BY_SERVICE_UUID; | ||||||
|     this->uuid_ = esp32_ble_tracker::ESPBTUUID::from_uint32(uuid); |     this->uuid_ = esp32_ble_tracker::ESPBTUUID::from_uint32(uuid); | ||||||
|   } |   } | ||||||
|   void set_service_uuid128(uint8_t *uuid) { |   void set_service_uuid128(uint8_t *uuid) { | ||||||
|     this->by_address_ = false; |     this->match_by_ = MATCH_BY_SERVICE_UUID; | ||||||
|     this->uuid_ = esp32_ble_tracker::ESPBTUUID::from_raw(uuid); |     this->uuid_ = esp32_ble_tracker::ESPBTUUID::from_raw(uuid); | ||||||
|   } |   } | ||||||
|  |   void set_ibeacon_uuid(uint8_t *uuid) { | ||||||
|  |     this->match_by_ = MATCH_BY_IBEACON_UUID; | ||||||
|  |     this->ibeacon_uuid_ = esp32_ble_tracker::ESPBTUUID::from_raw(uuid); | ||||||
|  |   } | ||||||
|  |   void set_ibeacon_major(uint16_t major) { | ||||||
|  |     this->check_ibeacon_major_ = true; | ||||||
|  |     this->ibeacon_major_ = major; | ||||||
|  |   } | ||||||
|  |   void set_ibeacon_minor(uint16_t minor) { | ||||||
|  |     this->check_ibeacon_minor_ = true; | ||||||
|  |     this->ibeacon_minor_ = minor; | ||||||
|  |   } | ||||||
|   void on_scan_end() override { |   void on_scan_end() override { | ||||||
|     if (!this->found_) |     if (!this->found_) | ||||||
|       this->publish_state(NAN); |       this->publish_state(NAN); | ||||||
|     this->found_ = false; |     this->found_ = false; | ||||||
|   } |   } | ||||||
|   bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override { |   bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override { | ||||||
|     if (this->by_address_) { |     switch (this->match_by_) { | ||||||
|       if (device.address_uint64() == this->address_) { |       case MATCH_BY_MAC_ADDRESS: | ||||||
|         this->publish_state(device.get_rssi()); |         if (device.address_uint64() == this->address_) { | ||||||
|         this->found_ = true; |  | ||||||
|         return true; |  | ||||||
|       } |  | ||||||
|     } else { |  | ||||||
|       for (auto uuid : device.get_service_uuids()) { |  | ||||||
|         if (this->uuid_ == uuid) { |  | ||||||
|           this->publish_state(device.get_rssi()); |           this->publish_state(device.get_rssi()); | ||||||
|           this->found_ = true; |           this->found_ = true; | ||||||
|           return true; |           return true; | ||||||
|         } |         } | ||||||
|       } |         break; | ||||||
|  |       case MATCH_BY_SERVICE_UUID: | ||||||
|  |         for (auto uuid : device.get_service_uuids()) { | ||||||
|  |           if (this->uuid_ == uuid) { | ||||||
|  |             this->publish_state(device.get_rssi()); | ||||||
|  |             this->found_ = true; | ||||||
|  |             return true; | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |         break; | ||||||
|  |       case MATCH_BY_IBEACON_UUID: | ||||||
|  |         if (!device.get_ibeacon().has_value()) { | ||||||
|  |           return false; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         auto ibeacon = device.get_ibeacon().value(); | ||||||
|  |  | ||||||
|  |         if (this->ibeacon_uuid_ != ibeacon.get_uuid()) { | ||||||
|  |           return false; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if (this->check_ibeacon_major_ && this->ibeacon_major_ != ibeacon.get_major()) { | ||||||
|  |           return false; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if (this->check_ibeacon_minor_ && this->ibeacon_minor_ != ibeacon.get_minor()) { | ||||||
|  |           return false; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         this->publish_state(device.get_rssi()); | ||||||
|  |         this->found_ = true; | ||||||
|  |         return true; | ||||||
|     } |     } | ||||||
|     return false; |     return false; | ||||||
|   } |   } | ||||||
| @@ -54,10 +91,20 @@ class BLERSSISensor : public sensor::Sensor, public esp32_ble_tracker::ESPBTDevi | |||||||
|   float get_setup_priority() const override { return setup_priority::DATA; } |   float get_setup_priority() const override { return setup_priority::DATA; } | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|  |   enum MatchType { MATCH_BY_MAC_ADDRESS, MATCH_BY_SERVICE_UUID, MATCH_BY_IBEACON_UUID }; | ||||||
|  |   MatchType match_by_; | ||||||
|  |  | ||||||
|   bool found_{false}; |   bool found_{false}; | ||||||
|   bool by_address_{false}; |  | ||||||
|   uint64_t address_; |   uint64_t address_; | ||||||
|  |  | ||||||
|   esp32_ble_tracker::ESPBTUUID uuid_; |   esp32_ble_tracker::ESPBTUUID uuid_; | ||||||
|  |  | ||||||
|  |   esp32_ble_tracker::ESPBTUUID ibeacon_uuid_; | ||||||
|  |   uint16_t ibeacon_major_; | ||||||
|  |   bool check_ibeacon_major_; | ||||||
|  |   uint16_t ibeacon_minor_; | ||||||
|  |   bool check_ibeacon_minor_; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| }  // namespace ble_rssi | }  // namespace ble_rssi | ||||||
|   | |||||||
| @@ -2,6 +2,9 @@ import esphome.codegen as cg | |||||||
| import esphome.config_validation as cv | import esphome.config_validation as cv | ||||||
| from esphome.components import sensor, esp32_ble_tracker | from esphome.components import sensor, esp32_ble_tracker | ||||||
| from esphome.const import ( | from esphome.const import ( | ||||||
|  |     CONF_IBEACON_MAJOR, | ||||||
|  |     CONF_IBEACON_MINOR, | ||||||
|  |     CONF_IBEACON_UUID, | ||||||
|     CONF_SERVICE_UUID, |     CONF_SERVICE_UUID, | ||||||
|     CONF_MAC_ADDRESS, |     CONF_MAC_ADDRESS, | ||||||
|     DEVICE_CLASS_SIGNAL_STRENGTH, |     DEVICE_CLASS_SIGNAL_STRENGTH, | ||||||
| @@ -16,6 +19,15 @@ BLERSSISensor = ble_rssi_ns.class_( | |||||||
|     "BLERSSISensor", sensor.Sensor, cg.Component, esp32_ble_tracker.ESPBTDeviceListener |     "BLERSSISensor", sensor.Sensor, cg.Component, esp32_ble_tracker.ESPBTDeviceListener | ||||||
| ) | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def _validate(config): | ||||||
|  |     if CONF_IBEACON_MAJOR in config and CONF_IBEACON_UUID not in config: | ||||||
|  |         raise cv.Invalid("iBeacon major identifier requires iBeacon UUID") | ||||||
|  |     if CONF_IBEACON_MINOR in config and CONF_IBEACON_UUID not in config: | ||||||
|  |         raise cv.Invalid("iBeacon minor identifier requires iBeacon UUID") | ||||||
|  |     return config | ||||||
|  |  | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = cv.All( | CONFIG_SCHEMA = cv.All( | ||||||
|     sensor.sensor_schema( |     sensor.sensor_schema( | ||||||
|         BLERSSISensor, |         BLERSSISensor, | ||||||
| @@ -28,11 +40,15 @@ CONFIG_SCHEMA = cv.All( | |||||||
|         { |         { | ||||||
|             cv.Optional(CONF_MAC_ADDRESS): cv.mac_address, |             cv.Optional(CONF_MAC_ADDRESS): cv.mac_address, | ||||||
|             cv.Optional(CONF_SERVICE_UUID): esp32_ble_tracker.bt_uuid, |             cv.Optional(CONF_SERVICE_UUID): esp32_ble_tracker.bt_uuid, | ||||||
|  |             cv.Optional(CONF_IBEACON_MAJOR): cv.uint16_t, | ||||||
|  |             cv.Optional(CONF_IBEACON_MINOR): cv.uint16_t, | ||||||
|  |             cv.Optional(CONF_IBEACON_UUID): cv.uuid, | ||||||
|         } |         } | ||||||
|     ) |     ) | ||||||
|     .extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA) |     .extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA) | ||||||
|     .extend(cv.COMPONENT_SCHEMA), |     .extend(cv.COMPONENT_SCHEMA), | ||||||
|     cv.has_exactly_one_key(CONF_MAC_ADDRESS, CONF_SERVICE_UUID), |     cv.has_exactly_one_key(CONF_MAC_ADDRESS, CONF_SERVICE_UUID, CONF_IBEACON_UUID), | ||||||
|  |     _validate, | ||||||
| ) | ) | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -60,3 +76,13 @@ async def to_code(config): | |||||||
|         elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid128_format): |         elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid128_format): | ||||||
|             uuid128 = esp32_ble_tracker.as_reversed_hex_array(config[CONF_SERVICE_UUID]) |             uuid128 = esp32_ble_tracker.as_reversed_hex_array(config[CONF_SERVICE_UUID]) | ||||||
|             cg.add(var.set_service_uuid128(uuid128)) |             cg.add(var.set_service_uuid128(uuid128)) | ||||||
|  |  | ||||||
|  |     if CONF_IBEACON_UUID in config: | ||||||
|  |         ibeacon_uuid = esp32_ble_tracker.as_hex_array(str(config[CONF_IBEACON_UUID])) | ||||||
|  |         cg.add(var.set_ibeacon_uuid(ibeacon_uuid)) | ||||||
|  |  | ||||||
|  |         if CONF_IBEACON_MAJOR in config: | ||||||
|  |             cg.add(var.set_ibeacon_major(config[CONF_IBEACON_MAJOR])) | ||||||
|  |  | ||||||
|  |         if CONF_IBEACON_MINOR in config: | ||||||
|  |             cg.add(var.set_ibeacon_minor(config[CONF_IBEACON_MINOR])) | ||||||
|   | |||||||
							
								
								
									
										27
									
								
								esphome/components/bluetooth_proxy/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								esphome/components/bluetooth_proxy/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | |||||||
|  | from esphome.components import esp32_ble_tracker | ||||||
|  | import esphome.config_validation as cv | ||||||
|  | import esphome.codegen as cg | ||||||
|  | from esphome.const import CONF_ID | ||||||
|  |  | ||||||
|  | DEPENDENCIES = ["esp32", "esp32_ble_tracker"] | ||||||
|  | CODEOWNERS = ["@jesserockz"] | ||||||
|  |  | ||||||
|  |  | ||||||
|  | bluetooth_proxy_ns = cg.esphome_ns.namespace("bluetooth_proxy") | ||||||
|  |  | ||||||
|  | BluetoothProxy = bluetooth_proxy_ns.class_("BluetoothProxy", cg.Component) | ||||||
|  |  | ||||||
|  | CONFIG_SCHEMA = cv.Schema( | ||||||
|  |     { | ||||||
|  |         cv.GenerateID(): cv.declare_id(BluetoothProxy), | ||||||
|  |     } | ||||||
|  | ).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | async def to_code(config): | ||||||
|  |     var = cg.new_Pvariable(config[CONF_ID]) | ||||||
|  |     await cg.register_component(var, config) | ||||||
|  |  | ||||||
|  |     await esp32_ble_tracker.register_ble_device(var, config) | ||||||
|  |  | ||||||
|  |     cg.add_define("USE_BLUETOOTH_PROXY") | ||||||
							
								
								
									
										58
									
								
								esphome/components/bluetooth_proxy/bluetooth_proxy.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								esphome/components/bluetooth_proxy/bluetooth_proxy.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,58 @@ | |||||||
|  | #include "bluetooth_proxy.h" | ||||||
|  |  | ||||||
|  | #ifdef USE_API | ||||||
|  | #include "esphome/components/api/api_pb2.h" | ||||||
|  | #include "esphome/components/api/api_server.h" | ||||||
|  | #endif  // USE_API | ||||||
|  | #include "esphome/core/log.h" | ||||||
|  |  | ||||||
|  | #ifdef USE_ESP32 | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace bluetooth_proxy { | ||||||
|  |  | ||||||
|  | static const char *const TAG = "bluetooth_proxy"; | ||||||
|  |  | ||||||
|  | bool BluetoothProxy::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { | ||||||
|  |   ESP_LOGV(TAG, "Proxying packet from %s - %s. RSSI: %d dB", device.get_name().c_str(), device.address_str().c_str(), | ||||||
|  |            device.get_rssi()); | ||||||
|  |   this->send_api_packet_(device); | ||||||
|  |   return true; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void BluetoothProxy::send_api_packet_(const esp32_ble_tracker::ESPBTDevice &device) { | ||||||
|  | #ifndef USE_API | ||||||
|  |   return; | ||||||
|  | #else | ||||||
|  |   api::BluetoothLEAdvertisementResponse resp; | ||||||
|  |   resp.address = device.address_uint64(); | ||||||
|  |   if (!device.get_name().empty()) | ||||||
|  |     resp.name = device.get_name(); | ||||||
|  |   resp.rssi = device.get_rssi(); | ||||||
|  |   for (auto uuid : device.get_service_uuids()) { | ||||||
|  |     resp.service_uuids.push_back(uuid.to_string()); | ||||||
|  |   } | ||||||
|  |   for (auto &data : device.get_service_datas()) { | ||||||
|  |     api::BluetoothServiceData service_data; | ||||||
|  |     service_data.uuid = data.uuid.to_string(); | ||||||
|  |     for (auto d : data.data) | ||||||
|  |       service_data.data.push_back(d); | ||||||
|  |     resp.service_data.push_back(service_data); | ||||||
|  |   } | ||||||
|  |   for (auto &data : device.get_manufacturer_datas()) { | ||||||
|  |     api::BluetoothServiceData manufacturer_data; | ||||||
|  |     manufacturer_data.uuid = data.uuid.to_string(); | ||||||
|  |     for (auto d : data.data) | ||||||
|  |       manufacturer_data.data.push_back(d); | ||||||
|  |     resp.manufacturer_data.push_back(manufacturer_data); | ||||||
|  |   } | ||||||
|  |   api::global_api_server->send_bluetooth_le_advertisement(resp); | ||||||
|  | #endif | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void BluetoothProxy::dump_config() { ESP_LOGCONFIG(TAG, "Bluetooth Proxy:"); } | ||||||
|  |  | ||||||
|  | }  // namespace bluetooth_proxy | ||||||
|  | }  // namespace esphome | ||||||
|  |  | ||||||
|  | #endif  // USE_ESP32 | ||||||
							
								
								
									
										26
									
								
								esphome/components/bluetooth_proxy/bluetooth_proxy.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								esphome/components/bluetooth_proxy/bluetooth_proxy.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #ifdef USE_ESP32 | ||||||
|  |  | ||||||
|  | #include <map> | ||||||
|  |  | ||||||
|  | #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" | ||||||
|  | #include "esphome/core/automation.h" | ||||||
|  | #include "esphome/core/component.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace bluetooth_proxy { | ||||||
|  |  | ||||||
|  | class BluetoothProxy : public Component, public esp32_ble_tracker::ESPBTDeviceListener { | ||||||
|  |  public: | ||||||
|  |   bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override; | ||||||
|  |   void dump_config() override; | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   void send_api_packet_(const esp32_ble_tracker::ESPBTDevice &device); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | }  // namespace bluetooth_proxy | ||||||
|  | }  // namespace esphome | ||||||
|  |  | ||||||
|  | #endif  // USE_ESP32 | ||||||
| @@ -5,7 +5,6 @@ from esphome.const import ( | |||||||
|     CONF_DEVICE_CLASS, |     CONF_DEVICE_CLASS, | ||||||
|     CONF_ENTITY_CATEGORY, |     CONF_ENTITY_CATEGORY, | ||||||
|     CONF_ICON, |     CONF_ICON, | ||||||
|     CONF_ID, |  | ||||||
|     CONF_SOURCE_ID, |     CONF_SOURCE_ID, | ||||||
| ) | ) | ||||||
| from esphome.core.entity_helpers import inherit_property_from | from esphome.core.entity_helpers import inherit_property_from | ||||||
| @@ -15,12 +14,15 @@ from .. import copy_ns | |||||||
| CopySwitch = copy_ns.class_("CopySwitch", switch.Switch, cg.Component) | CopySwitch = copy_ns.class_("CopySwitch", switch.Switch, cg.Component) | ||||||
|  |  | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = switch.SWITCH_SCHEMA.extend( | CONFIG_SCHEMA = ( | ||||||
|     { |     switch.switch_schema(CopySwitch) | ||||||
|         cv.GenerateID(): cv.declare_id(CopySwitch), |     .extend( | ||||||
|         cv.Required(CONF_SOURCE_ID): cv.use_id(switch.Switch), |         { | ||||||
|     } |             cv.Required(CONF_SOURCE_ID): cv.use_id(switch.Switch), | ||||||
| ).extend(cv.COMPONENT_SCHEMA) |         } | ||||||
|  |     ) | ||||||
|  |     .extend(cv.COMPONENT_SCHEMA) | ||||||
|  | ) | ||||||
|  |  | ||||||
| FINAL_VALIDATE_SCHEMA = cv.All( | FINAL_VALIDATE_SCHEMA = cv.All( | ||||||
|     inherit_property_from(CONF_ICON, CONF_SOURCE_ID), |     inherit_property_from(CONF_ICON, CONF_SOURCE_ID), | ||||||
| @@ -30,8 +32,7 @@ FINAL_VALIDATE_SCHEMA = cv.All( | |||||||
|  |  | ||||||
|  |  | ||||||
| async def to_code(config): | async def to_code(config): | ||||||
|     var = cg.new_Pvariable(config[CONF_ID]) |     var = await switch.new_switch(config) | ||||||
|     await switch.register_switch(var, config) |  | ||||||
|     await cg.register_component(var, config) |     await cg.register_component(var, config) | ||||||
|  |  | ||||||
|     source = await cg.get_variable(config[CONF_SOURCE_ID]) |     source = await cg.get_variable(config[CONF_SOURCE_ID]) | ||||||
|   | |||||||
| @@ -13,8 +13,9 @@ void CSE7766Component::loop() { | |||||||
|     this->raw_data_index_ = 0; |     this->raw_data_index_ = 0; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   if (this->available() == 0) |   if (this->available() == 0) { | ||||||
|     return; |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|   this->last_transmission_ = now; |   this->last_transmission_ = now; | ||||||
|   while (this->available() != 0) { |   while (this->available() != 0) { | ||||||
| @@ -22,6 +23,7 @@ void CSE7766Component::loop() { | |||||||
|     if (!this->check_byte_()) { |     if (!this->check_byte_()) { | ||||||
|       this->raw_data_index_ = 0; |       this->raw_data_index_ = 0; | ||||||
|       this->status_set_warning(); |       this->status_set_warning(); | ||||||
|  |       continue; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     if (this->raw_data_index_ == 23) { |     if (this->raw_data_index_ == 23) { | ||||||
| @@ -51,8 +53,9 @@ bool CSE7766Component::check_byte_() { | |||||||
|  |  | ||||||
|   if (index == 23) { |   if (index == 23) { | ||||||
|     uint8_t checksum = 0; |     uint8_t checksum = 0; | ||||||
|     for (uint8_t i = 2; i < 23; i++) |     for (uint8_t i = 2; i < 23; i++) { | ||||||
|       checksum += this->raw_data_[i]; |       checksum += this->raw_data_[i]; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     if (checksum != this->raw_data_[23]) { |     if (checksum != this->raw_data_[23]) { | ||||||
|       ESP_LOGW(TAG, "Invalid checksum from CSE7766: 0x%02X != 0x%02X", checksum, this->raw_data_[23]); |       ESP_LOGW(TAG, "Invalid checksum from CSE7766: 0x%02X != 0x%02X", checksum, this->raw_data_[23]); | ||||||
| @@ -66,20 +69,34 @@ bool CSE7766Component::check_byte_() { | |||||||
| void CSE7766Component::parse_data_() { | void CSE7766Component::parse_data_() { | ||||||
|   ESP_LOGVV(TAG, "CSE7766 Data: "); |   ESP_LOGVV(TAG, "CSE7766 Data: "); | ||||||
|   for (uint8_t i = 0; i < 23; i++) { |   for (uint8_t i = 0; i < 23; i++) { | ||||||
|     ESP_LOGVV(TAG, "  i=%u: 0b" BYTE_TO_BINARY_PATTERN " (0x%02X)", i, BYTE_TO_BINARY(this->raw_data_[i]), |     ESP_LOGVV(TAG, "  %u: 0b" BYTE_TO_BINARY_PATTERN " (0x%02X)", i + 1, BYTE_TO_BINARY(this->raw_data_[i]), | ||||||
|               this->raw_data_[i]); |               this->raw_data_[i]); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   uint8_t header1 = this->raw_data_[0]; |   uint8_t header1 = this->raw_data_[0]; | ||||||
|   if (header1 == 0xAA) { |   if (header1 == 0xAA) { | ||||||
|     ESP_LOGW(TAG, "CSE7766 not calibrated!"); |     ESP_LOGE(TAG, "CSE7766 not calibrated!"); | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   if ((header1 & 0xF0) == 0xF0 && ((header1 >> 0) & 1) == 1) { |   bool power_cycle_exceeds_range = false; | ||||||
|     ESP_LOGW(TAG, "CSE7766 reports abnormal hardware: (0x%02X)", header1); |  | ||||||
|     ESP_LOGW(TAG, "  Coefficient storage area is abnormal."); |   if ((header1 & 0xF0) == 0xF0) { | ||||||
|     return; |     if (header1 & 0xD) { | ||||||
|  |       ESP_LOGE(TAG, "CSE7766 reports abnormal external circuit or chip damage: (0x%02X)", header1); | ||||||
|  |       if (header1 & (1 << 3)) { | ||||||
|  |         ESP_LOGE(TAG, "  Voltage cycle exceeds range."); | ||||||
|  |       } | ||||||
|  |       if (header1 & (1 << 2)) { | ||||||
|  |         ESP_LOGE(TAG, "  Current cycle exceeds range."); | ||||||
|  |       } | ||||||
|  |       if (header1 & (1 << 0)) { | ||||||
|  |         ESP_LOGE(TAG, "  Coefficient storage area is abnormal."); | ||||||
|  |       } | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     power_cycle_exceeds_range = header1 & (1 << 1); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   uint32_t voltage_calib = this->get_24_bit_uint_(2); |   uint32_t voltage_calib = this->get_24_bit_uint_(2); | ||||||
| @@ -92,46 +109,29 @@ void CSE7766Component::parse_data_() { | |||||||
|   uint8_t adj = this->raw_data_[20]; |   uint8_t adj = this->raw_data_[20]; | ||||||
|   uint32_t cf_pulses = (this->raw_data_[21] << 8) + this->raw_data_[22]; |   uint32_t cf_pulses = (this->raw_data_[21] << 8) + this->raw_data_[22]; | ||||||
|  |  | ||||||
|   bool power_ok = true; |   bool have_voltage = adj & 0x40; | ||||||
|   bool voltage_ok = true; |   if (have_voltage) { | ||||||
|   bool current_ok = true; |  | ||||||
|  |  | ||||||
|   if (header1 > 0xF0) { |  | ||||||
|     // ESP_LOGV(TAG, "CSE7766 reports abnormal hardware: (0x%02X)", byte); |  | ||||||
|     if ((header1 >> 3) & 1) { |  | ||||||
|       ESP_LOGV(TAG, "  Voltage cycle exceeds range."); |  | ||||||
|       voltage_ok = false; |  | ||||||
|     } |  | ||||||
|     if ((header1 >> 2) & 1) { |  | ||||||
|       ESP_LOGV(TAG, "  Current cycle exceeds range."); |  | ||||||
|       current_ok = false; |  | ||||||
|     } |  | ||||||
|     if ((header1 >> 1) & 1) { |  | ||||||
|       ESP_LOGV(TAG, "  Power cycle exceeds range."); |  | ||||||
|       power_ok = false; |  | ||||||
|     } |  | ||||||
|     if ((header1 >> 0) & 1) { |  | ||||||
|       ESP_LOGV(TAG, "  Coefficient storage area is abnormal."); |  | ||||||
|       return; |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   if ((adj & 0x40) == 0x40 && voltage_ok && current_ok) { |  | ||||||
|     // voltage cycle of serial port outputted is a complete cycle; |     // voltage cycle of serial port outputted is a complete cycle; | ||||||
|     this->voltage_acc_ += voltage_calib / float(voltage_cycle); |     this->voltage_acc_ += voltage_calib / float(voltage_cycle); | ||||||
|     this->voltage_counts_ += 1; |     this->voltage_counts_ += 1; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   float power = 0; |   bool have_power = adj & 0x10; | ||||||
|   if ((adj & 0x10) == 0x10 && voltage_ok && current_ok && power_ok) { |   float power = 0.0f; | ||||||
|  |  | ||||||
|  |   if (have_power) { | ||||||
|     // power cycle of serial port outputted is a complete cycle; |     // power cycle of serial port outputted is a complete cycle; | ||||||
|     power = power_calib / float(power_cycle); |     // According to the user manual, power cycle exceeding range means the measured power is 0 | ||||||
|  |     if (!power_cycle_exceeds_range) { | ||||||
|  |       power = power_calib / float(power_cycle); | ||||||
|  |     } | ||||||
|     this->power_acc_ += power; |     this->power_acc_ += power; | ||||||
|     this->power_counts_ += 1; |     this->power_counts_ += 1; | ||||||
|  |  | ||||||
|     uint32_t difference; |     uint32_t difference; | ||||||
|     if (this->cf_pulses_last_ == 0) |     if (this->cf_pulses_last_ == 0) { | ||||||
|       this->cf_pulses_last_ = cf_pulses; |       this->cf_pulses_last_ = cf_pulses; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     if (cf_pulses < this->cf_pulses_last_) { |     if (cf_pulses < this->cf_pulses_last_) { | ||||||
|       difference = cf_pulses + (0x10000 - this->cf_pulses_last_); |       difference = cf_pulses + (0x10000 - this->cf_pulses_last_); | ||||||
| @@ -139,41 +139,52 @@ void CSE7766Component::parse_data_() { | |||||||
|       difference = cf_pulses - this->cf_pulses_last_; |       difference = cf_pulses - this->cf_pulses_last_; | ||||||
|     } |     } | ||||||
|     this->cf_pulses_last_ = cf_pulses; |     this->cf_pulses_last_ = cf_pulses; | ||||||
|     this->energy_total_ += difference * float(power_calib) / 1000000.0 / 3600.0; |     this->energy_total_ += difference * float(power_calib) / 1000000.0f / 3600.0f; | ||||||
|  |     this->energy_total_counts_ += 1; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   if ((adj & 0x20) == 0x20 && current_ok && voltage_ok && power != 0.0) { |   if (adj & 0x20) { | ||||||
|     // indicates current cycle of serial port outputted is a complete cycle; |     // indicates current cycle of serial port outputted is a complete cycle; | ||||||
|     this->current_acc_ += current_calib / float(current_cycle); |     float current = 0.0f; | ||||||
|  |     if (have_voltage && !have_power) { | ||||||
|  |       // Testing has shown that when we have voltage and current but not power, that means the power is 0. | ||||||
|  |       // We report a power of 0, which in turn means we should report a current of 0. | ||||||
|  |       this->power_counts_ += 1; | ||||||
|  |     } else if (power != 0.0f) { | ||||||
|  |       current = current_calib / float(current_cycle); | ||||||
|  |     } | ||||||
|  |     this->current_acc_ += current; | ||||||
|     this->current_counts_ += 1; |     this->current_counts_ += 1; | ||||||
|   } |   } | ||||||
| } | } | ||||||
| void CSE7766Component::update() { | void CSE7766Component::update() { | ||||||
|   float voltage = this->voltage_counts_ > 0 ? this->voltage_acc_ / this->voltage_counts_ : 0.0f; |   const auto publish_state = [](const char *name, sensor::Sensor *sensor, float &acc, uint32_t &counts) { | ||||||
|   float current = this->current_counts_ > 0 ? this->current_acc_ / this->current_counts_ : 0.0f; |     if (counts != 0) { | ||||||
|   float power = this->power_counts_ > 0 ? this->power_acc_ / this->power_counts_ : 0.0f; |       const auto avg = acc / counts; | ||||||
|  |  | ||||||
|   ESP_LOGV(TAG, "Got voltage_acc=%.2f current_acc=%.2f power_acc=%.2f", this->voltage_acc_, this->current_acc_, |       ESP_LOGV(TAG, "Got %s_acc=%.2f %s_counts=%d %s=%.1f", name, acc, name, counts, name, avg); | ||||||
|            this->power_acc_); |  | ||||||
|   ESP_LOGV(TAG, "Got voltage_counts=%d current_counts=%d power_counts=%d", this->voltage_counts_, this->current_counts_, |  | ||||||
|            this->power_counts_); |  | ||||||
|   ESP_LOGD(TAG, "Got voltage=%.1fV current=%.1fA power=%.1fW", voltage, current, power); |  | ||||||
|  |  | ||||||
|   if (this->voltage_sensor_ != nullptr) |       if (sensor != nullptr) { | ||||||
|     this->voltage_sensor_->publish_state(voltage); |         sensor->publish_state(avg); | ||||||
|   if (this->current_sensor_ != nullptr) |       } | ||||||
|     this->current_sensor_->publish_state(current); |  | ||||||
|   if (this->power_sensor_ != nullptr) |  | ||||||
|     this->power_sensor_->publish_state(power); |  | ||||||
|   if (this->energy_sensor_ != nullptr) |  | ||||||
|     this->energy_sensor_->publish_state(this->energy_total_); |  | ||||||
|  |  | ||||||
|   this->voltage_acc_ = 0.0f; |       acc = 0.0f; | ||||||
|   this->current_acc_ = 0.0f; |       counts = 0; | ||||||
|   this->power_acc_ = 0.0f; |     } | ||||||
|   this->voltage_counts_ = 0; |   }; | ||||||
|   this->power_counts_ = 0; |  | ||||||
|   this->current_counts_ = 0; |   publish_state("voltage", this->voltage_sensor_, this->voltage_acc_, this->voltage_counts_); | ||||||
|  |   publish_state("current", this->current_sensor_, this->current_acc_, this->current_counts_); | ||||||
|  |   publish_state("power", this->power_sensor_, this->power_acc_, this->power_counts_); | ||||||
|  |  | ||||||
|  |   if (this->energy_total_counts_ != 0) { | ||||||
|  |     ESP_LOGV(TAG, "Got energy_total=%.2f energy_total_counts=%d", this->energy_total_, this->energy_total_counts_); | ||||||
|  |  | ||||||
|  |     if (this->energy_sensor_ != nullptr) { | ||||||
|  |       this->energy_sensor_->publish_state(this->energy_total_); | ||||||
|  |     } | ||||||
|  |     this->energy_total_counts_ = 0; | ||||||
|  |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| uint32_t CSE7766Component::get_24_bit_uint_(uint8_t start_index) { | uint32_t CSE7766Component::get_24_bit_uint_(uint8_t start_index) { | ||||||
|   | |||||||
| @@ -39,6 +39,8 @@ class CSE7766Component : public PollingComponent, public uart::UARTDevice { | |||||||
|   uint32_t voltage_counts_{0}; |   uint32_t voltage_counts_{0}; | ||||||
|   uint32_t current_counts_{0}; |   uint32_t current_counts_{0}; | ||||||
|   uint32_t power_counts_{0}; |   uint32_t power_counts_{0}; | ||||||
|  |   // Setting this to 1 means it will always publish 0 once at startup | ||||||
|  |   uint32_t energy_total_counts_{1}; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| }  // namespace cse7766 | }  // namespace cse7766 | ||||||
|   | |||||||
| @@ -10,13 +10,7 @@ CONFIG_SCHEMA = cv.Schema( | |||||||
|     { |     { | ||||||
|         cv.GenerateID(): cv.declare_id(CustomSwitchConstructor), |         cv.GenerateID(): cv.declare_id(CustomSwitchConstructor), | ||||||
|         cv.Required(CONF_LAMBDA): cv.returning_lambda, |         cv.Required(CONF_LAMBDA): cv.returning_lambda, | ||||||
|         cv.Required(CONF_SWITCHES): cv.ensure_list( |         cv.Required(CONF_SWITCHES): cv.ensure_list(switch.switch_schema(switch.Switch)), | ||||||
|             switch.SWITCH_SCHEMA.extend( |  | ||||||
|                 { |  | ||||||
|                     cv.GenerateID(): cv.declare_id(switch.Switch), |  | ||||||
|                 } |  | ||||||
|             ) |  | ||||||
|         ), |  | ||||||
|     } |     } | ||||||
| ) | ) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -349,13 +349,7 @@ CONFIG_SCHEMA = cv.Schema( | |||||||
|                     CONF_ICON: ICON_BLUETOOTH, |                     CONF_ICON: ICON_BLUETOOTH, | ||||||
|                 }, |                 }, | ||||||
|             ], |             ], | ||||||
|         ): [ |         ): [switch.switch_schema(DemoSwitch).extend(cv.COMPONENT_SCHEMA)], | ||||||
|             switch.SWITCH_SCHEMA.extend(cv.COMPONENT_SCHEMA).extend( |  | ||||||
|                 { |  | ||||||
|                     cv.GenerateID(): cv.declare_id(DemoSwitch), |  | ||||||
|                 } |  | ||||||
|             ) |  | ||||||
|         ], |  | ||||||
|         cv.Optional( |         cv.Optional( | ||||||
|             CONF_TEXT_SENSORS, |             CONF_TEXT_SENSORS, | ||||||
|             default=[ |             default=[ | ||||||
| @@ -422,9 +416,8 @@ async def to_code(config): | |||||||
|         await cg.register_component(var, conf) |         await cg.register_component(var, conf) | ||||||
|  |  | ||||||
|     for conf in config[CONF_SWITCHES]: |     for conf in config[CONF_SWITCHES]: | ||||||
|         var = cg.new_Pvariable(conf[CONF_ID]) |         var = await switch.new_switch(conf) | ||||||
|         await cg.register_component(var, conf) |         await cg.register_component(var, conf) | ||||||
|         await switch.register_switch(var, conf) |  | ||||||
|  |  | ||||||
|     for conf in config[CONF_TEXT_SENSORS]: |     for conf in config[CONF_TEXT_SENSORS]: | ||||||
|         var = await text_sensor.new_text_sensor(conf) |         var = await text_sensor.new_text_sensor(conf) | ||||||
|   | |||||||
							
								
								
									
										0
									
								
								esphome/components/dps310/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								esphome/components/dps310/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										189
									
								
								esphome/components/dps310/dps310.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										189
									
								
								esphome/components/dps310/dps310.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,189 @@ | |||||||
|  | #include "dps310.h" | ||||||
|  | #include "esphome/core/log.h" | ||||||
|  | #include "esphome/core/hal.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace dps310 { | ||||||
|  |  | ||||||
|  | static const char *const TAG = "dps310"; | ||||||
|  |  | ||||||
|  | void DPS310Component::setup() { | ||||||
|  |   uint8_t coef_data_raw[DPS310_NUM_COEF_REGS]; | ||||||
|  |   auto timer = DPS310_INIT_TIMEOUT; | ||||||
|  |   uint8_t reg = 0; | ||||||
|  |  | ||||||
|  |   ESP_LOGCONFIG(TAG, "Setting up DPS310..."); | ||||||
|  |   // first, reset the sensor | ||||||
|  |   if (!this->write_byte(DPS310_REG_RESET, DPS310_CMD_RESET)) { | ||||||
|  |     this->mark_failed(); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   delay(10); | ||||||
|  |   // wait for the sensor and its coefficients to be ready | ||||||
|  |   while (timer-- && (!(reg & DPS310_BIT_SENSOR_RDY) || !(reg & DPS310_BIT_COEF_RDY))) { | ||||||
|  |     reg = this->read_byte(DPS310_REG_MEAS_CFG).value_or(0); | ||||||
|  |     delay(5); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (!(reg & DPS310_BIT_SENSOR_RDY) || !(reg & DPS310_BIT_COEF_RDY)) {  // the flags were not set in time | ||||||
|  |     this->mark_failed(); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   // read device ID | ||||||
|  |   if (!this->read_byte(DPS310_REG_PROD_REV_ID, &this->prod_rev_id_)) { | ||||||
|  |     this->mark_failed(); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   // read in coefficients used to calculate the compensated pressure and temperature values | ||||||
|  |   if (!this->read_bytes(DPS310_REG_COEF, coef_data_raw, DPS310_NUM_COEF_REGS)) { | ||||||
|  |     this->mark_failed(); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   // read in coefficients source register, too -- we need this a few lines down | ||||||
|  |   if (!this->read_byte(DPS310_REG_TMP_COEF_SRC, ®)) { | ||||||
|  |     this->mark_failed(); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   // set up operational stuff | ||||||
|  |   if (!this->write_byte(DPS310_REG_PRS_CFG, DPS310_VAL_PRS_CFG)) { | ||||||
|  |     this->mark_failed(); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   if (!this->write_byte(DPS310_REG_TMP_CFG, DPS310_VAL_TMP_CFG | (reg & DPS310_BIT_TMP_COEF_SRC))) { | ||||||
|  |     this->mark_failed(); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   if (!this->write_byte(DPS310_REG_CFG, DPS310_VAL_REG_CFG)) { | ||||||
|  |     this->mark_failed(); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   if (!this->write_byte(DPS310_REG_MEAS_CFG, 0x07)) {  // enable background mode | ||||||
|  |     this->mark_failed(); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   this->c0_ =  // we only ever use c0/2, so just divide by 2 here to save time later | ||||||
|  |       DPS310Component::twos_complement( | ||||||
|  |           int16_t(((uint16_t) coef_data_raw[0] << 4) | (((uint16_t) coef_data_raw[1] >> 4) & 0x0F)), 12) / | ||||||
|  |       2; | ||||||
|  |  | ||||||
|  |   this->c1_ = | ||||||
|  |       DPS310Component::twos_complement(int16_t((((uint16_t) coef_data_raw[1] & 0x0F) << 8) | coef_data_raw[2]), 12); | ||||||
|  |  | ||||||
|  |   this->c00_ = ((uint32_t) coef_data_raw[3] << 12) | ((uint32_t) coef_data_raw[4] << 4) | | ||||||
|  |                (((uint32_t) coef_data_raw[5] >> 4) & 0x0F); | ||||||
|  |   this->c00_ = DPS310Component::twos_complement(c00_, 20); | ||||||
|  |  | ||||||
|  |   this->c10_ = | ||||||
|  |       (((uint32_t) coef_data_raw[5] & 0x0F) << 16) | ((uint32_t) coef_data_raw[6] << 8) | (uint32_t) coef_data_raw[7]; | ||||||
|  |   this->c10_ = DPS310Component::twos_complement(c10_, 20); | ||||||
|  |  | ||||||
|  |   this->c01_ = int16_t(((uint16_t) coef_data_raw[8] << 8) | (uint16_t) coef_data_raw[9]); | ||||||
|  |   this->c11_ = int16_t(((uint16_t) coef_data_raw[10] << 8) | (uint16_t) coef_data_raw[11]); | ||||||
|  |   this->c20_ = int16_t(((uint16_t) coef_data_raw[12] << 8) | (uint16_t) coef_data_raw[13]); | ||||||
|  |   this->c21_ = int16_t(((uint16_t) coef_data_raw[14] << 8) | (uint16_t) coef_data_raw[15]); | ||||||
|  |   this->c30_ = int16_t(((uint16_t) coef_data_raw[16] << 8) | (uint16_t) coef_data_raw[17]); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void DPS310Component::dump_config() { | ||||||
|  |   ESP_LOGCONFIG(TAG, "DPS310:"); | ||||||
|  |   ESP_LOGCONFIG(TAG, "  Product ID: %u", this->prod_rev_id_ & 0x0F); | ||||||
|  |   ESP_LOGCONFIG(TAG, "  Revision ID: %u", (this->prod_rev_id_ >> 4) & 0x0F); | ||||||
|  |   LOG_I2C_DEVICE(this); | ||||||
|  |   if (this->is_failed()) { | ||||||
|  |     ESP_LOGE(TAG, "Communication with DPS310 failed!"); | ||||||
|  |   } | ||||||
|  |   LOG_UPDATE_INTERVAL(this); | ||||||
|  |   LOG_SENSOR("  ", "Temperature", this->temperature_sensor_); | ||||||
|  |   LOG_SENSOR("  ", "Pressure", this->pressure_sensor_); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | float DPS310Component::get_setup_priority() const { return setup_priority::DATA; } | ||||||
|  |  | ||||||
|  | void DPS310Component::update() { | ||||||
|  |   if (!this->update_in_progress_) { | ||||||
|  |     this->update_in_progress_ = true; | ||||||
|  |     this->read_(); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void DPS310Component::read_() { | ||||||
|  |   uint8_t reg = 0; | ||||||
|  |   if (!this->read_byte(DPS310_REG_MEAS_CFG, ®)) { | ||||||
|  |     this->status_set_warning(); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if ((!this->got_pres_) && (reg & DPS310_BIT_PRS_RDY)) { | ||||||
|  |     this->read_pressure_(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if ((!this->got_temp_) && (reg & DPS310_BIT_TMP_RDY)) { | ||||||
|  |     this->read_temperature_(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (this->got_pres_ && this->got_temp_) { | ||||||
|  |     this->calculate_values_(this->raw_temperature_, this->raw_pressure_); | ||||||
|  |     this->got_pres_ = false; | ||||||
|  |     this->got_temp_ = false; | ||||||
|  |     this->update_in_progress_ = false; | ||||||
|  |     this->status_clear_warning(); | ||||||
|  |   } else { | ||||||
|  |     auto f = std::bind(&DPS310Component::read_, this); | ||||||
|  |     this->set_timeout("dps310", 10, f); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void DPS310Component::read_pressure_() { | ||||||
|  |   uint8_t bytes[3]; | ||||||
|  |   if (!this->read_bytes(DPS310_REG_PRS_B2, bytes, 3)) { | ||||||
|  |     this->status_set_warning(); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   this->got_pres_ = true; | ||||||
|  |   this->raw_pressure_ = DPS310Component::twos_complement( | ||||||
|  |       int32_t((uint32_t(bytes[0]) << 16) | (uint32_t(bytes[1]) << 8) | (uint32_t(bytes[2]))), 24); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void DPS310Component::read_temperature_() { | ||||||
|  |   uint8_t bytes[3]; | ||||||
|  |   if (!this->read_bytes(DPS310_REG_TMP_B2, bytes, 3)) { | ||||||
|  |     this->status_set_warning(); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   this->got_temp_ = true; | ||||||
|  |   this->raw_temperature_ = DPS310Component::twos_complement( | ||||||
|  |       int32_t((uint32_t(bytes[0]) << 16) | (uint32_t(bytes[1]) << 8) | (uint32_t(bytes[2]))), 24); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Calculations are taken from the datasheet which can be found here: | ||||||
|  | // https://www.infineon.com/dgdl/Infineon-DPS310-DataSheet-v01_02-EN.pdf?fileId=5546d462576f34750157750826c42242 | ||||||
|  | // Sections "How to Calculate Compensated Pressure Values" and "How to Calculate Compensated Temperature Values" | ||||||
|  | // Variable names below match variable names from the datasheet but lowercased | ||||||
|  | void DPS310Component::calculate_values_(int32_t raw_temperature, int32_t raw_pressure) { | ||||||
|  |   const float t_raw_sc = (float) raw_temperature / DPS310_SCALE_FACTOR; | ||||||
|  |   const float p_raw_sc = (float) raw_pressure / DPS310_SCALE_FACTOR; | ||||||
|  |  | ||||||
|  |   const float temperature = t_raw_sc * this->c1_ + this->c0_;  // c0/2 done earlier! | ||||||
|  |  | ||||||
|  |   const float pressure = (this->c00_ + p_raw_sc * (this->c10_ + p_raw_sc * (this->c20_ + p_raw_sc * this->c30_)) + | ||||||
|  |                           t_raw_sc * this->c01_ + t_raw_sc * p_raw_sc * (this->c11_ + p_raw_sc * this->c21_)) / | ||||||
|  |                          100;  // divide by 100 for hPa | ||||||
|  |  | ||||||
|  |   if (this->temperature_sensor_ != nullptr) { | ||||||
|  |     this->temperature_sensor_->publish_state(temperature); | ||||||
|  |   } | ||||||
|  |   if (this->pressure_sensor_ != nullptr) { | ||||||
|  |     this->pressure_sensor_->publish_state(pressure); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | int32_t DPS310Component::twos_complement(int32_t val, uint8_t bits) { | ||||||
|  |   if (val & ((uint32_t) 1 << (bits - 1))) { | ||||||
|  |     val -= (uint32_t) 1 << bits; | ||||||
|  |   } | ||||||
|  |   return val; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | }  // namespace dps310 | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										65
									
								
								esphome/components/dps310/dps310.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								esphome/components/dps310/dps310.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,65 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include "esphome/core/component.h" | ||||||
|  | #include "esphome/components/sensor/sensor.h" | ||||||
|  | #include "esphome/components/i2c/i2c.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace dps310 { | ||||||
|  |  | ||||||
|  | static const uint8_t DPS310_REG_PRS_B2 = 0x00;        // Highest byte of pressure data | ||||||
|  | static const uint8_t DPS310_REG_TMP_B2 = 0x03;        // Highest byte of temperature data | ||||||
|  | static const uint8_t DPS310_REG_PRS_CFG = 0x06;       // Pressure configuration | ||||||
|  | static const uint8_t DPS310_REG_TMP_CFG = 0x07;       // Temperature configuration | ||||||
|  | static const uint8_t DPS310_REG_MEAS_CFG = 0x08;      // Sensor configuration | ||||||
|  | static const uint8_t DPS310_REG_CFG = 0x09;           // Interrupt/FIFO configuration | ||||||
|  | static const uint8_t DPS310_REG_RESET = 0x0C;         // Soft reset | ||||||
|  | static const uint8_t DPS310_REG_PROD_REV_ID = 0x0D;   // Register that contains the part ID | ||||||
|  | static const uint8_t DPS310_REG_COEF = 0x10;          // Top of calibration coefficient data space | ||||||
|  | static const uint8_t DPS310_REG_TMP_COEF_SRC = 0x28;  // Temperature calibration src | ||||||
|  |  | ||||||
|  | static const uint8_t DPS310_BIT_PRS_RDY = 0x10;       // Pressure measurement is ready | ||||||
|  | static const uint8_t DPS310_BIT_TMP_RDY = 0x20;       // Temperature measurement is ready | ||||||
|  | static const uint8_t DPS310_BIT_SENSOR_RDY = 0x40;    // Sensor initialization complete when bit is set | ||||||
|  | static const uint8_t DPS310_BIT_COEF_RDY = 0x80;      // Coefficients are available when bit is set | ||||||
|  | static const uint8_t DPS310_BIT_TMP_COEF_SRC = 0x80;  // Temperature measurement source (0 = ASIC, 1 = MEMS element) | ||||||
|  | static const uint8_t DPS310_BIT_REQ_PRES = 0x01;      // Set this bit to request pressure reading | ||||||
|  | static const uint8_t DPS310_BIT_REQ_TEMP = 0x02;      // Set this bit to request temperature reading | ||||||
|  |  | ||||||
|  | static const uint8_t DPS310_CMD_RESET = 0x89;  // What to write to reset the device | ||||||
|  |  | ||||||
|  | static const uint8_t DPS310_VAL_PRS_CFG = 0x01;  // Value written to DPS310_REG_PRS_CFG at startup | ||||||
|  | static const uint8_t DPS310_VAL_TMP_CFG = 0x01;  // Value written to DPS310_REG_TMP_CFG at startup | ||||||
|  | static const uint8_t DPS310_VAL_REG_CFG = 0x00;  // Value written to DPS310_REG_CFG at startup | ||||||
|  |  | ||||||
|  | static const uint8_t DPS310_INIT_TIMEOUT = 20;       // How long to wait for DPS310 start-up to complete | ||||||
|  | static const uint8_t DPS310_NUM_COEF_REGS = 18;      // Number of coefficients we need to read from the device | ||||||
|  | static const int32_t DPS310_SCALE_FACTOR = 1572864;  // Measurement compensation scale factor | ||||||
|  |  | ||||||
|  | class DPS310Component : 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; } | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   void read_(); | ||||||
|  |   void read_pressure_(); | ||||||
|  |   void read_temperature_(); | ||||||
|  |   void calculate_values_(int32_t raw_temperature, int32_t raw_pressure); | ||||||
|  |   static int32_t twos_complement(int32_t val, uint8_t bits); | ||||||
|  |  | ||||||
|  |   sensor::Sensor *temperature_sensor_; | ||||||
|  |   sensor::Sensor *pressure_sensor_; | ||||||
|  |   int32_t raw_pressure_, raw_temperature_, c00_, c10_; | ||||||
|  |   int16_t c0_, c1_, c01_, c11_, c20_, c21_, c30_; | ||||||
|  |   uint8_t prod_rev_id_; | ||||||
|  |   bool got_pres_, got_temp_, update_in_progress_; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | }  // namespace dps310 | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										62
									
								
								esphome/components/dps310/sensor.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								esphome/components/dps310/sensor.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,62 @@ | |||||||
|  | import esphome.codegen as cg | ||||||
|  | import esphome.config_validation as cv | ||||||
|  | from esphome.components import i2c, sensor | ||||||
|  | from esphome.const import ( | ||||||
|  |     CONF_ID, | ||||||
|  |     CONF_PRESSURE, | ||||||
|  |     CONF_TEMPERATURE, | ||||||
|  |     DEVICE_CLASS_PRESSURE, | ||||||
|  |     DEVICE_CLASS_TEMPERATURE, | ||||||
|  |     STATE_CLASS_MEASUREMENT, | ||||||
|  |     UNIT_CELSIUS, | ||||||
|  |     ICON_GAUGE, | ||||||
|  |     ICON_THERMOMETER, | ||||||
|  |     UNIT_HECTOPASCAL, | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | CODEOWNERS = ["@kbx81"] | ||||||
|  |  | ||||||
|  | DEPENDENCIES = ["i2c"] | ||||||
|  |  | ||||||
|  | dps310_ns = cg.esphome_ns.namespace("dps310") | ||||||
|  | DPS310Component = dps310_ns.class_( | ||||||
|  |     "DPS310Component", cg.PollingComponent, i2c.I2CDevice | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | CONFIG_SCHEMA = ( | ||||||
|  |     cv.Schema( | ||||||
|  |         { | ||||||
|  |             cv.GenerateID(): cv.declare_id(DPS310Component), | ||||||
|  |             cv.Required(CONF_TEMPERATURE): sensor.sensor_schema( | ||||||
|  |                 unit_of_measurement=UNIT_CELSIUS, | ||||||
|  |                 icon=ICON_THERMOMETER, | ||||||
|  |                 accuracy_decimals=1, | ||||||
|  |                 device_class=DEVICE_CLASS_TEMPERATURE, | ||||||
|  |                 state_class=STATE_CLASS_MEASUREMENT, | ||||||
|  |             ), | ||||||
|  |             cv.Required(CONF_PRESSURE): sensor.sensor_schema( | ||||||
|  |                 unit_of_measurement=UNIT_HECTOPASCAL, | ||||||
|  |                 icon=ICON_GAUGE, | ||||||
|  |                 accuracy_decimals=1, | ||||||
|  |                 device_class=DEVICE_CLASS_PRESSURE, | ||||||
|  |                 state_class=STATE_CLASS_MEASUREMENT, | ||||||
|  |             ), | ||||||
|  |         } | ||||||
|  |     ) | ||||||
|  |     .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) | ||||||
|  |  | ||||||
|  |     if CONF_TEMPERATURE in config: | ||||||
|  |         sens = await sensor.new_sensor(config[CONF_TEMPERATURE]) | ||||||
|  |         cg.add(var.set_temperature_sensor(sens)) | ||||||
|  |  | ||||||
|  |     if CONF_PRESSURE in config: | ||||||
|  |         sens = await sensor.new_sensor(config[CONF_PRESSURE]) | ||||||
|  |         cg.add(var.set_pressure_sensor(sens)) | ||||||
| @@ -141,7 +141,7 @@ class ESP32Preferences : public ESPPreferences { | |||||||
|     ESP_LOGD(TAG, "Saving %d preferences to flash: %d cached, %d written, %d failed", cached + written + failed, cached, |     ESP_LOGD(TAG, "Saving %d preferences to flash: %d cached, %d written, %d failed", cached + written + failed, cached, | ||||||
|              written, failed); |              written, failed); | ||||||
|     if (failed > 0) { |     if (failed > 0) { | ||||||
|       ESP_LOGD(TAG, "Error saving %d preferences to flash. Last error=%s for key=%s", failed, esp_err_to_name(last_err), |       ESP_LOGE(TAG, "Error saving %d preferences to flash. Last error=%s for key=%s", failed, esp_err_to_name(last_err), | ||||||
|                last_key.c_str()); |                last_key.c_str()); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -170,6 +170,17 @@ class ESP32Preferences : public ESPPreferences { | |||||||
|     } |     } | ||||||
|     return to_save.data != stored_data.data; |     return to_save.data != stored_data.data; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   bool reset() override { | ||||||
|  |     ESP_LOGD(TAG, "Cleaning up preferences in flash..."); | ||||||
|  |     s_pending_save.clear(); | ||||||
|  |  | ||||||
|  |     nvs_flash_deinit(); | ||||||
|  |     nvs_flash_erase(); | ||||||
|  |     // Make the handle invalid to prevent any saves until restart | ||||||
|  |     nvs_handle = 0; | ||||||
|  |     return true; | ||||||
|  |   } | ||||||
| }; | }; | ||||||
|  |  | ||||||
| void setup_preferences() { | void setup_preferences() { | ||||||
|   | |||||||
| @@ -23,6 +23,8 @@ CONF_ESP32_BLE_ID = "esp32_ble_id" | |||||||
| CONF_SCAN_PARAMETERS = "scan_parameters" | CONF_SCAN_PARAMETERS = "scan_parameters" | ||||||
| CONF_WINDOW = "window" | CONF_WINDOW = "window" | ||||||
| CONF_ACTIVE = "active" | CONF_ACTIVE = "active" | ||||||
|  | CONF_CONTINUOUS = "continuous" | ||||||
|  | CONF_ON_SCAN_END = "on_scan_end" | ||||||
| esp32_ble_tracker_ns = cg.esphome_ns.namespace("esp32_ble_tracker") | esp32_ble_tracker_ns = cg.esphome_ns.namespace("esp32_ble_tracker") | ||||||
| ESP32BLETracker = esp32_ble_tracker_ns.class_("ESP32BLETracker", cg.Component) | ESP32BLETracker = esp32_ble_tracker_ns.class_("ESP32BLETracker", cg.Component) | ||||||
| ESPBTClient = esp32_ble_tracker_ns.class_("ESPBTClient") | ESPBTClient = esp32_ble_tracker_ns.class_("ESPBTClient") | ||||||
| @@ -42,6 +44,16 @@ BLEManufacturerDataAdvertiseTrigger = esp32_ble_tracker_ns.class_( | |||||||
|     "BLEManufacturerDataAdvertiseTrigger", |     "BLEManufacturerDataAdvertiseTrigger", | ||||||
|     automation.Trigger.template(adv_data_t_const_ref), |     automation.Trigger.template(adv_data_t_const_ref), | ||||||
| ) | ) | ||||||
|  | BLEEndOfScanTrigger = esp32_ble_tracker_ns.class_( | ||||||
|  |     "BLEEndOfScanTrigger", automation.Trigger.template() | ||||||
|  | ) | ||||||
|  | # Actions | ||||||
|  | ESP32BLEStartScanAction = esp32_ble_tracker_ns.class_( | ||||||
|  |     "ESP32BLEStartScanAction", automation.Action | ||||||
|  | ) | ||||||
|  | ESP32BLEStopScanAction = esp32_ble_tracker_ns.class_( | ||||||
|  |     "ESP32BLEStopScanAction", automation.Action | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
| def validate_scan_parameters(config): | def validate_scan_parameters(config): | ||||||
| @@ -138,6 +150,7 @@ CONFIG_SCHEMA = cv.Schema( | |||||||
|                         CONF_WINDOW, default="30ms" |                         CONF_WINDOW, default="30ms" | ||||||
|                     ): cv.positive_time_period_milliseconds, |                     ): cv.positive_time_period_milliseconds, | ||||||
|                     cv.Optional(CONF_ACTIVE, default=True): cv.boolean, |                     cv.Optional(CONF_ACTIVE, default=True): cv.boolean, | ||||||
|  |                     cv.Optional(CONF_CONTINUOUS, default=True): cv.boolean, | ||||||
|                 } |                 } | ||||||
|             ), |             ), | ||||||
|             validate_scan_parameters, |             validate_scan_parameters, | ||||||
| @@ -168,6 +181,9 @@ CONFIG_SCHEMA = cv.Schema( | |||||||
|                 cv.Required(CONF_MANUFACTURER_ID): bt_uuid, |                 cv.Required(CONF_MANUFACTURER_ID): bt_uuid, | ||||||
|             } |             } | ||||||
|         ), |         ), | ||||||
|  |         cv.Optional(CONF_ON_SCAN_END): automation.validate_automation( | ||||||
|  |             {cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(BLEEndOfScanTrigger)} | ||||||
|  |         ), | ||||||
|     } |     } | ||||||
| ).extend(cv.COMPONENT_SCHEMA) | ).extend(cv.COMPONENT_SCHEMA) | ||||||
|  |  | ||||||
| @@ -186,6 +202,7 @@ async def to_code(config): | |||||||
|     cg.add(var.set_scan_interval(int(params[CONF_INTERVAL].total_milliseconds / 0.625))) |     cg.add(var.set_scan_interval(int(params[CONF_INTERVAL].total_milliseconds / 0.625))) | ||||||
|     cg.add(var.set_scan_window(int(params[CONF_WINDOW].total_milliseconds / 0.625))) |     cg.add(var.set_scan_window(int(params[CONF_WINDOW].total_milliseconds / 0.625))) | ||||||
|     cg.add(var.set_scan_active(params[CONF_ACTIVE])) |     cg.add(var.set_scan_active(params[CONF_ACTIVE])) | ||||||
|  |     cg.add(var.set_scan_continuous(params[CONF_CONTINUOUS])) | ||||||
|     for conf in config.get(CONF_ON_BLE_ADVERTISE, []): |     for conf in config.get(CONF_ON_BLE_ADVERTISE, []): | ||||||
|         trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) |         trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) | ||||||
|         if CONF_MAC_ADDRESS in conf: |         if CONF_MAC_ADDRESS in conf: | ||||||
| @@ -215,10 +232,59 @@ async def to_code(config): | |||||||
|         if CONF_MAC_ADDRESS in conf: |         if CONF_MAC_ADDRESS in conf: | ||||||
|             cg.add(trigger.set_address(conf[CONF_MAC_ADDRESS].as_hex)) |             cg.add(trigger.set_address(conf[CONF_MAC_ADDRESS].as_hex)) | ||||||
|         await automation.build_automation(trigger, [(adv_data_t_const_ref, "x")], conf) |         await automation.build_automation(trigger, [(adv_data_t_const_ref, "x")], conf) | ||||||
|  |     for conf in config.get(CONF_ON_SCAN_END, []): | ||||||
|  |         trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) | ||||||
|  |         await automation.build_automation(trigger, [], conf) | ||||||
|  |  | ||||||
|     if CORE.using_esp_idf: |     if CORE.using_esp_idf: | ||||||
|         add_idf_sdkconfig_option("CONFIG_BT_ENABLED", True) |         add_idf_sdkconfig_option("CONFIG_BT_ENABLED", True) | ||||||
|  |  | ||||||
|  |     cg.add_define("USE_OTA_STATE_CALLBACK")  # To be notified when an OTA update starts | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ESP32_BLE_START_SCAN_ACTION_SCHEMA = cv.Schema( | ||||||
|  |     { | ||||||
|  |         cv.GenerateID(): cv.use_id(ESP32BLETracker), | ||||||
|  |         cv.Optional(CONF_CONTINUOUS, default=False): cv.templatable(cv.boolean), | ||||||
|  |     } | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @automation.register_action( | ||||||
|  |     "esp32_ble_tracker.start_scan", | ||||||
|  |     ESP32BLEStartScanAction, | ||||||
|  |     ESP32_BLE_START_SCAN_ACTION_SCHEMA, | ||||||
|  | ) | ||||||
|  | async def esp32_ble_tracker_start_scan_action_to_code( | ||||||
|  |     config, action_id, template_arg, args | ||||||
|  | ): | ||||||
|  |     paren = await cg.get_variable(config[CONF_ID]) | ||||||
|  |     var = cg.new_Pvariable(action_id, template_arg, paren) | ||||||
|  |     cg.add(var.set_continuous(config[CONF_CONTINUOUS])) | ||||||
|  |     return var | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ESP32_BLE_STOP_SCAN_ACTION_SCHEMA = automation.maybe_simple_id( | ||||||
|  |     cv.Schema( | ||||||
|  |         { | ||||||
|  |             cv.GenerateID(): cv.use_id(ESP32BLETracker), | ||||||
|  |         } | ||||||
|  |     ) | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @automation.register_action( | ||||||
|  |     "esp32_ble_tracker.stop_scan", | ||||||
|  |     ESP32BLEStopScanAction, | ||||||
|  |     ESP32_BLE_STOP_SCAN_ACTION_SCHEMA, | ||||||
|  | ) | ||||||
|  | async def esp32_ble_tracker_stop_scan_action_to_code( | ||||||
|  |     config, action_id, template_arg, args | ||||||
|  | ): | ||||||
|  |     var = cg.new_Pvariable(action_id, template_arg) | ||||||
|  |     await cg.register_parented(var, config[CONF_ID]) | ||||||
|  |     return var | ||||||
|  |  | ||||||
|  |  | ||||||
| async def register_ble_device(var, config): | async def register_ble_device(var, config): | ||||||
|     paren = await cg.get_variable(config[CONF_ESP32_BLE_ID]) |     paren = await cg.get_variable(config[CONF_ESP32_BLE_ID]) | ||||||
|   | |||||||
| @@ -76,6 +76,32 @@ class BLEManufacturerDataAdvertiseTrigger : public Trigger<const adv_data_t &>, | |||||||
|   ESPBTUUID uuid_; |   ESPBTUUID uuid_; | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | class BLEEndOfScanTrigger : public Trigger<>, public ESPBTDeviceListener { | ||||||
|  |  public: | ||||||
|  |   explicit BLEEndOfScanTrigger(ESP32BLETracker *parent) { parent->register_listener(this); } | ||||||
|  |  | ||||||
|  |   bool parse_device(const ESPBTDevice &device) override { return false; } | ||||||
|  |   void on_scan_end() override { this->trigger(); } | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | template<typename... Ts> class ESP32BLEStartScanAction : public Action<Ts...> { | ||||||
|  |  public: | ||||||
|  |   ESP32BLEStartScanAction(ESP32BLETracker *parent) : parent_(parent) {} | ||||||
|  |   TEMPLATABLE_VALUE(bool, continuous) | ||||||
|  |   void play(Ts... x) override { | ||||||
|  |     this->parent_->set_scan_continuous(this->continuous_.value(x...)); | ||||||
|  |     this->parent_->start_scan(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   ESP32BLETracker *parent_; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | template<typename... Ts> class ESP32BLEStopScanAction : public Action<Ts...>, public Parented<ESP32BLETracker> { | ||||||
|  |  public: | ||||||
|  |   void play(Ts... x) override { this->parent_->stop_scan(); } | ||||||
|  | }; | ||||||
|  |  | ||||||
| }  // namespace esp32_ble_tracker | }  // namespace esp32_ble_tracker | ||||||
| }  // namespace esphome | }  // namespace esphome | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,10 +1,11 @@ | |||||||
| #ifdef USE_ESP32 | #ifdef USE_ESP32 | ||||||
|  |  | ||||||
| #include "esp32_ble_tracker.h" | #include "esp32_ble_tracker.h" | ||||||
| #include "esphome/core/log.h" |  | ||||||
| #include "esphome/core/application.h" | #include "esphome/core/application.h" | ||||||
| #include "esphome/core/helpers.h" | #include "esphome/core/defines.h" | ||||||
| #include "esphome/core/hal.h" | #include "esphome/core/hal.h" | ||||||
|  | #include "esphome/core/helpers.h" | ||||||
|  | #include "esphome/core/log.h" | ||||||
|  |  | ||||||
| #include <nvs_flash.h> | #include <nvs_flash.h> | ||||||
| #include <freertos/FreeRTOSConfig.h> | #include <freertos/FreeRTOSConfig.h> | ||||||
| @@ -15,6 +16,10 @@ | |||||||
| #include <esp_gap_ble_api.h> | #include <esp_gap_ble_api.h> | ||||||
| #include <esp_bt_defs.h> | #include <esp_bt_defs.h> | ||||||
|  |  | ||||||
|  | #ifdef USE_OTA | ||||||
|  | #include "esphome/components/ota/ota_component.h" | ||||||
|  | #endif | ||||||
|  |  | ||||||
| #ifdef USE_ARDUINO | #ifdef USE_ARDUINO | ||||||
| #include <esp32-hal-bt.h> | #include <esp32-hal-bt.h> | ||||||
| #endif | #endif | ||||||
| @@ -46,13 +51,23 @@ void ESP32BLETracker::setup() { | |||||||
|   global_esp32_ble_tracker = this; |   global_esp32_ble_tracker = this; | ||||||
|   this->scan_result_lock_ = xSemaphoreCreateMutex(); |   this->scan_result_lock_ = xSemaphoreCreateMutex(); | ||||||
|   this->scan_end_lock_ = xSemaphoreCreateMutex(); |   this->scan_end_lock_ = xSemaphoreCreateMutex(); | ||||||
|  |   this->scanner_idle_ = true; | ||||||
|   if (!ESP32BLETracker::ble_setup()) { |   if (!ESP32BLETracker::ble_setup()) { | ||||||
|     this->mark_failed(); |     this->mark_failed(); | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   global_esp32_ble_tracker->start_scan_(true); | #ifdef USE_OTA | ||||||
|  |   ota::global_ota_component->add_on_state_callback([this](ota::OTAState state, float progress, uint8_t error) { | ||||||
|  |     if (state == ota::OTA_STARTED) { | ||||||
|  |       this->stop_scan(); | ||||||
|  |     } | ||||||
|  |   }); | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  |   if (this->scan_continuous_) { | ||||||
|  |     this->start_scan_(true); | ||||||
|  |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| void ESP32BLETracker::loop() { | void ESP32BLETracker::loop() { | ||||||
| @@ -68,14 +83,25 @@ void ESP32BLETracker::loop() { | |||||||
|     ble_event = this->ble_events_.pop(); |     ble_event = this->ble_events_.pop(); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   if (this->scanner_idle_) { | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|   bool connecting = false; |   bool connecting = false; | ||||||
|   for (auto *client : this->clients_) { |   for (auto *client : this->clients_) { | ||||||
|     if (client->state() == ClientState::CONNECTING || client->state() == ClientState::DISCOVERED) |     if (client->state() == ClientState::CONNECTING || client->state() == ClientState::DISCOVERED) | ||||||
|       connecting = true; |       connecting = true; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   if (!connecting && xSemaphoreTake(this->scan_end_lock_, 0L)) { |   if (!connecting && xSemaphoreTake(this->scan_end_lock_, 0L)) { | ||||||
|     xSemaphoreGive(this->scan_end_lock_); |     xSemaphoreGive(this->scan_end_lock_); | ||||||
|     global_esp32_ble_tracker->start_scan_(false); |     if (this->scan_continuous_) { | ||||||
|  |       this->start_scan_(false); | ||||||
|  |     } else if (xSemaphoreTake(this->scan_end_lock_, 0L) && !this->scanner_idle_) { | ||||||
|  |       xSemaphoreGive(this->scan_end_lock_); | ||||||
|  |       this->end_of_scan_(); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   if (xSemaphoreTake(this->scan_result_lock_, 5L / portTICK_PERIOD_MS)) { |   if (xSemaphoreTake(this->scan_result_lock_, 5L / portTICK_PERIOD_MS)) { | ||||||
| @@ -134,6 +160,22 @@ void ESP32BLETracker::loop() { | |||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | void ESP32BLETracker::start_scan() { | ||||||
|  |   if (xSemaphoreTake(this->scan_end_lock_, 0L)) { | ||||||
|  |     xSemaphoreGive(this->scan_end_lock_); | ||||||
|  |     this->start_scan_(true); | ||||||
|  |   } else { | ||||||
|  |     ESP_LOGW(TAG, "Scan requested when a scan is already in progress. Ignoring."); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void ESP32BLETracker::stop_scan() { | ||||||
|  |   ESP_LOGD(TAG, "Stopping scan."); | ||||||
|  |   this->scan_continuous_ = false; | ||||||
|  |   esp_ble_gap_stop_scanning(); | ||||||
|  |   this->cancel_timeout("scan"); | ||||||
|  | } | ||||||
|  |  | ||||||
| bool ESP32BLETracker::ble_setup() { | bool ESP32BLETracker::ble_setup() { | ||||||
|   // Initialize non-volatile storage for the bluetooth controller |   // Initialize non-volatile storage for the bluetooth controller | ||||||
|   esp_err_t err = nvs_flash_init(); |   esp_err_t err = nvs_flash_init(); | ||||||
| @@ -225,6 +267,7 @@ void ESP32BLETracker::start_scan_(bool first) { | |||||||
|       listener->on_scan_end(); |       listener->on_scan_end(); | ||||||
|   } |   } | ||||||
|   this->already_discovered_.clear(); |   this->already_discovered_.clear(); | ||||||
|  |   this->scanner_idle_ = false; | ||||||
|   this->scan_params_.scan_type = this->scan_active_ ? BLE_SCAN_TYPE_ACTIVE : BLE_SCAN_TYPE_PASSIVE; |   this->scan_params_.scan_type = this->scan_active_ ? BLE_SCAN_TYPE_ACTIVE : BLE_SCAN_TYPE_PASSIVE; | ||||||
|   this->scan_params_.own_addr_type = BLE_ADDR_TYPE_PUBLIC; |   this->scan_params_.own_addr_type = BLE_ADDR_TYPE_PUBLIC; | ||||||
|   this->scan_params_.scan_filter_policy = BLE_SCAN_FILTER_ALLOW_ALL; |   this->scan_params_.scan_filter_policy = BLE_SCAN_FILTER_ALLOW_ALL; | ||||||
| @@ -240,6 +283,22 @@ void ESP32BLETracker::start_scan_(bool first) { | |||||||
|   }); |   }); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | void ESP32BLETracker::end_of_scan_() { | ||||||
|  |   if (!xSemaphoreTake(this->scan_end_lock_, 0L)) { | ||||||
|  |     ESP_LOGW(TAG, "Cannot clean up end of scan!"); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   ESP_LOGD(TAG, "End of scan."); | ||||||
|  |   this->scanner_idle_ = true; | ||||||
|  |   this->already_discovered_.clear(); | ||||||
|  |   xSemaphoreGive(this->scan_end_lock_); | ||||||
|  |   this->cancel_timeout("scan"); | ||||||
|  |  | ||||||
|  |   for (auto *listener : this->listeners_) | ||||||
|  |     listener->on_scan_end(); | ||||||
|  | } | ||||||
|  |  | ||||||
| void ESP32BLETracker::register_client(ESPBTClient *client) { | void ESP32BLETracker::register_client(ESPBTClient *client) { | ||||||
|   client->app_id = ++this->app_id_; |   client->app_id = ++this->app_id_; | ||||||
|   this->clients_.push_back(client); |   this->clients_.push_back(client); | ||||||
| @@ -253,21 +312,21 @@ void ESP32BLETracker::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_ga | |||||||
| void ESP32BLETracker::real_gap_event_handler_(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) { | void ESP32BLETracker::real_gap_event_handler_(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) { | ||||||
|   switch (event) { |   switch (event) { | ||||||
|     case ESP_GAP_BLE_SCAN_RESULT_EVT: |     case ESP_GAP_BLE_SCAN_RESULT_EVT: | ||||||
|       global_esp32_ble_tracker->gap_scan_result_(param->scan_rst); |       this->gap_scan_result_(param->scan_rst); | ||||||
|       break; |       break; | ||||||
|     case ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT: |     case ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT: | ||||||
|       global_esp32_ble_tracker->gap_scan_set_param_complete_(param->scan_param_cmpl); |       this->gap_scan_set_param_complete_(param->scan_param_cmpl); | ||||||
|       break; |       break; | ||||||
|     case ESP_GAP_BLE_SCAN_START_COMPLETE_EVT: |     case ESP_GAP_BLE_SCAN_START_COMPLETE_EVT: | ||||||
|       global_esp32_ble_tracker->gap_scan_start_complete_(param->scan_start_cmpl); |       this->gap_scan_start_complete_(param->scan_start_cmpl); | ||||||
|       break; |       break; | ||||||
|     case ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT: |     case ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT: | ||||||
|       global_esp32_ble_tracker->gap_scan_stop_complete_(param->scan_stop_cmpl); |       this->gap_scan_stop_complete_(param->scan_stop_cmpl); | ||||||
|       break; |       break; | ||||||
|     default: |     default: | ||||||
|       break; |       break; | ||||||
|   } |   } | ||||||
|   for (auto *client : global_esp32_ble_tracker->clients_) { |   for (auto *client : this->clients_) { | ||||||
|     client->gap_event_handler(event, param); |     client->gap_event_handler(event, param); | ||||||
|   } |   } | ||||||
| } | } | ||||||
| @@ -305,7 +364,7 @@ void ESP32BLETracker::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_i | |||||||
|  |  | ||||||
| void ESP32BLETracker::real_gattc_event_handler_(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, | void ESP32BLETracker::real_gattc_event_handler_(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, | ||||||
|                                                 esp_ble_gattc_cb_param_t *param) { |                                                 esp_ble_gattc_cb_param_t *param) { | ||||||
|   for (auto *client : global_esp32_ble_tracker->clients_) { |   for (auto *client : this->clients_) { | ||||||
|     client->gattc_event_handler(event, gattc_if, param); |     client->gattc_event_handler(event, gattc_if, param); | ||||||
|   } |   } | ||||||
| } | } | ||||||
| @@ -719,7 +778,9 @@ void ESP32BLETracker::dump_config() { | |||||||
|   ESP_LOGCONFIG(TAG, "  Scan Interval: %.1f ms", this->scan_interval_ * 0.625f); |   ESP_LOGCONFIG(TAG, "  Scan Interval: %.1f ms", this->scan_interval_ * 0.625f); | ||||||
|   ESP_LOGCONFIG(TAG, "  Scan Window: %.1f ms", this->scan_window_ * 0.625f); |   ESP_LOGCONFIG(TAG, "  Scan Window: %.1f ms", this->scan_window_ * 0.625f); | ||||||
|   ESP_LOGCONFIG(TAG, "  Scan Type: %s", this->scan_active_ ? "ACTIVE" : "PASSIVE"); |   ESP_LOGCONFIG(TAG, "  Scan Type: %s", this->scan_active_ ? "ACTIVE" : "PASSIVE"); | ||||||
|  |   ESP_LOGCONFIG(TAG, "  Continuous Scanning: %s", this->scan_continuous_ ? "True" : "False"); | ||||||
| } | } | ||||||
|  |  | ||||||
| void ESP32BLETracker::print_bt_device_info(const ESPBTDevice &device) { | void ESP32BLETracker::print_bt_device_info(const ESPBTDevice &device) { | ||||||
|   const uint64_t address = device.address_uint64(); |   const uint64_t address = device.address_uint64(); | ||||||
|   for (auto &disc : this->already_discovered_) { |   for (auto &disc : this->already_discovered_) { | ||||||
|   | |||||||
| @@ -1,6 +1,7 @@ | |||||||
| #pragma once | #pragma once | ||||||
|  |  | ||||||
| #include "esphome/core/component.h" | #include "esphome/core/component.h" | ||||||
|  | #include "esphome/core/automation.h" | ||||||
| #include "esphome/core/helpers.h" | #include "esphome/core/helpers.h" | ||||||
| #include "queue.h" | #include "queue.h" | ||||||
|  |  | ||||||
| @@ -171,6 +172,7 @@ class ESP32BLETracker : public Component { | |||||||
|   void set_scan_interval(uint32_t scan_interval) { scan_interval_ = scan_interval; } |   void set_scan_interval(uint32_t scan_interval) { scan_interval_ = scan_interval; } | ||||||
|   void set_scan_window(uint32_t scan_window) { scan_window_ = scan_window; } |   void set_scan_window(uint32_t scan_window) { scan_window_ = scan_window; } | ||||||
|   void set_scan_active(bool scan_active) { scan_active_ = scan_active; } |   void set_scan_active(bool scan_active) { scan_active_ = scan_active; } | ||||||
|  |   void set_scan_continuous(bool scan_continuous) { scan_continuous_ = scan_continuous; } | ||||||
|  |  | ||||||
|   /// Setup the FreeRTOS task and the Bluetooth stack. |   /// Setup the FreeRTOS task and the Bluetooth stack. | ||||||
|   void setup() override; |   void setup() override; | ||||||
| @@ -188,11 +190,16 @@ class ESP32BLETracker : public Component { | |||||||
|  |  | ||||||
|   void print_bt_device_info(const ESPBTDevice &device); |   void print_bt_device_info(const ESPBTDevice &device); | ||||||
|  |  | ||||||
|  |   void start_scan(); | ||||||
|  |   void stop_scan(); | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   /// The FreeRTOS task managing the bluetooth interface. |   /// The FreeRTOS task managing the bluetooth interface. | ||||||
|   static bool ble_setup(); |   static bool ble_setup(); | ||||||
|   /// Start a single scan by setting up the parameters and doing some esp-idf calls. |   /// Start a single scan by setting up the parameters and doing some esp-idf calls. | ||||||
|   void start_scan_(bool first); |   void start_scan_(bool first); | ||||||
|  |   /// Called when a scan ends | ||||||
|  |   void end_of_scan_(); | ||||||
|   /// Callback that will handle all GAP events and redistribute them to other callbacks. |   /// Callback that will handle all GAP events and redistribute them to other callbacks. | ||||||
|   static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param); |   static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param); | ||||||
|   void real_gap_event_handler_(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param); |   void real_gap_event_handler_(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param); | ||||||
| @@ -221,7 +228,9 @@ class ESP32BLETracker : public Component { | |||||||
|   uint32_t scan_duration_; |   uint32_t scan_duration_; | ||||||
|   uint32_t scan_interval_; |   uint32_t scan_interval_; | ||||||
|   uint32_t scan_window_; |   uint32_t scan_window_; | ||||||
|  |   bool scan_continuous_; | ||||||
|   bool scan_active_; |   bool scan_active_; | ||||||
|  |   bool scanner_idle_; | ||||||
|   SemaphoreHandle_t scan_result_lock_; |   SemaphoreHandle_t scan_result_lock_; | ||||||
|   SemaphoreHandle_t scan_end_lock_; |   SemaphoreHandle_t scan_end_lock_; | ||||||
|   size_t scan_result_index_{0}; |   size_t scan_result_index_{0}; | ||||||
|   | |||||||
| @@ -243,17 +243,34 @@ class ESP8266Preferences : public ESPPreferences { | |||||||
|       } |       } | ||||||
|     } |     } | ||||||
|     if (erase_res != SPI_FLASH_RESULT_OK) { |     if (erase_res != SPI_FLASH_RESULT_OK) { | ||||||
|       ESP_LOGV(TAG, "Erase ESP8266 flash failed!"); |       ESP_LOGE(TAG, "Erase ESP8266 flash failed!"); | ||||||
|       return false; |       return false; | ||||||
|     } |     } | ||||||
|     if (write_res != SPI_FLASH_RESULT_OK) { |     if (write_res != SPI_FLASH_RESULT_OK) { | ||||||
|       ESP_LOGV(TAG, "Write ESP8266 flash failed!"); |       ESP_LOGE(TAG, "Write ESP8266 flash failed!"); | ||||||
|       return false; |       return false; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     s_flash_dirty = false; |     s_flash_dirty = false; | ||||||
|     return true; |     return true; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   bool reset() override { | ||||||
|  |     ESP_LOGD(TAG, "Cleaning up preferences in flash..."); | ||||||
|  |     SpiFlashOpResult erase_res; | ||||||
|  |     { | ||||||
|  |       InterruptLock lock; | ||||||
|  |       erase_res = spi_flash_erase_sector(get_esp8266_flash_sector()); | ||||||
|  |     } | ||||||
|  |     if (erase_res != SPI_FLASH_RESULT_OK) { | ||||||
|  |       ESP_LOGE(TAG, "Erase ESP8266 flash failed!"); | ||||||
|  |       return false; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // Protect flash from writing till restart | ||||||
|  |     s_prevent_write = true; | ||||||
|  |     return true; | ||||||
|  |   } | ||||||
| }; | }; | ||||||
|  |  | ||||||
| void setup_preferences() { | void setup_preferences() { | ||||||
|   | |||||||
| @@ -31,6 +31,7 @@ EthernetType = ethernet_ns.enum("EthernetType") | |||||||
| ETHERNET_TYPES = { | ETHERNET_TYPES = { | ||||||
|     "LAN8720": EthernetType.ETHERNET_TYPE_LAN8720, |     "LAN8720": EthernetType.ETHERNET_TYPE_LAN8720, | ||||||
|     "TLK110": EthernetType.ETHERNET_TYPE_TLK110, |     "TLK110": EthernetType.ETHERNET_TYPE_TLK110, | ||||||
|  |     "IP101": EthernetType.ETHERNET_TYPE_IP101, | ||||||
| } | } | ||||||
|  |  | ||||||
| eth_clock_mode_t = cg.global_ns.enum("eth_clock_mode_t") | eth_clock_mode_t = cg.global_ns.enum("eth_clock_mode_t") | ||||||
|   | |||||||
| @@ -6,6 +6,7 @@ | |||||||
| #ifdef USE_ESP32_FRAMEWORK_ARDUINO | #ifdef USE_ESP32_FRAMEWORK_ARDUINO | ||||||
|  |  | ||||||
| #include <eth_phy/phy_lan8720.h> | #include <eth_phy/phy_lan8720.h> | ||||||
|  | #include <eth_phy/phy_ip101.h> | ||||||
| #include <eth_phy/phy_tlk110.h> | #include <eth_phy/phy_tlk110.h> | ||||||
| #include <lwip/dns.h> | #include <lwip/dns.h> | ||||||
|  |  | ||||||
| @@ -33,6 +34,7 @@ EthernetComponent *global_eth_component;  // NOLINT(cppcoreguidelines-avoid-non- | |||||||
|   } |   } | ||||||
|  |  | ||||||
| EthernetComponent::EthernetComponent() { global_eth_component = this; } | EthernetComponent::EthernetComponent() { global_eth_component = this; } | ||||||
|  |  | ||||||
| void EthernetComponent::setup() { | void EthernetComponent::setup() { | ||||||
|   ESP_LOGCONFIG(TAG, "Setting up Ethernet..."); |   ESP_LOGCONFIG(TAG, "Setting up Ethernet..."); | ||||||
|  |  | ||||||
| @@ -52,6 +54,10 @@ void EthernetComponent::setup() { | |||||||
|       memcpy(&this->eth_config_, &phy_tlk110_default_ethernet_config, sizeof(eth_config_t)); |       memcpy(&this->eth_config_, &phy_tlk110_default_ethernet_config, sizeof(eth_config_t)); | ||||||
|       break; |       break; | ||||||
|     } |     } | ||||||
|  |     case ETHERNET_TYPE_IP101: { | ||||||
|  |       memcpy(&this->eth_config_, &phy_ip101_default_ethernet_config, sizeof(eth_config_t)); | ||||||
|  |       break; | ||||||
|  |     } | ||||||
|     default: { |     default: { | ||||||
|       this->mark_failed(); |       this->mark_failed(); | ||||||
|       return; |       return; | ||||||
| @@ -76,6 +82,7 @@ void EthernetComponent::setup() { | |||||||
|   err = esp_eth_enable(); |   err = esp_eth_enable(); | ||||||
|   ESPHL_ERROR_CHECK(err, "ETH enable error"); |   ESPHL_ERROR_CHECK(err, "ETH enable error"); | ||||||
| } | } | ||||||
|  |  | ||||||
| void EthernetComponent::loop() { | void EthernetComponent::loop() { | ||||||
|   const uint32_t now = millis(); |   const uint32_t now = millis(); | ||||||
|  |  | ||||||
| @@ -115,16 +122,39 @@ void EthernetComponent::loop() { | |||||||
|       break; |       break; | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| void EthernetComponent::dump_config() { | void EthernetComponent::dump_config() { | ||||||
|  |   std::string eth_type; | ||||||
|  |   switch (this->type_) { | ||||||
|  |     case ETHERNET_TYPE_LAN8720: | ||||||
|  |       eth_type = "LAN8720"; | ||||||
|  |       break; | ||||||
|  |  | ||||||
|  |     case ETHERNET_TYPE_TLK110: | ||||||
|  |       eth_type = "TLK110"; | ||||||
|  |       break; | ||||||
|  |  | ||||||
|  |     case ETHERNET_TYPE_IP101: | ||||||
|  |       eth_type = "IP101"; | ||||||
|  |       break; | ||||||
|  |  | ||||||
|  |     default: | ||||||
|  |       eth_type = "Unknown"; | ||||||
|  |       break; | ||||||
|  |   } | ||||||
|  |  | ||||||
|   ESP_LOGCONFIG(TAG, "Ethernet:"); |   ESP_LOGCONFIG(TAG, "Ethernet:"); | ||||||
|   this->dump_connect_params_(); |   this->dump_connect_params_(); | ||||||
|   LOG_PIN("  Power Pin: ", this->power_pin_); |   LOG_PIN("  Power Pin: ", this->power_pin_); | ||||||
|   ESP_LOGCONFIG(TAG, "  MDC Pin: %u", this->mdc_pin_); |   ESP_LOGCONFIG(TAG, "  MDC Pin: %u", this->mdc_pin_); | ||||||
|   ESP_LOGCONFIG(TAG, "  MDIO Pin: %u", this->mdio_pin_); |   ESP_LOGCONFIG(TAG, "  MDIO Pin: %u", this->mdio_pin_); | ||||||
|   ESP_LOGCONFIG(TAG, "  Type: %s", this->type_ == ETHERNET_TYPE_LAN8720 ? "LAN8720" : "TLK110"); |   ESP_LOGCONFIG(TAG, "  Type: %s", eth_type.c_str()); | ||||||
| } | } | ||||||
|  |  | ||||||
| float EthernetComponent::get_setup_priority() const { return setup_priority::WIFI; } | float EthernetComponent::get_setup_priority() const { return setup_priority::WIFI; } | ||||||
|  |  | ||||||
| bool EthernetComponent::can_proceed() { return this->is_connected(); } | bool EthernetComponent::can_proceed() { return this->is_connected(); } | ||||||
|  |  | ||||||
| network::IPAddress EthernetComponent::get_ip_address() { | network::IPAddress EthernetComponent::get_ip_address() { | ||||||
|   tcpip_adapter_ip_info_t ip; |   tcpip_adapter_ip_info_t ip; | ||||||
|   tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_ETH, &ip); |   tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_ETH, &ip); | ||||||
| @@ -213,17 +243,21 @@ void EthernetComponent::start_connect_() { | |||||||
|   this->connect_begin_ = millis(); |   this->connect_begin_ = millis(); | ||||||
|   this->status_set_warning(); |   this->status_set_warning(); | ||||||
| } | } | ||||||
|  |  | ||||||
| void EthernetComponent::eth_phy_config_gpio() { | void EthernetComponent::eth_phy_config_gpio() { | ||||||
|   phy_rmii_configure_data_interface_pins(); |   phy_rmii_configure_data_interface_pins(); | ||||||
|   phy_rmii_smi_configure_pins(global_eth_component->mdc_pin_, global_eth_component->mdio_pin_); |   phy_rmii_smi_configure_pins(global_eth_component->mdc_pin_, global_eth_component->mdio_pin_); | ||||||
| } | } | ||||||
|  |  | ||||||
| void EthernetComponent::eth_phy_power_enable(bool enable) { | void EthernetComponent::eth_phy_power_enable(bool enable) { | ||||||
|   global_eth_component->power_pin_->digital_write(enable); |   global_eth_component->power_pin_->digital_write(enable); | ||||||
|   // power up takes some time, datasheet says max 300µs |   // power up takes some time, datasheet says max 300µs | ||||||
|   delay(1); |   delay(1); | ||||||
|   global_eth_component->orig_power_enable_fun_(enable); |   global_eth_component->orig_power_enable_fun_(enable); | ||||||
| } | } | ||||||
|  |  | ||||||
| bool EthernetComponent::is_connected() { return this->state_ == EthernetComponentState::CONNECTED; } | bool EthernetComponent::is_connected() { return this->state_ == EthernetComponentState::CONNECTED; } | ||||||
|  |  | ||||||
| void EthernetComponent::dump_connect_params_() { | void EthernetComponent::dump_connect_params_() { | ||||||
|   tcpip_adapter_ip_info_t ip; |   tcpip_adapter_ip_info_t ip; | ||||||
|   tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_ETH, &ip); |   tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_ETH, &ip); | ||||||
| @@ -250,6 +284,7 @@ void EthernetComponent::dump_connect_params_() { | |||||||
|   ESP_LOGCONFIG(TAG, "  Link Up: %s", YESNO(this->eth_config_.phy_check_link())); |   ESP_LOGCONFIG(TAG, "  Link Up: %s", YESNO(this->eth_config_.phy_check_link())); | ||||||
|   ESP_LOGCONFIG(TAG, "  Link Speed: %u", this->eth_config_.phy_get_speed_mode() ? 100 : 10); |   ESP_LOGCONFIG(TAG, "  Link Speed: %u", this->eth_config_.phy_get_speed_mode() ? 100 : 10); | ||||||
| } | } | ||||||
|  |  | ||||||
| void EthernetComponent::set_phy_addr(uint8_t phy_addr) { this->phy_addr_ = phy_addr; } | void EthernetComponent::set_phy_addr(uint8_t phy_addr) { this->phy_addr_ = phy_addr; } | ||||||
| void EthernetComponent::set_power_pin(GPIOPin *power_pin) { this->power_pin_ = power_pin; } | void EthernetComponent::set_power_pin(GPIOPin *power_pin) { this->power_pin_ = power_pin; } | ||||||
| void EthernetComponent::set_mdc_pin(uint8_t mdc_pin) { this->mdc_pin_ = mdc_pin; } | void EthernetComponent::set_mdc_pin(uint8_t mdc_pin) { this->mdc_pin_ = mdc_pin; } | ||||||
| @@ -257,12 +292,14 @@ void EthernetComponent::set_mdio_pin(uint8_t mdio_pin) { this->mdio_pin_ = mdio_ | |||||||
| void EthernetComponent::set_type(EthernetType type) { this->type_ = type; } | void EthernetComponent::set_type(EthernetType type) { this->type_ = type; } | ||||||
| void EthernetComponent::set_clk_mode(eth_clock_mode_t clk_mode) { this->clk_mode_ = clk_mode; } | void EthernetComponent::set_clk_mode(eth_clock_mode_t clk_mode) { this->clk_mode_ = clk_mode; } | ||||||
| void EthernetComponent::set_manual_ip(const ManualIP &manual_ip) { this->manual_ip_ = manual_ip; } | void EthernetComponent::set_manual_ip(const ManualIP &manual_ip) { this->manual_ip_ = manual_ip; } | ||||||
|  |  | ||||||
| std::string EthernetComponent::get_use_address() const { | std::string EthernetComponent::get_use_address() const { | ||||||
|   if (this->use_address_.empty()) { |   if (this->use_address_.empty()) { | ||||||
|     return App.get_name() + ".local"; |     return App.get_name() + ".local"; | ||||||
|   } |   } | ||||||
|   return this->use_address_; |   return this->use_address_; | ||||||
| } | } | ||||||
|  |  | ||||||
| void EthernetComponent::set_use_address(const std::string &use_address) { this->use_address_ = use_address; } | void EthernetComponent::set_use_address(const std::string &use_address) { this->use_address_ = use_address; } | ||||||
|  |  | ||||||
| }  // namespace ethernet | }  // namespace ethernet | ||||||
|   | |||||||
| @@ -17,6 +17,7 @@ namespace ethernet { | |||||||
| enum EthernetType { | enum EthernetType { | ||||||
|   ETHERNET_TYPE_LAN8720 = 0, |   ETHERNET_TYPE_LAN8720 = 0, | ||||||
|   ETHERNET_TYPE_TLK110, |   ETHERNET_TYPE_TLK110, | ||||||
|  |   ETHERNET_TYPE_IP101, | ||||||
| }; | }; | ||||||
|  |  | ||||||
| struct ManualIP { | struct ManualIP { | ||||||
|   | |||||||
							
								
								
									
										5
									
								
								esphome/components/factory_reset/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								esphome/components/factory_reset/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | |||||||
|  | import esphome.codegen as cg | ||||||
|  |  | ||||||
|  | CODEOWNERS = ["@anatoly-savchenkov"] | ||||||
|  |  | ||||||
|  | factory_reset_ns = cg.esphome_ns.namespace("factory_reset") | ||||||
							
								
								
									
										30
									
								
								esphome/components/factory_reset/button/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								esphome/components/factory_reset/button/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,30 @@ | |||||||
|  | import esphome.codegen as cg | ||||||
|  | import esphome.config_validation as cv | ||||||
|  | from esphome.components import button | ||||||
|  | from esphome.const import ( | ||||||
|  |     CONF_ID, | ||||||
|  |     DEVICE_CLASS_RESTART, | ||||||
|  |     ENTITY_CATEGORY_CONFIG, | ||||||
|  |     ICON_RESTART_ALERT, | ||||||
|  | ) | ||||||
|  | from .. import factory_reset_ns | ||||||
|  |  | ||||||
|  | FactoryResetButton = factory_reset_ns.class_( | ||||||
|  |     "FactoryResetButton", button.Button, cg.Component | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | CONFIG_SCHEMA = ( | ||||||
|  |     button.button_schema( | ||||||
|  |         device_class=DEVICE_CLASS_RESTART, | ||||||
|  |         entity_category=ENTITY_CATEGORY_CONFIG, | ||||||
|  |         icon=ICON_RESTART_ALERT, | ||||||
|  |     ) | ||||||
|  |     .extend({cv.GenerateID(): cv.declare_id(FactoryResetButton)}) | ||||||
|  |     .extend(cv.COMPONENT_SCHEMA) | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | async def to_code(config): | ||||||
|  |     var = cg.new_Pvariable(config[CONF_ID]) | ||||||
|  |     await cg.register_component(var, config) | ||||||
|  |     await button.register_button(var, config) | ||||||
| @@ -0,0 +1,21 @@ | |||||||
|  | #include "factory_reset_button.h" | ||||||
|  | #include "esphome/core/hal.h" | ||||||
|  | #include "esphome/core/log.h" | ||||||
|  | #include "esphome/core/application.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace factory_reset { | ||||||
|  |  | ||||||
|  | static const char *const TAG = "factory_reset.button"; | ||||||
|  |  | ||||||
|  | void FactoryResetButton::dump_config() { LOG_BUTTON("", "Factory Reset Button", this); } | ||||||
|  | void FactoryResetButton::press_action() { | ||||||
|  |   ESP_LOGI(TAG, "Resetting to factory defaults..."); | ||||||
|  |   // Let MQTT settle a bit | ||||||
|  |   delay(100);  // NOLINT | ||||||
|  |   global_preferences->reset(); | ||||||
|  |   App.safe_reboot(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | }  // namespace factory_reset | ||||||
|  | }  // namespace esphome | ||||||
| @@ -0,0 +1,18 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include "esphome/core/component.h" | ||||||
|  | #include "esphome/components/button/button.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace factory_reset { | ||||||
|  |  | ||||||
|  | class FactoryResetButton : public button::Button, public Component { | ||||||
|  |  public: | ||||||
|  |   void dump_config() override; | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   void press_action() override; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | }  // namespace factory_reset | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										35
									
								
								esphome/components/factory_reset/switch/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								esphome/components/factory_reset/switch/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,35 @@ | |||||||
|  | import esphome.codegen as cg | ||||||
|  | import esphome.config_validation as cv | ||||||
|  | from esphome.components import switch | ||||||
|  | from esphome.const import ( | ||||||
|  |     CONF_ENTITY_CATEGORY, | ||||||
|  |     CONF_ID, | ||||||
|  |     CONF_INVERTED, | ||||||
|  |     CONF_ICON, | ||||||
|  |     ENTITY_CATEGORY_CONFIG, | ||||||
|  |     ICON_RESTART_ALERT, | ||||||
|  | ) | ||||||
|  | from .. import factory_reset_ns | ||||||
|  |  | ||||||
|  | FactoryResetSwitch = factory_reset_ns.class_( | ||||||
|  |     "FactoryResetSwitch", switch.Switch, cg.Component | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | CONFIG_SCHEMA = switch.SWITCH_SCHEMA.extend( | ||||||
|  |     { | ||||||
|  |         cv.GenerateID(): cv.declare_id(FactoryResetSwitch), | ||||||
|  |         cv.Optional(CONF_INVERTED): cv.invalid( | ||||||
|  |             "Factory Reset switches do not support inverted mode!" | ||||||
|  |         ), | ||||||
|  |         cv.Optional(CONF_ICON, default=ICON_RESTART_ALERT): cv.icon, | ||||||
|  |         cv.Optional( | ||||||
|  |             CONF_ENTITY_CATEGORY, default=ENTITY_CATEGORY_CONFIG | ||||||
|  |         ): cv.entity_category, | ||||||
|  |     } | ||||||
|  | ).extend(cv.COMPONENT_SCHEMA) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | async def to_code(config): | ||||||
|  |     var = cg.new_Pvariable(config[CONF_ID]) | ||||||
|  |     await cg.register_component(var, config) | ||||||
|  |     await switch.register_switch(var, config) | ||||||
| @@ -0,0 +1,26 @@ | |||||||
|  | #include "factory_reset_switch.h" | ||||||
|  | #include "esphome/core/hal.h" | ||||||
|  | #include "esphome/core/log.h" | ||||||
|  | #include "esphome/core/application.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace factory_reset { | ||||||
|  |  | ||||||
|  | static const char *const TAG = "factory_reset.switch"; | ||||||
|  |  | ||||||
|  | void FactoryResetSwitch::dump_config() { LOG_SWITCH("", "Factory Reset Switch", this); } | ||||||
|  | void FactoryResetSwitch::write_state(bool state) { | ||||||
|  |   // Acknowledge | ||||||
|  |   this->publish_state(false); | ||||||
|  |  | ||||||
|  |   if (state) { | ||||||
|  |     ESP_LOGI(TAG, "Resetting to factory defaults..."); | ||||||
|  |     // Let MQTT settle a bit | ||||||
|  |     delay(100);  // NOLINT | ||||||
|  |     global_preferences->reset(); | ||||||
|  |     App.safe_reboot(); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | }  // namespace factory_reset | ||||||
|  | }  // namespace esphome | ||||||
| @@ -0,0 +1,18 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include "esphome/core/component.h" | ||||||
|  | #include "esphome/components/switch/switch.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace factory_reset { | ||||||
|  |  | ||||||
|  | class FactoryResetSwitch : public switch_::Switch, public Component { | ||||||
|  |  public: | ||||||
|  |   void dump_config() override; | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   void write_state(bool state) override; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | }  // namespace factory_reset | ||||||
|  | }  // namespace esphome | ||||||
| @@ -51,7 +51,7 @@ void FingerprintGrowComponent::update() { | |||||||
| void FingerprintGrowComponent::setup() { | void FingerprintGrowComponent::setup() { | ||||||
|   ESP_LOGCONFIG(TAG, "Setting up Grow Fingerprint Reader..."); |   ESP_LOGCONFIG(TAG, "Setting up Grow Fingerprint Reader..."); | ||||||
|   if (this->check_password_()) { |   if (this->check_password_()) { | ||||||
|     if (this->new_password_ != nullptr) { |     if (this->new_password_ != -1) { | ||||||
|       if (this->set_password_()) |       if (this->set_password_()) | ||||||
|         return; |         return; | ||||||
|     } else { |     } else { | ||||||
| @@ -202,9 +202,9 @@ bool FingerprintGrowComponent::check_password_() { | |||||||
| } | } | ||||||
|  |  | ||||||
| bool FingerprintGrowComponent::set_password_() { | bool FingerprintGrowComponent::set_password_() { | ||||||
|   ESP_LOGI(TAG, "Setting new password: %d", *this->new_password_); |   ESP_LOGI(TAG, "Setting new password: %d", this->new_password_); | ||||||
|   this->data_ = {SET_PASSWORD, (uint8_t)(*this->new_password_ >> 24), (uint8_t)(*this->new_password_ >> 16), |   this->data_ = {SET_PASSWORD, (uint8_t)(this->new_password_ >> 24), (uint8_t)(this->new_password_ >> 16), | ||||||
|                  (uint8_t)(*this->new_password_ >> 8), (uint8_t)(*this->new_password_ & 0xFF)}; |                  (uint8_t)(this->new_password_ >> 8), (uint8_t)(this->new_password_ & 0xFF)}; | ||||||
|   if (this->send_command_() == OK) { |   if (this->send_command_() == OK) { | ||||||
|     ESP_LOGI(TAG, "New password successfully set"); |     ESP_LOGI(TAG, "New password successfully set"); | ||||||
|     ESP_LOGI(TAG, "Define the new password in your configuration and reflash now"); |     ESP_LOGI(TAG, "Define the new password in your configuration and reflash now"); | ||||||
|   | |||||||
| @@ -96,7 +96,7 @@ class FingerprintGrowComponent : public PollingComponent, public uart::UARTDevic | |||||||
|   } |   } | ||||||
|   void set_sensing_pin(GPIOPin *sensing_pin) { this->sensing_pin_ = sensing_pin; } |   void set_sensing_pin(GPIOPin *sensing_pin) { this->sensing_pin_ = sensing_pin; } | ||||||
|   void set_password(uint32_t password) { this->password_ = password; } |   void set_password(uint32_t password) { this->password_ = password; } | ||||||
|   void set_new_password(uint32_t new_password) { this->new_password_ = &new_password; } |   void set_new_password(uint32_t new_password) { this->new_password_ = new_password; } | ||||||
|   void set_fingerprint_count_sensor(sensor::Sensor *fingerprint_count_sensor) { |   void set_fingerprint_count_sensor(sensor::Sensor *fingerprint_count_sensor) { | ||||||
|     this->fingerprint_count_sensor_ = fingerprint_count_sensor; |     this->fingerprint_count_sensor_ = fingerprint_count_sensor; | ||||||
|   } |   } | ||||||
| @@ -153,7 +153,7 @@ class FingerprintGrowComponent : public PollingComponent, public uart::UARTDevic | |||||||
|   uint8_t address_[4] = {0xFF, 0xFF, 0xFF, 0xFF}; |   uint8_t address_[4] = {0xFF, 0xFF, 0xFF, 0xFF}; | ||||||
|   uint16_t capacity_ = 64; |   uint16_t capacity_ = 64; | ||||||
|   uint32_t password_ = 0x0; |   uint32_t password_ = 0x0; | ||||||
|   uint32_t *new_password_{nullptr}; |   uint32_t new_password_ = -1; | ||||||
|   GPIOPin *sensing_pin_{nullptr}; |   GPIOPin *sensing_pin_{nullptr}; | ||||||
|   uint8_t enrollment_image_ = 0; |   uint8_t enrollment_image_ = 0; | ||||||
|   uint16_t enrollment_slot_ = 0; |   uint16_t enrollment_slot_ = 0; | ||||||
|   | |||||||
| @@ -1,6 +1,7 @@ | |||||||
| import functools | import functools | ||||||
| from pathlib import Path | from pathlib import Path | ||||||
| import hashlib | import hashlib | ||||||
|  | import os | ||||||
| import re | import re | ||||||
|  |  | ||||||
| import requests | import requests | ||||||
| @@ -9,6 +10,7 @@ from esphome import core | |||||||
| from esphome.components import display | from esphome.components import display | ||||||
| import esphome.config_validation as cv | import esphome.config_validation as cv | ||||||
| import esphome.codegen as cg | import esphome.codegen as cg | ||||||
|  | from esphome.helpers import copy_file_if_changed | ||||||
| from esphome.const import ( | from esphome.const import ( | ||||||
|     CONF_FAMILY, |     CONF_FAMILY, | ||||||
|     CONF_FILE, |     CONF_FILE, | ||||||
| @@ -88,21 +90,33 @@ def validate_truetype_file(value): | |||||||
|     return cv.file_(value) |     return cv.file_(value) | ||||||
|  |  | ||||||
|  |  | ||||||
| def _compute_gfonts_local_path(value) -> Path: | def _compute_local_font_dir(name) -> Path: | ||||||
|     name = f"{value[CONF_FAMILY]}@{value[CONF_WEIGHT]}@{value[CONF_ITALIC]}@v1" |  | ||||||
|     base_dir = Path(CORE.config_dir) / ".esphome" / DOMAIN |     base_dir = Path(CORE.config_dir) / ".esphome" / DOMAIN | ||||||
|     h = hashlib.new("sha256") |     h = hashlib.new("sha256") | ||||||
|     h.update(name.encode()) |     h.update(name.encode()) | ||||||
|     return base_dir / h.hexdigest()[:8] / "font.ttf" |     return base_dir / h.hexdigest()[:8] | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def _compute_gfonts_local_path(value) -> Path: | ||||||
|  |     name = f"{value[CONF_FAMILY]}@{value[CONF_WEIGHT]}@{value[CONF_ITALIC]}@v1" | ||||||
|  |     return _compute_local_font_dir(name) / "font.ttf" | ||||||
|  |  | ||||||
|  |  | ||||||
| TYPE_LOCAL = "local" | TYPE_LOCAL = "local" | ||||||
|  | TYPE_LOCAL_BITMAP = "local_bitmap" | ||||||
| TYPE_GFONTS = "gfonts" | TYPE_GFONTS = "gfonts" | ||||||
| LOCAL_SCHEMA = cv.Schema( | LOCAL_SCHEMA = cv.Schema( | ||||||
|     { |     { | ||||||
|         cv.Required(CONF_PATH): validate_truetype_file, |         cv.Required(CONF_PATH): validate_truetype_file, | ||||||
|     } |     } | ||||||
| ) | ) | ||||||
|  |  | ||||||
|  | LOCAL_BITMAP_SCHEMA = cv.Schema( | ||||||
|  |     { | ||||||
|  |         cv.Required(CONF_PATH): cv.file_, | ||||||
|  |     } | ||||||
|  | ) | ||||||
|  |  | ||||||
| CONF_ITALIC = "italic" | CONF_ITALIC = "italic" | ||||||
| FONT_WEIGHTS = { | FONT_WEIGHTS = { | ||||||
|     "thin": 100, |     "thin": 100, | ||||||
| @@ -132,7 +146,7 @@ def download_gfonts(value): | |||||||
|     if path.is_file(): |     if path.is_file(): | ||||||
|         return value |         return value | ||||||
|     try: |     try: | ||||||
|         req = requests.get(url) |         req = requests.get(url, timeout=30) | ||||||
|         req.raise_for_status() |         req.raise_for_status() | ||||||
|     except requests.exceptions.RequestException as e: |     except requests.exceptions.RequestException as e: | ||||||
|         raise cv.Invalid( |         raise cv.Invalid( | ||||||
| @@ -148,7 +162,7 @@ def download_gfonts(value): | |||||||
|  |  | ||||||
|     ttf_url = match.group(1) |     ttf_url = match.group(1) | ||||||
|     try: |     try: | ||||||
|         req = requests.get(ttf_url) |         req = requests.get(ttf_url, timeout=30) | ||||||
|         req.raise_for_status() |         req.raise_for_status() | ||||||
|     except requests.exceptions.RequestException as e: |     except requests.exceptions.RequestException as e: | ||||||
|         raise cv.Invalid(f"Could not download ttf file for {name} ({ttf_url}): {e}") |         raise cv.Invalid(f"Could not download ttf file for {name} ({ttf_url}): {e}") | ||||||
| @@ -185,6 +199,15 @@ def validate_file_shorthand(value): | |||||||
|         if weight is not None: |         if weight is not None: | ||||||
|             data[CONF_WEIGHT] = weight[1:] |             data[CONF_WEIGHT] = weight[1:] | ||||||
|         return FILE_SCHEMA(data) |         return FILE_SCHEMA(data) | ||||||
|  |  | ||||||
|  |     if value.endswith(".pcf") or value.endswith(".bdf"): | ||||||
|  |         return FILE_SCHEMA( | ||||||
|  |             { | ||||||
|  |                 CONF_TYPE: TYPE_LOCAL_BITMAP, | ||||||
|  |                 CONF_PATH: value, | ||||||
|  |             } | ||||||
|  |         ) | ||||||
|  |  | ||||||
|     return FILE_SCHEMA( |     return FILE_SCHEMA( | ||||||
|         { |         { | ||||||
|             CONF_TYPE: TYPE_LOCAL, |             CONF_TYPE: TYPE_LOCAL, | ||||||
| @@ -197,6 +220,7 @@ TYPED_FILE_SCHEMA = cv.typed_schema( | |||||||
|     { |     { | ||||||
|         TYPE_LOCAL: LOCAL_SCHEMA, |         TYPE_LOCAL: LOCAL_SCHEMA, | ||||||
|         TYPE_GFONTS: GFONTS_SCHEMA, |         TYPE_GFONTS: GFONTS_SCHEMA, | ||||||
|  |         TYPE_LOCAL_BITMAP: LOCAL_BITMAP_SCHEMA, | ||||||
|     } |     } | ||||||
| ) | ) | ||||||
|  |  | ||||||
| @@ -228,27 +252,121 @@ FONT_SCHEMA = cv.Schema( | |||||||
|  |  | ||||||
| CONFIG_SCHEMA = cv.All(validate_pillow_installed, FONT_SCHEMA) | CONFIG_SCHEMA = cv.All(validate_pillow_installed, FONT_SCHEMA) | ||||||
|  |  | ||||||
|  | # PIL doesn't provide a consistent interface for both TrueType and bitmap | ||||||
|  | # fonts. So, we use our own wrappers to give us the consistency that we need. | ||||||
|  |  | ||||||
| async def to_code(config): |  | ||||||
|  | class TrueTypeFontWrapper: | ||||||
|  |     def __init__(self, font): | ||||||
|  |         self.font = font | ||||||
|  |  | ||||||
|  |     def getoffset(self, glyph): | ||||||
|  |         _, (offset_x, offset_y) = self.font.font.getsize(glyph) | ||||||
|  |         return offset_x, offset_y | ||||||
|  |  | ||||||
|  |     def getmask(self, glyph, **kwargs): | ||||||
|  |         return self.font.getmask(glyph, **kwargs) | ||||||
|  |  | ||||||
|  |     def getmetrics(self, glyphs): | ||||||
|  |         return self.font.getmetrics() | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class BitmapFontWrapper: | ||||||
|  |     def __init__(self, font): | ||||||
|  |         self.font = font | ||||||
|  |         self.max_height = 0 | ||||||
|  |  | ||||||
|  |     def getoffset(self, glyph): | ||||||
|  |         return 0, 0 | ||||||
|  |  | ||||||
|  |     def getmask(self, glyph, **kwargs): | ||||||
|  |         return self.font.getmask(glyph, **kwargs) | ||||||
|  |  | ||||||
|  |     def getmetrics(self, glyphs): | ||||||
|  |         max_height = 0 | ||||||
|  |         for glyph in glyphs: | ||||||
|  |             mask = self.getmask(glyph, mode="1") | ||||||
|  |             _, height = mask.size | ||||||
|  |             if height > max_height: | ||||||
|  |                 max_height = height | ||||||
|  |         return (max_height, 0) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def convert_bitmap_to_pillow_font(filepath): | ||||||
|  |     from PIL import PcfFontFile, BdfFontFile | ||||||
|  |  | ||||||
|  |     local_bitmap_font_file = _compute_local_font_dir(filepath) / os.path.basename( | ||||||
|  |         filepath | ||||||
|  |     ) | ||||||
|  |  | ||||||
|  |     copy_file_if_changed(filepath, local_bitmap_font_file) | ||||||
|  |  | ||||||
|  |     with open(local_bitmap_font_file, "rb") as fp: | ||||||
|  |         try: | ||||||
|  |             try: | ||||||
|  |                 p = PcfFontFile.PcfFontFile(fp) | ||||||
|  |             except SyntaxError: | ||||||
|  |                 fp.seek(0) | ||||||
|  |                 p = BdfFontFile.BdfFontFile(fp) | ||||||
|  |  | ||||||
|  |             # Convert to pillow-formatted fonts, which have a .pil and .pbm extension. | ||||||
|  |             p.save(local_bitmap_font_file) | ||||||
|  |         except (SyntaxError, OSError) as err: | ||||||
|  |             raise core.EsphomeError( | ||||||
|  |                 f"Failed to parse as bitmap font: '{filepath}': {err}" | ||||||
|  |             ) | ||||||
|  |  | ||||||
|  |     local_pil_font_file = os.path.splitext(local_bitmap_font_file)[0] + ".pil" | ||||||
|  |     return cv.file_(local_pil_font_file) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def load_bitmap_font(filepath): | ||||||
|     from PIL import ImageFont |     from PIL import ImageFont | ||||||
|  |  | ||||||
|     conf = config[CONF_FILE] |     # Convert bpf and pcf files to pillow fonts, first. | ||||||
|     if conf[CONF_TYPE] == TYPE_LOCAL: |     pil_font_path = convert_bitmap_to_pillow_font(filepath) | ||||||
|         path = CORE.relative_config_path(conf[CONF_PATH]) |  | ||||||
|     elif conf[CONF_TYPE] == TYPE_GFONTS: |  | ||||||
|         path = _compute_gfonts_local_path(conf) |  | ||||||
|     try: |     try: | ||||||
|         font = ImageFont.truetype(str(path), config[CONF_SIZE]) |         font = ImageFont.load(str(pil_font_path)) | ||||||
|  |     except Exception as e: | ||||||
|  |         raise core.EsphomeError( | ||||||
|  |             f"Failed to load bitmap font file: {pil_font_path} : {e}" | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |     return BitmapFontWrapper(font) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def load_ttf_font(path, size): | ||||||
|  |     from PIL import ImageFont | ||||||
|  |  | ||||||
|  |     try: | ||||||
|  |         font = ImageFont.truetype(str(path), size) | ||||||
|     except Exception as e: |     except Exception as e: | ||||||
|         raise core.EsphomeError(f"Could not load truetype file {path}: {e}") |         raise core.EsphomeError(f"Could not load truetype file {path}: {e}") | ||||||
|  |  | ||||||
|     ascent, descent = font.getmetrics() |     return TrueTypeFontWrapper(font) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | async def to_code(config): | ||||||
|  |     conf = config[CONF_FILE] | ||||||
|  |     if conf[CONF_TYPE] == TYPE_LOCAL_BITMAP: | ||||||
|  |         font = load_bitmap_font(CORE.relative_config_path(conf[CONF_PATH])) | ||||||
|  |     elif conf[CONF_TYPE] == TYPE_LOCAL: | ||||||
|  |         path = CORE.relative_config_path(conf[CONF_PATH]) | ||||||
|  |         font = load_ttf_font(path, config[CONF_SIZE]) | ||||||
|  |     elif conf[CONF_TYPE] == TYPE_GFONTS: | ||||||
|  |         path = _compute_gfonts_local_path(conf) | ||||||
|  |         font = load_ttf_font(path, config[CONF_SIZE]) | ||||||
|  |     else: | ||||||
|  |         raise core.EsphomeError(f"Could not load font: unknown type: {conf[CONF_TYPE]}") | ||||||
|  |  | ||||||
|  |     ascent, descent = font.getmetrics(config[CONF_GLYPHS]) | ||||||
|  |  | ||||||
|     glyph_args = {} |     glyph_args = {} | ||||||
|     data = [] |     data = [] | ||||||
|     for glyph in config[CONF_GLYPHS]: |     for glyph in config[CONF_GLYPHS]: | ||||||
|         mask = font.getmask(glyph, mode="1") |         mask = font.getmask(glyph, mode="1") | ||||||
|         _, (offset_x, offset_y) = font.font.getsize(glyph) |         offset_x, offset_y = font.getoffset(glyph) | ||||||
|         width, height = mask.size |         width, height = mask.size | ||||||
|         width8 = ((width + 7) // 8) * 8 |         width8 = ((width + 7) // 8) * 8 | ||||||
|         glyph_data = [0] * (height * width8 // 8) |         glyph_data = [0] * (height * width8 // 8) | ||||||
|   | |||||||
| @@ -2,7 +2,7 @@ import esphome.codegen as cg | |||||||
| import esphome.config_validation as cv | import esphome.config_validation as cv | ||||||
| from esphome import pins | from esphome import pins | ||||||
| from esphome.components import switch | from esphome.components import switch | ||||||
| from esphome.const import CONF_ID, CONF_INTERLOCK, CONF_PIN, CONF_RESTORE_MODE | from esphome.const import CONF_INTERLOCK, CONF_PIN, CONF_RESTORE_MODE | ||||||
| from .. import gpio_ns | from .. import gpio_ns | ||||||
|  |  | ||||||
| GPIOSwitch = gpio_ns.class_("GPIOSwitch", switch.Switch, cg.Component) | GPIOSwitch = gpio_ns.class_("GPIOSwitch", switch.Switch, cg.Component) | ||||||
| @@ -18,25 +18,27 @@ RESTORE_MODES = { | |||||||
| } | } | ||||||
|  |  | ||||||
| CONF_INTERLOCK_WAIT_TIME = "interlock_wait_time" | CONF_INTERLOCK_WAIT_TIME = "interlock_wait_time" | ||||||
| CONFIG_SCHEMA = switch.SWITCH_SCHEMA.extend( | CONFIG_SCHEMA = ( | ||||||
|     { |     switch.switch_schema(GPIOSwitch) | ||||||
|         cv.GenerateID(): cv.declare_id(GPIOSwitch), |     .extend( | ||||||
|         cv.Required(CONF_PIN): pins.gpio_output_pin_schema, |         { | ||||||
|         cv.Optional(CONF_RESTORE_MODE, default="RESTORE_DEFAULT_OFF"): cv.enum( |             cv.Required(CONF_PIN): pins.gpio_output_pin_schema, | ||||||
|             RESTORE_MODES, upper=True, space="_" |             cv.Optional(CONF_RESTORE_MODE, default="RESTORE_DEFAULT_OFF"): cv.enum( | ||||||
|         ), |                 RESTORE_MODES, upper=True, space="_" | ||||||
|         cv.Optional(CONF_INTERLOCK): cv.ensure_list(cv.use_id(switch.Switch)), |             ), | ||||||
|         cv.Optional( |             cv.Optional(CONF_INTERLOCK): cv.ensure_list(cv.use_id(switch.Switch)), | ||||||
|             CONF_INTERLOCK_WAIT_TIME, default="0ms" |             cv.Optional( | ||||||
|         ): cv.positive_time_period_milliseconds, |                 CONF_INTERLOCK_WAIT_TIME, default="0ms" | ||||||
|     } |             ): cv.positive_time_period_milliseconds, | ||||||
| ).extend(cv.COMPONENT_SCHEMA) |         } | ||||||
|  |     ) | ||||||
|  |     .extend(cv.COMPONENT_SCHEMA) | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
| async def to_code(config): | async def to_code(config): | ||||||
|     var = cg.new_Pvariable(config[CONF_ID]) |     var = await switch.new_switch(config) | ||||||
|     await cg.register_component(var, config) |     await cg.register_component(var, config) | ||||||
|     await switch.register_switch(var, config) |  | ||||||
|  |  | ||||||
|     pin = await cg.gpio_pin_expression(config[CONF_PIN]) |     pin = await cg.gpio_pin_expression(config[CONF_PIN]) | ||||||
|     cg.add(var.set_pin(pin)) |     cg.add(var.set_pin(pin)) | ||||||
|   | |||||||
| @@ -16,8 +16,17 @@ enum HLW8012SensorModels { | |||||||
|   HLW8012_SENSOR_MODEL_BL0937 |   HLW8012_SENSOR_MODEL_BL0937 | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | #ifdef HAS_PCNT | ||||||
|  | #define USE_PCNT true | ||||||
|  | #else | ||||||
|  | #define USE_PCNT false | ||||||
|  | #endif | ||||||
|  |  | ||||||
| class HLW8012Component : public PollingComponent { | class HLW8012Component : public PollingComponent { | ||||||
|  public: |  public: | ||||||
|  |   HLW8012Component() | ||||||
|  |       : cf_store_(*pulse_counter::get_storage(USE_PCNT)), cf1_store_(*pulse_counter::get_storage(USE_PCNT)) {} | ||||||
|  |  | ||||||
|   void setup() override; |   void setup() override; | ||||||
|   void dump_config() override; |   void dump_config() override; | ||||||
|   float get_setup_priority() const override; |   float get_setup_priority() const override; | ||||||
| @@ -49,9 +58,9 @@ class HLW8012Component : public PollingComponent { | |||||||
|   uint64_t cf_total_pulses_{0}; |   uint64_t cf_total_pulses_{0}; | ||||||
|   GPIOPin *sel_pin_; |   GPIOPin *sel_pin_; | ||||||
|   InternalGPIOPin *cf_pin_; |   InternalGPIOPin *cf_pin_; | ||||||
|   pulse_counter::PulseCounterStorage cf_store_; |   pulse_counter::PulseCounterStorageBase &cf_store_; | ||||||
|   InternalGPIOPin *cf1_pin_; |   InternalGPIOPin *cf1_pin_; | ||||||
|   pulse_counter::PulseCounterStorage cf1_store_; |   pulse_counter::PulseCounterStorageBase &cf1_store_; | ||||||
|   sensor::Sensor *voltage_sensor_{nullptr}; |   sensor::Sensor *voltage_sensor_{nullptr}; | ||||||
|   sensor::Sensor *current_sensor_{nullptr}; |   sensor::Sensor *current_sensor_{nullptr}; | ||||||
|   sensor::Sensor *power_sensor_{nullptr}; |   sensor::Sensor *power_sensor_{nullptr}; | ||||||
|   | |||||||
| @@ -4,12 +4,15 @@ from esphome.components import binary_sensor | |||||||
| from esphome.const import ( | from esphome.const import ( | ||||||
|     CONF_ID, |     CONF_ID, | ||||||
|     DEVICE_CLASS_COLD, |     DEVICE_CLASS_COLD, | ||||||
|  |     DEVICE_CLASS_PROBLEM, | ||||||
| ) | ) | ||||||
|  |  | ||||||
| from . import hydreon_rgxx_ns, HydreonRGxxComponent | from . import hydreon_rgxx_ns, HydreonRGxxComponent | ||||||
|  |  | ||||||
| CONF_HYDREON_RGXX_ID = "hydreon_rgxx_id" | CONF_HYDREON_RGXX_ID = "hydreon_rgxx_id" | ||||||
| CONF_TOO_COLD = "too_cold" | CONF_TOO_COLD = "too_cold" | ||||||
|  | CONF_LENS_BAD = "lens_bad" | ||||||
|  | CONF_EM_SAT = "em_sat" | ||||||
|  |  | ||||||
| HydreonRGxxBinarySensor = hydreon_rgxx_ns.class_( | HydreonRGxxBinarySensor = hydreon_rgxx_ns.class_( | ||||||
|     "HydreonRGxxBinaryComponent", cg.Component |     "HydreonRGxxBinaryComponent", cg.Component | ||||||
| @@ -23,6 +26,12 @@ CONFIG_SCHEMA = cv.Schema( | |||||||
|         cv.Optional(CONF_TOO_COLD): binary_sensor.binary_sensor_schema( |         cv.Optional(CONF_TOO_COLD): binary_sensor.binary_sensor_schema( | ||||||
|             device_class=DEVICE_CLASS_COLD |             device_class=DEVICE_CLASS_COLD | ||||||
|         ), |         ), | ||||||
|  |         cv.Optional(CONF_LENS_BAD): binary_sensor.binary_sensor_schema( | ||||||
|  |             device_class=DEVICE_CLASS_PROBLEM, | ||||||
|  |         ), | ||||||
|  |         cv.Optional(CONF_EM_SAT): binary_sensor.binary_sensor_schema( | ||||||
|  |             device_class=DEVICE_CLASS_PROBLEM, | ||||||
|  |         ), | ||||||
|     } |     } | ||||||
| ) | ) | ||||||
|  |  | ||||||
| @@ -31,6 +40,14 @@ async def to_code(config): | |||||||
|     main_sensor = await cg.get_variable(config[CONF_HYDREON_RGXX_ID]) |     main_sensor = await cg.get_variable(config[CONF_HYDREON_RGXX_ID]) | ||||||
|     bin_component = cg.new_Pvariable(config[CONF_ID], main_sensor) |     bin_component = cg.new_Pvariable(config[CONF_ID], main_sensor) | ||||||
|     await cg.register_component(bin_component, config) |     await cg.register_component(bin_component, config) | ||||||
|     if CONF_TOO_COLD in config: |  | ||||||
|         tc = await binary_sensor.new_binary_sensor(config[CONF_TOO_COLD]) |     mapping = { | ||||||
|         cg.add(main_sensor.set_too_cold_sensor(tc)) |         CONF_TOO_COLD: main_sensor.set_too_cold_sensor, | ||||||
|  |         CONF_LENS_BAD: main_sensor.set_lens_bad_sensor, | ||||||
|  |         CONF_EM_SAT: main_sensor.set_em_sat_sensor, | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     for key, value in mapping.items(): | ||||||
|  |         if key in config: | ||||||
|  |             sensor = await binary_sensor.new_binary_sensor(config[key]) | ||||||
|  |             cg.add(value(sensor)) | ||||||
|   | |||||||
| @@ -9,6 +9,7 @@ static const int MAX_DATA_LENGTH_BYTES = 80; | |||||||
| static const uint8_t ASCII_LF = 0x0A; | static const uint8_t ASCII_LF = 0x0A; | ||||||
| #define HYDREON_RGXX_COMMA , | #define HYDREON_RGXX_COMMA , | ||||||
| static const char *const PROTOCOL_NAMES[] = {HYDREON_RGXX_PROTOCOL_LIST(, HYDREON_RGXX_COMMA)}; | static const char *const PROTOCOL_NAMES[] = {HYDREON_RGXX_PROTOCOL_LIST(, HYDREON_RGXX_COMMA)}; | ||||||
|  | static const char *const IGNORE_STRINGS[] = {HYDREON_RGXX_IGNORE_LIST(, HYDREON_RGXX_COMMA)}; | ||||||
|  |  | ||||||
| void HydreonRGxxComponent::dump_config() { | void HydreonRGxxComponent::dump_config() { | ||||||
|   this->check_uart_settings(9600, 1, esphome::uart::UART_CONFIG_PARITY_NONE, 8); |   this->check_uart_settings(9600, 1, esphome::uart::UART_CONFIG_PARITY_NONE, 8); | ||||||
| @@ -34,33 +35,37 @@ void HydreonRGxxComponent::setup() { | |||||||
|   this->schedule_reboot_(); |   this->schedule_reboot_(); | ||||||
| } | } | ||||||
|  |  | ||||||
| bool HydreonRGxxComponent::sensor_missing_() { | int HydreonRGxxComponent::num_sensors_missing_() { | ||||||
|   if (this->sensors_received_ == -1) { |   if (this->sensors_received_ == -1) { | ||||||
|     // no request sent yet, don't check |     return -1; | ||||||
|     return false; |  | ||||||
|   } else { |  | ||||||
|     if (this->sensors_received_ == 0) { |  | ||||||
|       ESP_LOGW(TAG, "No data at all"); |  | ||||||
|       return true; |  | ||||||
|     } |  | ||||||
|     for (int i = 0; i < NUM_SENSORS; i++) { |  | ||||||
|       if (this->sensors_[i] == nullptr) { |  | ||||||
|         continue; |  | ||||||
|       } |  | ||||||
|       if ((this->sensors_received_ >> i & 1) == 0) { |  | ||||||
|         ESP_LOGW(TAG, "Missing %s", PROTOCOL_NAMES[i]); |  | ||||||
|         return true; |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|     return false; |  | ||||||
|   } |   } | ||||||
|  |   int ret = NUM_SENSORS; | ||||||
|  |   for (int i = 0; i < NUM_SENSORS; i++) { | ||||||
|  |     if (this->sensors_[i] == nullptr) { | ||||||
|  |       ret -= 1; | ||||||
|  |       continue; | ||||||
|  |     } | ||||||
|  |     if ((this->sensors_received_ >> i & 1) != 0) { | ||||||
|  |       ret -= 1; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   return ret; | ||||||
| } | } | ||||||
|  |  | ||||||
| void HydreonRGxxComponent::update() { | void HydreonRGxxComponent::update() { | ||||||
|   if (this->boot_count_ > 0) { |   if (this->boot_count_ > 0) { | ||||||
|     if (this->sensor_missing_()) { |     if (this->num_sensors_missing_() > 0) { | ||||||
|  |       for (int i = 0; i < NUM_SENSORS; i++) { | ||||||
|  |         if (this->sensors_[i] == nullptr) { | ||||||
|  |           continue; | ||||||
|  |         } | ||||||
|  |         if ((this->sensors_received_ >> i & 1) == 0) { | ||||||
|  |           ESP_LOGW(TAG, "Missing %s", PROTOCOL_NAMES[i]); | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |  | ||||||
|       this->no_response_count_++; |       this->no_response_count_++; | ||||||
|       ESP_LOGE(TAG, "data missing %d times", this->no_response_count_); |       ESP_LOGE(TAG, "missing %d sensors; %d times in a row", this->num_sensors_missing_(), this->no_response_count_); | ||||||
|       if (this->no_response_count_ > 15) { |       if (this->no_response_count_ > 15) { | ||||||
|         ESP_LOGE(TAG, "asking sensor to reboot"); |         ESP_LOGE(TAG, "asking sensor to reboot"); | ||||||
|         for (auto &sensor : this->sensors_) { |         for (auto &sensor : this->sensors_) { | ||||||
| @@ -79,8 +84,16 @@ void HydreonRGxxComponent::update() { | |||||||
|     if (this->too_cold_sensor_ != nullptr) { |     if (this->too_cold_sensor_ != nullptr) { | ||||||
|       this->too_cold_sensor_->publish_state(this->too_cold_); |       this->too_cold_sensor_->publish_state(this->too_cold_); | ||||||
|     } |     } | ||||||
|  |     if (this->lens_bad_sensor_ != nullptr) { | ||||||
|  |       this->lens_bad_sensor_->publish_state(this->lens_bad_); | ||||||
|  |     } | ||||||
|  |     if (this->em_sat_sensor_ != nullptr) { | ||||||
|  |       this->em_sat_sensor_->publish_state(this->em_sat_); | ||||||
|  |     } | ||||||
| #endif | #endif | ||||||
|     this->too_cold_ = false; |     this->too_cold_ = false; | ||||||
|  |     this->lens_bad_ = false; | ||||||
|  |     this->em_sat_ = false; | ||||||
|     this->sensors_received_ = 0; |     this->sensors_received_ = 0; | ||||||
|   } |   } | ||||||
| } | } | ||||||
| @@ -146,6 +159,25 @@ void HydreonRGxxComponent::process_line_() { | |||||||
|     ESP_LOGI(TAG, "Comment: %s", this->buffer_.substr(0, this->buffer_.size() - 2).c_str()); |     ESP_LOGI(TAG, "Comment: %s", this->buffer_.substr(0, this->buffer_.size() - 2).c_str()); | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|  |   std::string::size_type newlineposn = this->buffer_.find('\n'); | ||||||
|  |   if (newlineposn <= 1) { | ||||||
|  |     // allow both \r\n and \n | ||||||
|  |     ESP_LOGD(TAG, "Received empty line"); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   if (newlineposn <= 2) { | ||||||
|  |     // single character lines, such as acknowledgements | ||||||
|  |     ESP_LOGD(TAG, "Received ack: %s", this->buffer_.substr(0, this->buffer_.size() - 2).c_str()); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   if (this->buffer_.find("LensBad") != std::string::npos) { | ||||||
|  |     ESP_LOGW(TAG, "Received LensBad!"); | ||||||
|  |     this->lens_bad_ = true; | ||||||
|  |   } | ||||||
|  |   if (this->buffer_.find("EmSat") != std::string::npos) { | ||||||
|  |     ESP_LOGW(TAG, "Received EmSat!"); | ||||||
|  |     this->em_sat_ = true; | ||||||
|  |   } | ||||||
|   if (this->buffer_starts_with_("PwrDays")) { |   if (this->buffer_starts_with_("PwrDays")) { | ||||||
|     if (this->boot_count_ <= 0) { |     if (this->boot_count_ <= 0) { | ||||||
|       this->boot_count_ = 1; |       this->boot_count_ = 1; | ||||||
| @@ -200,7 +232,16 @@ void HydreonRGxxComponent::process_line_() { | |||||||
|       ESP_LOGD(TAG, "Received %s: %f", PROTOCOL_NAMES[i], this->sensors_[i]->get_raw_state()); |       ESP_LOGD(TAG, "Received %s: %f", PROTOCOL_NAMES[i], this->sensors_[i]->get_raw_state()); | ||||||
|       this->sensors_received_ |= (1 << i); |       this->sensors_received_ |= (1 << i); | ||||||
|     } |     } | ||||||
|  |     if (this->request_temperature_ && this->num_sensors_missing_() == 1) { | ||||||
|  |       this->write_str("T\n"); | ||||||
|  |     } | ||||||
|   } else { |   } else { | ||||||
|  |     for (const auto *ignore : IGNORE_STRINGS) { | ||||||
|  |       if (this->buffer_starts_with_(ignore)) { | ||||||
|  |         ESP_LOGI(TAG, "Ignoring %s", this->buffer_.substr(0, this->buffer_.size() - 2).c_str()); | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|     ESP_LOGI(TAG, "Got unknown line: %s", this->buffer_.c_str()); |     ESP_LOGI(TAG, "Got unknown line: %s", this->buffer_.c_str()); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -26,13 +26,18 @@ static const uint8_t NUM_SENSORS = 1; | |||||||
| #define HYDREON_RGXX_PROTOCOL_LIST(F, SEP) F("") | #define HYDREON_RGXX_PROTOCOL_LIST(F, SEP) F("") | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
|  | #define HYDREON_RGXX_IGNORE_LIST(F, SEP) F("Emitters") SEP F("Event") SEP F("Reset") | ||||||
|  |  | ||||||
| class HydreonRGxxComponent : public PollingComponent, public uart::UARTDevice { | class HydreonRGxxComponent : public PollingComponent, public uart::UARTDevice { | ||||||
|  public: |  public: | ||||||
|   void set_sensor(sensor::Sensor *sensor, int index) { this->sensors_[index] = sensor; } |   void set_sensor(sensor::Sensor *sensor, int index) { this->sensors_[index] = sensor; } | ||||||
| #ifdef USE_BINARY_SENSOR | #ifdef USE_BINARY_SENSOR | ||||||
|   void set_too_cold_sensor(binary_sensor::BinarySensor *sensor) { this->too_cold_sensor_ = sensor; } |   void set_too_cold_sensor(binary_sensor::BinarySensor *sensor) { this->too_cold_sensor_ = sensor; } | ||||||
|  |   void set_lens_bad_sensor(binary_sensor::BinarySensor *sensor) { this->lens_bad_sensor_ = sensor; } | ||||||
|  |   void set_em_sat_sensor(binary_sensor::BinarySensor *sensor) { this->em_sat_sensor_ = sensor; } | ||||||
| #endif | #endif | ||||||
|   void set_model(RGModel model) { model_ = model; } |   void set_model(RGModel model) { model_ = model; } | ||||||
|  |   void set_request_temperature(bool b) { request_temperature_ = b; } | ||||||
|  |  | ||||||
|   /// Schedule data readings. |   /// Schedule data readings. | ||||||
|   void update() override; |   void update() override; | ||||||
| @@ -49,11 +54,13 @@ class HydreonRGxxComponent : public PollingComponent, public uart::UARTDevice { | |||||||
|   void schedule_reboot_(); |   void schedule_reboot_(); | ||||||
|   bool buffer_starts_with_(const std::string &prefix); |   bool buffer_starts_with_(const std::string &prefix); | ||||||
|   bool buffer_starts_with_(const char *prefix); |   bool buffer_starts_with_(const char *prefix); | ||||||
|   bool sensor_missing_(); |   int num_sensors_missing_(); | ||||||
|  |  | ||||||
|   sensor::Sensor *sensors_[NUM_SENSORS] = {nullptr}; |   sensor::Sensor *sensors_[NUM_SENSORS] = {nullptr}; | ||||||
| #ifdef USE_BINARY_SENSOR | #ifdef USE_BINARY_SENSOR | ||||||
|   binary_sensor::BinarySensor *too_cold_sensor_ = nullptr; |   binary_sensor::BinarySensor *too_cold_sensor_ = nullptr; | ||||||
|  |   binary_sensor::BinarySensor *lens_bad_sensor_ = nullptr; | ||||||
|  |   binary_sensor::BinarySensor *em_sat_sensor_ = nullptr; | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
|   int16_t boot_count_ = 0; |   int16_t boot_count_ = 0; | ||||||
| @@ -62,6 +69,9 @@ class HydreonRGxxComponent : public PollingComponent, public uart::UARTDevice { | |||||||
|   RGModel model_ = RG9; |   RGModel model_ = RG9; | ||||||
|   int sw_version_ = 0; |   int sw_version_ = 0; | ||||||
|   bool too_cold_ = false; |   bool too_cold_ = false; | ||||||
|  |   bool lens_bad_ = false; | ||||||
|  |   bool em_sat_ = false; | ||||||
|  |   bool request_temperature_ = false; | ||||||
|  |  | ||||||
|   // bit field showing which sensors we have received data for |   // bit field showing which sensors we have received data for | ||||||
|   int sensors_received_ = -1; |   int sensors_received_ = -1; | ||||||
|   | |||||||
| @@ -5,8 +5,11 @@ from esphome.const import ( | |||||||
|     CONF_ID, |     CONF_ID, | ||||||
|     CONF_MODEL, |     CONF_MODEL, | ||||||
|     CONF_MOISTURE, |     CONF_MOISTURE, | ||||||
|  |     CONF_TEMPERATURE, | ||||||
|     DEVICE_CLASS_HUMIDITY, |     DEVICE_CLASS_HUMIDITY, | ||||||
|     STATE_CLASS_MEASUREMENT, |     STATE_CLASS_MEASUREMENT, | ||||||
|  |     UNIT_CELSIUS, | ||||||
|  |     ICON_THERMOMETER, | ||||||
| ) | ) | ||||||
|  |  | ||||||
| from . import RGModel, HydreonRGxxComponent | from . import RGModel, HydreonRGxxComponent | ||||||
| @@ -33,6 +36,7 @@ SUPPORTED_SENSORS = { | |||||||
|     CONF_TOTAL_ACC: ["RG_15"], |     CONF_TOTAL_ACC: ["RG_15"], | ||||||
|     CONF_R_INT: ["RG_15"], |     CONF_R_INT: ["RG_15"], | ||||||
|     CONF_MOISTURE: ["RG_9"], |     CONF_MOISTURE: ["RG_9"], | ||||||
|  |     CONF_TEMPERATURE: ["RG_9"], | ||||||
| } | } | ||||||
| PROTOCOL_NAMES = { | PROTOCOL_NAMES = { | ||||||
|     CONF_MOISTURE: "R", |     CONF_MOISTURE: "R", | ||||||
| @@ -40,6 +44,7 @@ PROTOCOL_NAMES = { | |||||||
|     CONF_R_INT: "RInt", |     CONF_R_INT: "RInt", | ||||||
|     CONF_EVENT_ACC: "EventAcc", |     CONF_EVENT_ACC: "EventAcc", | ||||||
|     CONF_TOTAL_ACC: "TotalAcc", |     CONF_TOTAL_ACC: "TotalAcc", | ||||||
|  |     CONF_TEMPERATURE: "t", | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -92,6 +97,12 @@ CONFIG_SCHEMA = cv.All( | |||||||
|                 device_class=DEVICE_CLASS_HUMIDITY, |                 device_class=DEVICE_CLASS_HUMIDITY, | ||||||
|                 state_class=STATE_CLASS_MEASUREMENT, |                 state_class=STATE_CLASS_MEASUREMENT, | ||||||
|             ), |             ), | ||||||
|  |             cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( | ||||||
|  |                 unit_of_measurement=UNIT_CELSIUS, | ||||||
|  |                 accuracy_decimals=0, | ||||||
|  |                 icon=ICON_THERMOMETER, | ||||||
|  |                 state_class=STATE_CLASS_MEASUREMENT, | ||||||
|  |             ), | ||||||
|         } |         } | ||||||
|     ) |     ) | ||||||
|     .extend(cv.polling_component_schema("60s")) |     .extend(cv.polling_component_schema("60s")) | ||||||
| @@ -108,7 +119,7 @@ async def to_code(config): | |||||||
|     cg.add_define( |     cg.add_define( | ||||||
|         "HYDREON_RGXX_PROTOCOL_LIST(F, sep)", |         "HYDREON_RGXX_PROTOCOL_LIST(F, sep)", | ||||||
|         cg.RawExpression( |         cg.RawExpression( | ||||||
|             " sep ".join([f'F("{name}")' for name in PROTOCOL_NAMES.values()]) |             " sep ".join([f'F("{name} ")' for name in PROTOCOL_NAMES.values()]) | ||||||
|         ), |         ), | ||||||
|     ) |     ) | ||||||
|     cg.add_define("HYDREON_RGXX_NUM_SENSORS", len(PROTOCOL_NAMES)) |     cg.add_define("HYDREON_RGXX_NUM_SENSORS", len(PROTOCOL_NAMES)) | ||||||
| @@ -117,3 +128,5 @@ async def to_code(config): | |||||||
|         if conf in config: |         if conf in config: | ||||||
|             sens = await sensor.new_sensor(config[conf]) |             sens = await sensor.new_sensor(config[conf]) | ||||||
|             cg.add(var.set_sensor(sens, i)) |             cg.add(var.set_sensor(sens, i)) | ||||||
|  |  | ||||||
|  |     cg.add(var.set_request_temperature(CONF_TEMPERATURE in config)) | ||||||
|   | |||||||
| @@ -1,5 +1,6 @@ | |||||||
| import esphome.codegen as cg | import esphome.codegen as cg | ||||||
| import esphome.config_validation as cv | import esphome.config_validation as cv | ||||||
|  | import esphome.final_validate as fv | ||||||
| from esphome import pins | from esphome import pins | ||||||
| from esphome.const import ( | from esphome.const import ( | ||||||
|     CONF_FREQUENCY, |     CONF_FREQUENCY, | ||||||
| @@ -110,3 +111,27 @@ async def register_i2c_device(var, config): | |||||||
|     parent = await cg.get_variable(config[CONF_I2C_ID]) |     parent = await cg.get_variable(config[CONF_I2C_ID]) | ||||||
|     cg.add(var.set_i2c_bus(parent)) |     cg.add(var.set_i2c_bus(parent)) | ||||||
|     cg.add(var.set_i2c_address(config[CONF_ADDRESS])) |     cg.add(var.set_i2c_address(config[CONF_ADDRESS])) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def final_validate_device_schema( | ||||||
|  |     name: str, *, min_frequency: cv.frequency = None, max_frequency: cv.frequency = None | ||||||
|  | ): | ||||||
|  |     hub_schema = {} | ||||||
|  |     if min_frequency is not None: | ||||||
|  |         hub_schema[cv.Required(CONF_FREQUENCY)] = cv.Range( | ||||||
|  |             min=cv.frequency(min_frequency), | ||||||
|  |             min_included=True, | ||||||
|  |             msg=f"Component {name} requires a minimum frequency of {min_frequency} for the I2C bus", | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |     if max_frequency is not None: | ||||||
|  |         hub_schema[cv.Required(CONF_FREQUENCY)] = cv.Range( | ||||||
|  |             max=cv.frequency(max_frequency), | ||||||
|  |             max_included=True, | ||||||
|  |             msg=f"Component {name} cannot be used with a frequency of over {max_frequency} for the I2C bus", | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |     return cv.Schema( | ||||||
|  |         {cv.Required(CONF_I2C_ID): fv.id_declaration_match_schema(hub_schema)}, | ||||||
|  |         extra=cv.ALLOW_EXTRA, | ||||||
|  |     ) | ||||||
|   | |||||||
| @@ -10,7 +10,6 @@ namespace ili9341 { | |||||||
| static const char *const TAG = "ili9341"; | static const char *const TAG = "ili9341"; | ||||||
|  |  | ||||||
| void ILI9341Display::setup_pins_() { | void ILI9341Display::setup_pins_() { | ||||||
|   this->init_internal_(this->get_buffer_length_()); |  | ||||||
|   this->dc_pin_->setup();  // OUTPUT |   this->dc_pin_->setup();  // OUTPUT | ||||||
|   this->dc_pin_->digital_write(false); |   this->dc_pin_->digital_write(false); | ||||||
|   if (this->reset_pin_ != nullptr) { |   if (this->reset_pin_ != nullptr) { | ||||||
| @@ -28,15 +27,14 @@ void ILI9341Display::setup_pins_() { | |||||||
|  |  | ||||||
| void ILI9341Display::dump_config() { | void ILI9341Display::dump_config() { | ||||||
|   LOG_DISPLAY("", "ili9341", this); |   LOG_DISPLAY("", "ili9341", this); | ||||||
|   ESP_LOGCONFIG(TAG, "  Width: %d, Height: %d,  Rotation: %d", this->width_, this->height_, this->rotation_); |  | ||||||
|   LOG_PIN("  Reset Pin: ", this->reset_pin_); |   LOG_PIN("  Reset Pin: ", this->reset_pin_); | ||||||
|   LOG_PIN("  DC Pin: ", this->dc_pin_); |   LOG_PIN("  DC Pin: ", this->dc_pin_); | ||||||
|   LOG_PIN("  Busy Pin: ", this->busy_pin_); |   LOG_PIN("  Busy Pin: ", this->busy_pin_); | ||||||
|   LOG_PIN("  Backlight Pin: ", this->led_pin_); |  | ||||||
|   LOG_UPDATE_INTERVAL(this); |   LOG_UPDATE_INTERVAL(this); | ||||||
| } | } | ||||||
|  |  | ||||||
| float ILI9341Display::get_setup_priority() const { return setup_priority::PROCESSOR; } | float ILI9341Display::get_setup_priority() const { return setup_priority::HARDWARE; } | ||||||
|  |  | ||||||
| void ILI9341Display::command(uint8_t value) { | void ILI9341Display::command(uint8_t value) { | ||||||
|   this->start_command_(); |   this->start_command_(); | ||||||
|   this->write_byte(value); |   this->write_byte(value); | ||||||
| @@ -88,10 +86,19 @@ void ILI9341Display::display_() { | |||||||
|   // we will only update the changed window to the display |   // we will only update the changed window to the display | ||||||
|   uint16_t w = this->x_high_ - this->x_low_ + 1; |   uint16_t w = this->x_high_ - this->x_low_ + 1; | ||||||
|   uint16_t h = this->y_high_ - this->y_low_ + 1; |   uint16_t h = this->y_high_ - this->y_low_ + 1; | ||||||
|  |   uint32_t start_pos = ((this->y_low_ * this->width_) + x_low_); | ||||||
|  |  | ||||||
|  |   // check if something was displayed | ||||||
|  |   if ((this->x_high_ < this->x_low_) || (this->y_high_ < this->y_low_)) { | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|   set_addr_window_(this->x_low_, this->y_low_, w, h); |   set_addr_window_(this->x_low_, this->y_low_, w, h); | ||||||
|  |  | ||||||
|  |   ESP_LOGVV("ILI9341", "Start ILI9341Display::display_(xl:%d, xh:%d, yl:%d, yh:%d, w:%d, h:%d, start_pos:%d)", | ||||||
|  |             this->x_low_, this->x_high_, this->y_low_, this->y_high_, w, h, start_pos); | ||||||
|  |  | ||||||
|   this->start_data_(); |   this->start_data_(); | ||||||
|   uint32_t start_pos = ((this->y_low_ * this->width_) + x_low_); |  | ||||||
|   for (uint16_t row = 0; row < h; row++) { |   for (uint16_t row = 0; row < h; row++) { | ||||||
|     uint32_t pos = start_pos + (row * width_); |     uint32_t pos = start_pos + (row * width_); | ||||||
|     uint32_t rem = w; |     uint32_t rem = w; | ||||||
| @@ -101,7 +108,9 @@ void ILI9341Display::display_() { | |||||||
|       this->write_array(transfer_buffer_, 2 * sz); |       this->write_array(transfer_buffer_, 2 * sz); | ||||||
|       pos += sz; |       pos += sz; | ||||||
|       rem -= sz; |       rem -= sz; | ||||||
|  |       App.feed_wdt(); | ||||||
|     } |     } | ||||||
|  |     App.feed_wdt(); | ||||||
|   } |   } | ||||||
|   this->end_data_(); |   this->end_data_(); | ||||||
|  |  | ||||||
| @@ -121,20 +130,10 @@ void ILI9341Display::fill(Color color) { | |||||||
|   this->y_high_ = this->get_height_internal() - 1; |   this->y_high_ = this->get_height_internal() - 1; | ||||||
| } | } | ||||||
|  |  | ||||||
| void ILI9341Display::fill_internal_(Color color) { | void ILI9341Display::fill_internal_(uint8_t color) { | ||||||
|   if (color.raw_32 == Color::BLACK.raw_32) { |   memset(transfer_buffer_, color, sizeof(transfer_buffer_)); | ||||||
|     memset(transfer_buffer_, 0, sizeof(transfer_buffer_)); |  | ||||||
|   } else { |  | ||||||
|     uint8_t *dst = transfer_buffer_; |  | ||||||
|     auto color565 = display::ColorUtil::color_to_565(color); |  | ||||||
|  |  | ||||||
|     while (dst < transfer_buffer_ + sizeof(transfer_buffer_)) { |   uint32_t rem = (this->get_buffer_length_() * 2); | ||||||
|       *dst++ = (uint8_t)(color565 >> 8); |  | ||||||
|       *dst++ = (uint8_t) color565; |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   uint32_t rem = this->get_width_internal() * this->get_height_internal(); |  | ||||||
|  |  | ||||||
|   this->set_addr_window_(0, 0, this->get_width_internal(), this->get_height_internal()); |   this->set_addr_window_(0, 0, this->get_width_internal(), this->get_height_internal()); | ||||||
|   this->start_data_(); |   this->start_data_(); | ||||||
| @@ -147,26 +146,58 @@ void ILI9341Display::fill_internal_(Color color) { | |||||||
|  |  | ||||||
|   this->end_data_(); |   this->end_data_(); | ||||||
|  |  | ||||||
|   memset(buffer_, 0, (this->get_width_internal()) * (this->get_height_internal())); |   memset(buffer_, color, this->get_buffer_length_()); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void ILI9341Display::rotate_my_(uint8_t m) { | ||||||
|  |   uint8_t rotation = m & 3;  // can't be higher than 3 | ||||||
|  |   switch (rotation) { | ||||||
|  |     case 0: | ||||||
|  |       m = (MADCTL_MX | MADCTL_BGR); | ||||||
|  |       // _width = ILI9341_TFTWIDTH; | ||||||
|  |       // _height = ILI9341_TFTHEIGHT; | ||||||
|  |       break; | ||||||
|  |     case 1: | ||||||
|  |       m = (MADCTL_MV | MADCTL_BGR); | ||||||
|  |       // _width = ILI9341_TFTHEIGHT; | ||||||
|  |       // _height = ILI9341_TFTWIDTH; | ||||||
|  |       break; | ||||||
|  |     case 2: | ||||||
|  |       m = (MADCTL_MY | MADCTL_BGR); | ||||||
|  |       // _width = ILI9341_TFTWIDTH; | ||||||
|  |       // _height = ILI9341_TFTHEIGHT; | ||||||
|  |       break; | ||||||
|  |     case 3: | ||||||
|  |       m = (MADCTL_MX | MADCTL_MY | MADCTL_MV | MADCTL_BGR); | ||||||
|  |       // _width = ILI9341_TFTHEIGHT; | ||||||
|  |       // _height = ILI9341_TFTWIDTH; | ||||||
|  |       break; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   this->command(ILI9341_MADCTL); | ||||||
|  |   this->data(m); | ||||||
| } | } | ||||||
|  |  | ||||||
| void HOT ILI9341Display::draw_absolute_pixel_internal(int x, int y, Color color) { | void HOT ILI9341Display::draw_absolute_pixel_internal(int x, int y, Color color) { | ||||||
|   if (x >= this->get_width_internal() || x < 0 || y >= this->get_height_internal() || y < 0) |   if (x >= this->get_width_internal() || x < 0 || y >= this->get_height_internal() || y < 0) | ||||||
|     return; |     return; | ||||||
|  |  | ||||||
|   // low and high watermark may speed up drawing from buffer |  | ||||||
|   this->x_low_ = (x < this->x_low_) ? x : this->x_low_; |  | ||||||
|   this->y_low_ = (y < this->y_low_) ? y : this->y_low_; |  | ||||||
|   this->x_high_ = (x > this->x_high_) ? x : this->x_high_; |  | ||||||
|   this->y_high_ = (y > this->y_high_) ? y : this->y_high_; |  | ||||||
|  |  | ||||||
|   uint32_t pos = (y * width_) + x; |   uint32_t pos = (y * width_) + x; | ||||||
|  |   uint8_t new_color; | ||||||
|  |  | ||||||
|   if (this->buffer_color_mode_ == BITS_8) { |   if (this->buffer_color_mode_ == BITS_8) { | ||||||
|     uint8_t color332 = display::ColorUtil::color_to_332(color, display::ColorOrder::COLOR_ORDER_RGB); |     new_color = display::ColorUtil::color_to_332(color, display::ColorOrder::COLOR_ORDER_RGB); | ||||||
|     buffer_[pos] = color332; |  | ||||||
|   } else {  // if (this->buffer_color_mode_ == BITS_8_INDEXED) { |   } else {  // if (this->buffer_color_mode_ == BITS_8_INDEXED) { | ||||||
|     uint8_t index = display::ColorUtil::color_to_index8_palette888(color, this->palette_); |     new_color = display::ColorUtil::color_to_index8_palette888(color, this->palette_); | ||||||
|     buffer_[pos] = index; |   } | ||||||
|  |  | ||||||
|  |   if (buffer_[pos] != new_color) { | ||||||
|  |     buffer_[pos] = new_color; | ||||||
|  |     // low and high watermark may speed up drawing from buffer | ||||||
|  |     this->x_low_ = (x < this->x_low_) ? x : this->x_low_; | ||||||
|  |     this->y_low_ = (y < this->y_low_) ? y : this->y_low_; | ||||||
|  |     this->x_high_ = (x > this->x_high_) ? x : this->x_high_; | ||||||
|  |     this->y_high_ = (y > this->y_high_) ? y : this->y_high_; | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -252,7 +283,6 @@ void ILI9341M5Stack::initialize() { | |||||||
|   this->width_ = 320; |   this->width_ = 320; | ||||||
|   this->height_ = 240; |   this->height_ = 240; | ||||||
|   this->invert_display_(true); |   this->invert_display_(true); | ||||||
|   this->fill_internal_(Color::BLACK); |  | ||||||
| } | } | ||||||
|  |  | ||||||
| //   24_TFT display | //   24_TFT display | ||||||
| @@ -260,7 +290,6 @@ void ILI9341TFT24::initialize() { | |||||||
|   this->init_lcd_(INITCMD_TFT); |   this->init_lcd_(INITCMD_TFT); | ||||||
|   this->width_ = 240; |   this->width_ = 240; | ||||||
|   this->height_ = 320; |   this->height_ = 320; | ||||||
|   this->fill_internal_(Color::BLACK); |  | ||||||
| } | } | ||||||
|  |  | ||||||
| //   24_TFT rotated display | //   24_TFT rotated display | ||||||
| @@ -268,7 +297,6 @@ void ILI9341TFT24R::initialize() { | |||||||
|   this->init_lcd_(INITCMD_TFT); |   this->init_lcd_(INITCMD_TFT); | ||||||
|   this->width_ = 320; |   this->width_ = 320; | ||||||
|   this->height_ = 240; |   this->height_ = 240; | ||||||
|   this->fill_internal_(Color::BLACK); |  | ||||||
| } | } | ||||||
|  |  | ||||||
| }  // namespace ili9341 | }  // namespace ili9341 | ||||||
|   | |||||||
| @@ -5,6 +5,7 @@ | |||||||
| #include "esphome/components/display/display_buffer.h" | #include "esphome/components/display/display_buffer.h" | ||||||
| #include "ili9341_defines.h" | #include "ili9341_defines.h" | ||||||
| #include "ili9341_init.h" | #include "ili9341_init.h" | ||||||
|  | #include "esphome/core/log.h" | ||||||
|  |  | ||||||
| namespace esphome { | namespace esphome { | ||||||
| namespace ili9341 { | namespace ili9341 { | ||||||
| @@ -47,6 +48,14 @@ class ILI9341Display : public PollingComponent, | |||||||
|   void setup() override { |   void setup() override { | ||||||
|     this->setup_pins_(); |     this->setup_pins_(); | ||||||
|     this->initialize(); |     this->initialize(); | ||||||
|  |  | ||||||
|  |     this->x_low_ = this->width_; | ||||||
|  |     this->y_low_ = this->height_; | ||||||
|  |     this->x_high_ = 0; | ||||||
|  |     this->y_high_ = 0; | ||||||
|  |  | ||||||
|  |     this->init_internal_(this->get_buffer_length_()); | ||||||
|  |     this->fill_internal_(0x00); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   display::DisplayType get_display_type() override { return display::DisplayType::DISPLAY_TYPE_COLOR; } |   display::DisplayType get_display_type() override { return display::DisplayType::DISPLAY_TYPE_COLOR; } | ||||||
| @@ -59,8 +68,9 @@ class ILI9341Display : public PollingComponent, | |||||||
|   void set_addr_window_(uint16_t x, uint16_t y, uint16_t w, uint16_t h); |   void set_addr_window_(uint16_t x, uint16_t y, uint16_t w, uint16_t h); | ||||||
|   void invert_display_(bool invert); |   void invert_display_(bool invert); | ||||||
|   void reset_(); |   void reset_(); | ||||||
|   void fill_internal_(Color color); |   void fill_internal_(uint8_t color); | ||||||
|   void display_(); |   void display_(); | ||||||
|  |   void rotate_my_(uint8_t m); | ||||||
|  |  | ||||||
|   ILI9341Model model_; |   ILI9341Model model_; | ||||||
|   int16_t width_{320};   ///< Display width as modified by current rotation |   int16_t width_{320};   ///< Display width as modified by current rotation | ||||||
|   | |||||||
| @@ -23,6 +23,8 @@ void IntegrationSensor::setup() { | |||||||
| } | } | ||||||
| void IntegrationSensor::dump_config() { LOG_SENSOR("", "Integration Sensor", this); } | void IntegrationSensor::dump_config() { LOG_SENSOR("", "Integration Sensor", this); } | ||||||
| void IntegrationSensor::process_sensor_value_(float value) { | void IntegrationSensor::process_sensor_value_(float value) { | ||||||
|  |   if (std::isnan(value)) | ||||||
|  |     return; | ||||||
|   const uint32_t now = millis(); |   const uint32_t now = millis(); | ||||||
|   const double old_value = this->last_value_; |   const double old_value = this->last_value_; | ||||||
|   const double new_value = value; |   const double new_value = value; | ||||||
|   | |||||||
| @@ -14,6 +14,9 @@ void MCP23008::setup() { | |||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   // Read current output register state | ||||||
|  |   this->read_reg(mcp23x08_base::MCP23X08_OLAT, &this->olat_); | ||||||
|  |  | ||||||
|   if (this->open_drain_ints_) { |   if (this->open_drain_ints_) { | ||||||
|     // enable open-drain interrupt pins, 3.3V-safe |     // enable open-drain interrupt pins, 3.3V-safe | ||||||
|     this->write_reg(mcp23x08_base::MCP23X08_IOCON, 0x04); |     this->write_reg(mcp23x08_base::MCP23X08_IOCON, 0x04); | ||||||
|   | |||||||
| @@ -15,6 +15,10 @@ void MCP23016::setup() { | |||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   // Read current output register state | ||||||
|  |   this->read_reg_(MCP23016_OLAT0, &this->olat_0_); | ||||||
|  |   this->read_reg_(MCP23016_OLAT1, &this->olat_1_); | ||||||
|  |  | ||||||
|   // all pins input |   // all pins input | ||||||
|   this->write_reg_(MCP23016_IODIR0, 0xFF); |   this->write_reg_(MCP23016_IODIR0, 0xFF); | ||||||
|   this->write_reg_(MCP23016_IODIR1, 0xFF); |   this->write_reg_(MCP23016_IODIR1, 0xFF); | ||||||
|   | |||||||
| @@ -14,6 +14,10 @@ void MCP23017::setup() { | |||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   // Read current output register state | ||||||
|  |   this->read_reg(mcp23x17_base::MCP23X17_OLATA, &this->olat_a_); | ||||||
|  |   this->read_reg(mcp23x17_base::MCP23X17_OLATB, &this->olat_b_); | ||||||
|  |  | ||||||
|   if (this->open_drain_ints_) { |   if (this->open_drain_ints_) { | ||||||
|     // enable open-drain interrupt pins, 3.3V-safe |     // enable open-drain interrupt pins, 3.3V-safe | ||||||
|     this->write_reg(mcp23x17_base::MCP23X17_IOCONA, 0x04); |     this->write_reg(mcp23x17_base::MCP23X17_IOCONA, 0x04); | ||||||
|   | |||||||
| @@ -23,6 +23,9 @@ void MCP23S08::setup() { | |||||||
|   this->transfer_byte(0b00011000);  // Enable HAEN pins for addressing |   this->transfer_byte(0b00011000);  // Enable HAEN pins for addressing | ||||||
|   this->disable(); |   this->disable(); | ||||||
|  |  | ||||||
|  |   // Read current output register state | ||||||
|  |   this->read_reg(mcp23x08_base::MCP23X08_OLAT, &this->olat_); | ||||||
|  |  | ||||||
|   if (this->open_drain_ints_) { |   if (this->open_drain_ints_) { | ||||||
|     // enable open-drain interrupt pins, 3.3V-safe |     // enable open-drain interrupt pins, 3.3V-safe | ||||||
|     this->write_reg(mcp23x08_base::MCP23X08_IOCON, 0x04); |     this->write_reg(mcp23x08_base::MCP23X08_IOCON, 0x04); | ||||||
|   | |||||||
| @@ -23,6 +23,10 @@ void MCP23S17::setup() { | |||||||
|   this->transfer_byte(0b00011000);  // Enable HAEN pins for addressing |   this->transfer_byte(0b00011000);  // Enable HAEN pins for addressing | ||||||
|   this->disable(); |   this->disable(); | ||||||
|  |  | ||||||
|  |   // Read current output register state | ||||||
|  |   this->read_reg(mcp23x17_base::MCP23X17_OLATA, &this->olat_a_); | ||||||
|  |   this->read_reg(mcp23x17_base::MCP23X17_OLATB, &this->olat_b_); | ||||||
|  |  | ||||||
|   if (this->open_drain_ints_) { |   if (this->open_drain_ints_) { | ||||||
|     // enable open-drain interrupt pins, 3.3V-safe |     // enable open-drain interrupt pins, 3.3V-safe | ||||||
|     this->write_reg(mcp23x17_base::MCP23X17_IOCONA, 0x04); |     this->write_reg(mcp23x17_base::MCP23X17_IOCONA, 0x04); | ||||||
|   | |||||||
| @@ -2,7 +2,6 @@ import esphome.codegen as cg | |||||||
| import esphome.config_validation as cv | import esphome.config_validation as cv | ||||||
| from esphome.components import spi | from esphome.components import spi | ||||||
| from esphome.const import CONF_ID | from esphome.const import CONF_ID | ||||||
| from esphome.core import CORE |  | ||||||
|  |  | ||||||
| DEPENDENCIES = ["spi"] | DEPENDENCIES = ["spi"] | ||||||
| AUTO_LOAD = ["sensor"] | AUTO_LOAD = ["sensor"] | ||||||
| @@ -24,6 +23,3 @@ async def to_code(config): | |||||||
|     var = cg.new_Pvariable(config[CONF_ID]) |     var = cg.new_Pvariable(config[CONF_ID]) | ||||||
|     await cg.register_component(var, config) |     await cg.register_component(var, config) | ||||||
|     await spi.register_spi_device(var, config) |     await spi.register_spi_device(var, config) | ||||||
|  |  | ||||||
|     if CORE.is_esp32: |  | ||||||
|         cg.add_library("SPI", None) |  | ||||||
|   | |||||||
							
								
								
									
										0
									
								
								esphome/components/mcp9600/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								esphome/components/mcp9600/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										115
									
								
								esphome/components/mcp9600/mcp9600.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										115
									
								
								esphome/components/mcp9600/mcp9600.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,115 @@ | |||||||
|  | #include "mcp9600.h" | ||||||
|  | #include "esphome/core/log.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace mcp9600 { | ||||||
|  |  | ||||||
|  | static const char *const TAG = "mcp9600"; | ||||||
|  |  | ||||||
|  | static const uint8_t MCP9600_REGISTER_HOT_JUNCTION = 0x00; | ||||||
|  | // static const uint8_t MCP9600_REGISTER_JUNCTION_DELTA = 0x01; // Unused, but kept for future reference | ||||||
|  | static const uint8_t MCP9600_REGISTER_COLD_JUNTION = 0x02; | ||||||
|  | // static const uint8_t MCP9600_REGISTER_RAW_DATA_ADC = 0x03; // Unused, but kept for future reference | ||||||
|  | static const uint8_t MCP9600_REGISTER_STATUS = 0x04; | ||||||
|  | static const uint8_t MCP9600_REGISTER_SENSOR_CONFIG = 0x05; | ||||||
|  | static const uint8_t MCP9600_REGISTER_CONFIG = 0x06; | ||||||
|  | static const uint8_t MCP9600_REGISTER_ALERT1_CONFIG = 0x08; | ||||||
|  | static const uint8_t MCP9600_REGISTER_ALERT2_CONFIG = 0x09; | ||||||
|  | static const uint8_t MCP9600_REGISTER_ALERT3_CONFIG = 0x0A; | ||||||
|  | static const uint8_t MCP9600_REGISTER_ALERT4_CONFIG = 0x0B; | ||||||
|  | static const uint8_t MCP9600_REGISTER_ALERT1_HYSTERESIS = 0x0C; | ||||||
|  | static const uint8_t MCP9600_REGISTER_ALERT2_HYSTERESIS = 0x0D; | ||||||
|  | static const uint8_t MCP9600_REGISTER_ALERT3_HYSTERESIS = 0x0E; | ||||||
|  | static const uint8_t MCP9600_REGISTER_ALERT4_HYSTERESIS = 0x0F; | ||||||
|  | static const uint8_t MCP9600_REGISTER_ALERT1_LIMIT = 0x10; | ||||||
|  | static const uint8_t MCP9600_REGISTER_ALERT2_LIMIT = 0x11; | ||||||
|  | static const uint8_t MCP9600_REGISTER_ALERT3_LIMIT = 0x12; | ||||||
|  | static const uint8_t MCP9600_REGISTER_ALERT4_LIMIT = 0x13; | ||||||
|  | static const uint8_t MCP9600_REGISTER_DEVICE_ID = 0x20; | ||||||
|  |  | ||||||
|  | void MCP9600Component::setup() { | ||||||
|  |   ESP_LOGCONFIG(TAG, "Setting up MCP9600..."); | ||||||
|  |  | ||||||
|  |   uint16_t dev_id = 0; | ||||||
|  |   this->read_byte_16(MCP9600_REGISTER_DEVICE_ID, &dev_id); | ||||||
|  |   this->device_id_ = (uint8_t)(dev_id >> 8); | ||||||
|  |  | ||||||
|  |   // Allows both MCP9600's and MCP9601's to be connected. | ||||||
|  |   if (this->device_id_ != (uint8_t) 0x40 && this->device_id_ != (uint8_t) 0x41) { | ||||||
|  |     this->error_code_ = COMMUNICATION_FAILED; | ||||||
|  |     this->mark_failed(); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   bool success = this->write_byte(MCP9600_REGISTER_STATUS, 0x00); | ||||||
|  |   success |= this->write_byte(MCP9600_REGISTER_SENSOR_CONFIG, uint8_t(0x00 | thermocouple_type_ << 4)); | ||||||
|  |   success |= this->write_byte(MCP9600_REGISTER_CONFIG, 0x00); | ||||||
|  |   success |= this->write_byte(MCP9600_REGISTER_ALERT1_CONFIG, 0x00); | ||||||
|  |   success |= this->write_byte(MCP9600_REGISTER_ALERT2_CONFIG, 0x00); | ||||||
|  |   success |= this->write_byte(MCP9600_REGISTER_ALERT3_CONFIG, 0x00); | ||||||
|  |   success |= this->write_byte(MCP9600_REGISTER_ALERT4_CONFIG, 0x00); | ||||||
|  |   success |= this->write_byte(MCP9600_REGISTER_ALERT1_HYSTERESIS, 0x00); | ||||||
|  |   success |= this->write_byte(MCP9600_REGISTER_ALERT2_HYSTERESIS, 0x00); | ||||||
|  |   success |= this->write_byte(MCP9600_REGISTER_ALERT3_HYSTERESIS, 0x00); | ||||||
|  |   success |= this->write_byte(MCP9600_REGISTER_ALERT4_HYSTERESIS, 0x00); | ||||||
|  |   success |= this->write_byte_16(MCP9600_REGISTER_ALERT1_LIMIT, 0x0000); | ||||||
|  |   success |= this->write_byte_16(MCP9600_REGISTER_ALERT2_LIMIT, 0x0000); | ||||||
|  |   success |= this->write_byte_16(MCP9600_REGISTER_ALERT3_LIMIT, 0x0000); | ||||||
|  |   success |= this->write_byte_16(MCP9600_REGISTER_ALERT4_LIMIT, 0x0000); | ||||||
|  |  | ||||||
|  |   if (!success) { | ||||||
|  |     this->error_code_ = FAILED_TO_UPDATE_CONFIGURATION; | ||||||
|  |     this->mark_failed(); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void MCP9600Component::dump_config() { | ||||||
|  |   ESP_LOGCONFIG(TAG, "MCP9600:"); | ||||||
|  |   LOG_I2C_DEVICE(this); | ||||||
|  |   LOG_UPDATE_INTERVAL(this); | ||||||
|  |  | ||||||
|  |   ESP_LOGCONFIG(TAG, "  Device ID: 0x%x", this->device_id_); | ||||||
|  |  | ||||||
|  |   LOG_SENSOR("  ", "Hot Junction Temperature", this->hot_junction_sensor_); | ||||||
|  |   LOG_SENSOR("  ", "Cold Junction Temperature", this->cold_junction_sensor_); | ||||||
|  |  | ||||||
|  |   switch (this->error_code_) { | ||||||
|  |     case COMMUNICATION_FAILED: | ||||||
|  |       ESP_LOGE(TAG, "Connected device does not match a known MCP9600 or MCP901 sensor"); | ||||||
|  |       break; | ||||||
|  |     case FAILED_TO_UPDATE_CONFIGURATION: | ||||||
|  |       ESP_LOGE(TAG, "Failed to update device configuration"); | ||||||
|  |       break; | ||||||
|  |     case NONE: | ||||||
|  |     default: | ||||||
|  |       break; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void MCP9600Component::update() { | ||||||
|  |   if (this->hot_junction_sensor_ != nullptr) { | ||||||
|  |     uint16_t raw_hot_junction_temperature; | ||||||
|  |     if (!this->read_byte_16(MCP9600_REGISTER_HOT_JUNCTION, &raw_hot_junction_temperature)) { | ||||||
|  |       this->status_set_warning(); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     float hot_junction_temperature = int16_t(raw_hot_junction_temperature) * 0.0625; | ||||||
|  |     this->hot_junction_sensor_->publish_state(hot_junction_temperature); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (this->cold_junction_sensor_ != nullptr) { | ||||||
|  |     uint16_t raw_cold_junction_temperature; | ||||||
|  |     if (!this->read_byte_16(MCP9600_REGISTER_COLD_JUNTION, &raw_cold_junction_temperature)) { | ||||||
|  |       this->status_set_warning(); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     float cold_junction_temperature = int16_t(raw_cold_junction_temperature) * 0.0625; | ||||||
|  |     this->cold_junction_sensor_->publish_state(cold_junction_temperature); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   this->status_clear_warning(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | }  // namespace mcp9600 | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										51
									
								
								esphome/components/mcp9600/mcp9600.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								esphome/components/mcp9600/mcp9600.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,51 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include "esphome/core/component.h" | ||||||
|  | #include "esphome/components/sensor/sensor.h" | ||||||
|  | #include "esphome/components/i2c/i2c.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace mcp9600 { | ||||||
|  |  | ||||||
|  | enum MCP9600ThermocoupleType : uint8_t { | ||||||
|  |   MCP9600_THERMOCOUPLE_TYPE_K = 0b000, | ||||||
|  |   MCP9600_THERMOCOUPLE_TYPE_J = 0b001, | ||||||
|  |   MCP9600_THERMOCOUPLE_TYPE_T = 0b010, | ||||||
|  |   MCP9600_THERMOCOUPLE_TYPE_N = 0b011, | ||||||
|  |   MCP9600_THERMOCOUPLE_TYPE_S = 0b100, | ||||||
|  |   MCP9600_THERMOCOUPLE_TYPE_E = 0b101, | ||||||
|  |   MCP9600_THERMOCOUPLE_TYPE_B = 0b110, | ||||||
|  |   MCP9600_THERMOCOUPLE_TYPE_R = 0b111, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | class MCP9600Component : public PollingComponent, public i2c::I2CDevice { | ||||||
|  |  public: | ||||||
|  |   void setup() override; | ||||||
|  |   void dump_config() override; | ||||||
|  |   void update() override; | ||||||
|  |  | ||||||
|  |   float get_setup_priority() const override { return setup_priority::DATA; } | ||||||
|  |  | ||||||
|  |   void set_hot_junction(sensor::Sensor *hot_junction) { this->hot_junction_sensor_ = hot_junction; } | ||||||
|  |   void set_cold_junction(sensor::Sensor *cold_junction) { this->cold_junction_sensor_ = cold_junction; } | ||||||
|  |   void set_thermocouple_type(MCP9600ThermocoupleType thermocouple_type) { | ||||||
|  |     this->thermocouple_type_ = thermocouple_type; | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   uint8_t device_id_{0}; | ||||||
|  |  | ||||||
|  |   sensor::Sensor *hot_junction_sensor_{nullptr}; | ||||||
|  |   sensor::Sensor *cold_junction_sensor_{nullptr}; | ||||||
|  |  | ||||||
|  |   MCP9600ThermocoupleType thermocouple_type_{MCP9600_THERMOCOUPLE_TYPE_K}; | ||||||
|  |  | ||||||
|  |   enum ErrorCode { | ||||||
|  |     NONE, | ||||||
|  |     COMMUNICATION_FAILED, | ||||||
|  |     FAILED_TO_UPDATE_CONFIGURATION, | ||||||
|  |   } error_code_{NONE}; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | }  // namespace mcp9600 | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										81
									
								
								esphome/components/mcp9600/sensor.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								esphome/components/mcp9600/sensor.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,81 @@ | |||||||
|  | import esphome.codegen as cg | ||||||
|  | import esphome.config_validation as cv | ||||||
|  | from esphome.components import i2c, sensor | ||||||
|  | from esphome.const import ( | ||||||
|  |     CONF_ID, | ||||||
|  |     DEVICE_CLASS_TEMPERATURE, | ||||||
|  |     STATE_CLASS_MEASUREMENT, | ||||||
|  |     UNIT_CELSIUS, | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | CONF_THERMOCOUPLE_TYPE = "thermocouple_type" | ||||||
|  | CONF_HOT_JUNCTION = "hot_junction" | ||||||
|  | CONF_COLD_JUNCTION = "cold_junction" | ||||||
|  |  | ||||||
|  | DEPENDENCIES = ["i2c"] | ||||||
|  | CODEOWNERS = ["@MrEditor97"] | ||||||
|  |  | ||||||
|  | mcp9600_ns = cg.esphome_ns.namespace("mcp9600") | ||||||
|  | MCP9600Component = mcp9600_ns.class_( | ||||||
|  |     "MCP9600Component", cg.PollingComponent, i2c.I2CDevice | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | MCP9600ThermocoupleType = mcp9600_ns.enum("MCP9600ThermocoupleType") | ||||||
|  | THERMOCOUPLE_TYPE = { | ||||||
|  |     "K": MCP9600ThermocoupleType.MCP9600_THERMOCOUPLE_TYPE_K, | ||||||
|  |     "J": MCP9600ThermocoupleType.MCP9600_THERMOCOUPLE_TYPE_J, | ||||||
|  |     "T": MCP9600ThermocoupleType.MCP9600_THERMOCOUPLE_TYPE_T, | ||||||
|  |     "N": MCP9600ThermocoupleType.MCP9600_THERMOCOUPLE_TYPE_N, | ||||||
|  |     "S": MCP9600ThermocoupleType.MCP9600_THERMOCOUPLE_TYPE_S, | ||||||
|  |     "E": MCP9600ThermocoupleType.MCP9600_THERMOCOUPLE_TYPE_E, | ||||||
|  |     "B": MCP9600ThermocoupleType.MCP9600_THERMOCOUPLE_TYPE_B, | ||||||
|  |     "R": MCP9600ThermocoupleType.MCP9600_THERMOCOUPLE_TYPE_R, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | CONFIG_SCHEMA = ( | ||||||
|  |     cv.Schema( | ||||||
|  |         { | ||||||
|  |             cv.GenerateID(): cv.declare_id(MCP9600Component), | ||||||
|  |             cv.Optional(CONF_THERMOCOUPLE_TYPE, default="K"): cv.enum( | ||||||
|  |                 THERMOCOUPLE_TYPE, upper=True | ||||||
|  |             ), | ||||||
|  |             cv.Optional(CONF_HOT_JUNCTION): sensor.sensor_schema( | ||||||
|  |                 unit_of_measurement=UNIT_CELSIUS, | ||||||
|  |                 accuracy_decimals=1, | ||||||
|  |                 device_class=DEVICE_CLASS_TEMPERATURE, | ||||||
|  |                 state_class=STATE_CLASS_MEASUREMENT, | ||||||
|  |             ), | ||||||
|  |             cv.Optional(CONF_COLD_JUNCTION): sensor.sensor_schema( | ||||||
|  |                 unit_of_measurement=UNIT_CELSIUS, | ||||||
|  |                 accuracy_decimals=1, | ||||||
|  |                 device_class=DEVICE_CLASS_TEMPERATURE, | ||||||
|  |                 state_class=STATE_CLASS_MEASUREMENT, | ||||||
|  |             ), | ||||||
|  |         } | ||||||
|  |     ) | ||||||
|  |     .extend(cv.polling_component_schema("60s")) | ||||||
|  |     .extend(i2c.i2c_device_schema(0x67)) | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | FINAL_VALIDATE_SCHEMA = i2c.final_validate_device_schema( | ||||||
|  |     "mcp9600", min_frequency="100khz" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | 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_thermocouple_type(config[CONF_THERMOCOUPLE_TYPE])) | ||||||
|  |  | ||||||
|  |     if CONF_HOT_JUNCTION in config: | ||||||
|  |         conf = config[CONF_HOT_JUNCTION] | ||||||
|  |         sens = await sensor.new_sensor(conf) | ||||||
|  |         cg.add(var.set_hot_junction(sens)) | ||||||
|  |  | ||||||
|  |     if CONF_COLD_JUNCTION in config: | ||||||
|  |         conf = config[CONF_COLD_JUNCTION] | ||||||
|  |         sens = await sensor.new_sensor(conf) | ||||||
|  |         cg.add(var.set_cold_junction(sens)) | ||||||
| @@ -35,22 +35,6 @@ void Modbus::loop() { | |||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| uint16_t crc16(const uint8_t *data, uint8_t len) { |  | ||||||
|   uint16_t crc = 0xFFFF; |  | ||||||
|   while (len--) { |  | ||||||
|     crc ^= *data++; |  | ||||||
|     for (uint8_t i = 0; i < 8; i++) { |  | ||||||
|       if ((crc & 0x01) != 0) { |  | ||||||
|         crc >>= 1; |  | ||||||
|         crc ^= 0xA001; |  | ||||||
|       } else { |  | ||||||
|         crc >>= 1; |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|   return crc; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| bool Modbus::parse_modbus_byte_(uint8_t byte) { | bool Modbus::parse_modbus_byte_(uint8_t byte) { | ||||||
|   size_t at = this->rx_buffer_.size(); |   size_t at = this->rx_buffer_.size(); | ||||||
|   this->rx_buffer_.push_back(byte); |   this->rx_buffer_.push_back(byte); | ||||||
|   | |||||||
| @@ -40,8 +40,6 @@ class Modbus : public uart::UARTDevice, public Component { | |||||||
|   std::vector<ModbusDevice *> devices_; |   std::vector<ModbusDevice *> devices_; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| uint16_t crc16(const uint8_t *data, uint8_t len); |  | ||||||
|  |  | ||||||
| class ModbusDevice { | class ModbusDevice { | ||||||
|  public: |  public: | ||||||
|   void set_parent(Modbus *parent) { parent_ = parent; } |   void set_parent(Modbus *parent) { parent_ = parent; } | ||||||
|   | |||||||
| @@ -236,7 +236,7 @@ size_t ModbusController::create_register_ranges_() { | |||||||
|       } |       } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     if (curr->start_address == r.start_address) { |     if (curr->start_address == r.start_address && curr->register_type == r.register_type) { | ||||||
|       // use the lowest non zero value for the whole range |       // use the lowest non zero value for the whole range | ||||||
|       // Because zero is the default value for skip_updates it is excluded from getting the min value. |       // Because zero is the default value for skip_updates it is excluded from getting the min value. | ||||||
|       if (curr->skip_updates != 0) { |       if (curr->skip_updates != 0) { | ||||||
|   | |||||||
| @@ -32,11 +32,11 @@ ModbusSwitch = modbus_controller_ns.class_( | |||||||
| ) | ) | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = cv.All( | CONFIG_SCHEMA = cv.All( | ||||||
|     switch.SWITCH_SCHEMA.extend(cv.COMPONENT_SCHEMA) |     switch.switch_schema(ModbusSwitch) | ||||||
|  |     .extend(cv.COMPONENT_SCHEMA) | ||||||
|     .extend(ModbusItemBaseSchema) |     .extend(ModbusItemBaseSchema) | ||||||
|     .extend( |     .extend( | ||||||
|         { |         { | ||||||
|             cv.GenerateID(): cv.declare_id(ModbusSwitch), |  | ||||||
|             cv.Optional(CONF_REGISTER_TYPE): cv.enum(MODBUS_REGISTER_TYPE), |             cv.Optional(CONF_REGISTER_TYPE): cv.enum(MODBUS_REGISTER_TYPE), | ||||||
|             cv.Optional(CONF_USE_WRITE_MULTIPLE, default=False): cv.boolean, |             cv.Optional(CONF_USE_WRITE_MULTIPLE, default=False): cv.boolean, | ||||||
|             cv.Optional(CONF_WRITE_LAMBDA): cv.returning_lambda, |             cv.Optional(CONF_WRITE_LAMBDA): cv.returning_lambda, | ||||||
|   | |||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user